1 /** 2 * Copyright: Copyright Jason White, 2016 3 * License: MIT 4 * Authors: Jason White 5 * 6 * Description: 7 * All command line interface options. 8 */ 9 module button.cli.options; 10 11 import std.meta : AliasSeq; 12 13 import darg; 14 15 struct Command 16 { 17 string name; 18 } 19 20 struct Description 21 { 22 string description; 23 } 24 25 struct GlobalOptions 26 { 27 @Option("help") 28 @Help("Prints help on command line usage.") 29 OptionFlag help; 30 31 @Option("version") 32 @Help("Prints version information.") 33 OptionFlag version_; 34 35 @Argument("command", Multiplicity.optional) 36 string command; 37 38 @Argument("args", Multiplicity.zeroOrMore) 39 const(string)[] args; 40 } 41 42 // Generate usage and help strings at compile-time. 43 immutable globalUsage = usageString!GlobalOptions("button"); 44 immutable globalHelp = helpString!GlobalOptions(); 45 46 @Command("help") 47 @Description("Displays help on a given command.") 48 struct HelpOptions 49 { 50 @Argument("command", Multiplicity.optional) 51 @Help("Command to get help on.") 52 string command; 53 } 54 55 @Command("version") 56 @Description("Prints the current version of the program.") 57 struct VersionOptions 58 { 59 } 60 61 @Command("build") 62 @Description("Runs a build.") 63 struct BuildOptions 64 { 65 @Option("file", "f") 66 @Help("Path to the build description.") 67 string path; 68 69 @Option("dryrun", "n") 70 @Help("Don't make any functional changes. Just print what might happen.") 71 OptionFlag dryRun; 72 73 @Option("threads", "j") 74 @Help("The number of threads to use. Default is the number of logical 75 cores.") 76 @MetaVar("N") 77 size_t threads; 78 79 @Option("color") 80 @Help("When to colorize the output.") 81 @MetaVar("{auto,never,always}") 82 string color = "auto"; 83 84 @Option("verbose", "v") 85 @Help("Display additional information such as how long each task took to"~ 86 " complete.") 87 OptionFlag verbose; 88 89 @Option("autopilot") 90 @Help("After building, continue watching for changes to inputs and"~ 91 " building again as necessary.") 92 OptionFlag autopilot; 93 94 @Option("watchdir") 95 @Help("Used with `--autopilot`. Directory to watch for changes in. Since"~ 96 " FUSE does not work with inotify, this is useful to use when"~ 97 " building in a union file system.") 98 string watchDir = "."; 99 100 @Option("delay") 101 @Help("Used with `--autopilot`. The number of milliseconds to wait for"~ 102 " additional changes after receiving a change event before starting"~ 103 " a build.") 104 size_t delay = 50; 105 } 106 107 @Command("graph") 108 @Description("Generates a graph for input into GraphViz.") 109 struct GraphOptions 110 { 111 import button.edgedata : EdgeType; 112 113 @Option("file", "f") 114 @Help("Path to the build description.") 115 string path; 116 117 @Option("changes", "C") 118 @Help("Only display the subgraph that will be traversed on an update.") 119 OptionFlag changes; 120 121 @Option("cached") 122 @Help("Display the cached graph from the previous build.") 123 OptionFlag cached; 124 125 @Option("full") 126 @Help("Display the full name of each vertex.") 127 OptionFlag full; 128 129 @Option("edges", "e") 130 @MetaVar("{explicit,implicit,both}") 131 @Help("Type of edges to show.") 132 EdgeType edges = EdgeType.explicit; 133 134 @Option("threads", "j") 135 @Help("The number of threads to use. Default is the number of logical 136 cores.") 137 @MetaVar("N") 138 size_t threads; 139 } 140 141 @Command("status") 142 @Description("Prints the status of the build. That is, which files have been 143 modified and which tasks are pending.") 144 struct StatusOptions 145 { 146 @Option("file", "f") 147 @Help("Path to the build description.") 148 string path; 149 150 @Option("cached") 151 @Help("Display the cached graph from the previous build.") 152 OptionFlag cached; 153 154 @Option("color") 155 @Help("When to colorize the output.") 156 @MetaVar("{auto,never,always}") 157 string color = "auto"; 158 159 @Option("threads", "j") 160 @Help("The number of threads to use. Default is the number of logical 161 cores.") 162 @MetaVar("N") 163 size_t threads; 164 } 165 166 @Command("clean") 167 @Description("Deletes all build outputs.") 168 struct CleanOptions 169 { 170 @Option("file", "f") 171 @Help("Path to the build description.") 172 string path; 173 174 @Option("dryrun", "n") 175 @Help("Don't make any functional changes. Just print what might happen.") 176 OptionFlag dryRun; 177 178 @Option("threads", "j") 179 @Help("The number of threads to use. Default is the number of logical 180 cores.") 181 @MetaVar("N") 182 size_t threads; 183 184 @Option("color") 185 @Help("When to colorize the output.") 186 @MetaVar("{auto,never,always}") 187 string color = "auto"; 188 189 @Option("purge") 190 @Help("Delete the build state too.") 191 OptionFlag purge; 192 } 193 194 @Command("init") 195 @Description("Initializes a directory with an initial build description.") 196 struct InitOptions 197 { 198 @Argument("dir", Multiplicity.optional) 199 @Help("Directory to initialize") 200 string dir = "."; 201 } 202 203 enum ConvertFormat 204 { 205 bash, 206 } 207 208 @Command("convert") 209 @Description("Converts the build description to another format for other build systems.") 210 struct ConvertOptions 211 { 212 @Option("file", "f") 213 @Help("Path to the build description.") 214 string path; 215 216 @Option("format") 217 @Help("Format of build description to convert to. Default is 'bash'.") 218 @MetaVar("{bash}") 219 ConvertFormat type; 220 221 @Argument("output") 222 @Help("Path to the output file.") 223 @MetaVar("FILE") 224 string output; 225 } 226 227 @Command("gc") 228 @Description("EXPERIMENTAL") 229 struct GCOptions 230 { 231 @Option("file", "f") 232 @Help("Path to the build description.") 233 string path; 234 235 @Option("dryrun", "n") 236 @Help("Don't make any functional changes. Just print what might happen.") 237 OptionFlag dryRun; 238 239 @Option("color") 240 @Help("When to colorize the output.") 241 @MetaVar("{auto,never,always}") 242 string color = "auto"; 243 } 244 245 /** 246 * List of all options structs. 247 */ 248 alias OptionsList = AliasSeq!( 249 HelpOptions, 250 VersionOptions, 251 BuildOptions, 252 GraphOptions, 253 StatusOptions, 254 CleanOptions, 255 InitOptions, 256 GCOptions, 257 ConvertOptions, 258 ); 259 260 /** 261 * Thrown when an invalid command name is given to $(D runCommand). 262 */ 263 class InvalidCommand : Exception 264 { 265 this(string msg) 266 { 267 super(msg); 268 } 269 } 270 271 /** 272 * Using the list of command functions, runs a command from the specified 273 * string. 274 * 275 * Throws: InvalidCommand if the given command name is not valid. 276 */ 277 int runCommand(Funcs...)(string name, GlobalOptions opts) 278 { 279 import std.traits : Parameters, getUDAs; 280 import std.format : format; 281 282 foreach (F; Funcs) 283 { 284 alias Options = Parameters!F[0]; 285 286 alias Commands = getUDAs!(Options, Command); 287 288 foreach (C; Commands) 289 { 290 if (C.name == name) 291 return F(parseArgs!Options(opts.args), opts); 292 } 293 } 294 295 throw new InvalidCommand("button: '%s' is not a valid command. See 'button help'." 296 .format(name)); 297 }