Skip to content

Commit 3d528e5

Browse files
committed
Improved PropertyManager::Expand() to properly expands strings. Added new unit tests. For issue #72.
1 parent ee5e12d commit 3d528e5

File tree

2 files changed

+63
-19
lines changed

2 files changed

+63
-19
lines changed

src/PropertyManager.cpp

Lines changed: 59 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -92,24 +92,70 @@ namespace shellanything
9292
return EMPTY_VALUE;
9393
}
9494

95-
std::string PropertyManager::Expand(const std::string & value) const
95+
bool IsPropertyReference(const std::string & token_open, const std::string & token_close, const std::string & value, size_t offset, std::string & name)
9696
{
97-
std::string output = value;
97+
size_t value_length = value.size();
98+
name.clear();
99+
if (offset >= value_length)
100+
return false;
98101

99-
//for each properties
100-
for (PropertyMap::const_iterator propertyIt = properties.begin(); propertyIt != properties.end(); propertyIt++)
102+
//Validate a token_open prefix at value[offset]
103+
for(size_t i=0; i<token_open.size(); i++)
101104
{
102-
const std::string & name = propertyIt->first;
103-
const std::string & value = propertyIt->second;
105+
if (value[offset+i] != token_open[i])
106+
return false;
107+
}
104108

105-
//generate the search token
106-
std::string token;
107-
token.append("${");
108-
token.append(name);
109-
token.append("}");
109+
//Found a token_open position at value[offset]
110+
//Search for the token_close position.
111+
size_t name_start_pos = offset + token_open.size();
112+
size_t token_close_pos = value.find(token_close, name_start_pos);
113+
if (token_close_pos == std::string::npos)
114+
return false; // token_close not found
115+
116+
//Found a token_close position.
117+
//Extract the name of the property.
118+
size_t length = (token_close_pos) - name_start_pos;
119+
if (length == 0)
120+
return false;
121+
std::string temp_name(&value[name_start_pos], length);
122+
123+
//Validate if name
124+
PropertyManager & pmgr = PropertyManager::GetInstance();
125+
bool exists = pmgr.HasProperty(temp_name);
126+
if (exists)
127+
name = temp_name;
128+
return exists;
129+
}
110130

111-
//process with search and replace
112-
ra::strings::Replace(output, token, value);
131+
std::string PropertyManager::Expand(const std::string & value) const
132+
{
133+
std::string output;
134+
output.reserve(value.size()*2);
135+
136+
static const std::string token_open = "${";
137+
static const std::string token_close = "}";
138+
139+
for(size_t i=0; i<value.size(); i++)
140+
{
141+
std::string name;
142+
if (strncmp(&value[i], token_open.c_str(), token_open.size()) == 0 && IsPropertyReference(token_open, token_close, value, i, name))
143+
{
144+
//Found a property reference at value[i]
145+
const std::string & property_value = this->GetProperty(name);
146+
147+
//Also expands property_value
148+
std::string expanded = this->Expand(property_value);
149+
150+
//Proceed with the string replacement
151+
output.append(expanded);
152+
153+
//Update i to skip this property reference
154+
size_t length = token_open.size() + name.size() + token_close.size();
155+
i += length-1; //-1 since the next for loop will increase i by 1.
156+
}
157+
else
158+
output.append(1, value[i]);
113159
}
114160

115161
return output;

test/TestPropertyManager.cpp

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -170,21 +170,19 @@ namespace shellanything { namespace test
170170
{
171171
PropertyManager & pmgr = PropertyManager::GetInstance();
172172

173-
//Note:
174-
//Using text1 for the embedded property replacement will not work as properties are replaced in alphabetical order.
175-
//In other words, once text3 is replaced for the string "${text1}", the text1 property is already processed.
176-
//Using text4 instead of text1 for the replacement would fix the issue.
177-
178173
pmgr.Clear();
179174
pmgr.SetProperty("text1", "with you");
180175
pmgr.SetProperty("text2", "the Force");
181176
pmgr.SetProperty("text3", "${text1}");
182177

183178
//Try replacement in reverse alphabetical order.
179+
//If the property replacement is implemented in alphabetical order (or creation order),
180+
//at the time ${text3} is replaced, property 'text1' should already be processed leaving the text as "${text1}" instead of "with you".
181+
//If the implementation is working as expected, "${text3}" should be replaced by "with you" skipping "${text1}".
184182
std::string expanded = pmgr.Expand("May ${text2} be ${text3}");
185183

186184
//Assert not all place holder was replaced
187-
ASSERT_EQ("May the Force be ${text1}", expanded);
185+
ASSERT_EQ("May the Force be with you", expanded);
188186
}
189187
//--------------------------------------------------------------------------------------------------
190188
TEST_F(TestPropertyManager, testExpandDouble)

0 commit comments

Comments
 (0)