|
- /*
- ==============================================================================
-
- This file is part of the JUCE library.
- Copyright (c) 2022 - Raw Material Software Limited
-
- JUCE is an open source library subject to commercial or open-source
- licensing.
-
- The code included in this file is provided under the terms of the ISC license
- http://www.isc.org/downloads/software-support-policy/isc-license. Permission
- To use, copy, modify, and/or distribute this software for any purpose with or
- without fee is hereby granted provided that the above copyright notice and
- this permission notice appear in all copies.
-
- JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
- EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
- DISCLAIMED.
-
- ==============================================================================
- */
-
- namespace juce
- {
-
- static File resolveFilename (const String& name)
- {
- return File::getCurrentWorkingDirectory().getChildFile (name.unquoted());
- }
-
- static File checkFileExists (const File& f)
- {
- if (! f.exists())
- ConsoleApplication::fail ("Could not find file: " + f.getFullPathName());
-
- return f;
- }
-
- static File checkFolderExists (const File& f)
- {
- if (! f.isDirectory())
- ConsoleApplication::fail ("Could not find folder: " + f.getFullPathName());
-
- return f;
- }
-
- static File resolveFilenameForOption (const ArgumentList& args, StringRef option, const String& filename)
- {
- if (filename.isEmpty())
- {
- args.failIfOptionIsMissing (option);
- ConsoleApplication::fail ("Expected a filename after the " + option + " option");
- }
-
- return resolveFilename (filename);
- }
-
- File ArgumentList::Argument::resolveAsFile() const
- {
- return resolveFilename (text);
- }
-
- File ArgumentList::Argument::resolveAsExistingFile() const
- {
- return checkFileExists (resolveAsFile());
- }
-
- File ArgumentList::Argument::resolveAsExistingFolder() const
- {
- auto f = resolveAsFile();
-
- if (! f.isDirectory())
- ConsoleApplication::fail ("Could not find folder: " + f.getFullPathName());
-
- return f;
- }
-
- static bool isShortOptionFormat (StringRef s) { return s[0] == '-' && s[1] != '-'; }
- static bool isLongOptionFormat (StringRef s) { return s[0] == '-' && s[1] == '-' && s[2] != '-'; }
- static bool isOptionFormat (StringRef s) { return s[0] == '-'; }
-
- bool ArgumentList::Argument::isLongOption() const { return isLongOptionFormat (text); }
- bool ArgumentList::Argument::isShortOption() const { return isShortOptionFormat (text); }
- bool ArgumentList::Argument::isOption() const { return isOptionFormat (text); }
-
- bool ArgumentList::Argument::isLongOption (const String& option) const
- {
- if (! isLongOptionFormat (option))
- {
- jassert (! isShortOptionFormat (option)); // this will always fail to match
- return isLongOption ("--" + option);
- }
-
- return text.upToFirstOccurrenceOf ("=", false, false) == option;
- }
-
- String ArgumentList::Argument::getLongOptionValue() const
- {
- if (isLongOption())
- {
- auto equalsIndex = text.indexOfChar ('=');
-
- if (equalsIndex > 0)
- return text.substring (equalsIndex + 1);
- }
-
- return {};
- }
-
- bool ArgumentList::Argument::isShortOption (char option) const
- {
- jassert (option != '-'); // this is probably not what you intended to pass in
-
- return isShortOption() && text.containsChar (String (option)[0]);
- }
-
- bool ArgumentList::Argument::operator== (StringRef wildcard) const
- {
- for (auto& o : StringArray::fromTokens (wildcard, "|", {}))
- {
- if (text == o)
- return true;
-
- if (isShortOptionFormat (o) && o.length() == 2 && isShortOption ((char) o[1]))
- return true;
-
- if (isLongOptionFormat (o) && isLongOption (o))
- return true;
- }
-
- return false;
- }
-
- bool ArgumentList::Argument::operator!= (StringRef s) const { return ! operator== (s); }
-
- //==============================================================================
- ArgumentList::ArgumentList (String exeName, StringArray args)
- : executableName (std::move (exeName))
- {
- args.trim();
- args.removeEmptyStrings();
-
- for (auto& a : args)
- arguments.add ({ a.unquoted() });
- }
-
- ArgumentList::ArgumentList (int argc, char* argv[])
- : ArgumentList (argv[0], StringArray (argv + 1, argc - 1))
- {
- }
-
- ArgumentList::ArgumentList (const String& exeName, const String& args)
- : ArgumentList (exeName, StringArray::fromTokens (args, true))
- {
- }
-
- int ArgumentList::size() const { return arguments.size(); }
- ArgumentList::Argument ArgumentList::operator[] (int index) const { return arguments[index]; }
-
- void ArgumentList::checkMinNumArguments (int expectedMinNumberOfArgs) const
- {
- if (size() < expectedMinNumberOfArgs)
- ConsoleApplication::fail ("Not enough arguments!");
- }
-
- int ArgumentList::indexOfOption (StringRef option) const
- {
- jassert (option == String (option).trim()); // passing non-trimmed strings will always fail to find a match!
-
- for (int i = 0; i < arguments.size(); ++i)
- if (arguments.getReference (i) == option)
- return i;
-
- return -1;
- }
-
- bool ArgumentList::containsOption (StringRef option) const
- {
- return indexOfOption (option) >= 0;
- }
-
- bool ArgumentList::removeOptionIfFound (StringRef option)
- {
- auto i = indexOfOption (option);
-
- if (i >= 0)
- arguments.remove (i);
-
- return i >= 0;
- }
-
- void ArgumentList::failIfOptionIsMissing (StringRef option) const
- {
- if (indexOfOption (option) < 0)
- ConsoleApplication::fail ("Expected the option " + option);
- }
-
- String ArgumentList::getValueForOption (StringRef option) const
- {
- jassert (isOptionFormat (option)); // the thing you're searching for must be an option
-
- for (int i = 0; i < arguments.size(); ++i)
- {
- auto& arg = arguments.getReference (i);
-
- if (arg == option)
- {
- if (arg.isShortOption())
- {
- if (i < arguments.size() - 1 && ! arguments.getReference (i + 1).isOption())
- return arguments.getReference (i + 1).text;
-
- return {};
- }
-
- if (arg.isLongOption())
- return arg.getLongOptionValue();
- }
- }
-
- return {};
- }
-
- String ArgumentList::removeValueForOption (StringRef option)
- {
- jassert (isOptionFormat (option)); // the thing you're searching for must be an option
-
- for (int i = 0; i < arguments.size(); ++i)
- {
- auto& arg = arguments.getReference (i);
-
- if (arg == option)
- {
- if (arg.isShortOption())
- {
- if (i < arguments.size() - 1 && ! arguments.getReference (i + 1).isOption())
- {
- auto result = arguments.getReference (i + 1).text;
- arguments.removeRange (i, 2);
- return result;
- }
-
- arguments.remove (i);
- return {};
- }
-
- if (arg.isLongOption())
- {
- auto result = arg.getLongOptionValue();
- arguments.remove (i);
- return result;
- }
- }
- }
-
- return {};
- }
-
- File ArgumentList::getFileForOption (StringRef option) const
- {
- return resolveFilenameForOption (*this, option, getValueForOption (option));
- }
-
- File ArgumentList::getFileForOptionAndRemove (StringRef option)
- {
- return resolveFilenameForOption (*this, option, removeValueForOption (option));
- }
-
- File ArgumentList::getExistingFileForOption (StringRef option) const
- {
- return checkFileExists (getFileForOption (option));
- }
-
- File ArgumentList::getExistingFileForOptionAndRemove (StringRef option)
- {
- return checkFileExists (getFileForOptionAndRemove (option));
- }
-
- File ArgumentList::getExistingFolderForOption (StringRef option) const
- {
- return checkFolderExists (getFileForOption (option));
- }
-
- File ArgumentList::getExistingFolderForOptionAndRemove (StringRef option)
- {
- return checkFolderExists (getFileForOptionAndRemove (option));
- }
-
- //==============================================================================
- struct ConsoleAppFailureCode
- {
- String errorMessage;
- int returnCode;
- };
-
- void ConsoleApplication::fail (String errorMessage, int returnCode)
- {
- throw ConsoleAppFailureCode { std::move (errorMessage), returnCode };
- }
-
- int ConsoleApplication::invokeCatchingFailures (std::function<int()>&& f)
- {
- int returnCode = 0;
-
- try
- {
- returnCode = f();
- }
- catch (const ConsoleAppFailureCode& error)
- {
- std::cerr << error.errorMessage << std::endl << std::flush;
- returnCode = error.returnCode;
- }
-
- return returnCode;
- }
-
- const ConsoleApplication::Command* ConsoleApplication::findCommand (const ArgumentList& args, bool optionMustBeFirstArg) const
- {
- for (auto& c : commands)
- {
- auto index = args.indexOfOption (c.commandOption);
-
- if (optionMustBeFirstArg ? (index == 0) : (index >= 0))
- return &c;
- }
-
- if (commandIfNoOthersRecognised >= 0)
- return &commands[(size_t) commandIfNoOthersRecognised];
-
- return {};
- }
-
- int ConsoleApplication::findAndRunCommand (const ArgumentList& args, bool optionMustBeFirstArg) const
- {
- return invokeCatchingFailures ([&args, optionMustBeFirstArg, this]
- {
- if (auto c = findCommand (args, optionMustBeFirstArg))
- c->command (args);
- else
- fail ("Unrecognised arguments");
-
- return 0;
- });
- }
-
- int ConsoleApplication::findAndRunCommand (int argc, char* argv[]) const
- {
- return findAndRunCommand (ArgumentList (argc, argv));
- }
-
- void ConsoleApplication::addCommand (Command c)
- {
- commands.emplace_back (std::move (c));
- }
-
- void ConsoleApplication::addDefaultCommand (Command c)
- {
- commandIfNoOthersRecognised = (int) commands.size();
- addCommand (std::move (c));
- }
-
- void ConsoleApplication::addHelpCommand (String arg, String helpMessage, bool makeDefaultCommand)
- {
- Command c { arg, arg, "Prints the list of commands", {},
- [this, helpMessage] (const ArgumentList& args)
- {
- std::cout << helpMessage << std::endl;
- printCommandList (args);
- }};
-
- if (makeDefaultCommand)
- addDefaultCommand (std::move (c));
- else
- addCommand (std::move (c));
- }
-
- void ConsoleApplication::addVersionCommand (String arg, String versionText)
- {
- addCommand ({ arg, arg, "Prints the current version number", {},
- [versionText] (const ArgumentList&)
- {
- std::cout << versionText << std::endl;
- }});
- }
-
- const std::vector<ConsoleApplication::Command>& ConsoleApplication::getCommands() const
- {
- return commands;
- }
-
- static String getExeNameAndArgs (const ArgumentList& args, const ConsoleApplication::Command& command)
- {
- auto exeName = args.executableName.fromLastOccurrenceOf ("/", false, false)
- .fromLastOccurrenceOf ("\\", false, false);
-
- return " " + exeName + " " + command.argumentDescription;
- }
-
- static void printCommandDescription (const ArgumentList& args, const ConsoleApplication::Command& command,
- int descriptionIndent)
- {
- auto nameAndArgs = getExeNameAndArgs (args, command);
-
- if (nameAndArgs.length() > descriptionIndent)
- std::cout << nameAndArgs << std::endl << String().paddedRight (' ', descriptionIndent);
- else
- std::cout << nameAndArgs.paddedRight (' ', descriptionIndent);
-
- std::cout << command.shortDescription << std::endl;
- }
-
- void ConsoleApplication::printCommandList (const ArgumentList& args) const
- {
- int descriptionIndent = 0;
-
- for (auto& c : commands)
- descriptionIndent = std::max (descriptionIndent, getExeNameAndArgs (args, c).length());
-
- descriptionIndent = std::min (descriptionIndent + 2, 40);
-
- for (auto& c : commands)
- printCommandDescription (args, c, descriptionIndent);
-
- std::cout << std::endl;
- }
-
- void ConsoleApplication::printCommandDetails (const ArgumentList& args, const Command& command) const
- {
- auto len = getExeNameAndArgs (args, command).length();
-
- printCommandDescription (args, command, std::min (len + 3, 40));
-
- if (command.longDescription.isNotEmpty())
- std::cout << std::endl << command.longDescription << std::endl;
- }
-
-
- } // namespace juce
|