From 25750e7495e178fc22a07cd1d9dcfd34c7141036 Mon Sep 17 00:00:00 2001 From: "A. Wilcox" Date: Mon, 21 Oct 2019 00:18:02 -0500 Subject: hscript: Implement Language, add tests --- hscript/meta.cc | 72 ++++++++++++++++++++++ hscript/meta.hh | 9 ++- hscript/script.cc | 63 +++++++++++++------ tests/fixtures/0115-language-simple.installfile | 6 ++ tests/fixtures/0116-language-country.installfile | 6 ++ tests/fixtures/0117-language-codeset.installfile | 6 ++ .../fixtures/0118-language-c-exception.installfile | 6 ++ tests/fixtures/0119-language-invalid.installfile | 6 ++ tests/fixtures/0120-language-iso.installfile | 6 ++ tests/fixtures/0121-language-duplicate.installfile | 7 +++ tests/spec/validator_spec.rb | 43 +++++++++++++ 11 files changed, 209 insertions(+), 21 deletions(-) create mode 100644 tests/fixtures/0115-language-simple.installfile create mode 100644 tests/fixtures/0116-language-country.installfile create mode 100644 tests/fixtures/0117-language-codeset.installfile create mode 100644 tests/fixtures/0118-language-c-exception.installfile create mode 100644 tests/fixtures/0119-language-invalid.installfile create mode 100644 tests/fixtures/0120-language-iso.installfile create mode 100644 tests/fixtures/0121-language-duplicate.installfile diff --git a/hscript/meta.cc b/hscript/meta.cc index 7e8cfe8..e5e0ba4 100644 --- a/hscript/meta.cc +++ b/hscript/meta.cc @@ -13,6 +13,7 @@ #include #include #include +#include #include #ifdef HAS_INSTALL_ENV # include @@ -179,6 +180,77 @@ Key *PkgInstall::parseFromData(const std::string &data, int lineno, int *errors, } +/* All ISO 639-1 language codes. + * Source: https://www.loc.gov/standards/iso639-2/ISO-639-2_utf-8.txt + * Python to construct table: + * >>> f = open('ISO-639-2_utf-8.txt') + * >>> x = csv.reader(f, delimiter='|') + * >>> langs = [lang[2] for lang in iter(x) if lang != ''] + * >>> print('"' + '", "'.join(langs) + '", "C."') + */ +const std::set valid_langs = { + "aa", "ab", "af", "ak", "sq", "am", "ar", "an", "hy", "as", "av", "ae", + "ay", "az", "ba", "bm", "eu", "be", "bn", "bh", "bi", "bs", "br", "bg", + "my", "ca", "ch", "ce", "zh", "cu", "cv", "kw", "co", "cr", "cs", "da", + "dv", "nl", "dz", "en", "eo", "et", "ee", "fo", "fj", "fi", "fr", "fy", + "ff", "ka", "de", "gd", "ga", "gl", "gv", "el", "gn", "gu", "ht", "ha", + "he", "hz", "hi", "ho", "hr", "hu", "ig", "is", "io", "ii", "iu", "ie", + "ia", "id", "ik", "it", "jv", "ja", "kl", "kn", "ks", "kr", "kk", "km", + "ki", "rw", "ky", "kv", "kg", "ko", "kj", "ku", "lo", "la", "lv", "li", + "ln", "lt", "lb", "lu", "lg", "mk", "mh", "ml", "mi", "mr", "ms", "mg", + "mt", "mn", "na", "nv", "nr", "nd", "ng", "ne", "nn", "nb", "no", "ny", + "oc", "oj", "or", "om", "os", "pa", "fa", "pi", "pl", "pt", "ps", "qu", + "rm", "ro", "rn", "ru", "sg", "sa", "si", "sk", "sl", "se", "sm", "sn", + "sd", "so", "st", "es", "sc", "sr", "ss", "su", "sw", "sv", "ty", "ta", + "tt", "te", "tg", "tl", "th", "bo", "ti", "to", "tn", "ts", "tk", "tr", + "tw", "ug", "uk", "ur", "uz", "ve", "vi", "vo", "cy", "wa", "wo", "xh", + "yi", "yo", "za", "zu", "C." +}; + + +Key *Language::parseFromData(const std::string &data, int lineno, int *errors, + int *warnings) { + if(data.length() < 2 || + valid_langs.find(data.substr(0, 2)) == valid_langs.end()) { + if(errors) *errors += 1; + output_error("installfile:" + std::to_string(lineno), + "language: invalid language specified", + "language must be a valid ISO 639-1 language code"); + return nullptr; + } + + /* We know a valid language appears, but is it real? */ + if(data.length() > 2) { + /* data[1] is . if language is C.UTF-8 */ + if(data[2] != '_' && data[1] != '.') { + if(errors) *errors += 1; + output_error("installfile:" + std::to_string(lineno), + "language: invalid language specified", + "language must be a valid ISO 639-1 language code, " + "optionally followed by '_' and a country code"); + return nullptr; + } + /* we don't really care about the country code, but we do care about + * codeset - we (via musl) *only* support UTF-8. */ + std::string::size_type dot = data.find_first_of('.'); + if(dot != std::string::npos && data.substr(dot+1, 5) != "UTF-8") { + if(errors) *errors += 1; + output_error("installfile:" + std::to_string(lineno), + "language: invalid language specified", + "you cannot specify a non-UTF-8 codeset"); + return nullptr; + } + } + + return new Language(lineno, data); +} + + +bool Language::execute(ScriptOptions) const { + return false; +} + + Key *Firmware::parseFromData(const std::string &data, int lineno, int *errors, int *warnings) { bool value; diff --git a/hscript/meta.hh b/hscript/meta.hh index 8a50576..7dc3f68 100644 --- a/hscript/meta.hh +++ b/hscript/meta.hh @@ -45,6 +45,13 @@ public: }; class Language : public StringKey { +private: + Language(int _line, const std::string &my_lang) : + StringKey(_line, my_lang) {} +public: + static Key *parseFromData(const std::string &data, int lineno, int *errors, + int *warnings); + bool execute(ScriptOptions) const override; }; class Keymap : public StringKey { @@ -63,7 +70,7 @@ class Timezone : public StringKey { class Repository : public StringKey { private: - Repository(int _line, const std::string my_url) : + Repository(int _line, const std::string &my_url) : StringKey(_line, my_url) {} public: static Key *parseFromData(const std::string &data, int lineno, int *errors, diff --git a/hscript/script.cc b/hscript/script.cc index 33003bf..69a46cc 100644 --- a/hscript/script.cc +++ b/hscript/script.cc @@ -88,8 +88,8 @@ struct Script::ScriptPrivate { std::set packages; /*! The root shadow line. */ std::unique_ptr rootpw; - /*! Target system's mountpoints. */ - std::vector< std::unique_ptr > mounts; + /*! The system language. */ + std::unique_ptr lang; /*! Network addressing configuration */ std::vector< std::unique_ptr > addresses; @@ -103,6 +103,8 @@ struct Script::ScriptPrivate { /*! Disk identification keys */ std::vector< std::unique_ptr > diskids; + /*! Target system's mountpoints. */ + std::vector< std::unique_ptr > mounts; #ifdef NON_LIBRE_FIRMWARE std::unique_ptr firmware; @@ -119,18 +121,6 @@ struct Script::ScriptPrivate { int *errors, int *warnings, const ScriptOptions &opts) { if(key_name == "network") { return store_network(obj, lineno, errors, warnings, opts); - } else if(key_name == "hostname") { - return store_hostname(obj, lineno, errors, warnings, opts); - } else if(key_name == "pkginstall") { - return store_pkginstall(obj, lineno, errors, warnings, opts); - } else if(key_name == "rootpw") { - return store_rootpw(obj, lineno, errors, warnings, opts); - } else if(key_name == "firmware") { - return store_firmware(obj, lineno, errors, warnings, opts); - } else if(key_name == "mount") { - std::unique_ptr mount(dynamic_cast(obj)); - this->mounts.push_back(std::move(mount)); - return true; } else if(key_name == "netaddress") { std::unique_ptr addr(dynamic_cast(obj)); this->addresses.push_back(std::move(addr)); @@ -139,14 +129,20 @@ struct Script::ScriptPrivate { std::unique_ptr ssid(dynamic_cast(obj)); this->ssids.push_back(std::move(ssid)); return true; + } else if(key_name == "hostname") { + return store_hostname(obj, lineno, errors, warnings, opts); + } else if(key_name == "pkginstall") { + return store_pkginstall(obj, lineno, errors, warnings, opts); + } else if(key_name == "rootpw") { + return store_rootpw(obj, lineno, errors, warnings, opts); + } else if(key_name == "language") { + return store_lang(obj, lineno, errors, warnings, opts); + } else if(key_name == "firmware") { + return store_firmware(obj, lineno, errors, warnings, opts); } else if(key_name == "repository") { std::unique_ptr repo(dynamic_cast(obj)); this->repos.push_back(std::move(repo)); return true; - } else if(key_name == "diskid") { - std::unique_ptr diskid(dynamic_cast(obj)); - this->diskids.push_back(std::move(diskid)); - return true; } else if(key_name == "username") { return store_username(obj, lineno, errors, warnings, opts); } else if(key_name == "useralias") { @@ -157,6 +153,14 @@ struct Script::ScriptPrivate { return store_usericon(obj, lineno, errors, warnings, opts); } else if(key_name == "usergroups") { return store_usergroups(obj, lineno, errors, warnings, opts); + } else if(key_name == "diskid") { + std::unique_ptr diskid(dynamic_cast(obj)); + this->diskids.push_back(std::move(diskid)); + return true; + } else if(key_name == "mount") { + std::unique_ptr mount(dynamic_cast(obj)); + this->mounts.push_back(std::move(mount)); + return true; } else { return false; } @@ -241,6 +245,18 @@ struct Script::ScriptPrivate { #endif } + bool store_lang(Keys::Key *obj, int lineno, int *errors, int *, + ScriptOptions) { + if(this->lang) { + DUPLICATE_ERROR(this->lang, std::string("language"), + this->lang->value()) + return false; + } + std::unique_ptr l(dynamic_cast(obj)); + this->lang = std::move(l); + return true; + } + bool store_username(Keys::Key *obj, int lineno, int *errors, int *, ScriptOptions) { if(accounts.size() >= 255) { @@ -527,9 +543,16 @@ bool Script::validate() const { /* REQ: Runner.Validate.rootpw */ if(!this->internal->rootpw->validate(this->opts)) failures++; - /* language */ + /* REQ: Runner.Validate.language */ + if(internal->lang && !internal->lang->validate(this->opts)) failures++; + /* keymap */ - /* firmware */ + +#ifdef NON_LIBRE_FIRMWARE + /* REQ: Runner.Validate.firmware */ + if(!this->internal->firmware->validate(this->opts)) failures++; +#endif + /* timezone */ /* REQ: Script.repository */ diff --git a/tests/fixtures/0115-language-simple.installfile b/tests/fixtures/0115-language-simple.installfile new file mode 100644 index 0000000..ced3f0c --- /dev/null +++ b/tests/fixtures/0115-language-simple.installfile @@ -0,0 +1,6 @@ +network false +hostname test.machine +pkginstall adelie-base +rootpw $6$gumtLGmHwOVIRpQR$2M9PUO24hy5mofzWWf9a.YLbzOgOlUby1g0hDj.wG67E2wrrvys59fq02PPdxBdbgkLZFtjfEx6MHZwMBamwu/ +mount /dev/sda1 / +language cy diff --git a/tests/fixtures/0116-language-country.installfile b/tests/fixtures/0116-language-country.installfile new file mode 100644 index 0000000..a6812a9 --- /dev/null +++ b/tests/fixtures/0116-language-country.installfile @@ -0,0 +1,6 @@ +network false +hostname test.machine +pkginstall adelie-base +rootpw $6$gumtLGmHwOVIRpQR$2M9PUO24hy5mofzWWf9a.YLbzOgOlUby1g0hDj.wG67E2wrrvys59fq02PPdxBdbgkLZFtjfEx6MHZwMBamwu/ +mount /dev/sda1 / +language ie_IE diff --git a/tests/fixtures/0117-language-codeset.installfile b/tests/fixtures/0117-language-codeset.installfile new file mode 100644 index 0000000..ae18d4a --- /dev/null +++ b/tests/fixtures/0117-language-codeset.installfile @@ -0,0 +1,6 @@ +network false +hostname test.machine +pkginstall adelie-base +rootpw $6$gumtLGmHwOVIRpQR$2M9PUO24hy5mofzWWf9a.YLbzOgOlUby1g0hDj.wG67E2wrrvys59fq02PPdxBdbgkLZFtjfEx6MHZwMBamwu/ +mount /dev/sda1 / +language zh_TW.UTF-8 diff --git a/tests/fixtures/0118-language-c-exception.installfile b/tests/fixtures/0118-language-c-exception.installfile new file mode 100644 index 0000000..4e2f53d --- /dev/null +++ b/tests/fixtures/0118-language-c-exception.installfile @@ -0,0 +1,6 @@ +network false +hostname test.machine +pkginstall adelie-base +rootpw $6$gumtLGmHwOVIRpQR$2M9PUO24hy5mofzWWf9a.YLbzOgOlUby1g0hDj.wG67E2wrrvys59fq02PPdxBdbgkLZFtjfEx6MHZwMBamwu/ +mount /dev/sda1 / +language C.UTF-8 diff --git a/tests/fixtures/0119-language-invalid.installfile b/tests/fixtures/0119-language-invalid.installfile new file mode 100644 index 0000000..209eacf --- /dev/null +++ b/tests/fixtures/0119-language-invalid.installfile @@ -0,0 +1,6 @@ +network false +hostname test.machine +pkginstall adelie-base +rootpw $6$gumtLGmHwOVIRpQR$2M9PUO24hy5mofzWWf9a.YLbzOgOlUby1g0hDj.wG67E2wrrvys59fq02PPdxBdbgkLZFtjfEx6MHZwMBamwu/ +mount /dev/sda1 / +language klingon diff --git a/tests/fixtures/0120-language-iso.installfile b/tests/fixtures/0120-language-iso.installfile new file mode 100644 index 0000000..63f92e5 --- /dev/null +++ b/tests/fixtures/0120-language-iso.installfile @@ -0,0 +1,6 @@ +network false +hostname test.machine +pkginstall adelie-base +rootpw $6$gumtLGmHwOVIRpQR$2M9PUO24hy5mofzWWf9a.YLbzOgOlUby1g0hDj.wG67E2wrrvys59fq02PPdxBdbgkLZFtjfEx6MHZwMBamwu/ +mount /dev/sda1 / +language en_US.ISO-8859-1 diff --git a/tests/fixtures/0121-language-duplicate.installfile b/tests/fixtures/0121-language-duplicate.installfile new file mode 100644 index 0000000..83f7314 --- /dev/null +++ b/tests/fixtures/0121-language-duplicate.installfile @@ -0,0 +1,7 @@ +network false +hostname test.machine +pkginstall adelie-base +rootpw $6$gumtLGmHwOVIRpQR$2M9PUO24hy5mofzWWf9a.YLbzOgOlUby1g0hDj.wG67E2wrrvys59fq02PPdxBdbgkLZFtjfEx6MHZwMBamwu/ +mount /dev/sda1 / +language en +language en diff --git a/tests/spec/validator_spec.rb b/tests/spec/validator_spec.rb index 08f5886..3ed43ea 100644 --- a/tests/spec/validator_spec.rb +++ b/tests/spec/validator_spec.rb @@ -186,6 +186,44 @@ RSpec.describe 'HorizonScript validation', :type => :aruba do expect(last_command_started).to have_output(/error: .*firmware.*value/) end end + context "for 'language' key" do + # Runner.Validate.language + # Runner.Validate.language.Format + it "supports a simple language" do + use_fixture '0115-language-simple.installfile' + run_validate + expect(last_command_started).to have_output(PARSER_SUCCESS) + expect(last_command_started).to have_output(VALIDATOR_SUCCESS) + end + it "supports a language_territory value" do + use_fixture '0116-language-country.installfile' + run_validate + expect(last_command_started).to have_output(PARSER_SUCCESS) + expect(last_command_started).to have_output(VALIDATOR_SUCCESS) + end + it "supports the UTF-8 codeset" do + use_fixture '0117-language-codeset.installfile' + run_validate + expect(last_command_started).to have_output(PARSER_SUCCESS) + expect(last_command_started).to have_output(VALIDATOR_SUCCESS) + end + it "supports the special case C.UTF-8" do + use_fixture '0118-language-c-exception.installfile' + run_validate + expect(last_command_started).to have_output(PARSER_SUCCESS) + expect(last_command_started).to have_output(VALIDATOR_SUCCESS) + end + it "requires a valid language" do + use_fixture '0119-language-invalid.installfile' + run_validate + expect(last_command_started).to have_output(/error: .*language.*invalid/) + end + it "requires the UTF-8 codeset" do + use_fixture '0120-language-iso.installfile' + run_validate + expect(last_command_started).to have_output(/error: .*language.*codeset/) + end + end context "for 'mount' key" do # Runner.Validate.mount. it "fails with an invalid value" do @@ -537,6 +575,11 @@ RSpec.describe 'HorizonScript validation', :type => :aruba do skip "This build does not support firmware" if last_command_started.stdout !~ /supports non-free/ expect(last_command_started).to have_output(/error: .*duplicate.*firmware/) end + it "fails with a duplicate 'language' key" do + use_fixture '0121-language-duplicate.installfile' + run_validate + expect(last_command_started).to have_output(/error: .*duplicate.*language/) + end end context "user account keys:" do context "'username'" do -- cgit v1.2.3-70-g09d2