-
Notifications
You must be signed in to change notification settings - Fork 193
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Fix #3831 - New (labs) CLI command for measure new
and measure copy
#4875
Changes from all commits
f7d7766
5fc4a13
8e63487
8d630b7
096b02c
a86a24e
3b5ea39
775e802
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -48,9 +48,18 @@ | |
#include <utilities/idd/IddEnums.hxx> | ||
|
||
#include <fmt/format.h> | ||
#include <fmt/ranges.h> | ||
|
||
#include <boost/optional.hpp> | ||
#include <memory> | ||
#include <stdexcept> | ||
|
||
#include <algorithm> // For std::transform | ||
#include <cstdlib> // For std::exit | ||
#include <memory> // make_shared | ||
#include <map> // For std::map | ||
#include <stdexcept> // For std::runtime_error | ||
#include <string> | ||
#include <string_view> | ||
#include <vector> | ||
|
||
namespace openstudio { | ||
namespace cli { | ||
|
@@ -91,6 +100,202 @@ namespace cli { | |
->option_text("PORT") | ||
->excludes(directoryPathOpt); | ||
|
||
{ | ||
auto* newMeasureSubCommand = measureCommand->add_subcommand("new", "Create a new measure"); | ||
|
||
// TODO: this includes 'UtilityMeasure' which I don't think we want to include | ||
std::vector<std::string> measureTypeNames; | ||
{ | ||
const auto& m = openstudio::MeasureType::getNames(); | ||
std::transform(m.cbegin(), m.cend(), back_inserter(measureTypeNames), [](const auto& x) { return x.second; }); | ||
// fmt::print("measureTypeNames={}\n", measureTypeNames); | ||
[[maybe_unused]] auto* measureTypeOpt = | ||
newMeasureSubCommand | ||
->add_option("-t,--type", opt->newMeasureOpts.measureType, | ||
fmt::format("Type of Measure [Default: '{}']: {}", opt->newMeasureOpts.measureType.valueName(), measureTypeNames)) | ||
->option_text("measureType") | ||
->check(CLI::IsMember(measureTypeNames)); | ||
} | ||
Comment on lines
+106
to
+118
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. TODO |
||
|
||
std::vector<std::string> measureLanguageNames; | ||
{ | ||
const auto& m = openstudio::MeasureLanguage::getNames(); | ||
std::transform(m.cbegin(), m.cend(), back_inserter(measureLanguageNames), [](const auto& x) { return x.second; }); | ||
// fmt::print("measureLanguageNames={}\n", measureLanguageNames); | ||
[[maybe_unused]] auto* measureLanguageOpt = newMeasureSubCommand | ||
->add_option("-l,--language", opt->newMeasureOpts.measureLanguage, | ||
fmt::format("Language of Measure [Default: '{}']: {}", | ||
opt->newMeasureOpts.measureLanguage.valueName(), measureLanguageNames)) | ||
->option_text("measureLanguage") | ||
->check(CLI::IsMember(measureLanguageNames)); | ||
} | ||
|
||
newMeasureSubCommand->add_option("-c,--class-name", opt->newMeasureOpts.className, "Measure Class Name [Required]") | ||
->option_text("className") | ||
->required(true); | ||
|
||
newMeasureSubCommand->add_option("-n,--name", opt->newMeasureOpts.name, "Measure Human Readable Name [Default: className]") | ||
->option_text("name") | ||
->required(false); | ||
|
||
std::vector<std::string> taxonomyTags; | ||
std::vector<std::string> firstLevelTaxonomyTerms = openstudio::BCLMeasure::suggestedFirstLevelTaxonomyTerms(); | ||
taxonomyTags.reserve(35); // 2023-05-03: at this moment, this is the total number | ||
for (auto& firstLevelTaxonomyTerm : firstLevelTaxonomyTerms) { | ||
auto secondLevelTaxonomyTerms = openstudio::BCLMeasure::suggestedSecondLevelTaxonomyTerms(firstLevelTaxonomyTerm); | ||
taxonomyTags.reserve(taxonomyTags.size() + secondLevelTaxonomyTerms.size()); | ||
std::transform(secondLevelTaxonomyTerms.begin(), secondLevelTaxonomyTerms.end(), std::back_inserter(taxonomyTags), | ||
[&firstLevelTaxonomyTerm](auto&& secondLevelTaxonomyTerm) { | ||
return fmt::format("{}.{}", firstLevelTaxonomyTerm, secondLevelTaxonomyTerm); | ||
}); | ||
} | ||
|
||
newMeasureSubCommand | ||
->add_option("--taxonomy-tag", opt->newMeasureOpts.taxonomyTag, fmt::format("Taxonomy Tag [Default: '{}']", opt->newMeasureOpts.taxonomyTag)) | ||
->option_text("tag") | ||
->required(false) | ||
->check(CLI::IsMember(taxonomyTags)) | ||
->capture_default_str(); | ||
newMeasureSubCommand->add_option("-d,--description", opt->newMeasureOpts.description, "Description")->option_text("desc")->required(false); | ||
newMeasureSubCommand->add_option("-m,--modeler-description", opt->newMeasureOpts.modelerDescription, "Modeler Description") | ||
->option_text("modeler_desc") | ||
->required(false); | ||
|
||
[[maybe_unused]] auto* directoryPathOpt = | ||
newMeasureSubCommand->add_option("DIRECTORY", opt->newMeasureOpts.directoryPath, "Directory for the measure") | ||
->option_text(" ") | ||
->required(true) | ||
->check(CLI::NonexistentPath); | ||
Comment on lines
+164
to
+168
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Throw if the target directory exists. BCLMeasure's ctor to create a new one would throw anyways, but this throws earlier and a lot more clearly.
|
||
|
||
{ | ||
static constexpr auto helpOptionsGroupName = "Help"; | ||
static constexpr auto newMeasureExamples = R"(Examples: | ||
1. Create a Ruby ModelMeasure: | ||
``` | ||
$ openstudio labs measure new --class-name MyExampleRubyModelMeasure | ||
$ openstudio labs measure new --class-name MyExampleRubyModelMeasure --type ModelMeasure --language Ruby | ||
``` | ||
2. Pass all optional args to create a Python EnergyPlusMeasure: | ||
``` | ||
Comment on lines
+170
to
+183
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Helpers, because I feel like this is going to be more than helpful given the number of options we can optionally pass |
||
$ openstudio labs measure new --class-name MyExamplePythonMeasure --type EnergyPlusMeasure --language Python --name "My Python Measure" --description "This is my measure" --modeler-description "This does complicated stuff" --taxonomy-tag "Envelope.Form" ./test_measure | ||
$ openstudio labs measure new -c MyExamplePythonMeasure -t EnergyPlusMeasure -l Python -n "My Python Measure" -d "This is my measure" -m "This does complicated stuff" --taxonomy-tag "Envelope.Form" ./test_measure | ||
``` | ||
3. List taxonomy tags | ||
``` | ||
$ openstudio labs measure new --list-taxonomy-tags | ||
$ openstudio labs measure new --list-for-first-taxonomy-tag HVAC | ||
``` | ||
)"; | ||
newMeasureSubCommand->set_help_flag("-h,--help", "Print this help message and exit")->group(helpOptionsGroupName); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Since I'm making a specific "Help" group, might as well make the CLI11 default '--help' fall into that group. |
||
|
||
newMeasureSubCommand | ||
->add_flag_callback( | ||
"--examples", | ||
[]() { | ||
fmt::print("{}\n", newMeasureExamples); | ||
std::exit(0); // NOLINT(concurrency-mt-unsafe) | ||
}, | ||
"Show Example usage") | ||
->group(helpOptionsGroupName); | ||
|
||
[[maybe_unused]] auto* listTaxonomyFlag = newMeasureSubCommand | ||
->add_flag_callback( | ||
"--list-taxonomy-tags", | ||
[taxonomyTags]() { | ||
fmt::print("{}\n", taxonomyTags); | ||
std::exit(0); // NOLINT(concurrency-mt-unsafe) | ||
}, | ||
"List all available Taxonomy tags") | ||
->group(helpOptionsGroupName); | ||
Comment on lines
+207
to
+215
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Couple of flags for taxonomy lookup, because even though I could just try to pass Also added one that looks up from a first level tag: NOTE: maybe this should live onto the |
||
|
||
newMeasureSubCommand | ||
->add_option_function<std::string>( | ||
"--list-for-first-taxonomy-tag", | ||
[taxonomyTags](const std::string& firstLevelTaxonomyTerm) { | ||
auto secondLevelTaxonomyTerms = openstudio::BCLMeasure::suggestedSecondLevelTaxonomyTerms(firstLevelTaxonomyTerm); | ||
std::vector<std::string> taxonomyTags; | ||
taxonomyTags.reserve(secondLevelTaxonomyTerms.size()); | ||
std::transform(secondLevelTaxonomyTerms.begin(), secondLevelTaxonomyTerms.end(), std::back_inserter(taxonomyTags), | ||
[&firstLevelTaxonomyTerm](auto&& secondLevelTaxonomyTerm) { | ||
return fmt::format("{}.{}", firstLevelTaxonomyTerm, secondLevelTaxonomyTerm); | ||
}); | ||
fmt::print("{}\n", taxonomyTags); | ||
std::exit(0); // NOLINT(concurrency-mt-unsafe) | ||
}, | ||
"Limit taxonomy tags to this first level") | ||
->option_text("tag") | ||
// ->excludes(listTaxonomyFlag) | ||
->check(CLI::IsMember(firstLevelTaxonomyTerms)) | ||
->group(helpOptionsGroupName); | ||
|
||
// newMeasureSubCommand->footer(std::string{newMeasureExamples}); | ||
} | ||
|
||
newMeasureSubCommand->callback([opt] { | ||
// opt->newMeasureOpts.debug_print(); | ||
|
||
if (opt->newMeasureOpts.name.empty()) { | ||
opt->newMeasureOpts.name = opt->newMeasureOpts.className; | ||
} | ||
Comment on lines
+243
to
+245
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If the "--name" isn't passed, default to the required |
||
|
||
BCLMeasure b(opt->newMeasureOpts.name, opt->newMeasureOpts.className, opt->newMeasureOpts.directoryPath, opt->newMeasureOpts.taxonomyTag, | ||
opt->newMeasureOpts.measureType, opt->newMeasureOpts.description, opt->newMeasureOpts.modelerDescription, | ||
opt->newMeasureOpts.measureLanguage); | ||
|
||
fmt::print("Created a {} {} with class name '{}' in '{}'\n", opt->newMeasureOpts.measureLanguage.valueName(), | ||
opt->newMeasureOpts.measureType.valueName(), opt->newMeasureOpts.className, | ||
openstudio::toString(openstudio::filesystem::canonical(b.directory()))); | ||
|
||
std::exit(0); // NOLINT(concurrency-mt-unsafe) | ||
}); | ||
} | ||
|
||
{ | ||
|
||
auto* copyMeasureSubCommand = measureCommand->add_subcommand("copy", "Copy a measure"); | ||
[[maybe_unused]] auto* existingDirectoryPathOpt = | ||
copyMeasureSubCommand->add_option("EXISTING_DIRECTORY", opt->directoryPath, "Existing Directory for the measure") | ||
->option_text(" ") | ||
->required(true) | ||
->check(CLI::ExistingDirectory); | ||
[[maybe_unused]] auto* newDirectoryPathOpt = | ||
copyMeasureSubCommand->add_option("NEW_DIRECTORY", opt->newMeasureOpts.directoryPath, "New Directory for the measure") | ||
->option_text(" ") | ||
->required(true) | ||
->check(CLI::NonexistentPath); | ||
|
||
copyMeasureSubCommand->callback([opt] { | ||
boost::optional<BCLMeasure> b_; | ||
try { | ||
b_ = BCLMeasure(opt->directoryPath); | ||
} catch (...) { | ||
fmt::print(stderr, "Could not find a valid measure at '{}'\n", openstudio::toString(opt->directoryPath)); | ||
std::exit(1); | ||
} | ||
auto bClone_ = b_->clone(opt->newMeasureOpts.directoryPath); | ||
if (bClone_) { | ||
// Force updating the UID | ||
bClone_->changeUID(); | ||
bClone_->checkForUpdatesFiles(); | ||
bClone_->checkForUpdatesXML(); | ||
bClone_->save(); | ||
fmt::print("Cloned the {} {} with class name '{}' from '{}' to '{}'\n", b_->measureLanguage().valueName(), b_->measureType().valueName(), | ||
b_->className(), openstudio::toString(openstudio::filesystem::canonical(b_->directory())), | ||
openstudio::toString(openstudio::filesystem::canonical(bClone_->directory()))); | ||
std::exit(0); // NOLINT(concurrency-mt-unsafe) | ||
} else { | ||
fmt::print(stderr, "Something went wrong when cloning the measure"); | ||
std::exit(1); // NOLINT(concurrency-mt-unsafe) | ||
} | ||
Comment on lines
+273
to
+295
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Copying a measure. Exit gracefully if the directory isn't a valid measure. Make sure we do end up with a new UID and everything is up to date in the measure.xml. |
||
}); | ||
} | ||
|
||
measureCommand->callback([opt, &rubyEngine, &pythonEngine] { MeasureUpdateOptions::execute(*opt, rubyEngine, pythonEngine); }); | ||
} | ||
|
||
|
@@ -370,7 +575,7 @@ print(f"{{measure_name}}, {{measure_typeinfo}}, {{measure_type}}") | |
} | ||
|
||
void MeasureUpdateOptions::execute(MeasureUpdateOptions const& opt, ScriptEngineInstance& rubyEngine, ScriptEngineInstance& pythonEngine) { | ||
opt.debug_print(); | ||
// opt.debug_print(); | ||
|
||
if (opt.server_port > 0) { | ||
const auto measureManagerCmd = fmt::format( | ||
|
@@ -463,5 +668,17 @@ end | |
fmt::print("\n\n"); | ||
} | ||
|
||
void MeasureNewOptions::debug_print() const { | ||
fmt::print("\nMeasureNewOptions:\n"); | ||
fmt::print("name={}\n", name); | ||
fmt::print("className={}\n", className); | ||
fmt::print("directoryPath={}\n", openstudio::toString(directoryPath)); | ||
fmt::print("taxonomyTag={}\n", taxonomyTag); | ||
fmt::print("measureType={}\n", measureType.valueName()); | ||
fmt::print("description={}\n", description); | ||
fmt::print("modelerDescription={}\n", modelerDescription); | ||
fmt::print("measureLanguage={}\n", measureLanguage.valueName()); | ||
}; | ||
|
||
} // namespace cli | ||
} // namespace openstudio |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -7,14 +7,30 @@ | |
#define CLI_MEASUREUPDATECOMMAND_HPP | ||
|
||
#include <CLI/App.hpp> | ||
|
||
#include "../utilities/core/Filesystem.hpp" | ||
#include "../utilities/bcl/BCLEnums.hpp" | ||
|
||
namespace openstudio { | ||
|
||
class ScriptEngineInstance; | ||
|
||
namespace cli { | ||
|
||
struct MeasureNewOptions | ||
{ | ||
std::string name; | ||
std::string className = "MyExampleMeasure"; | ||
openstudio::path directoryPath; | ||
std::string taxonomyTag = "Envelope.Fenestration"; | ||
openstudio::MeasureType measureType = openstudio::MeasureType::ModelMeasure; | ||
openstudio::MeasureLanguage measureLanguage = openstudio::MeasureLanguage::Ruby; | ||
std::string description = "DESCRIPTION_TEXT"; | ||
std::string modelerDescription = "MODELER_DESCRIPTION_TEXT"; | ||
|
||
void debug_print() const; | ||
}; | ||
Comment on lines
+20
to
+32
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Struct (and its defaults) for new/copy is here |
||
|
||
struct MeasureUpdateOptions | ||
{ | ||
public: | ||
|
@@ -33,6 +49,8 @@ namespace cli { | |
bool run_tests = false; | ||
|
||
unsigned server_port = 0; | ||
|
||
MeasureNewOptions newMeasureOpts; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is added to here. because we use make_shared for scoping issues... don't worry about it |
||
}; | ||
|
||
} // namespace cli | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Throughout the options, I'm going to validate input.
eg try to pass
measure new --type Badval
and you'll get: