1 /+ 2 This file is part of Reloaded Vibes. 3 Copyright (c) 2019 0xEAB 4 5 Distributed under the Boost Software License, Version 1.0. 6 (See accompanying file LICENSE_1_0.txt or copy at 7 https://www.boost.org/LICENSE_1_0.txt) 8 +/ 9 module reloadedvibes.app; 10 11 import std.algorithm : each, map; 12 import std.datetime : dur; 13 import std.file : exists, isDir; 14 import std.getopt; 15 import std.path : absolutePath, buildNormalizedPath; 16 import std.stdio : stdout, stderr; 17 18 import vibe.core.core : runTask, sleep; 19 import vibe.core.log : LogLevel, setLogLevel; 20 import vibe.http.server : HTTPListener; 21 22 import reloadedvibes.action; 23 import reloadedvibes.server; 24 import reloadedvibes.utils; 25 import reloadedvibes.watcher; 26 27 enum appName = "Reloaded Vibes"; 28 29 int main(string[] args) 30 { 31 immutable argc = args.length; 32 33 bool optPrintVersionInfo; 34 string optSocketService = "127.0.0.1:3001"; 35 bool optDisableService = false; 36 string[] optWatchDirectories = []; 37 string[] optActions = []; 38 string optSocketWebServer; 39 string optDocumentRootWebServer; 40 bool optNoInjectWebServer; 41 42 GetoptResult opt; 43 44 try 45 { 46 // dfmt off 47 opt = getopt( 48 args, 49 config.passThrough, 50 "s|socket", "Socket to bind the notification service to", &optSocketService, 51 "w|watch", "Paths to watch", &optWatchDirectories, 52 "a|action", "Commandlines to execute before reloading", &optActions, 53 "n|noservice", "Disable the notification service\n", &optDisableService, 54 55 "S|webserver", "<addr>:<port> Enables the built-in webserver", &optSocketWebServer, 56 "d|htdocs", "Document root for the built-in webserver", &optDocumentRootWebServer, 57 "j|noinject", "Disables script tag injection for HTML files\n", &optNoInjectWebServer, 58 59 "version", "Display the version of this program.", &optPrintVersionInfo, 60 ); 61 // dfmt on 62 } 63 catch (Exception ex) 64 { 65 stderr.writeln(ex.msg); 66 return 1; 67 } 68 69 // -- Help? 70 if ((argc == 1) || opt.helpWanted) 71 { 72 printHelp(args[0], opt); 73 return 0; 74 } 75 else if (optPrintVersionInfo) 76 { 77 printVersionInfo(); 78 return 0; 79 } 80 81 debug 82 { 83 setLogLevel(LogLevel.diagnostic); 84 } 85 else 86 { 87 setLogLevel(LogLevel.warn); 88 } 89 90 Socket service; 91 Watcher watcher; 92 Socket webserver; 93 HTTPListener[] listeners; 94 void delegate()[] doInit; 95 96 // -- Watcher 97 if (optWatchDirectories.length == 0) 98 { 99 stderr.writeln("No directory to watch specified, use --watch to pass one"); 100 return 1; 101 } 102 103 auto watchDirectories = optWatchDirectories.map!(x => x.absolutePath.buildNormalizedPath()); 104 105 watcher = new Watcher(watchDirectories); 106 107 // -- Service 108 if (!optDisableService) 109 { 110 if (!tryParseSocket(optSocketService, service)) 111 { 112 stderr.writeln("Bad service socket specified"); 113 return 1; 114 } 115 116 doInit ~= { listeners ~= registerService(service, watcher); }; 117 } 118 else 119 { 120 optNoInjectWebServer = true; 121 } 122 123 if (optActions.length > 0) 124 { 125 auto awcl = fromCommandLines(watcher, optActions); 126 127 // Initial execution 128 // Since the action is usually some preprocessor or something, 129 // it should also get executed on application launch 130 doInit ~= { 131 stdout.writeln("\nPre-executing actions..."); 132 awcl.notify(); 133 134 runTask(delegate() @trusted { 135 while (true) 136 { 137 awcl.query(); 138 sleep(dur!"msecs"(200)); 139 } 140 }); 141 }; 142 } 143 144 // -- Webserver 145 if (optSocketWebServer !is null) 146 { 147 if (!tryParseSocket(optSocketWebServer, webserver)) 148 { 149 stderr.writeln("Bad webserver socket specified"); 150 return 1; 151 } 152 153 if (optDocumentRootWebServer is null) 154 { 155 stderr.writeln("No document root specified, use --htdocs to do so"); 156 return 1; 157 } 158 159 if (!optDocumentRootWebServer.exists || !optDocumentRootWebServer.isDir) 160 { 161 stderr.writeln("Bad document root specified"); 162 return 1; 163 } 164 165 optDocumentRootWebServer = optDocumentRootWebServer.absolutePath.buildNormalizedPath(); 166 167 if (optNoInjectWebServer) 168 { 169 doInit ~= { 170 listeners ~= registerStaticWebserver(webserver, optDocumentRootWebServer); 171 }; 172 } 173 else 174 { 175 doInit ~= { 176 listeners ~= registerStaticWebserver(webserver, optDocumentRootWebServer, service); 177 }; 178 } 179 } 180 181 // -- Print info 182 183 stdout.writeln(appName, "\n"); 184 185 watchDirectories.each!(dir => stdout.writeln("Watching: ", dir)); 186 187 if (!optDisableService) 188 { 189 stdout.writeln(); 190 stdout.writeln("Notification service: http://", service.toString); 191 } 192 193 if (optSocketWebServer !is null) 194 { 195 // dfmt off 196 stdout.writeln(); 197 stdout.writeln("Built-in webserver: http://", webserver.toString); 198 stdout.writeln("Serving: ", optDocumentRootWebServer); 199 stdout.writeln("Script injection: ", ((optNoInjectWebServer) ? "disabled" : "enabled")); 200 // dfmt on 201 } 202 203 stdout.writeln(); 204 optActions.each!(act => stdout.writeln("Action: ", act)); 205 206 stdout.writeln(); 207 208 // -- Run 209 doInit.each!(x => x()); 210 run(listeners); 211 return 0; 212 } 213 214 void printHelp(string args0, GetoptResult opt) 215 { 216 // Ideally, this help text will not exceed a size of 217 // 80x23, so that it's fully visible on an 80x24 terminal. 218 219 size_t getIndent() 220 { 221 immutable l = args0.length + 5; 222 return (l <= 29) ? l : 8; 223 } 224 225 string makeIndent() 226 { 227 enum indent = " "; 228 return indent[0 .. getIndent()]; 229 } 230 231 immutable indent = makeIndent(); 232 233 // dfmt off 234 defaultGetoptPrinter( 235 appName ~ "\n\n Usage:\n " ~ args0 ~ " <options>\n\n Example:\n " 236 ~ args0 ~ " --socket=127.0.0.1:3001\n" 237 ~ indent ~ "--watch=./src --watch=./sass\n" 238 ~ indent ~ "--action=\"npm run build\" --action=\"./refreshDB.sh\"" 239 ~ "\n\nAvailable options:\n==================", 240 opt.options 241 ); 242 } 243 244 void printVersionInfo() 245 { 246 stdout.write(import("version.txt")); 247 }