Skip to content

Commit

Permalink
Merge pull request #4875 from NREL/3831_measure_new_CLI
Browse files Browse the repository at this point in the history
Fix #3831 -  New (labs) CLI command for `measure new` and `measure copy`
  • Loading branch information
jmarrec authored Jun 29, 2023
2 parents 26de116 + 775e802 commit ad5503e
Show file tree
Hide file tree
Showing 2 changed files with 238 additions and 3 deletions.
223 changes: 220 additions & 3 deletions src/cli/MeasureUpdateCommand.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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));
}

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);

{
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:
```
$ 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);

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);

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;
}

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)
}
});
}

measureCommand->callback([opt, &rubyEngine, &pythonEngine] { MeasureUpdateOptions::execute(*opt, rubyEngine, pythonEngine); });
}

Expand Down Expand Up @@ -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(
Expand Down Expand Up @@ -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
18 changes: 18 additions & 0 deletions src/cli/MeasureUpdateCommand.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
};

struct MeasureUpdateOptions
{
public:
Expand All @@ -33,6 +49,8 @@ namespace cli {
bool run_tests = false;

unsigned server_port = 0;

MeasureNewOptions newMeasureOpts;
};

} // namespace cli
Expand Down

0 comments on commit ad5503e

Please sign in to comment.