mirror of
				https://github.com/davidgiven/fluxengine.git
				synced 2025-10-24 11:11:02 -07:00 
			
		
		
		
	Migrate the 40track etc extension configs to actual options. Add the
ability to have --group=value options to make this cleaner.
This commit is contained in:
		
							
								
								
									
										18
									
								
								build.py
									
									
									
									
									
								
							
							
						
						
									
										18
									
								
								build.py
									
									
									
									
									
								
							| @@ -28,7 +28,7 @@ else: | ||||
|         ("acorndfs", "", "--200"), | ||||
|         ("agat", "", ""), | ||||
|         ("amiga", "", ""), | ||||
|         ("apple2", "", "--140 -c 40track_drive"), | ||||
|         ("apple2", "", "--140 --drivetype=40"), | ||||
|         ("atarist", "", "--360"), | ||||
|         ("atarist", "", "--370"), | ||||
|         ("atarist", "", "--400"), | ||||
| @@ -38,17 +38,17 @@ else: | ||||
|         ("atarist", "", "--800"), | ||||
|         ("atarist", "", "--820"), | ||||
|         ("bk", "", ""), | ||||
|         ("brother", "", "--120 -c 40track_drive"), | ||||
|         ("brother", "", "--120 --drivetype=40"), | ||||
|         ("brother", "", "--240"), | ||||
|         ( | ||||
|             "commodore", | ||||
|             "scripts/commodore1541_test.textpb", | ||||
|             "--171 -c 40track_drive", | ||||
|             "--171 --drivetype=40", | ||||
|         ), | ||||
|         ( | ||||
|             "commodore", | ||||
|             "scripts/commodore1541_test.textpb", | ||||
|             "--192 -c 40track_drive", | ||||
|             "--192 --drivetype=40", | ||||
|         ), | ||||
|         ("commodore", "", "--800"), | ||||
|         ("commodore", "", "--1620"), | ||||
| @@ -60,17 +60,17 @@ else: | ||||
|         ("ibm", "", "--1232"), | ||||
|         ("ibm", "", "--1440"), | ||||
|         ("ibm", "", "--1680"), | ||||
|         ("ibm", "", "--180 -c 40track_drive"), | ||||
|         ("ibm", "", "--160 -c 40track_drive"), | ||||
|         ("ibm", "", "--320 -c 40track_drive"), | ||||
|         ("ibm", "", "--360 -c 40track_drive"), | ||||
|         ("ibm", "", "--180 --drivetype=40"), | ||||
|         ("ibm", "", "--160 --drivetype=40"), | ||||
|         ("ibm", "", "--320 --drivetype=40"), | ||||
|         ("ibm", "", "--360 --drivetype=40"), | ||||
|         ("ibm", "", "--720_96"), | ||||
|         ("ibm", "", "--720_135"), | ||||
|         ("mac", "scripts/mac400_test.textpb", "--400"), | ||||
|         ("mac", "scripts/mac800_test.textpb", "--800"), | ||||
|         ("n88basic", "", ""), | ||||
|         ("rx50", "", ""), | ||||
|         ("tartu", "", "--390 -c 40track_drive"), | ||||
|         ("tartu", "", "--390 --drivetype=40"), | ||||
|         ("tartu", "", "--780"), | ||||
|         ("tids990", "", ""), | ||||
|         ("victor9k", "", "--612"), | ||||
|   | ||||
| @@ -10,6 +10,6 @@ no way to detect this automatically. | ||||
| For example: | ||||
|  | ||||
| ``` | ||||
| fluxengine read -c ibm --180 40track_drive | ||||
| fluxengine read ibm --180 40track_drive | ||||
| ``` | ||||
|  | ||||
|   | ||||
| @@ -11,6 +11,6 @@ connector. | ||||
| For example: | ||||
|  | ||||
| ``` | ||||
| fluxengine read -c apple2 --160 apple2_drive | ||||
| fluxengine read apple2 --160 apple2_drive | ||||
| ``` | ||||
|  | ||||
|   | ||||
| @@ -10,6 +10,6 @@ on Greaseweazle hardware. | ||||
| For example: | ||||
|  | ||||
| ``` | ||||
| fluxengine read -c ibm --720 shugart_drive | ||||
| fluxengine read ibm --720 shugart_drive | ||||
| ``` | ||||
|  | ||||
|   | ||||
| @@ -5,6 +5,7 @@ | ||||
| #include "lib/core/utils.h" | ||||
| #include <fstream> | ||||
| #include <google/protobuf/text_format.h> | ||||
| #include <fmt/ranges.h> | ||||
|  | ||||
| static Config config; | ||||
|  | ||||
| @@ -181,35 +182,8 @@ ConfigProto* Config::combined() | ||||
|     { | ||||
|         _combinedConfig = _baseConfig; | ||||
|  | ||||
|         /* First apply any standalone options. */ | ||||
|  | ||||
|         std::set<std::string> options = _appliedOptions; | ||||
|         for (const auto& option : _baseConfig.option()) | ||||
|         { | ||||
|             if (options.find(option.name()) != options.end()) | ||||
|             { | ||||
|                 _combinedConfig.MergeFrom(option.config()); | ||||
|                 options.erase(option.name()); | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         /* Then apply any group options. */ | ||||
|  | ||||
|         for (auto& group : _baseConfig.option_group()) | ||||
|         { | ||||
|             const OptionProto* selectedOption = &*group.option().begin(); | ||||
|  | ||||
|             for (auto& option : group.option()) | ||||
|             { | ||||
|                 if (options.find(option.name()) != options.end()) | ||||
|                 { | ||||
|                     selectedOption = &option; | ||||
|                     options.erase(option.name()); | ||||
|                 } | ||||
|             } | ||||
|  | ||||
|             _combinedConfig.MergeFrom(selectedOption->config()); | ||||
|         } | ||||
|         for (const auto& optionInfo : _appliedOptions) | ||||
|             _combinedConfig.MergeFrom(optionInfo.option->config()); | ||||
|  | ||||
|         /* Add in the user overrides. */ | ||||
|  | ||||
| @@ -241,51 +215,27 @@ std::vector<std::string> Config::validate() | ||||
| { | ||||
|     std::vector<std::string> results; | ||||
|  | ||||
|     std::set<std::string> optionNames = _appliedOptions; | ||||
|     std::set<const OptionProto*> appliedOptions; | ||||
|     for (const auto& option : _baseConfig.option()) | ||||
|     { | ||||
|         if (optionNames.find(option.name()) != optionNames.end()) | ||||
|     /* Ensure that only one item in each group is set. */ | ||||
|  | ||||
|     std::map<const OptionGroupProto*, const OptionProto*> optionsByGroup; | ||||
|     for (auto [group, option, hasArgument] : _appliedOptions) | ||||
|         if (group) | ||||
|         { | ||||
|             appliedOptions.insert(&option); | ||||
|             optionNames.erase(option.name()); | ||||
|             auto& o = optionsByGroup[group]; | ||||
|             if (o) | ||||
|                 results.push_back( | ||||
|                     fmt::format("multiple mutually exclusive values set for " | ||||
|                                 "group '{}': valid values are: {}", | ||||
|                         group->comment(), | ||||
|                         fmt::join(std::views::transform( | ||||
|                                       group->option(), &OptionProto::name), | ||||
|                             ", "))); | ||||
|             o = option; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /* Then apply any group options. */ | ||||
|  | ||||
|     for (auto& group : _baseConfig.option_group()) | ||||
|     { | ||||
|         int count = 0; | ||||
|  | ||||
|         for (auto& option : group.option()) | ||||
|         { | ||||
|             if (optionNames.find(option.name()) != optionNames.end()) | ||||
|             { | ||||
|                 optionNames.erase(option.name()); | ||||
|                 appliedOptions.insert(&option); | ||||
|  | ||||
|                 count++; | ||||
|                 if (count == 2) | ||||
|                     results.push_back( | ||||
|                         fmt::format("multiple mutually exclusive options set " | ||||
|                                     "for group '{}'", | ||||
|                             group.comment())); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /* Check for unknown options. */ | ||||
|  | ||||
|     if (!optionNames.empty()) | ||||
|     { | ||||
|         for (auto& name : optionNames) | ||||
|             results.push_back(fmt::format("'{}' is not a known option", name)); | ||||
|     } | ||||
|  | ||||
|     /* Check option requirements. */ | ||||
|  | ||||
|     for (auto& option : appliedOptions) | ||||
|     for (auto [group, option, hasArgument] : _appliedOptions) | ||||
|     { | ||||
|         try | ||||
|         { | ||||
| @@ -360,11 +310,12 @@ void Config::readBaseConfig(std::string data) | ||||
|         error("couldn't load external config proto"); | ||||
| } | ||||
|  | ||||
| const OptionProto& Config::findOption(const std::string& optionName) | ||||
| Config::OptionInfo Config::findOption( | ||||
|     const std::string& name, const std::string value) | ||||
| { | ||||
|     const OptionProto* found = nullptr; | ||||
|  | ||||
|     auto searchOptionList = [&](auto& optionList) | ||||
|     auto searchOptionList = [&](auto& optionList, const std::string& optionName) | ||||
|     { | ||||
|         for (const auto& option : optionList) | ||||
|         { | ||||
| @@ -377,17 +328,39 @@ const OptionProto& Config::findOption(const std::string& optionName) | ||||
|         return false; | ||||
|     }; | ||||
|  | ||||
|     if (searchOptionList(base()->option())) | ||||
|         return *found; | ||||
|     /* First look for any group names which match. */ | ||||
|  | ||||
|     if (!value.empty()) | ||||
|         for (const auto& optionGroup : base()->option_group()) | ||||
|             if (optionGroup.name() == name) | ||||
|             { | ||||
|                 /* The option must therefore be one of these. */ | ||||
|  | ||||
|                 if (searchOptionList(optionGroup.option(), value)) | ||||
|                     return {&optionGroup, found, true}; | ||||
|  | ||||
|                 throw OptionNotFoundException(fmt::format( | ||||
|                     "value {} is not valid for option {}; valid values are: {}", | ||||
|                     value, | ||||
|                     name, | ||||
|                     fmt::join(std::views::transform( | ||||
|                                   optionGroup.option(), &OptionProto::name), | ||||
|                         ", "))); | ||||
|             } | ||||
|  | ||||
|     /* Now search for individual options. */ | ||||
|  | ||||
|     if (searchOptionList(base()->option(), name)) | ||||
|         return {nullptr, found, false}; | ||||
|  | ||||
|     for (const auto& optionGroup : base()->option_group()) | ||||
|     { | ||||
|         if (searchOptionList(optionGroup.option())) | ||||
|             return *found; | ||||
|         if (optionGroup.name().empty()) | ||||
|             if (searchOptionList(optionGroup.option(), name)) | ||||
|                 return {nullptr, found, false}; | ||||
|     } | ||||
|  | ||||
|     throw OptionNotFoundException( | ||||
|         fmt::format("option {} not found", optionName)); | ||||
|     throw OptionNotFoundException(fmt::format("option {} not found", name)); | ||||
| } | ||||
|  | ||||
| void Config::checkOptionValid(const OptionProto& option) | ||||
| @@ -445,22 +418,20 @@ bool Config::isOptionValid(const OptionProto& option) | ||||
|     } | ||||
| } | ||||
|  | ||||
| bool Config::isOptionValid(std::string option) | ||||
| { | ||||
|     return isOptionValid(findOption(option)); | ||||
| } | ||||
|  | ||||
| void Config::applyOption(const OptionProto& option) | ||||
| void Config::applyOption(const OptionInfo& optionInfo) | ||||
| { | ||||
|     auto* option = optionInfo.option; | ||||
|     log(OptionLogMessage{ | ||||
|         option.has_message() ? option.message() : option.comment()}); | ||||
|         option->has_message() ? option->message() : option->comment()}); | ||||
|  | ||||
|     _appliedOptions.insert(option.name()); | ||||
|     _appliedOptions.insert(optionInfo); | ||||
| } | ||||
|  | ||||
| void Config::applyOption(std::string option) | ||||
| bool Config::applyOption(const std::string& name, const std::string value) | ||||
| { | ||||
|     applyOption(findOption(option)); | ||||
|     auto optionInfo = findOption(name, value); | ||||
|     applyOption(optionInfo); | ||||
|     return optionInfo.usesValue; | ||||
| } | ||||
|  | ||||
| void Config::clearOptions() | ||||
|   | ||||
| @@ -66,6 +66,18 @@ struct FluxConstructor | ||||
|  | ||||
| class Config | ||||
| { | ||||
| private: | ||||
|     struct OptionInfo | ||||
|     { | ||||
|         bool operator==(const OptionInfo& other) const = default; | ||||
|         std::strong_ordering operator<=>( | ||||
|             const OptionInfo& other) const = default; | ||||
|  | ||||
|         const OptionGroupProto* group; | ||||
|         const OptionProto* option; | ||||
|         bool usesValue; | ||||
|     }; | ||||
|  | ||||
| public: | ||||
|     /* Direct access to the various proto layers. */ | ||||
|  | ||||
| @@ -124,12 +136,12 @@ public: | ||||
|     /* Option management: look up an option by name, determine whether an option | ||||
|      * is valid, and apply an option. */ | ||||
|  | ||||
|     const OptionProto& findOption(const std::string& option); | ||||
|     OptionInfo findOption( | ||||
|         const std::string& name, const std::string value = ""); | ||||
|     void checkOptionValid(const OptionProto& option); | ||||
|     bool isOptionValid(const OptionProto& option); | ||||
|     bool isOptionValid(std::string option); | ||||
|     void applyOption(const OptionProto& option); | ||||
|     void applyOption(std::string option); | ||||
|     void applyOption(const OptionInfo& optionInfo); | ||||
|     bool applyOption(const std::string& name, const std::string value = ""); | ||||
|     void clearOptions(); | ||||
|  | ||||
|     /* Adjust overall inputs and outputs. */ | ||||
| @@ -165,7 +177,7 @@ private: | ||||
|     ConfigProto _baseConfig; | ||||
|     ConfigProto _overridesConfig; | ||||
|     ConfigProto _combinedConfig; | ||||
|     std::set<std::string> _appliedOptions; | ||||
|     std::set<OptionInfo> _appliedOptions; | ||||
|     bool _configValid; | ||||
|  | ||||
|     FluxSourceProto _verificationFluxSourceProto; | ||||
|   | ||||
| @@ -73,5 +73,6 @@ message OptionProto | ||||
| message OptionGroupProto | ||||
| { | ||||
|     optional string comment = 1 [(help) = "help text for option group"]; | ||||
|     repeated OptionProto option = 2; | ||||
|     optional string name = 2 [(help) = "option group name"]; | ||||
|     repeated OptionProto option = 3; | ||||
| } | ||||
|   | ||||
| @@ -158,7 +158,7 @@ std::vector<std::string> FlagGroup::parseFlagsWithFilenames(int argc, | ||||
|                         index += usesthat; | ||||
|                     } | ||||
|                     else | ||||
|                         globalConfig().applyOption(path); | ||||
|                         usesthat = globalConfig().applyOption(path, value); | ||||
|                 } | ||||
|                 else | ||||
|                     error("unrecognised flag '-{}'; try --help", key); | ||||
| @@ -197,6 +197,7 @@ void FlagGroup::parseFlagsWithConfigFiles(int argc, | ||||
|     const char* argv[], | ||||
|     const std::map<std::string, const ConfigProto*>& configFiles) | ||||
| { | ||||
|     globalConfig().readBaseConfigFile("_global_options"); | ||||
|     FlagGroup({this, &configGroup}).parseFlags(argc, argv); | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -1,22 +0,0 @@ | ||||
| comment: 'Adjust configuration for a 40-track drive' | ||||
| is_extension: true | ||||
|  | ||||
| documentation: | ||||
| <<< | ||||
| This is an extension profile; adding this to the command line will configure | ||||
| FluxEngine to read from 40-track, 48tpi 5.25" drives. You have to tell it because there is | ||||
| no way to detect this automatically. | ||||
|  | ||||
| For example: | ||||
|  | ||||
| ``` | ||||
| fluxengine read ibm --180 40track_drive | ||||
| ``` | ||||
| >>> | ||||
|  | ||||
| drive { | ||||
|     tracks: "c0-40h0-1" | ||||
|     drive_type: DRIVETYPE_40TRACK | ||||
| } | ||||
|  | ||||
|  | ||||
							
								
								
									
										77
									
								
								src/formats/_global_options.textpb
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										77
									
								
								src/formats/_global_options.textpb
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,77 @@ | ||||
| comment: 'Options which can be applied everywhere.' | ||||
| is_extension: true | ||||
|  | ||||
| option_group { | ||||
| 	comment: "Drive type" | ||||
|     name: "drivetype" | ||||
|  | ||||
| 	option { | ||||
| 		name: "80" | ||||
| 		comment: '80 track drive' | ||||
|         set_by_default: true | ||||
|  | ||||
| 		config { | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	option { | ||||
| 		name: "40" | ||||
| 		comment: '40 track drive' | ||||
|  | ||||
| 		config { | ||||
|             drive { | ||||
|                 tracks: "c0-40h0-1" | ||||
|                 drive_type: DRIVETYPE_40TRACK | ||||
|             } | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	option { | ||||
| 		name: "160" | ||||
| 		comment: '160 track Apple II drive' | ||||
|  | ||||
| 		config { | ||||
|             drive { | ||||
|                 tracks: "c0-159h0" | ||||
|                 drive_type: DRIVETYPE_APPLE2 | ||||
|             } | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| option_group { | ||||
|     comment: 'Bus interface' | ||||
|     name: "bus" | ||||
|  | ||||
| 	option { | ||||
| 		name: "pc" | ||||
| 		comment: 'PC drive interface' | ||||
|         set_by_default: true | ||||
|     } | ||||
|  | ||||
| 	option { | ||||
| 		name: "shugart" | ||||
| 		comment: 'Shugart bus interface (only on Greaseweazle)' | ||||
|  | ||||
| 		config { | ||||
|             usb { | ||||
|                 greaseweazle { | ||||
|                     bus_type: SHUGART | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
| 	option { | ||||
| 		name: "appleii" | ||||
| 		comment: 'Apple II bus interface (only on Greaseweazle)' | ||||
|  | ||||
| 		config { | ||||
|             usb { | ||||
|                 greaseweazle { | ||||
|                     bus_type: APPLE2 | ||||
|                 } | ||||
|             } | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
| @@ -1,29 +0,0 @@ | ||||
| comment: 'Adjust configuration for a 40-track Apple II drive' | ||||
| is_extension: true | ||||
|  | ||||
| documentation: | ||||
| <<< | ||||
| This is an extension profile; adding this to the command line will configure | ||||
| FluxEngine to adjust the pinout and track spacing to work with an Apple II | ||||
| drive.  This only works on Greaseweazle hardware and requires a custom | ||||
| connector. | ||||
|  | ||||
| For example: | ||||
|  | ||||
| ``` | ||||
| fluxengine read apple2 --160 apple2_drive | ||||
| ``` | ||||
| >>> | ||||
|  | ||||
| usb { | ||||
|     greaseweazle { | ||||
|         bus_type: APPLE2 | ||||
|     } | ||||
| } | ||||
|  | ||||
| drive { | ||||
|     tracks: "c0-159h0" | ||||
|     drive_type: DRIVETYPE_APPLE2 | ||||
| } | ||||
|  | ||||
|  | ||||
| @@ -3,14 +3,13 @@ from build.c import cxxlibrary | ||||
| from scripts.build import protoencode | ||||
|  | ||||
| formats = [ | ||||
|     "40track_drive", | ||||
|     "_global_options", | ||||
|     "acornadfs", | ||||
|     "acorndfs", | ||||
|     "aeslanier", | ||||
|     "agat", | ||||
|     "amiga", | ||||
|     "ampro", | ||||
|     "apple2_drive", | ||||
|     "apple2", | ||||
|     "atarist", | ||||
|     "bk", | ||||
| @@ -33,7 +32,6 @@ formats = [ | ||||
|     "psos", | ||||
|     "rolandd20", | ||||
|     "rx50", | ||||
|     "shugart_drive", | ||||
|     "smaky6", | ||||
|     "tartu", | ||||
|     "ti99", | ||||
|   | ||||
| @@ -1,22 +0,0 @@ | ||||
| comment: 'Adjust configuration for a Shugart drive' | ||||
| is_extension: true | ||||
|  | ||||
| documentation: | ||||
| <<< | ||||
| This is an extension profile; adding this to the command line will configure | ||||
| FluxEngine to adjust the pinout to work with a Shugart drive. This only works | ||||
| on Greaseweazle hardware. | ||||
|  | ||||
| For example: | ||||
|  | ||||
| ``` | ||||
| fluxengine read ibm --720 shugart_drive | ||||
| ``` | ||||
| >>> | ||||
|  | ||||
| usb { | ||||
|     greaseweazle { | ||||
|         bus_type: SHUGART | ||||
|     } | ||||
| } | ||||
|  | ||||
| @@ -55,14 +55,14 @@ static void test_option_validity() | ||||
|         } | ||||
| 	)M"); | ||||
|  | ||||
|     AssertThat( | ||||
|         globalConfig().isOptionValid(globalConfig().findOption("option1")), | ||||
|     AssertThat(globalConfig().isOptionValid( | ||||
|                    *globalConfig().findOption("option1").option), | ||||
|         Equals(true)); | ||||
|     AssertThat( | ||||
|         globalConfig().isOptionValid(globalConfig().findOption("option2")), | ||||
|     AssertThat(globalConfig().isOptionValid( | ||||
|                    *globalConfig().findOption("option2").option), | ||||
|         Equals(false)); | ||||
|     AssertThat( | ||||
|         globalConfig().isOptionValid(globalConfig().findOption("option3")), | ||||
|     AssertThat(globalConfig().isOptionValid( | ||||
|                    *globalConfig().findOption("option3").option), | ||||
|         Equals(true)); | ||||
| } | ||||
|  | ||||
|   | ||||
		Reference in New Issue
	
	Block a user