From a5c49ffaaa3181cf876a2f64681c6717f8ab033f Mon Sep 17 00:00:00 2001 From: Antoine Beauchamp Date: Tue, 16 Nov 2021 17:15:34 -0500 Subject: [PATCH 1/2] Implemented new properties `selection.dir.count` and `selection.dir.empty` for issue #90. --- src/Context.cpp | 23 +++++++++++++- test/TestContext.cpp | 72 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 94 insertions(+), 1 deletion(-) diff --git a/src/Context.cpp b/src/Context.cpp index e232f4a3..41e7a82f 100644 --- a/src/Context.cpp +++ b/src/Context.cpp @@ -75,6 +75,8 @@ namespace shellanything std::string selection_path ; std::string selection_dir ; + std::string selection_dir_count ; + std::string selection_dir_empty ; std::string selection_parent_path ; std::string selection_parent_filename; std::string selection_filename ; @@ -109,7 +111,7 @@ namespace shellanything //${selection.filename_noext} is selection.filename without file extension //${selection.filename.extension} is the file extension of the clicked element. - //build properties for this specific element + // Build properties for this specific element std::string element_selection_path = element; std::string element_selection_dir = isFile ? ra::filesystem::GetParentPath(element_selection_path) : element; std::string element_selection_parent_path = ra::filesystem::GetParentPath(element_selection_path); @@ -155,8 +157,27 @@ namespace shellanything selection_charset .append( element_selection_charset ); } + // Directory based properties + if (elements.size() == 1) + { + const std::string & element = elements[0]; + bool isDir = ra::filesystem::DirectoryExistsUtf8(element.c_str()); + if (isDir) + { + ra::strings::StringVector files; + bool files_found = ra::filesystem::FindFilesUtf8(files, element.c_str(), 0); + if (files_found) + { + selection_dir_count = ra::strings::ToString(files.size()); + selection_dir_empty = (files.size() == 0 ? "true" : "false"); + } + } + } + pmgr.SetProperty("selection.path" , selection_path ); pmgr.SetProperty("selection.dir" , selection_dir ); + pmgr.SetProperty("selection.dir.count" , selection_dir_count ); + pmgr.SetProperty("selection.dir.empty" , selection_dir_empty ); pmgr.SetProperty("selection.parent.path" , selection_parent_path ); pmgr.SetProperty("selection.parent.filename" , selection_parent_filename); pmgr.SetProperty("selection.filename" , selection_filename ); diff --git a/test/TestContext.cpp b/test/TestContext.cpp index 7711e60f..42f9784d 100644 --- a/test/TestContext.cpp +++ b/test/TestContext.cpp @@ -26,6 +26,8 @@ #include "shellanything/Context.h" #include "PropertyManager.h" #include "rapidassist/process.h" +#include "rapidassist/filesystem.h" +#include "rapidassist/testing.h" namespace shellanything { namespace test { @@ -217,6 +219,76 @@ namespace shellanything { namespace test context.UnregisterProperties(); } //-------------------------------------------------------------------------------------------------- + TEST_F(TestContext, testRegisterPropertiesSingleDirectory) + { + PropertyManager & pmgr = PropertyManager::GetInstance(); + + Context context; +#ifdef _WIN32 + { + Context::ElementList elements; + elements.push_back("C:\\" ); + context.SetElements(elements); + } +#else + //TODO: complete with known path to files +#endif + + ASSERT_FALSE( pmgr.HasProperty("selection.dir.count") ); + ASSERT_FALSE( pmgr.HasProperty("selection.dir.empty") ); + + //act + context.RegisterProperties(); + + //assert + ASSERT_TRUE( pmgr.HasProperty("selection.dir.count") ); + ASSERT_TRUE( pmgr.HasProperty("selection.dir.empty") ); + + std::string selection_dir_count = pmgr.Expand("${selection.dir.count}"); + std::string selection_dir_empty = pmgr.Expand("${selection.dir.empty}"); + + ASSERT_TRUE(ra::strings::IsNumeric(selection_dir_count.c_str())); + ASSERT_EQ( std::string("false"), selection_dir_empty); + + context.UnregisterProperties(); + } + //-------------------------------------------------------------------------------------------------- + TEST_F(TestContext, testRegisterPropertiesEmptyDirectory) + { + PropertyManager & pmgr = PropertyManager::GetInstance(); + + //create an empty directory + std::string temp_dir = ra::filesystem::GetTemporaryDirectory(); + std::string empty_dir = temp_dir + ra::testing::GetTestQualifiedName(); + ASSERT_TRUE(ra::filesystem::CreateDirectory(empty_dir.c_str())); + + Context context; + Context::ElementList elements; + elements.push_back(empty_dir); + context.SetElements(elements); + + ASSERT_FALSE( pmgr.HasProperty("selection.dir.count") ); + ASSERT_FALSE( pmgr.HasProperty("selection.dir.empty") ); + + //act + context.RegisterProperties(); + + //assert + ASSERT_TRUE( pmgr.HasProperty("selection.dir.count") ); + ASSERT_TRUE( pmgr.HasProperty("selection.dir.empty") ); + + std::string selection_dir_count = pmgr.Expand("${selection.dir.count}"); + std::string selection_dir_empty = pmgr.Expand("${selection.dir.empty}"); + + ASSERT_EQ( std::string("0"), selection_dir_count); + ASSERT_EQ( std::string("true"), selection_dir_empty); + + context.UnregisterProperties(); + + //cleanup + ra::filesystem::DeleteDirectory(empty_dir.c_str()); + } + //-------------------------------------------------------------------------------------------------- TEST_F(TestContext, testRegisterPropertiesMultipleFiles) { PropertyManager & pmgr = PropertyManager::GetInstance(); From 496c8f7100c899d8fb262ff8d2eb918e71634fff Mon Sep 17 00:00:00 2001 From: Antoine Beauchamp Date: Thu, 16 Dec 2021 18:47:36 -0500 Subject: [PATCH 2/2] * Fixed issue #90: New property for detecting empty directories. * Fixed issue #100: Visibility/Validity attribute istrue and isfalse for validation against boolean properties. --- CHANGES | 14 +- UserManual.md | 100 ++++++++--- include/shellanything/Validator.h | 60 +++++++ src/ObjectFactory.cpp | 20 +++ src/PropertyManager.cpp | 8 + src/PropertyManager.h | 22 +++ src/Validator.cpp | 131 +++++++++++++- test/TestObjectFactory.cpp | 8 +- test/TestValidator.cpp | 166 ++++++++++++++++++ .../TestObjectFactory.testParseValidator.xml | 16 ++ 10 files changed, 512 insertions(+), 33 deletions(-) diff --git a/CHANGES b/CHANGES index 1dfcf13e..7fba1738 100644 --- a/CHANGES +++ b/CHANGES @@ -4,12 +4,14 @@ Changes for 0.7.0 * Using [File for Windows](https://github.com/Cirn09/file-windows) v5.38 with minimal changes. * Using [PCRE2](http://www.pcre.org/) version 10.30 * Deprecated support for 32-bit Windows. -* Fixed issue #59: Implement a new type of separator for creating a menu with multiple columns. -* Fixed issue #91: String encoding in source code. -* Fixed issue #92: New property for detecting file type based on content. -* Fixed issue #96: Problems with Files > 2Gb -* Fixed issue #97: Files and folders together do not start MENU. -* Fixed issue #98: Show only one log warning message about missing icon for a file extension. +* Fixed issue #59: Implement a new type of separator for creating a menu with multiple columns. +* Fixed issue #90: New property for detecting empty directories. +* Fixed issue #91: String encoding in source code. +* Fixed issue #92: New property for detecting file type based on content. +* Fixed issue #96: Problems with Files > 2Gb +* Fixed issue #97: Files and folders together do not start MENU. +* Fixed issue #98: Show only one log warning message about missing icon for a file extension. +* Fixed issue #100: Visibility/Validity attribute istrue and isfalse for validation against boolean properties. Changes for 0.6.1 diff --git a/UserManual.md b/UserManual.md index c623543e..483e7c42 100644 --- a/UserManual.md +++ b/UserManual.md @@ -19,6 +19,8 @@ This manual includes a description of the system functionalities and capabilitie * [exists attribute](#exists-attribute) * [properties attribute](#properties-attribute) * [exprtk attribute](#exprtk-attribute) + * [istrue attribute](#istrue-attribute) + * [isfalse attribute](#isfalse-attribute) * [inverse attribute](#inverse-attribute) * [Combining multiple <visibility> and <validity> elements](#combining-multiple-visibility-and-validity-elements) * [Icons](#icons) @@ -445,6 +447,46 @@ The `exprtk` attribute uses the *exprtk library* to parse the expression. For mo +### istrue attribute: ### + +The `istrue` attribute validates a menu if the specified value evaluates to *true*. + +If `istrue` attribute is set, the application will evaluate the given value. If the value evaluates to *true*, the validation is successful. To specify multiple values, one must separate each value with the `;` character. If multiple values are specified, **all values** must evaluates to *true* for the validation to be successful. + +If `istrue` attribute is not specified, then the validation is successful. + +For example, the following set a menu visible only when the property `initialized` evaluates to *true*: +```xml + +``` + +The following values evaluates to *true* : `true`, `yes`, `ok`, `on` and `1`. The evaluation is case insensitive. All other values **does not** evaluates to true and will fail the validation. + +For systems that are not configured in English, your system may use another value for *true*. For such system, ShellAnything has defined property `system.true` which allows one to set the property to the same value as `Yes` but in your system native language. +For example, you can set property `system.true` to `是的`, `sí`, `हां`, `sim`, `да`, `はい`, `oui` or `ja`. + + + +### isfalse attribute: ### + +The `isfalse` attribute validates a menu if the specified value evaluates to *false*. + +If `isfalse` attribute is set, the application will evaluate the given value. If the value evaluates to *false*, the validation is successful. To specify multiple values, one must separate each value with the `;` character. If multiple values are specified, **all values** must evaluates to *false* for the validation to be successful. + +If `isfalse` attribute is not specified, then the validation is successful. + +For example, the following set a menu visible only when the property `initialized` evaluates to *false*: +```xml + +``` + +The following values evaluates to *false* : `false`, `no`, `fail`, `off` and `0`. The evaluation is case insensitive. All other values **does not** evaluates to false and will fail the validation. + +For systems that are not configured in English, your system may use another value for *false*. For such system, ShellAnything has defined property `system.false` which allows one to set the property to the same value as `No` but in your system native language. +For example, you can set property `system.false` to `不`, `नहीं`, `não`, `нет`, `いいえ`, `non` or `Nein`. + + + ### inverse attribute: ### The `inverse` attribute inverts the logic of one or multiple attributes. For example, to inverse the meaning of the `maxfiles` attribute, set `inverse` attribute to the value `maxfiles`. @@ -464,6 +506,9 @@ The meaning of each inversed attribute in explained in the following table: | pattern | Validates a menu if the selected file or directory **does not** match the wildcard pattern matching algorithm.
If multiple patterns are specified, **no pattern** must match the selected files for the validation to be successful. | | exists | Validates a menu if the selected file or directory **does not** exists.
If multiple files/directories are specified, **all values** must _not exists_ on the system for the validation to be successful. | | properties | Validates a menu if the specified property is **empty** or **not defined**.
If multiple properties are specified, **all properties** must be _empty_ or _not defined_ for the validation to be successful. | +| exprtk | Validates a menu if the given string expression **does not** evaluates to logical _true_.
If multiple expressions are specified, **no expression** must evaluate to logical _true_ for the validation to be successful. | +| istrue | Validates a menu if the given value **does not** evaluates to logical _true_.
If multiple values are specified, **no value** must evaluate to logical _true_ for the validation to be successful. | +| isfalse | Validates a menu if the given value **does not** evaluates to logical _false_.
If multiple values are specified, **no value** must evaluate to logical _false_ for the validation to be successful. | Typical use case of the `inverse` attribute is about filtering out known file extensions. @@ -512,7 +557,7 @@ The validity element should look like this: Multiple <visibility> or <validity> elements can be added under a <menu> element. The logical `or` operator is used with each element of the same type. In other words, <visibility> elements are evaluated together and so are <validity> elements. This feature allows combining elements to achieve more complex validation. -For example, the following set a menu visible of if only a single file is selected ***or*** if only a single directory is selected: +For example, the following set a menu visible if only a single file is selected ***or*** if only a single directory is selected: ```xml @@ -1070,26 +1115,36 @@ The application provides a list of dynamic properties. The values of these prope The following table defines the list of dynamic properties and their utility: -| Property | Description | -|------------------------------|-------------------------------------------------------------------------------------------------------------------| -| selection.path | Matches the full path of the clicked element. | -| selection.dir | Matches the directory of the clicked element. | -| selection.filename | Matches the filename of the clicked element. | -| selection.filename.noext | Matches the filename of the clicked element without the file extension. | -| selection.parent.path | Matches the full path of the parent element. | -| selection.parent.filename | Matches the filename of the parent element. | -| selection.filename.extension | Matches the file extension of the clicked element. | -| selection.drive.letter | Matches the drive letter of the clicked element. For example 'C'. | -| selection.drive.path | Matches the drive path of the clicked element. For example 'C:\'. | -| selection.count | Matches the number of clicked elements (files and directories). | -| selection.files.count | Matches the number of clicked files. | -| selection.directories.count | Matches the number of clicked directories. | -| selection.mimetype | Matches the [MIME type](https://en.wikipedia.org/wiki/Media_type) of the selected file. See below for examples. | -| selection.description | Matches a general description of the file's content. See below for examples. | -| selection.charset | Matches the [Character Set](https://www.w3schools.com/html/html_charset.asp) of the file. See below for examples. | +| Property | Description | +|------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| selection.path | Matches the full path of the clicked element. | +| selection.dir | Matches the directory of the clicked element. When selecting a file, matches the directory of the clicked element. When selecting a directory, matches the path of the clicked element. | +| selection.dir.count | Matches the number of files and directories in directory `selection.dir`. | +| selection.dir.empty | Set to `true` when directory `selection.dir` is empty. Set to `false` otherwise. | +| selection.filename | Matches the filename of the clicked element. | +| selection.filename.noext | Matches the filename of the clicked element without the file extension. | +| selection.parent.path | Matches the full path of the parent element. | +| selection.parent.filename | Matches the filename of the parent element. | +| selection.filename.extension | Matches the file extension of the clicked element. | +| selection.drive.letter | Matches the drive letter of the clicked element. For example `C`. | +| selection.drive.path | Matches the drive path of the clicked element. For example `C:\`. | +| selection.count | Matches the number of clicked elements (files and directories). | +| selection.files.count | Matches the number of clicked files. | +| selection.directories.count | Matches the number of clicked directories. | +| selection.mimetype | Matches the [MIME type](https://en.wikipedia.org/wiki/Media_type) of the selected file. [See below](#mime-types-general-description-and-charset) for examples. | +| selection.description | Matches a general description of the file's content. See below for examples. | +| selection.charset | Matches the [Character Set](https://www.w3schools.com/html/html_charset.asp) of the file. [See below](#mime-types-general-description-and-charset) for examples. | Selection-based properties are encoded in utf-8. +**Notes:** +Properties `selection.drive.letter` and `selection.drive.path` are empty when user select files from a network share. + +Properties `selection.dir.count` and `selection.dir.empty` are empty when multiple elements are selected. + +The properties `selection.mimetype`, `selection.description` and `selection.mimetype` are based on the content of the selected file. The properties are provided by the *File* and *Libmagic* libraries to extract information about selected files. For more details, see the documentation at [github.com/Cirn09/file-windows](https://github.com/Cirn09/file-windows), [github.com/file/file](https://github.com/file/file) or the [official file documentation](http://www.darwinsys.com/file/). + + ### MIME types, general description and charset: ### A MIME type is a label used to identify a type of data. It is used so software can know how to handle the data. It serves the same purpose on the Internet that file extensions do on Microsoft Windows. A MIME type is calculated from the actual content in a file. @@ -1131,12 +1186,6 @@ Another option is to create your own *Configuration* and add the following menu Press `CTRL+C` when the message is shown to copy the values to the clipboard. -**Notes:** -Properties `selection.drive.letter` and `selection.drive.path` are empty when user select files from a network share. - -The properties `selection.mimetype`, `selection.description` and `selection.mimetype` are based on the content of the selected file. The properties are provided by the *File* and *Libmagic* libraries to extract information about selected files. For more details, see the documentation at [github.com/Cirn09/file-windows](https://github.com/Cirn09/file-windows), [github.com/file/file](https://github.com/file/file) or the [official file documentation](http://www.darwinsys.com/file/). - - ## Multi-selection-based properties ## @@ -1160,8 +1209,9 @@ The system will generates the following property values (note the `\r\n` charact | selection.drive.letter | C`\r\n`C`\r\n`C | | selection.drive.path | C:\\`\r\n`C:\\`\r\n`C:\\ | -Notes: +**Notes:** * Properties `selection.drive.letter` and `selection.drive.path` are empty when all selected files are from a network share. +* Properties `selection.dir.count` and `selection.dir.empty` are empty when multiple files are selected. * Properties `selection.count`, `selection.files.count` and `selection.directories.count` are not multi-selection-based properties. They are defined as a single value whether a single or multiple elements are selected. diff --git a/include/shellanything/Validator.h b/include/shellanything/Validator.h index 074e1923..0e3c2ab7 100644 --- a/include/shellanything/Validator.h +++ b/include/shellanything/Validator.h @@ -44,6 +44,10 @@ #define SA_EXISTS_ATTR_SEPARATOR_STR SA_DEFAULT_ATTRIBUTE_SEPARATOR_STR #define SA_PATTERN_ATTR_SEPARATOR_CHAR SA_DEFAULT_ATTRIBUTE_SEPARATOR_CHAR #define SA_PATTERN_ATTR_SEPARATOR_STR SA_DEFAULT_ATTRIBUTE_SEPARATOR_STR +#define SA_ISTRUE_ATTR_SEPARATOR_CHAR SA_DEFAULT_ATTRIBUTE_SEPARATOR_CHAR +#define SA_ISTRUE_ATTR_SEPARATOR_STR SA_DEFAULT_ATTRIBUTE_SEPARATOR_STR +#define SA_ISFALSE_ATTR_SEPARATOR_CHAR SA_DEFAULT_ATTRIBUTE_SEPARATOR_CHAR +#define SA_ISFALSE_ATTR_SEPARATOR_STR SA_DEFAULT_ATTRIBUTE_SEPARATOR_STR namespace shellanything { @@ -145,6 +149,26 @@ namespace shellanything /// void SetExprtk(const std::string & exprtk); + /// + /// Getter for the 'istrue' parameter. + /// + const std::string & GetIsTrue() const; + + /// + /// Setter for the 'istrue' parameter. + /// + void SetIsTrue(const std::string & istrue); + + /// + /// Getter for the 'isfalse' parameter. + /// + const std::string & GetIsFalse() const; + + /// + /// Setter for the 'isfalse' parameter. + /// + void SetIsFalse(const std::string & isfalse); + /// /// Getter for the 'inserve' parameter. /// @@ -176,6 +200,38 @@ namespace shellanything /// Returns true if the given context is valid against the set of constraints. Returns false otherwise. bool Validate(const Context & context) const; + /// + /// Validates if a given string can be evaluated as logical true. + /// + /// + /// The following values shall be evaluated to true: + /// true (case insensitive) + /// yes (case insensitive) + /// ok + /// on + /// 1 + /// ${system.true} + /// + /// The value to test as true + /// Returns true if the given value can be evaluated as logical true. Returns false otherwise. + static bool IsTrue(const std::string & value); + + /// + /// Validates if a given string can be evaluated as logical true. + /// + /// + /// The following values shall be evaluated to false: + /// false (case insensitive) + /// no (case insensitive) + /// fail + /// off + /// 0 + /// ${system.false} + /// + /// The value to test as true + /// Returns true if the given value can be evaluated as logical true. Returns false otherwise. + static bool IsFalse(const std::string & value); + private: bool ValidateProperties(const Context & context, const std::string & properties, bool inversed) const; bool ValidateFileExtensions(const Context & context, const std::string & file_extensions, bool inversed) const; @@ -185,6 +241,8 @@ namespace shellanything bool ValidateSingleFileSingleClass(const std::string & path, const std::string & class_, bool inversed) const; bool ValidatePattern(const Context & context, const std::string & pattern, bool inversed) const; bool ValidateExprtk(const Context & context, const std::string & exprtk, bool inversed) const; + bool ValidateIsTrue(const Context & context, const std::string & istrue, bool inversed) const; + bool ValidateIsFalse(const Context & context, const std::string & isfalse, bool inversed) const; private: int mMaxFiles; @@ -195,6 +253,8 @@ namespace shellanything std::string mClass; std::string mPattern; std::string mExprtk; + std::string mIsTrue; + std::string mIsFalse; std::string mInverse; }; diff --git a/src/ObjectFactory.cpp b/src/ObjectFactory.cpp index a6f05195..61f2513f 100644 --- a/src/ObjectFactory.cpp +++ b/src/ObjectFactory.cpp @@ -243,6 +243,26 @@ namespace shellanything } } + //parse istrue + std::string istrue; + if (ParseAttribute(element, "istrue", true, true, istrue, error)) + { + if (!istrue.empty()) + { + validator->SetIsTrue(istrue); + } + } + + //parse isfalse + std::string isfalse; + if (ParseAttribute(element, "isfalse", true, true, isfalse, error)) + { + if (!isfalse.empty()) + { + validator->SetIsFalse(isfalse); + } + } + //success return validator; } diff --git a/src/PropertyManager.cpp b/src/PropertyManager.cpp index 9fe24e59..514fff25 100644 --- a/src/PropertyManager.cpp +++ b/src/PropertyManager.cpp @@ -33,6 +33,11 @@ namespace shellanything { static const int EXPANDING_MAX_ITERATIONS = 20; + const std::string PropertyManager::SYSTEM_TRUE_PROPERTY_NAME = "system.true"; + const std::string PropertyManager::SYSTEM_TRUE_DEFAULT_VALUE = "true"; + const std::string PropertyManager::SYSTEM_FALSE_PROPERTY_NAME = "system.false"; + const std::string PropertyManager::SYSTEM_FALSE_DEFAULT_VALUE = "false"; + PropertyManager::PropertyManager() { RegisterEnvironmentVariables(); @@ -228,6 +233,9 @@ namespace shellanything SetProperty("line.separator" , prop_line_separator ); SetProperty("newline" , prop_line_separator ); + SetProperty(PropertyManager::SYSTEM_TRUE_PROPERTY_NAME , PropertyManager::SYSTEM_TRUE_DEFAULT_VALUE ); + SetProperty(PropertyManager::SYSTEM_FALSE_PROPERTY_NAME , PropertyManager::SYSTEM_FALSE_DEFAULT_VALUE ); + // Set default property for multi selection. Issue #52. SetProperty(Context::MULTI_SELECTION_SEPARATOR_PROPERTY_NAME, Context::DEFAULT_MULTI_SELECTION_SEPARATOR); } diff --git a/src/PropertyManager.h b/src/PropertyManager.h index bb842f0d..b2146b77 100644 --- a/src/PropertyManager.h +++ b/src/PropertyManager.h @@ -45,6 +45,28 @@ namespace shellanything PropertyManager(const PropertyManager&); PropertyManager& operator=(const PropertyManager&); + public: + + /// + /// Name of the property that defines the system true. + /// + static const std::string SYSTEM_TRUE_PROPERTY_NAME; + + /// + /// Default value for the property 'SYSTEM_TRUE_PROPERTY_NAME'. + /// + static const std::string SYSTEM_TRUE_DEFAULT_VALUE; + + /// + /// Name of the property that defines the system false. + /// + static const std::string SYSTEM_FALSE_PROPERTY_NAME; + + /// + /// Default value for the property 'SYSTEM_FALSE_PROPERTY_NAME'. + /// + static const std::string SYSTEM_FALSE_DEFAULT_VALUE; + public: //------------------------ diff --git a/src/Validator.cpp b/src/Validator.cpp index b2c82a5d..ce7a72b6 100644 --- a/src/Validator.cpp +++ b/src/Validator.cpp @@ -153,6 +153,26 @@ namespace shellanything mExprtk = exprtk; } + const std::string & Validator::GetIsTrue() const + { + return mIsTrue; + } + + void Validator::SetIsTrue(const std::string & istrue) + { + mIsTrue = istrue; + } + + const std::string & Validator::GetIsFalse() const + { + return mIsFalse; + } + + void Validator::SetIsFalse(const std::string & isfalse) + { + mIsFalse = isfalse; + } + const std::string & Validator::GetInserve() const { return mInverse; @@ -223,6 +243,8 @@ namespace shellanything bool Validator::Validate(const Context & context) const { + PropertyManager & pmgr = PropertyManager::GetInstance(); + bool maxfiles_inversed = IsInversed("maxfiles"); if (!maxfiles_inversed && context.GetNumFiles() > mMaxFiles) return false; //too many files selected @@ -236,7 +258,6 @@ namespace shellanything return false; //too many directories selected //validate properties - PropertyManager & pmgr = PropertyManager::GetInstance(); const std::string properties = pmgr.Expand(mProperties); if (!properties.empty()) { @@ -296,9 +317,69 @@ namespace shellanything return false; } + //validate istrue + const std::string istrue = pmgr.Expand(mIsTrue); + if (!istrue.empty()) + { + bool inversed = IsInversed("istrue"); + bool valid = ValidateIsTrue(context, istrue, inversed); + if (!valid) + return false; + } + + //validate isfalse + const std::string isfalse = pmgr.Expand(mIsFalse); + if (!isfalse.empty()) + { + bool inversed = IsInversed("isfalse"); + bool valid = ValidateIsFalse(context, isfalse, inversed); + if (!valid) + return false; + } + return true; } + bool Validator::IsTrue(const std::string & value) + { + std::string upper_case_value = ra::strings::Uppercase(value); + if (upper_case_value == "TRUE" || + upper_case_value == "YES" || + upper_case_value == "OK" || + upper_case_value == "ON" || + upper_case_value == "1" ) + return true; + + //check with system true + PropertyManager & pmgr = PropertyManager::GetInstance(); + std::string system_true = pmgr.GetProperty(PropertyManager::SYSTEM_TRUE_PROPERTY_NAME); + std::string upper_case_system_true = ra::strings::Uppercase(system_true); + if (upper_case_value == upper_case_system_true) + return true; + + return false; + } + + bool Validator::IsFalse(const std::string & value) + { + std::string upper_case_value = ra::strings::Uppercase(value); + if (upper_case_value == "FALSE" || + upper_case_value == "NO" || + upper_case_value == "FAIL" || + upper_case_value == "OFF" || + upper_case_value == "0" ) + return true; + + //check with system false + PropertyManager & pmgr = PropertyManager::GetInstance(); + std::string system_false = pmgr.GetProperty(PropertyManager::SYSTEM_FALSE_PROPERTY_NAME); + std::string upper_case_system_false = ra::strings::Uppercase(system_false); + if (upper_case_value == upper_case_system_false) + return true; + + return false; + } + bool Validator::ValidateProperties(const Context & context, const std::string & properties, bool inversed) const { if (properties.empty()) @@ -612,4 +693,52 @@ namespace shellanything return result; } + bool Validator::ValidateIsTrue(const Context & context, const std::string & istrue, bool inversed) const + { + if (istrue.empty()) + return true; + + PropertyManager & pmgr = PropertyManager::GetInstance(); + + //split + ra::strings::StringVector statements = ra::strings::Split(istrue, SA_ISTRUE_ATTR_SEPARATOR_STR); + + //for each boolean statement + for (size_t i = 0; i < statements.size(); i++) + { + const std::string & statement = statements[i]; + bool match = IsTrue(statement); + if (!inversed && !match) + return false; //current statement does not evaluate to true + if (inversed && match) + return false; //current statement evaluates as true + } + + return true; + } + + bool Validator::ValidateIsFalse(const Context & context, const std::string & isfalse, bool inversed) const + { + if (isfalse.empty()) + return true; + + PropertyManager & pmgr = PropertyManager::GetInstance(); + + //split + ra::strings::StringVector statements = ra::strings::Split(isfalse, SA_ISTRUE_ATTR_SEPARATOR_STR); + + //for each boolean statement + for (size_t i = 0; i < statements.size(); i++) + { + const std::string & statement = statements[i]; + bool match = IsFalse(statement); + if (!inversed && !match) + return false; //current statement does not evaluate to false + if (inversed && match) + return false; //current statement evaluates as false + } + + return true; + } + } //namespace shellanything diff --git a/test/TestObjectFactory.cpp b/test/TestObjectFactory.cpp index 7558a0c7..d1bcc4af 100644 --- a/test/TestObjectFactory.cpp +++ b/test/TestObjectFactory.cpp @@ -184,7 +184,7 @@ namespace shellanything { namespace test //ASSERT all menus are available Menu::MenuPtrList menus = cmgr.GetConfigurations()[0]->GetMenus(); - ASSERT_EQ( 12, menus.size() ); + ASSERT_EQ( 14, menus.size() ); //Assert tag properly parsed static const std::string expected_property = "bar"; @@ -197,6 +197,8 @@ namespace shellanything { namespace test static const std::string expected_class = "file"; static const std::string expected_pattern = "*IMG_*"; static const std::string expected_exprtk = "2>1"; + static const std::string expected_istrue = "yes"; + static const std::string expected_isfalse = "no"; //Assert each menus have a visibility assigned for(size_t i=0; iGetValidity(1)->GetMaxFiles() ); ASSERT_EQ( 0, menus[11]->GetValidity(1)->GetMaxDirectories() ); + //Assert remaining menus + ASSERT_EQ( expected_istrue, menus[12]->GetVisibility(0)->GetIsTrue() ); + ASSERT_EQ( expected_isfalse, menus[13]->GetVisibility(0)->GetIsFalse() ); + //Cleanup ASSERT_TRUE(workspace.Cleanup()) << "Failed deleting workspace directory '" << workspace.GetBaseDirectory() << "'."; } diff --git a/test/TestValidator.cpp b/test/TestValidator.cpp index 38ca3a5b..d74f091b 100644 --- a/test/TestValidator.cpp +++ b/test/TestValidator.cpp @@ -28,6 +28,7 @@ #include "shellanything/Context.h" #include "PropertyManager.h" #include "rapidassist/testing.h" +#include "rapidassist/process.h" namespace shellanything { namespace test { @@ -1035,6 +1036,171 @@ namespace shellanything { namespace test ASSERT_FALSE( menu.IsVisible() ); } //-------------------------------------------------------------------------------------------------- + TEST_F(TestValidator, testIsTrueStatement) + { + ASSERT_TRUE(Validator::IsTrue("true")); + ASSERT_TRUE(Validator::IsTrue("tRue")); + ASSERT_TRUE(Validator::IsTrue("TRUE")); + + ASSERT_TRUE(Validator::IsTrue("yes")); + ASSERT_TRUE(Validator::IsTrue("OK")); + ASSERT_TRUE(Validator::IsTrue("oN")); + ASSERT_TRUE(Validator::IsTrue("1")); + + ASSERT_FALSE(Validator::IsTrue("foo")); + + //test with system.true + PropertyManager & pmgr = PropertyManager::GetInstance(); + pmgr.Clear(); + ASSERT_FALSE(Validator::IsTrue("bar")); + pmgr.SetProperty(PropertyManager::SYSTEM_TRUE_PROPERTY_NAME, "BaR"); + ASSERT_TRUE(Validator::IsTrue("bar")); + pmgr.Clear(); + } + //-------------------------------------------------------------------------------------------------- + TEST_F(TestValidator, testIsFalseStatement) + { + ASSERT_TRUE(Validator::IsFalse("false")); + ASSERT_TRUE(Validator::IsFalse("fAlse")); + ASSERT_TRUE(Validator::IsFalse("FALSE")); + + ASSERT_TRUE(Validator::IsFalse("no")); + ASSERT_TRUE(Validator::IsFalse("fail")); + ASSERT_TRUE(Validator::IsFalse("oFF")); + ASSERT_TRUE(Validator::IsFalse("0")); + + ASSERT_FALSE(Validator::IsFalse("bar")); + //test with system.true + PropertyManager & pmgr = PropertyManager::GetInstance(); + pmgr.Clear(); + ASSERT_FALSE(Validator::IsFalse("foo")); + pmgr.SetProperty(PropertyManager::SYSTEM_FALSE_PROPERTY_NAME, "fOo"); + ASSERT_TRUE(Validator::IsFalse("foo")); + pmgr.Clear(); + } + //-------------------------------------------------------------------------------------------------- + TEST_F(TestValidator, testIsTrue) + { + Context c; + Context::ElementList elements; + elements.push_back( ra::process::GetCurrentProcessPath() ); + c.SetElements(elements); + + Validator v; + + //assert default + ASSERT_TRUE(v.Validate(c)); + + //assert failure when a false/unkown statement is specified + v.SetIsTrue("no"); + ASSERT_FALSE(v.Validate(c)); + v.SetIsTrue("foo"); + ASSERT_FALSE(v.Validate(c)); + + //assert success if the statement evaluates to true + static const char * statements[] = { + "true", + "tRue", + "TRUE", + "yes", + "OK", + "oN", + "1", + }; + static const size_t num_statements = sizeof(statements) / sizeof(statements[0]); + for (size_t i = 0; i < num_statements; i++) + { + const char * statement = statements[i]; + v.SetIsTrue(statement); + ASSERT_TRUE(v.Validate(c)) << "Statement '" << statement << "' is expected to evaluate to true"; + } + + //test with multiple values + std::string istrue; + + //assert success if all statements evaluates to true + istrue.clear(); + istrue += "yes"; + istrue += SA_ISTRUE_ATTR_SEPARATOR_STR; + istrue += "true"; + istrue += SA_ISTRUE_ATTR_SEPARATOR_STR; + istrue += "on"; + v.SetIsTrue(istrue); + ASSERT_TRUE(v.Validate(c)); + + //assert failure if the last element does not evaluates to true + //if multiple values are specified, all values must evaluate to true for the validation to be successful. + istrue.clear(); + istrue += "yes"; + istrue += SA_ISTRUE_ATTR_SEPARATOR_STR; + istrue += "true"; + istrue += SA_ISTRUE_ATTR_SEPARATOR_STR; + istrue += "foo"; + v.SetIsTrue(istrue); + ASSERT_FALSE(v.Validate(c)); + } + //-------------------------------------------------------------------------------------------------- + TEST_F(TestValidator, testIsFalse) + { + Context c; + Context::ElementList elements; + elements.push_back( ra::process::GetCurrentProcessPath() ); + c.SetElements(elements); + + Validator v; + + //assert default + ASSERT_TRUE(v.Validate(c)); + + //assert failure when a true/unkown statement is specified + v.SetIsFalse("yes"); + ASSERT_FALSE(v.Validate(c)); + v.SetIsFalse("foo"); + ASSERT_FALSE(v.Validate(c)); + + //assert success if the statement evaluates to false + static const char * statements[] = { + "false", + "faLSe", + "FALSE", + "no", + "FAIL", + "oFF", + "0", + }; + static const size_t num_statements = sizeof(statements) / sizeof(statements[0]); + for (size_t i = 0; i < num_statements; i++) + { + const char * statement = statements[i]; + v.SetIsFalse(statement); + ASSERT_TRUE(v.Validate(c)) << "Statement '" << statement << "' is expected to evaluate to false"; + } + + //test with multiple values + std::string isfalse; + + //assert success if all statements evaluates to true + isfalse.clear(); + isfalse += "no"; + isfalse += SA_ISTRUE_ATTR_SEPARATOR_STR; + isfalse += "false"; + isfalse += SA_ISTRUE_ATTR_SEPARATOR_STR; + isfalse += "off"; + v.SetIsFalse(isfalse); + ASSERT_TRUE(v.Validate(c)); + + //assert failure if the last element does not evaluates to false + //if multiple values are specified, all values must evaluate to false for the validation to be successful. + isfalse.clear(); + isfalse += "no"; + isfalse += SA_ISTRUE_ATTR_SEPARATOR_STR; + isfalse += "false"; + isfalse += SA_ISTRUE_ATTR_SEPARATOR_STR; + isfalse += "foo"; + v.SetIsFalse(isfalse); + ASSERT_FALSE(v.Validate(c)); + } + //-------------------------------------------------------------------------------------------------- } //namespace test } //namespace shellanything diff --git a/test/test_files/TestObjectFactory.testParseValidator.xml b/test/test_files/TestObjectFactory.testParseValidator.xml index e4fb6577..93a2f3ad 100644 --- a/test/test_files/TestObjectFactory.testParseValidator.xml +++ b/test/test_files/TestObjectFactory.testParseValidator.xml @@ -101,5 +101,21 @@ + + + + + + + + + + + + + + + +