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