The JUCE cross-platform C++ framework, with DISTRHO/KXStudio specific changes
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

280 lines
8.4KB

  1. /*
  2. ==============================================================================
  3. This file is part of the JUCE library.
  4. Copyright (c) 2017 - ROLI Ltd.
  5. JUCE is an open source library subject to commercial or open-source
  6. licensing.
  7. The code included in this file is provided under the terms of the ISC license
  8. http://www.isc.org/downloads/software-support-policy/isc-license. Permission
  9. To use, copy, modify, and/or distribute this software for any purpose with or
  10. without fee is hereby granted provided that the above copyright notice and
  11. this permission notice appear in all copies.
  12. JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
  13. EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
  14. DISCLAIMED.
  15. ==============================================================================
  16. */
  17. namespace juce
  18. {
  19. File ArgumentList::Argument::resolveAsFile() const
  20. {
  21. return File::getCurrentWorkingDirectory().getChildFile (text.unquoted());
  22. }
  23. File ArgumentList::Argument::resolveAsExistingFile() const
  24. {
  25. auto f = resolveAsFile();
  26. if (! f.exists())
  27. ConsoleApplication::fail ("Could not find file: " + f.getFullPathName());
  28. return f;
  29. }
  30. File ArgumentList::Argument::resolveAsExistingFolder() const
  31. {
  32. auto f = resolveAsFile();
  33. if (! f.isDirectory())
  34. ConsoleApplication::fail ("Could not find folder: " + f.getFullPathName());
  35. return f;
  36. }
  37. bool ArgumentList::Argument::isLongOption() const { return text[0] == '-' && text[1] == '-' && text[2] != '-'; }
  38. bool ArgumentList::Argument::isShortOption() const { return text[0] == '-' && text[1] != '-'; }
  39. bool ArgumentList::Argument::isLongOption (const String& option) const
  40. {
  41. if (option.startsWith ("--"))
  42. return text == option;
  43. jassert (! option.startsWithChar ('-')); // this will always fail to match
  44. return text == "--" + option;
  45. }
  46. bool ArgumentList::Argument::isShortOption (char option) const
  47. {
  48. jassert (option != '-'); // this is probably not what you intended to pass in
  49. return isShortOption() && text.containsChar (option);
  50. }
  51. static bool compareOptionStrings (StringRef s1, StringRef s2)
  52. {
  53. if (s1 == s2)
  54. return true;
  55. auto toks1 = StringArray::fromTokens (s1, "|", {});
  56. auto toks2 = StringArray::fromTokens (s2, "|", {});
  57. for (auto& part1 : toks1)
  58. for (auto& part2 : toks2)
  59. if (part1.trim() == part2.trim())
  60. return true;
  61. return false;
  62. }
  63. bool ArgumentList::Argument::operator== (StringRef s) const { return compareOptionStrings (text, s); }
  64. bool ArgumentList::Argument::operator!= (StringRef s) const { return ! operator== (s); }
  65. //==============================================================================
  66. ArgumentList::ArgumentList (String exeName, StringArray args)
  67. : executableName (std::move (exeName))
  68. {
  69. args.trim();
  70. for (auto& a : args)
  71. arguments.add ({ a });
  72. }
  73. ArgumentList::ArgumentList (int argc, char* argv[])
  74. : ArgumentList (argv[0], StringArray (argv + 1, argc - 1))
  75. {
  76. }
  77. ArgumentList::ArgumentList (const String& exeName, const String& args)
  78. : ArgumentList (exeName, StringArray::fromTokens (args, true))
  79. {
  80. }
  81. int ArgumentList::size() const { return arguments.size(); }
  82. ArgumentList::Argument ArgumentList::operator[] (int index) const { return arguments[index]; }
  83. void ArgumentList::checkMinNumArguments (int expectedMinNumberOfArgs) const
  84. {
  85. if (size() < expectedMinNumberOfArgs)
  86. ConsoleApplication::fail ("Not enough arguments!");
  87. }
  88. int ArgumentList::indexOfOption (StringRef option) const
  89. {
  90. jassert (option == String (option).trim()); // passing non-trimmed strings will always fail to find a match!
  91. for (int i = 0; i < arguments.size(); ++i)
  92. if (arguments.getReference(i) == option)
  93. return i;
  94. return -1;
  95. }
  96. bool ArgumentList::containsOption (StringRef option) const
  97. {
  98. return indexOfOption (option) >= 0;
  99. }
  100. void ArgumentList::failIfOptionIsMissing (StringRef option) const
  101. {
  102. if (! containsOption (option))
  103. ConsoleApplication::fail ("Expected the option " + option);
  104. }
  105. ArgumentList::Argument ArgumentList::getArgumentAfterOption (StringRef option) const
  106. {
  107. for (int i = 0; i < arguments.size() - 1; ++i)
  108. if (arguments.getReference(i) == option)
  109. return arguments.getReference (i + 1);
  110. return {};
  111. }
  112. File ArgumentList::getFileAfterOption (StringRef option) const
  113. {
  114. failIfOptionIsMissing (option);
  115. auto arg = getArgumentAfterOption (option);
  116. if (arg.text.isEmpty() || arg.text.startsWithChar ('-'))
  117. ConsoleApplication::fail ("Expected a filename after the " + option + " option");
  118. return arg.resolveAsFile();
  119. }
  120. File ArgumentList::getExistingFileAfterOption (StringRef option) const
  121. {
  122. failIfOptionIsMissing (option);
  123. auto arg = getArgumentAfterOption (option);
  124. if (arg.text.isEmpty())
  125. ConsoleApplication::fail ("Expected a filename after the " + option + " option");
  126. return arg.resolveAsExistingFile();
  127. }
  128. File ArgumentList::getExistingFolderAfterOption (StringRef option) const
  129. {
  130. failIfOptionIsMissing (option);
  131. auto arg = getArgumentAfterOption (option);
  132. if (arg.text.isEmpty())
  133. ConsoleApplication::fail ("Expected a folder name after the " + option + " option");
  134. return arg.resolveAsExistingFolder();
  135. }
  136. //==============================================================================
  137. struct ConsoleAppFailureCode
  138. {
  139. String errorMessage;
  140. int returnCode;
  141. };
  142. void ConsoleApplication::fail (String errorMessage, int returnCode)
  143. {
  144. throw ConsoleAppFailureCode { std::move (errorMessage), returnCode };
  145. }
  146. int ConsoleApplication::invokeCatchingFailures (std::function<int()>&& f)
  147. {
  148. int returnCode = 0;
  149. try
  150. {
  151. returnCode = f();
  152. }
  153. catch (const ConsoleAppFailureCode& error)
  154. {
  155. std::cout << error.errorMessage << std::endl;
  156. returnCode = error.returnCode;
  157. }
  158. return returnCode;
  159. }
  160. int ConsoleApplication::findAndRunCommand (const ArgumentList& args) const
  161. {
  162. for (auto& c : commands)
  163. if (args.containsOption (c.commandOption))
  164. return invokeCatchingFailures ([&] { c.command (args); return 0; });
  165. if (commandIfNoOthersRecognised.isNotEmpty())
  166. for (auto& c : commands)
  167. if (compareOptionStrings (c.commandOption, commandIfNoOthersRecognised))
  168. return invokeCatchingFailures ([&] { c.command (args); return 0; });
  169. fail ("Unrecognised arguments");
  170. return 0;
  171. }
  172. int ConsoleApplication::findAndRunCommand (int argc, char* argv[]) const
  173. {
  174. return findAndRunCommand (ArgumentList (argc, argv));
  175. }
  176. void ConsoleApplication::addCommand (Command c)
  177. {
  178. commands.emplace_back (std::move (c));
  179. }
  180. void ConsoleApplication::addHelpCommand (String arg, String helpMessage, bool invokeIfNoOtherCommandRecognised)
  181. {
  182. addCommand ({ arg, arg, "Prints this message",
  183. [this, helpMessage] (const ArgumentList& args) { printHelp (helpMessage, args); }});
  184. if (invokeIfNoOtherCommandRecognised)
  185. commandIfNoOthersRecognised = arg;
  186. }
  187. void ConsoleApplication::addVersionCommand (String arg, String versionText)
  188. {
  189. addCommand ({ arg, arg, "Prints the current version number",
  190. [versionText] (const ArgumentList&)
  191. {
  192. std::cout << versionText << std::endl;
  193. }});
  194. }
  195. void ConsoleApplication::printHelp (const String& preamble, const ArgumentList& args) const
  196. {
  197. std::cout << preamble << std::endl;
  198. auto exeName = args.executableName.fromLastOccurrenceOf ("/", false, false)
  199. .fromLastOccurrenceOf ("\\", false, false);
  200. StringArray namesAndArgs;
  201. int maxLength = 0;
  202. for (auto& c : commands)
  203. {
  204. auto nameAndArgs = exeName + " " + c.argumentDescription;
  205. namesAndArgs.add (nameAndArgs);
  206. maxLength = std::max (maxLength, nameAndArgs.length());
  207. }
  208. for (size_t i = 0; i < commands.size(); ++i)
  209. std::cout << " " << namesAndArgs[(int) i].paddedRight (' ', maxLength + 2)
  210. << commands[i].commandDescription << std::endl;
  211. std::cout << std::endl;
  212. }
  213. } // namespace juce