OpenShot Audio Library | OpenShotAudio  0.3.1
juce_ConsoleApplication.cpp
1 /*
2  ==============================================================================
3 
4  This file is part of the JUCE library.
5  Copyright (c) 2017 - ROLI Ltd.
6 
7  JUCE is an open source library subject to commercial or open-source
8  licensing.
9 
10  The code included in this file is provided under the terms of the ISC license
11  http://www.isc.org/downloads/software-support-policy/isc-license. Permission
12  To use, copy, modify, and/or distribute this software for any purpose with or
13  without fee is hereby granted provided that the above copyright notice and
14  this permission notice appear in all copies.
15 
16  JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
17  EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
18  DISCLAIMED.
19 
20  ==============================================================================
21 */
22 
23 namespace juce
24 {
25 
26 static inline File resolveFilename (const String& name)
27 {
28  return File::getCurrentWorkingDirectory().getChildFile (name.unquoted());
29 }
30 
31 static inline File checkFileExists (const File& f)
32 {
33  if (! f.exists())
34  ConsoleApplication::fail ("Could not find file: " + f.getFullPathName());
35 
36  return f;
37 }
38 
39 static inline File checkFolderExists (const File& f)
40 {
41  if (! f.isDirectory())
42  ConsoleApplication::fail ("Could not find folder: " + f.getFullPathName());
43 
44  return f;
45 }
46 
47 static inline File resolveFilenameForOption (const ArgumentList& args, StringRef option, const String& filename)
48 {
49  if (filename.isEmpty())
50  {
51  args.failIfOptionIsMissing (option);
52  ConsoleApplication::fail ("Expected a filename after the " + option + " option");
53  }
54 
55  return resolveFilename (filename);
56 }
57 
59 {
60  return resolveFilename (text);
61 }
62 
64 {
65  return checkFileExists (resolveAsFile());
66 }
67 
69 {
70  auto f = resolveAsFile();
71 
72  if (! f.isDirectory())
73  ConsoleApplication::fail ("Could not find folder: " + f.getFullPathName());
74 
75  return f;
76 }
77 
78 static inline bool isShortOptionFormat (StringRef s) { return s[0] == '-' && s[1] != '-'; }
79 static inline bool isLongOptionFormat (StringRef s) { return s[0] == '-' && s[1] == '-' && s[2] != '-'; }
80 static inline bool isOptionFormat (StringRef s) { return s[0] == '-'; }
81 
82 bool ArgumentList::Argument::isLongOption() const { return isLongOptionFormat (text); }
83 bool ArgumentList::Argument::isShortOption() const { return isShortOptionFormat (text); }
84 bool ArgumentList::Argument::isOption() const { return isOptionFormat (text); }
85 
86 bool ArgumentList::Argument::isLongOption (const String& option) const
87 {
88  if (! isLongOptionFormat (option))
89  {
90  jassert (! isShortOptionFormat (option)); // this will always fail to match
91  return isLongOption ("--" + option);
92  }
93 
94  return text.upToFirstOccurrenceOf ("=", false, false) == option;
95 }
96 
98 {
99  if (isLongOption())
100  {
101  auto equalsIndex = text.indexOfChar ('=');
102 
103  if (equalsIndex > 0)
104  return text.substring (equalsIndex + 1);
105  }
106 
107  return {};
108 }
109 
111 {
112  jassert (option != '-'); // this is probably not what you intended to pass in
113 
114  return isShortOption() && text.containsChar (String (option)[0]);
115 }
116 
118 {
119  for (auto& o : StringArray::fromTokens (wildcard, "|", {}))
120  {
121  if (text == o)
122  return true;
123 
124  if (isShortOptionFormat (o) && o.length() == 2 && isShortOption ((char) o[1]))
125  return true;
126 
127  if (isLongOptionFormat (o) && isLongOption (o))
128  return true;
129  }
130 
131  return false;
132 }
133 
135 
136 //==============================================================================
138  : executableName (std::move (exeName))
139 {
140  args.trim();
141  args.removeEmptyStrings();
142 
143  for (auto& a : args)
144  arguments.add ({ a });
145 }
146 
147 ArgumentList::ArgumentList (int argc, char* argv[])
148  : ArgumentList (argv[0], StringArray (argv + 1, argc - 1))
149 {
150 }
151 
152 ArgumentList::ArgumentList (const String& exeName, const String& args)
153  : ArgumentList (exeName, StringArray::fromTokens (args, true))
154 {
155 }
156 
157 int ArgumentList::size() const { return arguments.size(); }
158 ArgumentList::Argument ArgumentList::operator[] (int index) const { return arguments[index]; }
159 
160 void ArgumentList::checkMinNumArguments (int expectedMinNumberOfArgs) const
161 {
162  if (size() < expectedMinNumberOfArgs)
163  ConsoleApplication::fail ("Not enough arguments!");
164 }
165 
167 {
168  jassert (option == String (option).trim()); // passing non-trimmed strings will always fail to find a match!
169 
170  for (int i = 0; i < arguments.size(); ++i)
171  if (arguments.getReference(i) == option)
172  return i;
173 
174  return -1;
175 }
176 
178 {
179  return indexOfOption (option) >= 0;
180 }
181 
183 {
184  auto i = indexOfOption (option);
185 
186  if (i >= 0)
187  arguments.remove (i);
188 
189  return i >= 0;
190 }
191 
193 {
194  if (indexOfOption (option) < 0)
195  ConsoleApplication::fail ("Expected the option " + option);
196 }
197 
199 {
200  jassert (isOptionFormat (option)); // the thing you're searching for must be an option
201 
202  for (int i = 0; i < arguments.size(); ++i)
203  {
204  auto& arg = arguments.getReference(i);
205 
206  if (arg == option)
207  {
208  if (arg.isShortOption())
209  {
210  if (i < arguments.size() - 1 && ! arguments.getReference (i + 1).isOption())
211  return arguments.getReference (i + 1).text;
212 
213  return {};
214  }
215 
216  if (arg.isLongOption())
217  return arg.getLongOptionValue();
218  }
219  }
220 
221  return {};
222 }
223 
225 {
226  jassert (isOptionFormat (option)); // the thing you're searching for must be an option
227 
228  for (int i = 0; i < arguments.size(); ++i)
229  {
230  auto& arg = arguments.getReference(i);
231 
232  if (arg == option)
233  {
234  if (arg.isShortOption())
235  {
236  if (i < arguments.size() - 1 && ! arguments.getReference (i + 1).isOption())
237  {
238  auto result = arguments.getReference (i + 1).text;
239  arguments.removeRange (i, 2);
240  return result;
241  }
242 
243  arguments.remove (i);
244  return {};
245  }
246 
247  if (arg.isLongOption())
248  {
249  auto result = arg.getLongOptionValue();
250  arguments.remove (i);
251  return result;
252  }
253  }
254  }
255 
256  return {};
257 }
258 
260 {
261  return resolveFilenameForOption (*this, option, getValueForOption (option));
262 }
263 
265 {
266  return resolveFilenameForOption (*this, option, removeValueForOption (option));
267 }
268 
270 {
271  return checkFileExists (getFileForOption (option));
272 }
273 
275 {
276  return checkFileExists (getFileForOptionAndRemove (option));
277 }
278 
280 {
281  return checkFolderExists (getFileForOption (option));
282 }
283 
285 {
286  return checkFolderExists (getFileForOptionAndRemove (option));
287 }
288 
289 //==============================================================================
290 struct ConsoleAppFailureCode
291 {
292  String errorMessage;
293  int returnCode;
294 };
295 
296 void ConsoleApplication::fail (String errorMessage, int returnCode)
297 {
298  throw ConsoleAppFailureCode { std::move (errorMessage), returnCode };
299 }
300 
301 int ConsoleApplication::invokeCatchingFailures (std::function<int()>&& f)
302 {
303  int returnCode = 0;
304 
305  try
306  {
307  returnCode = f();
308  }
309  catch (const ConsoleAppFailureCode& error)
310  {
311  std::cerr << error.errorMessage << std::endl;
312  returnCode = error.returnCode;
313  }
314 
315  return returnCode;
316 }
317 
318 const ConsoleApplication::Command* ConsoleApplication::findCommand (const ArgumentList& args, bool optionMustBeFirstArg) const
319 {
320  for (auto& c : commands)
321  {
322  auto index = args.indexOfOption (c.commandOption);
323 
324  if (optionMustBeFirstArg ? (index == 0) : (index >= 0))
325  return &c;
326  }
327 
328  if (commandIfNoOthersRecognised >= 0)
329  return &commands[(size_t) commandIfNoOthersRecognised];
330 
331  return {};
332 }
333 
334 int ConsoleApplication::findAndRunCommand (const ArgumentList& args, bool optionMustBeFirstArg) const
335 {
336  return invokeCatchingFailures ([&args, optionMustBeFirstArg, this]
337  {
338  if (auto c = findCommand (args, optionMustBeFirstArg))
339  c->command (args);
340  else
341  fail ("Unrecognised arguments");
342 
343  return 0;
344  });
345 }
346 
347 int ConsoleApplication::findAndRunCommand (int argc, char* argv[]) const
348 {
349  return findAndRunCommand (ArgumentList (argc, argv));
350 }
351 
353 {
354  commands.emplace_back (std::move (c));
355 }
356 
358 {
359  commandIfNoOthersRecognised = (int) commands.size();
360  addCommand (std::move (c));
361 }
362 
363 void ConsoleApplication::addHelpCommand (String arg, String helpMessage, bool makeDefaultCommand)
364 {
365  Command c { arg, arg, "Prints the list of commands", {},
366  [this, helpMessage] (const ArgumentList& args)
367  {
368  std::cout << helpMessage << std::endl;
369  printCommandList (args);
370  }};
371 
372  if (makeDefaultCommand)
373  addDefaultCommand (std::move (c));
374  else
375  addCommand (std::move (c));
376 }
377 
379 {
380  addCommand ({ arg, arg, "Prints the current version number", {},
381  [versionText] (const ArgumentList&)
382  {
383  std::cout << versionText << std::endl;
384  }});
385 }
386 
387 const std::vector<ConsoleApplication::Command>& ConsoleApplication::getCommands() const
388 {
389  return commands;
390 }
391 
392 static String getExeNameAndArgs (const ArgumentList& args, const ConsoleApplication::Command& command)
393 {
394  auto exeName = args.executableName.fromLastOccurrenceOf ("/", false, false)
395  .fromLastOccurrenceOf ("\\", false, false);
396 
397  return " " + exeName + " " + command.argumentDescription;
398 }
399 
400 static void printCommandDescription (const ArgumentList& args, const ConsoleApplication::Command& command,
401  int descriptionIndent)
402 {
403  auto nameAndArgs = getExeNameAndArgs (args, command);
404 
405  if (nameAndArgs.length() > descriptionIndent)
406  std::cout << nameAndArgs << std::endl << String().paddedRight (' ', descriptionIndent);
407  else
408  std::cout << nameAndArgs.paddedRight (' ', descriptionIndent);
409 
410  std::cout << command.shortDescription << std::endl;
411 }
412 
414 {
415  int descriptionIndent = 0;
416 
417  for (auto& c : commands)
418  descriptionIndent = std::max (descriptionIndent, getExeNameAndArgs (args, c).length());
419 
420  descriptionIndent = std::min (descriptionIndent + 2, 40);
421 
422  for (auto& c : commands)
423  printCommandDescription (args, c, descriptionIndent);
424 
425  std::cout << std::endl;
426 }
427 
428 void ConsoleApplication::printCommandDetails (const ArgumentList& args, const Command& command) const
429 {
430  auto len = getExeNameAndArgs (args, command).length();
431 
432  printCommandDescription (args, command, std::min (len + 3, 40));
433 
434  if (command.longDescription.isNotEmpty())
435  std::cout << std::endl << command.longDescription << std::endl;
436 }
437 
438 
439 } // namespace juce
Array< Argument > arguments
ArgumentList(String executable, StringArray arguments)
int findAndRunCommand(const ArgumentList &, bool optionMustBeFirstArg=false) const
bool isNotEmpty() const noexcept
Definition: juce_String.h:302
String getValueForOption(StringRef option) const
bool removeOptionIfFound(StringRef option)
String removeValueForOption(StringRef option)
int indexOfOption(StringRef option) const
void printCommandDetails(const ArgumentList &, const Command &) const
void removeEmptyStrings(bool removeWhitespaceStrings=true)
static void fail(String errorMessage, int returnCode=1)
File getFileForOption(StringRef option) const
bool containsChar(juce_wchar character) const noexcept
Definition: juce_Uuid.h:140
File getExistingFileForOptionAndRemove(StringRef option)
File getChildFile(StringRef relativeOrAbsolutePath) const
Definition: juce_File.cpp:414
const Command * findCommand(const ArgumentList &, bool optionMustBeFirstArg) const
File getFileForOptionAndRemove(StringRef option)
String fromLastOccurrenceOf(StringRef substringToFind, bool includeSubStringInResult, bool ignoreCase) const
String paddedRight(juce_wchar padCharacter, int minimumLength) const
String substring(int startIndex, int endIndex) const
Argument operator[](int index) const
bool operator!=(StringRef stringToCompare) const
static StringArray fromTokens(StringRef stringToTokenise, bool preserveQuotedStrings)
const std::vector< Command > & getCommands() const
File getExistingFileForOption(StringRef option) const
void checkMinNumArguments(int expectedMinNumberOfArgs) const
void addHelpCommand(String helpArgument, String helpMessage, bool makeDefaultCommand)
String upToFirstOccurrenceOf(StringRef substringToEndWith, bool includeSubStringInResult, bool ignoreCase) const
void printCommandList(const ArgumentList &) const
bool operator==(StringRef stringToCompare) const
bool isLongOption(const String &optionRoot) const
void failIfOptionIsMissing(StringRef option) const
File getExistingFolderForOptionAndRemove(StringRef option)
int indexOfChar(juce_wchar characterToLookFor) const noexcept
static File getCurrentWorkingDirectory()
static int invokeCatchingFailures(std::function< int()> &&functionToCall)
int length() const noexcept
File getExistingFolderForOption(StringRef option) const
bool containsOption(StringRef option) const
void addVersionCommand(String versionArgument, String versionText)