Skip to content

Commit 4e2449f

Browse files
committed
* Implemented support for 'pattern' attribute, Validator::GetPattern() and SetPattern() & tests.
* Also updated documentation for 'pattern' attribute. For issue #58.
1 parent 8935494 commit 4e2449f

File tree

7 files changed

+227
-3
lines changed

7 files changed

+227
-3
lines changed

UserManual.md

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ This manual includes a description of the system functionalities and capabilitie
1515
* [class attribute](#class-attribute)
1616
* [maxfiles and maxfolders attributes](#maxfiles-and-maxfolders-attributes)
1717
* [fileextensions attribute](#fileextensions-attribute)
18+
* [pattern attribute](#pattern-attribute)
1819
* [exists attribute](#exists-attribute)
1920
* [properties attribute](#properties-attribute)
2021
* [inverse attribute](#inverse-attribute)
@@ -316,6 +317,42 @@ For example, the following set a menu visible only when the user right-click on
316317

317318

318319

320+
### pattern attribute: ###
321+
322+
The `pattern` attribute validates a menu based on a wildcard pattern matching algorithm. The wildcard pattern can include special characters such as `*` and `?` where
323+
* `*` Matches any string of zero or more characters.
324+
* `?` Matches any single character.
325+
326+
If `pattern` attribute is specified, the files selected by the user must match the wildcard pattern for the validation to be successful. To specify multiple patterns, one must separate each pattern value with the `;` character. If multiple patterns are specified, **at least one** pattern must match for the validation to be successful.
327+
328+
If multiple files are selected, the path of each file must match **at least one** pattern for the validation to be successful.
329+
330+
If `pattern` attribute is not specified, then the validation is successful.
331+
332+
For example, the following set a menu visible only when the user right-click on JPEG image files which filenames start by `IMG``:
333+
```xml
334+
<visibility pattern="*\IMG*.jpg" />
335+
```
336+
337+
The following table show useful pattern examples:
338+
339+
| Pattern | Meaning |
340+
|---------------------------------------------------------------|-----------------------------------------------------------------------------------------------|
341+
| \\e\*.??? | Matches filenames beginning with the letter `e`. |
342+
| \*e.??? | Matches filename ending with the letter `e`. |
343+
| \*vacations\* | Matches files that have the word `vacations` in their path. |
344+
| \*2019\* | Matches files that have the year 2019 in the filename or directory path. |
345+
| \*\\DCIM\\\* | Matches the files located in a Digital Camera Images directory. |
346+
| C:\Program Files\\\*;<br>C:\Program Files (x86)\\\* | Matches files that are located in `C:\Program Files` or `C:\Program Files (x86)` directories. |
347+
| ${env.USERPROFILE}\\Downloads;<br>${env.USERPROFILE}\\Desktop | Matches files that are located in the user's `Downloads` or `Desktop` directories. |
348+
| D:\\\* | Matches files located on the D: drive. |
349+
| \*\\IMG_????.JPG;<br>\*\\DSC_????.JPG | Matches Canon or Nikon image files. |
350+
351+
**Note:**
352+
The `pattern` attribute should not be used for matching files by file extension. The `fileextensions` attribute should be used instead.
353+
354+
355+
319356
### exists attribute: ###
320357

321358
The `exists` attribute validates a menu if the specified file or directory exists.
@@ -364,6 +401,7 @@ The meaning of each inversed attribute in explained in the following table:
364401
| maxfiles | Defines a minimum number of selected files. Validates a menu if **more than** _x_ files are selected.<br>If 'maxfiles` is set to 5, _more than_ 5 files must be selected for the validation to be successful. |
365402
| maxfolders | Defines a minimum number of selected folder. Validates a menu if **more than** _x_ folders are selected.<br>If 'maxfolders` is set to 3, _more than_ 3 directories must be selected for the validation to be successful. |
366403
| fileextensions | Validates a menu if the given file's extension **does not** match the file extension selected by the user.<br>If multiple file extensions are specified, **no extension** must match the selected files for the validation to be successful. |
404+
| pattern | Validates a menu if the selected file or directory **does not** match the wildcard pattern matching algorithm.<br>If multiple patterns are specified, **no pattern** must match the selected files for the validation to be successful. |
367405
| exists | Validates a menu if the selected file or directory **does not** exists.<br>If multiple files/directories are specified, **all values** must _not exists_ on the system for the validation to be successful. |
368406
| properties | Validates a menu if the specified property is **empty** or **not defined**.<br>If multiple properties are specified, **all properties** must be _empty_ or _not defined_ for the validation to be successful. |
369407

include/shellanything/Validator.h

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,16 @@ namespace shellanything
104104
/// </summary>
105105
void SetClass(const std::string & iClass);
106106

107+
/// <summary>
108+
/// Getter for the 'pattern' parameter.
109+
/// </summary>
110+
const std::string & GetPattern() const;
111+
112+
/// <summary>
113+
/// Setter for the 'pattern' parameter.
114+
/// </summary>
115+
void SetPattern(const std::string & iPattern);
116+
107117
/// <summary>
108118
/// Getter for the 'inserve' parameter.
109119
/// </summary>
@@ -141,6 +151,7 @@ namespace shellanything
141151
bool ValidateExists(const Context & context, const std::string & file_exists, bool inversed) const;
142152
bool ValidateClass(const Context & context, const std::string & class_, bool inversed) const;
143153
bool ValidateClassSingle(const Context & context, const std::string & class_, bool inversed) const;
154+
bool ValidatePattern(const Context & context, const std::string & pattern, bool inversed) const;
144155

145156
private:
146157
int mMaxFiles;
@@ -149,6 +160,7 @@ namespace shellanything
149160
std::string mFileExtensions;
150161
std::string mFileExists;
151162
std::string mClass;
163+
std::string mPattern;
152164
std::string mInverse;
153165
};
154166

src/ObjectFactory.cpp

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -169,6 +169,16 @@ namespace shellanything
169169
}
170170
}
171171

172+
//parse pattern
173+
std::string pattern;
174+
if (ParseAttribute(element, "pattern", true, true, pattern, error))
175+
{
176+
if (!pattern.empty())
177+
{
178+
result.SetPattern(pattern);
179+
}
180+
}
181+
172182
//parse maxfiles
173183
int maxfiles = -1;
174184
if (ParseAttribute(element, "maxfiles", true, true, maxfiles, error))

src/Validator.cpp

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
#include "shellanything/Validator.h"
2828
#include "PropertyManager.h"
2929
#include "DriveClass.h"
30+
#include "Wildcard.h"
3031
#include "rapidassist/strings.h"
3132
#include "rapidassist/filesystem_utf8.h"
3233

@@ -81,6 +82,7 @@ namespace shellanything
8182
mFileExtensions = validator.mFileExtensions ;
8283
mFileExists = validator.mFileExists ;
8384
mClass = validator.mClass ;
85+
mPattern = validator.mPattern ;
8486
mInverse = validator.mInverse ;
8587
}
8688
return (*this);
@@ -146,6 +148,16 @@ namespace shellanything
146148
mClass = iClass;
147149
}
148150

151+
const std::string & Validator::GetPattern() const
152+
{
153+
return mPattern;
154+
}
155+
156+
void Validator::SetPattern(const std::string & iPattern)
157+
{
158+
mPattern = iPattern;
159+
}
160+
149161
const std::string & Validator::GetInserve() const
150162
{
151163
return mInverse;
@@ -269,6 +281,16 @@ namespace shellanything
269281
return false;
270282
}
271283

284+
//validate pattern
285+
const std::string pattern = pmgr.Expand(mPattern);
286+
if (!pattern.empty())
287+
{
288+
bool inversed = IsInversed("pattern");
289+
bool valid = ValidatePattern(iContext, pattern, inversed);
290+
if (!valid)
291+
return false;
292+
}
293+
272294
return true;
273295
}
274296

@@ -507,4 +529,45 @@ namespace shellanything
507529
return true;
508530
}
509531

532+
bool WildcardMatch(const ra::strings::StringVector & patterns, const char * value)
533+
{
534+
for(size_t j=0; j<patterns.size(); j++)
535+
{
536+
const std::string & pattern = patterns[j];
537+
bool match = WildcardMatch(pattern.c_str(), value);
538+
if (match)
539+
return true;
540+
}
541+
return false;
542+
}
543+
544+
bool Validator::ValidatePattern(const Context & context, const std::string & pattern, bool inversed) const
545+
{
546+
if (pattern.empty())
547+
return true;
548+
549+
PropertyManager & pmgr = PropertyManager::GetInstance();
550+
551+
//split
552+
ra::strings::StringVector patterns = ra::strings::Split(pattern, ";");
553+
Uppercase(patterns);
554+
555+
//for each file selected
556+
const Context::ElementList & elements = context.GetElements();
557+
for(size_t i=0; i<elements.size(); i++)
558+
{
559+
const std::string & element = elements[i];
560+
std::string element_uppercase = ra::strings::Uppercase(element);
561+
562+
//each element must match one of the patterns
563+
bool match = WildcardMatch(patterns, element_uppercase.c_str());
564+
if (!inversed && !match)
565+
return false; //current file does not match any patterns
566+
if (inversed && match)
567+
return false; //current element is not accepted
568+
}
569+
570+
return true;
571+
}
572+
510573
} //namespace shellanything

test/TestObjectFactory.cpp

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -150,9 +150,9 @@ namespace shellanything { namespace test
150150
Configuration::ConfigurationPtrList configs = cmgr.GetConfigurations();
151151
ASSERT_EQ( 1, configs.size() );
152152

153-
//ASSERT a 8 menus are available
153+
//ASSERT all menus are available
154154
Menu::MenuPtrList menus = cmgr.GetConfigurations()[0]->GetMenus();
155-
ASSERT_EQ( 9, menus.size() );
155+
ASSERT_EQ( 10, menus.size() );
156156

157157
//assert <visibility> tag properly parsed
158158
static const std::string expected_property = "bar";
@@ -163,6 +163,7 @@ namespace shellanything { namespace test
163163
static const std::string expected_inverse_many = "maxfiles;maxfolders";
164164
static const std::string expected_inverse_unknown = "foo";
165165
static const std::string expected_class = "file";
166+
static const std::string expected_pattern = "*IMG_*";
166167

167168
ASSERT_EQ( expected_property, menus[0]->GetVisibility().GetProperties() );
168169
ASSERT_EQ( 5, menus[1]->GetVisibility().GetMaxFiles() );
@@ -174,6 +175,7 @@ namespace shellanything { namespace test
174175
ASSERT_EQ( expected_inverse_many, menus[6]->GetVisibility().GetInserve() );
175176
ASSERT_EQ( expected_inverse_unknown, menus[7]->GetVisibility().GetInserve() );
176177
ASSERT_EQ( expected_class, menus[8]->GetVisibility().GetClass() );
178+
ASSERT_EQ( expected_pattern, menus[9]->GetVisibility().GetPattern() );
177179

178180
//cleanup
179181
ASSERT_TRUE( ra::filesystem::DeleteFile(template_target_path.c_str()) ) << "Failed deleting file '" << template_target_path << "'.";

test/TestValidator.cpp

Lines changed: 92 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -362,6 +362,50 @@ namespace shellanything { namespace test
362362

363363
}
364364
//--------------------------------------------------------------------------------------------------
365+
TEST_F(TestValidator, testPattern)
366+
{
367+
Context c;
368+
#ifdef _WIN32
369+
{
370+
Context::ElementList elements;
371+
elements.push_back("C:\\Windows\\System32\\kernel32.dll");
372+
elements.push_back("C:\\Windows\\System32\\cmd.exe" );
373+
elements.push_back("C:\\Windows\\System32\\notepad.exe" );
374+
elements.push_back("C:\\Windows\\System32\\services.msc");
375+
c.SetElements(elements);
376+
}
377+
#else
378+
//TODO: complete with known path to files
379+
#endif
380+
381+
Validator v;
382+
383+
//assert default
384+
ASSERT_TRUE( v.Validate(c) );
385+
386+
//assert failure when no pattern is matching
387+
v.SetPattern("foo");
388+
ASSERT_FALSE( v.Validate(c) );
389+
390+
//assert failure when patterns is matching only a single file
391+
v.SetPattern("*cmd.exe");
392+
ASSERT_FALSE( v.Validate(c) );
393+
394+
//assert success when patterns are matching all files
395+
v.SetPattern("*.dll;*.exe;*.msc");
396+
ASSERT_TRUE( v.Validate(c) );
397+
v.SetPattern("*.exe;*.dll;*.msc"); //random order
398+
ASSERT_TRUE( v.Validate(c) );
399+
400+
//assert success when more than required patterns are provided
401+
v.SetPattern("*e*;*.dll;*.exe;*.msc;*a*;");
402+
ASSERT_TRUE( v.Validate(c) );
403+
404+
//assert failure when multiple files are selected and a single pattern is missing
405+
v.SetPattern("*.dll;*.exe"); //missing msc file extension
406+
ASSERT_FALSE( v.Validate(c) );
407+
}
408+
//--------------------------------------------------------------------------------------------------
365409
TEST_F(TestValidator, testIsInversed)
366410
{
367411
Validator v;
@@ -578,7 +622,7 @@ namespace shellanything { namespace test
578622
v.SetFileExtensions("dll");
579623
ASSERT_FALSE( v.Validate(c) );
580624

581-
//assert failure when all properties are defined
625+
//assert failure when all file extensions are defined
582626
v.SetFileExtensions("dll;exe;msc");
583627
ASSERT_FALSE( v.Validate(c) );
584628
v.SetFileExtensions("exe;dll;msc"); //random order
@@ -641,6 +685,53 @@ namespace shellanything { namespace test
641685
ASSERT_FALSE( v.Validate(c) );
642686
}
643687
//--------------------------------------------------------------------------------------------------
688+
TEST_F(TestValidator, testPatternInversed)
689+
{
690+
Context c;
691+
#ifdef _WIN32
692+
{
693+
Context::ElementList elements;
694+
elements.push_back("C:\\Windows\\System32\\kernel32.dll");
695+
elements.push_back("C:\\Windows\\System32\\cmd.exe" );
696+
elements.push_back("C:\\Windows\\System32\\notepad.exe" );
697+
elements.push_back("C:\\Windows\\System32\\services.msc");
698+
c.SetElements(elements);
699+
}
700+
#else
701+
//TODO: complete with known path to files
702+
#endif
703+
704+
Validator v;
705+
v.SetInserve("pattern");
706+
707+
//assert default
708+
ASSERT_TRUE( v.Validate(c) );
709+
710+
//assert success when no pattern is matching
711+
v.SetPattern("foo");
712+
ASSERT_TRUE( v.Validate(c) );
713+
714+
//assert failure when a pattern is matching a single file
715+
v.SetPattern("*.dll");
716+
ASSERT_FALSE( v.Validate(c) );
717+
718+
//assert failure when pattern is matching all files
719+
v.SetPattern("*.dll;*.exe;*.msc");
720+
ASSERT_FALSE( v.Validate(c) );
721+
v.SetPattern("*.exe;*.dll;*.msc"); //random order
722+
ASSERT_FALSE( v.Validate(c) );
723+
724+
//assert failure when more than required patterns are provided
725+
v.SetPattern("*e*;*.dll;*.exe;*.msc;*a*;");
726+
ASSERT_FALSE( v.Validate(c) );
727+
728+
// If multiple patterns are specified, no pattern must match for the validation to be successful.
729+
v.SetPattern("*.foo;*.bar;*.baz;");
730+
ASSERT_TRUE( v.Validate(c) );
731+
v.SetPattern("*.foo;*.exe;*.bar;*.baz;");
732+
ASSERT_FALSE( v.Validate(c) );
733+
}
734+
//--------------------------------------------------------------------------------------------------
644735
TEST_F(TestValidator, testInversedAll)
645736
{
646737
Validator v;

test/test_files/TestObjectFactory.testParseValidator.xml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,5 +73,13 @@
7373
</actions>
7474
</menu>
7575

76+
<menu name="menu09">
77+
<!-- pattern attribute is set -->
78+
<visibility pattern="*IMG_*" />
79+
<actions>
80+
<property name="baz" value="true" />
81+
</actions>
82+
</menu>
83+
7684
</shell>
7785
</root>

0 commit comments

Comments
 (0)