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.

440 lines
13KB

  1. /*
  2. ==============================================================================
  3. This file is part of the JUCE library.
  4. Copyright (c) 2022 - Raw Material Software Limited
  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. static File resolveFilename (const String& name)
  20. {
  21. return File::getCurrentWorkingDirectory().getChildFile (name.unquoted());
  22. }
  23. static File checkFileExists (const File& f)
  24. {
  25. if (! f.exists())
  26. ConsoleApplication::fail ("Could not find file: " + f.getFullPathName());
  27. return f;
  28. }
  29. static File checkFolderExists (const File& f)
  30. {
  31. if (! f.isDirectory())
  32. ConsoleApplication::fail ("Could not find folder: " + f.getFullPathName());
  33. return f;
  34. }
  35. static File resolveFilenameForOption (const ArgumentList& args, StringRef option, const String& filename)
  36. {
  37. if (filename.isEmpty())
  38. {
  39. args.failIfOptionIsMissing (option);
  40. ConsoleApplication::fail ("Expected a filename after the " + option + " option");
  41. }
  42. return resolveFilename (filename);
  43. }
  44. File ArgumentList::Argument::resolveAsFile() const
  45. {
  46. return resolveFilename (text);
  47. }
  48. File ArgumentList::Argument::resolveAsExistingFile() const
  49. {
  50. return checkFileExists (resolveAsFile());
  51. }
  52. File ArgumentList::Argument::resolveAsExistingFolder() const
  53. {
  54. auto f = resolveAsFile();
  55. if (! f.isDirectory())
  56. ConsoleApplication::fail ("Could not find folder: " + f.getFullPathName());
  57. return f;
  58. }
  59. static bool isShortOptionFormat (StringRef s) { return s[0] == '-' && s[1] != '-'; }
  60. static bool isLongOptionFormat (StringRef s) { return s[0] == '-' && s[1] == '-' && s[2] != '-'; }
  61. static bool isOptionFormat (StringRef s) { return s[0] == '-'; }
  62. bool ArgumentList::Argument::isLongOption() const { return isLongOptionFormat (text); }
  63. bool ArgumentList::Argument::isShortOption() const { return isShortOptionFormat (text); }
  64. bool ArgumentList::Argument::isOption() const { return isOptionFormat (text); }
  65. bool ArgumentList::Argument::isLongOption (const String& option) const
  66. {
  67. if (! isLongOptionFormat (option))
  68. {
  69. jassert (! isShortOptionFormat (option)); // this will always fail to match
  70. return isLongOption ("--" + option);
  71. }
  72. return text.upToFirstOccurrenceOf ("=", false, false) == option;
  73. }
  74. String ArgumentList::Argument::getLongOptionValue() const
  75. {
  76. if (isLongOption())
  77. {
  78. auto equalsIndex = text.indexOfChar ('=');
  79. if (equalsIndex > 0)
  80. return text.substring (equalsIndex + 1);
  81. }
  82. return {};
  83. }
  84. bool ArgumentList::Argument::isShortOption (char option) const
  85. {
  86. jassert (option != '-'); // this is probably not what you intended to pass in
  87. return isShortOption() && text.containsChar (String (option)[0]);
  88. }
  89. bool ArgumentList::Argument::operator== (StringRef wildcard) const
  90. {
  91. for (auto& o : StringArray::fromTokens (wildcard, "|", {}))
  92. {
  93. if (text == o)
  94. return true;
  95. if (isShortOptionFormat (o) && o.length() == 2 && isShortOption ((char) o[1]))
  96. return true;
  97. if (isLongOptionFormat (o) && isLongOption (o))
  98. return true;
  99. }
  100. return false;
  101. }
  102. bool ArgumentList::Argument::operator!= (StringRef s) const { return ! operator== (s); }
  103. //==============================================================================
  104. ArgumentList::ArgumentList (String exeName, StringArray args)
  105. : executableName (std::move (exeName))
  106. {
  107. args.trim();
  108. args.removeEmptyStrings();
  109. for (auto& a : args)
  110. arguments.add ({ a.unquoted() });
  111. }
  112. ArgumentList::ArgumentList (int argc, char* argv[])
  113. : ArgumentList (argv[0], StringArray (argv + 1, argc - 1))
  114. {
  115. }
  116. ArgumentList::ArgumentList (const String& exeName, const String& args)
  117. : ArgumentList (exeName, StringArray::fromTokens (args, true))
  118. {
  119. }
  120. int ArgumentList::size() const { return arguments.size(); }
  121. ArgumentList::Argument ArgumentList::operator[] (int index) const { return arguments[index]; }
  122. void ArgumentList::checkMinNumArguments (int expectedMinNumberOfArgs) const
  123. {
  124. if (size() < expectedMinNumberOfArgs)
  125. ConsoleApplication::fail ("Not enough arguments!");
  126. }
  127. int ArgumentList::indexOfOption (StringRef option) const
  128. {
  129. jassert (option == String (option).trim()); // passing non-trimmed strings will always fail to find a match!
  130. for (int i = 0; i < arguments.size(); ++i)
  131. if (arguments.getReference (i) == option)
  132. return i;
  133. return -1;
  134. }
  135. bool ArgumentList::containsOption (StringRef option) const
  136. {
  137. return indexOfOption (option) >= 0;
  138. }
  139. bool ArgumentList::removeOptionIfFound (StringRef option)
  140. {
  141. auto i = indexOfOption (option);
  142. if (i >= 0)
  143. arguments.remove (i);
  144. return i >= 0;
  145. }
  146. void ArgumentList::failIfOptionIsMissing (StringRef option) const
  147. {
  148. if (indexOfOption (option) < 0)
  149. ConsoleApplication::fail ("Expected the option " + option);
  150. }
  151. String ArgumentList::getValueForOption (StringRef option) const
  152. {
  153. jassert (isOptionFormat (option)); // the thing you're searching for must be an option
  154. for (int i = 0; i < arguments.size(); ++i)
  155. {
  156. auto& arg = arguments.getReference (i);
  157. if (arg == option)
  158. {
  159. if (arg.isShortOption())
  160. {
  161. if (i < arguments.size() - 1 && ! arguments.getReference (i + 1).isOption())
  162. return arguments.getReference (i + 1).text;
  163. return {};
  164. }
  165. if (arg.isLongOption())
  166. return arg.getLongOptionValue();
  167. }
  168. }
  169. return {};
  170. }
  171. String ArgumentList::removeValueForOption (StringRef option)
  172. {
  173. jassert (isOptionFormat (option)); // the thing you're searching for must be an option
  174. for (int i = 0; i < arguments.size(); ++i)
  175. {
  176. auto& arg = arguments.getReference (i);
  177. if (arg == option)
  178. {
  179. if (arg.isShortOption())
  180. {
  181. if (i < arguments.size() - 1 && ! arguments.getReference (i + 1).isOption())
  182. {
  183. auto result = arguments.getReference (i + 1).text;
  184. arguments.removeRange (i, 2);
  185. return result;
  186. }
  187. arguments.remove (i);
  188. return {};
  189. }
  190. if (arg.isLongOption())
  191. {
  192. auto result = arg.getLongOptionValue();
  193. arguments.remove (i);
  194. return result;
  195. }
  196. }
  197. }
  198. return {};
  199. }
  200. File ArgumentList::getFileForOption (StringRef option) const
  201. {
  202. return resolveFilenameForOption (*this, option, getValueForOption (option));
  203. }
  204. File ArgumentList::getFileForOptionAndRemove (StringRef option)
  205. {
  206. return resolveFilenameForOption (*this, option, removeValueForOption (option));
  207. }
  208. File ArgumentList::getExistingFileForOption (StringRef option) const
  209. {
  210. return checkFileExists (getFileForOption (option));
  211. }
  212. File ArgumentList::getExistingFileForOptionAndRemove (StringRef option)
  213. {
  214. return checkFileExists (getFileForOptionAndRemove (option));
  215. }
  216. File ArgumentList::getExistingFolderForOption (StringRef option) const
  217. {
  218. return checkFolderExists (getFileForOption (option));
  219. }
  220. File ArgumentList::getExistingFolderForOptionAndRemove (StringRef option)
  221. {
  222. return checkFolderExists (getFileForOptionAndRemove (option));
  223. }
  224. //==============================================================================
  225. struct ConsoleAppFailureCode
  226. {
  227. String errorMessage;
  228. int returnCode;
  229. };
  230. void ConsoleApplication::fail (String errorMessage, int returnCode)
  231. {
  232. throw ConsoleAppFailureCode { std::move (errorMessage), returnCode };
  233. }
  234. int ConsoleApplication::invokeCatchingFailures (std::function<int()>&& f)
  235. {
  236. int returnCode = 0;
  237. try
  238. {
  239. returnCode = f();
  240. }
  241. catch (const ConsoleAppFailureCode& error)
  242. {
  243. std::cerr << error.errorMessage << std::endl << std::flush;
  244. returnCode = error.returnCode;
  245. }
  246. return returnCode;
  247. }
  248. const ConsoleApplication::Command* ConsoleApplication::findCommand (const ArgumentList& args, bool optionMustBeFirstArg) const
  249. {
  250. for (auto& c : commands)
  251. {
  252. auto index = args.indexOfOption (c.commandOption);
  253. if (optionMustBeFirstArg ? (index == 0) : (index >= 0))
  254. return &c;
  255. }
  256. if (commandIfNoOthersRecognised >= 0)
  257. return &commands[(size_t) commandIfNoOthersRecognised];
  258. return {};
  259. }
  260. int ConsoleApplication::findAndRunCommand (const ArgumentList& args, bool optionMustBeFirstArg) const
  261. {
  262. return invokeCatchingFailures ([&args, optionMustBeFirstArg, this]
  263. {
  264. if (auto c = findCommand (args, optionMustBeFirstArg))
  265. c->command (args);
  266. else
  267. fail ("Unrecognised arguments");
  268. return 0;
  269. });
  270. }
  271. int ConsoleApplication::findAndRunCommand (int argc, char* argv[]) const
  272. {
  273. return findAndRunCommand (ArgumentList (argc, argv));
  274. }
  275. void ConsoleApplication::addCommand (Command c)
  276. {
  277. commands.emplace_back (std::move (c));
  278. }
  279. void ConsoleApplication::addDefaultCommand (Command c)
  280. {
  281. commandIfNoOthersRecognised = (int) commands.size();
  282. addCommand (std::move (c));
  283. }
  284. void ConsoleApplication::addHelpCommand (String arg, String helpMessage, bool makeDefaultCommand)
  285. {
  286. Command c { arg, arg, "Prints the list of commands", {},
  287. [this, helpMessage] (const ArgumentList& args)
  288. {
  289. std::cout << helpMessage << std::endl;
  290. printCommandList (args);
  291. }};
  292. if (makeDefaultCommand)
  293. addDefaultCommand (std::move (c));
  294. else
  295. addCommand (std::move (c));
  296. }
  297. void ConsoleApplication::addVersionCommand (String arg, String versionText)
  298. {
  299. addCommand ({ arg, arg, "Prints the current version number", {},
  300. [versionText] (const ArgumentList&)
  301. {
  302. std::cout << versionText << std::endl;
  303. }});
  304. }
  305. const std::vector<ConsoleApplication::Command>& ConsoleApplication::getCommands() const
  306. {
  307. return commands;
  308. }
  309. static String getExeNameAndArgs (const ArgumentList& args, const ConsoleApplication::Command& command)
  310. {
  311. auto exeName = args.executableName.fromLastOccurrenceOf ("/", false, false)
  312. .fromLastOccurrenceOf ("\\", false, false);
  313. return " " + exeName + " " + command.argumentDescription;
  314. }
  315. static void printCommandDescription (const ArgumentList& args, const ConsoleApplication::Command& command,
  316. int descriptionIndent)
  317. {
  318. auto nameAndArgs = getExeNameAndArgs (args, command);
  319. if (nameAndArgs.length() > descriptionIndent)
  320. std::cout << nameAndArgs << std::endl << String().paddedRight (' ', descriptionIndent);
  321. else
  322. std::cout << nameAndArgs.paddedRight (' ', descriptionIndent);
  323. std::cout << command.shortDescription << std::endl;
  324. }
  325. void ConsoleApplication::printCommandList (const ArgumentList& args) const
  326. {
  327. int descriptionIndent = 0;
  328. for (auto& c : commands)
  329. descriptionIndent = std::max (descriptionIndent, getExeNameAndArgs (args, c).length());
  330. descriptionIndent = std::min (descriptionIndent + 2, 40);
  331. for (auto& c : commands)
  332. printCommandDescription (args, c, descriptionIndent);
  333. std::cout << std::endl;
  334. }
  335. void ConsoleApplication::printCommandDetails (const ArgumentList& args, const Command& command) const
  336. {
  337. auto len = getExeNameAndArgs (args, command).length();
  338. printCommandDescription (args, command, std::min (len + 3, 40));
  339. if (command.longDescription.isNotEmpty())
  340. std::cout << std::endl << command.longDescription << std::endl;
  341. }
  342. } // namespace juce