2 ==============================================================================
4 This file is part of the JUCE library.
5 Copyright (c) 2022 - Raw Material Software Limited
7 JUCE is an open source library subject to commercial or open-source
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.
16 JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
17 EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
20 ==============================================================================
26 static File
resolveFilename (const String
& name
)
28 return File::getCurrentWorkingDirectory().getChildFile (name
.unquoted());
31 static File
checkFileExists (const File
& f
)
34 ConsoleApplication::fail ("Could not find file: " + f
.getFullPathName());
39 static File
checkFolderExists (const File
& f
)
41 if (! f
.isDirectory())
42 ConsoleApplication::fail ("Could not find folder: " + f
.getFullPathName());
47 static File
resolveFilenameForOption (const ArgumentList
& args
, StringRef option
, const String
& filename
)
49 if (filename
.isEmpty())
51 args
.failIfOptionIsMissing (option
);
52 ConsoleApplication::fail ("Expected a filename after the " + option
+ " option");
55 return resolveFilename (filename
);
58 File
ArgumentList::Argument::resolveAsFile() const
60 return resolveFilename (text
);
63 File
ArgumentList::Argument::resolveAsExistingFile() const
65 return checkFileExists (resolveAsFile());
68 File
ArgumentList::Argument::resolveAsExistingFolder() const
70 auto f
= resolveAsFile();
72 if (! f
.isDirectory())
73 ConsoleApplication::fail ("Could not find folder: " + f
.getFullPathName());
78 static bool isShortOptionFormat (StringRef s
) { return s
[0] == '-' && s
[1] != '-'; }
79 static bool isLongOptionFormat (StringRef s
) { return s
[0] == '-' && s
[1] == '-' && s
[2] != '-'; }
80 static bool isOptionFormat (StringRef s
) { return s
[0] == '-'; }
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
); }
86 bool ArgumentList::Argument::isLongOption (const String
& option
) const
88 if (! isLongOptionFormat (option
))
90 jassert (! isShortOptionFormat (option
)); // this will always fail to match
91 return isLongOption ("--" + option
);
94 return text
.upToFirstOccurrenceOf ("=", false, false) == option
;
97 String
ArgumentList::Argument::getLongOptionValue() const
101 auto equalsIndex
= text
.indexOfChar ('=');
104 return text
.substring (equalsIndex
+ 1);
110 bool ArgumentList::Argument::isShortOption (char option
) const
112 jassert (option
!= '-'); // this is probably not what you intended to pass in
114 return isShortOption() && text
.containsChar (String (option
)[0]);
117 bool ArgumentList::Argument::operator== (StringRef wildcard
) const
119 for (auto& o
: StringArray::fromTokens (wildcard
, "|", {}))
124 if (isShortOptionFormat (o
) && o
.length() == 2 && isShortOption ((char) o
[1]))
127 if (isLongOptionFormat (o
) && isLongOption (o
))
134 bool ArgumentList::Argument::operator!= (StringRef s
) const { return ! operator== (s
); }
136 //==============================================================================
137 ArgumentList::ArgumentList (String exeName
, StringArray args
)
138 : executableName (std::move (exeName
))
141 args
.removeEmptyStrings();
144 arguments
.add ({ a
.unquoted() });
147 ArgumentList::ArgumentList (int argc
, char* argv
[])
148 : ArgumentList (argv
[0], StringArray (argv
+ 1, argc
- 1))
152 ArgumentList::ArgumentList (const String
& exeName
, const String
& args
)
153 : ArgumentList (exeName
, StringArray::fromTokens (args
, true))
157 int ArgumentList::size() const { return arguments
.size(); }
158 ArgumentList::Argument
ArgumentList::operator[] (int index
) const { return arguments
[index
]; }
160 void ArgumentList::checkMinNumArguments (int expectedMinNumberOfArgs
) const
162 if (size() < expectedMinNumberOfArgs
)
163 ConsoleApplication::fail ("Not enough arguments!");
166 int ArgumentList::indexOfOption (StringRef option
) const
168 jassert (option
== String (option
).trim()); // passing non-trimmed strings will always fail to find a match!
170 for (int i
= 0; i
< arguments
.size(); ++i
)
171 if (arguments
.getReference (i
) == option
)
177 bool ArgumentList::containsOption (StringRef option
) const
179 return indexOfOption (option
) >= 0;
182 bool ArgumentList::removeOptionIfFound (StringRef option
)
184 auto i
= indexOfOption (option
);
187 arguments
.remove (i
);
192 void ArgumentList::failIfOptionIsMissing (StringRef option
) const
194 if (indexOfOption (option
) < 0)
195 ConsoleApplication::fail ("Expected the option " + option
);
198 String
ArgumentList::getValueForOption (StringRef option
) const
200 jassert (isOptionFormat (option
)); // the thing you're searching for must be an option
202 for (int i
= 0; i
< arguments
.size(); ++i
)
204 auto& arg
= arguments
.getReference(i
);
208 if (arg
.isShortOption())
210 if (i
< arguments
.size() - 1 && ! arguments
.getReference (i
+ 1).isOption())
211 return arguments
.getReference (i
+ 1).text
;
216 if (arg
.isLongOption())
217 return arg
.getLongOptionValue();
224 String
ArgumentList::removeValueForOption (StringRef option
)
226 jassert (isOptionFormat (option
)); // the thing you're searching for must be an option
228 for (int i
= 0; i
< arguments
.size(); ++i
)
230 auto& arg
= arguments
.getReference(i
);
234 if (arg
.isShortOption())
236 if (i
< arguments
.size() - 1 && ! arguments
.getReference (i
+ 1).isOption())
238 auto result
= arguments
.getReference (i
+ 1).text
;
239 arguments
.removeRange (i
, 2);
243 arguments
.remove (i
);
247 if (arg
.isLongOption())
249 auto result
= arg
.getLongOptionValue();
250 arguments
.remove (i
);
259 File
ArgumentList::getFileForOption (StringRef option
) const
261 return resolveFilenameForOption (*this, option
, getValueForOption (option
));
264 File
ArgumentList::getFileForOptionAndRemove (StringRef option
)
266 return resolveFilenameForOption (*this, option
, removeValueForOption (option
));
269 File
ArgumentList::getExistingFileForOption (StringRef option
) const
271 return checkFileExists (getFileForOption (option
));
274 File
ArgumentList::getExistingFileForOptionAndRemove (StringRef option
)
276 return checkFileExists (getFileForOptionAndRemove (option
));
279 File
ArgumentList::getExistingFolderForOption (StringRef option
) const
281 return checkFolderExists (getFileForOption (option
));
284 File
ArgumentList::getExistingFolderForOptionAndRemove (StringRef option
)
286 return checkFolderExists (getFileForOptionAndRemove (option
));
289 //==============================================================================
290 struct ConsoleAppFailureCode
296 void ConsoleApplication::fail (String errorMessage
, int returnCode
)
298 throw ConsoleAppFailureCode
{ std::move (errorMessage
), returnCode
};
301 int ConsoleApplication::invokeCatchingFailures (std::function
<int()>&& f
)
309 catch (const ConsoleAppFailureCode
& error
)
311 std::cerr
<< error
.errorMessage
<< std::endl
;
312 returnCode
= error
.returnCode
;
318 const ConsoleApplication::Command
* ConsoleApplication::findCommand (const ArgumentList
& args
, bool optionMustBeFirstArg
) const
320 for (auto& c
: commands
)
322 auto index
= args
.indexOfOption (c
.commandOption
);
324 if (optionMustBeFirstArg
? (index
== 0) : (index
>= 0))
328 if (commandIfNoOthersRecognised
>= 0)
329 return &commands
[(size_t) commandIfNoOthersRecognised
];
334 int ConsoleApplication::findAndRunCommand (const ArgumentList
& args
, bool optionMustBeFirstArg
) const
336 return invokeCatchingFailures ([&args
, optionMustBeFirstArg
, this]
338 if (auto c
= findCommand (args
, optionMustBeFirstArg
))
341 fail ("Unrecognised arguments");
347 int ConsoleApplication::findAndRunCommand (int argc
, char* argv
[]) const
349 return findAndRunCommand (ArgumentList (argc
, argv
));
352 void ConsoleApplication::addCommand (Command c
)
354 commands
.emplace_back (std::move (c
));
357 void ConsoleApplication::addDefaultCommand (Command c
)
359 commandIfNoOthersRecognised
= (int) commands
.size();
360 addCommand (std::move (c
));
363 void ConsoleApplication::addHelpCommand (String arg
, String helpMessage
, bool makeDefaultCommand
)
365 Command c
{ arg
, arg
, "Prints the list of commands", {},
366 [this, helpMessage
] (const ArgumentList
& args
)
368 std::cout
<< helpMessage
<< std::endl
;
369 printCommandList (args
);
372 if (makeDefaultCommand
)
373 addDefaultCommand (std::move (c
));
375 addCommand (std::move (c
));
378 void ConsoleApplication::addVersionCommand (String arg
, String versionText
)
380 addCommand ({ arg
, arg
, "Prints the current version number", {},
381 [versionText
] (const ArgumentList
&)
383 std::cout
<< versionText
<< std::endl
;
387 const std::vector
<ConsoleApplication::Command
>& ConsoleApplication::getCommands() const
392 static String
getExeNameAndArgs (const ArgumentList
& args
, const ConsoleApplication::Command
& command
)
394 auto exeName
= args
.executableName
.fromLastOccurrenceOf ("/", false, false)
395 .fromLastOccurrenceOf ("\\", false, false);
397 return " " + exeName
+ " " + command
.argumentDescription
;
400 static void printCommandDescription (const ArgumentList
& args
, const ConsoleApplication::Command
& command
,
401 int descriptionIndent
)
403 auto nameAndArgs
= getExeNameAndArgs (args
, command
);
405 if (nameAndArgs
.length() > descriptionIndent
)
406 std::cout
<< nameAndArgs
<< std::endl
<< String().paddedRight (' ', descriptionIndent
);
408 std::cout
<< nameAndArgs
.paddedRight (' ', descriptionIndent
);
410 std::cout
<< command
.shortDescription
<< std::endl
;
413 void ConsoleApplication::printCommandList (const ArgumentList
& args
) const
415 int descriptionIndent
= 0;
417 for (auto& c
: commands
)
418 descriptionIndent
= std::max (descriptionIndent
, getExeNameAndArgs (args
, c
).length());
420 descriptionIndent
= std::min (descriptionIndent
+ 2, 40);
422 for (auto& c
: commands
)
423 printCommandDescription (args
, c
, descriptionIndent
);
425 std::cout
<< std::endl
;
428 void ConsoleApplication::printCommandDetails (const ArgumentList
& args
, const Command
& command
) const
430 auto len
= getExeNameAndArgs (args
, command
).length();
432 printCommandDescription (args
, command
, std::min (len
+ 3, 40));
434 if (command
.longDescription
.isNotEmpty())
435 std::cout
<< std::endl
<< command
.longDescription
<< std::endl
;