Skip to content
Newer
Older
100644 259 lines (224 sloc) 9.19 KB
b363796 @guillermooo reorganized parser-related code
guillermooo authored Sep 24, 2011
1 """a simple 'parser' for :ex commands
2 """
3
4
5 from collections import namedtuple
d734791 @guillermooo refactor the arg passing to ex commands
guillermooo authored Oct 1, 2011
6 from itertools import takewhile, dropwhile
b363796 @guillermooo reorganized parser-related code
guillermooo authored Sep 24, 2011
7 import re
8
9
91b278a @guillermooo code cleanup
guillermooo authored Oct 3, 2011
10 # holds info about a parsed ex command
11 EX_CMD = namedtuple('ex_command', 'name command forced range args')
12 # defines an ex command data for later parsing
52a64f8 @guillermooo renaming for accuracy
guillermooo authored Oct 3, 2011
13 ex_cmd_data = namedtuple('ex_cmd_data', 'command args wants_plusplus wants_plus args_parser')
91b278a @guillermooo code cleanup
guillermooo authored Oct 3, 2011
14
a232c75 @guillermooo initial detection of '< '> marks
guillermooo authored Sep 29, 2011
15 EX_RANGE_REGEXP = re.compile(r'^(:?([.$%]|(:?/.*?/|\?.*?\?){1,2}|\d+|[\'`][a-zA-Z0-9<>])([-+]\d+)?)(([,;])(:?([.$]|(:?/.*?/|\?.*?\?){1,2}|\d+|[\'`][a-zA-Z0-9<>])([-+]\d+)?))?')
f39dc82 @guillermooo improve detection of range-only commands
guillermooo authored Sep 27, 2011
16 EX_ONLY_RANGE_REGEXP = re.compile(r'^(?:([%$.]|\d+|/.*?(?<!\\)/|\?.*?\?)([-+]\d+)*(?:([,;])([%$.]|\d+|/.*?(?<!\\)/|\?.*?\?)([-+]\d+)*)?)|(^[/?].*)$')
b363796 @guillermooo reorganized parser-related code
guillermooo authored Sep 24, 2011
17
18
19 EX_COMMANDS = {
caefef2 @guillermooo use namedtuples for the command data
guillermooo authored Sep 28, 2011
20 ('write', 'w'): ex_cmd_data(
21 command='ex_write_file',
d734791 @guillermooo refactor the arg passing to ex commands
guillermooo authored Oct 1, 2011
22 args=[],
23 wants_plusplus=True,
24 wants_plus=True,
52a64f8 @guillermooo renaming for accuracy
guillermooo authored Oct 3, 2011
25 args_parser='extract_write_args'),
caefef2 @guillermooo use namedtuples for the command data
guillermooo authored Sep 28, 2011
26 ('wall', 'wa'): ex_cmd_data(
27 command='ex_write_all',
d734791 @guillermooo refactor the arg passing to ex commands
guillermooo authored Oct 1, 2011
28 args=[],
29 wants_plusplus=False,
30 wants_plus=False,
52a64f8 @guillermooo renaming for accuracy
guillermooo authored Oct 3, 2011
31 args_parser=None),
caefef2 @guillermooo use namedtuples for the command data
guillermooo authored Sep 28, 2011
32 ('pwd', 'pw'): ex_cmd_data(
33 command='ex_print_working_dir',
d734791 @guillermooo refactor the arg passing to ex commands
guillermooo authored Oct 1, 2011
34 args=[],
35 wants_plusplus=False,
36 wants_plus=False,
52a64f8 @guillermooo renaming for accuracy
guillermooo authored Oct 3, 2011
37 args_parser=None),
caefef2 @guillermooo use namedtuples for the command data
guillermooo authored Sep 28, 2011
38 ('buffers', 'buffers'): ex_cmd_data(
39 command='ex_prompt_select_open_file',
d734791 @guillermooo refactor the arg passing to ex commands
guillermooo authored Oct 1, 2011
40 args=[],
41 wants_plusplus=False,
42 wants_plus=False,
52a64f8 @guillermooo renaming for accuracy
guillermooo authored Oct 3, 2011
43 args_parser=None),
4077abc @guillermooo initial implementation of :move and :file and :files
guillermooo authored Oct 2, 2011
44 ('files', 'files'): ex_cmd_data(
45 command='ex_prompt_select_open_file',
46 args=[],
47 wants_plusplus=False,
48 wants_plus=False,
52a64f8 @guillermooo renaming for accuracy
guillermooo authored Oct 3, 2011
49 args_parser=None),
caefef2 @guillermooo use namedtuples for the command data
guillermooo authored Sep 28, 2011
50 ('ls', 'ls'): ex_cmd_data(
51 command='ex_prompt_select_open_file',
d734791 @guillermooo refactor the arg passing to ex commands
guillermooo authored Oct 1, 2011
52 args=[],
53 wants_plusplus=False,
54 wants_plus=False,
52a64f8 @guillermooo renaming for accuracy
guillermooo authored Oct 3, 2011
55 args_parser=None),
caefef2 @guillermooo use namedtuples for the command data
guillermooo authored Sep 28, 2011
56 ('map', 'map'): ex_cmd_data(
57 command='ex_map',
d734791 @guillermooo refactor the arg passing to ex commands
guillermooo authored Oct 1, 2011
58 args=[],
59 wants_plusplus=False,
60 wants_plus=False,
52a64f8 @guillermooo renaming for accuracy
guillermooo authored Oct 3, 2011
61 args_parser=None),
caefef2 @guillermooo use namedtuples for the command data
guillermooo authored Sep 28, 2011
62 ('abbreviate', 'ab'): ex_cmd_data(
63 command='ex_abbreviate',
d734791 @guillermooo refactor the arg passing to ex commands
guillermooo authored Oct 1, 2011
64 args=[],
65 wants_plusplus=False,
66 wants_plus=False,
52a64f8 @guillermooo renaming for accuracy
guillermooo authored Oct 3, 2011
67 args_parser=None),
caefef2 @guillermooo use namedtuples for the command data
guillermooo authored Sep 28, 2011
68 ('read', 'r'): ex_cmd_data(
69 command='ex_read_shell_out',
1a42487 @guillermooo improve :r! command
guillermooo authored Oct 1, 2011
70 args=['name'],
71 wants_plusplus=True,
d734791 @guillermooo refactor the arg passing to ex commands
guillermooo authored Oct 1, 2011
72 wants_plus=False,
52a64f8 @guillermooo renaming for accuracy
guillermooo authored Oct 3, 2011
73 args_parser=None),
a3eef0c @guillermooo add :enew command
guillermooo authored Sep 29, 2011
74 ('enew', 'ene'): ex_cmd_data(
75 command='ex_new_file',
d734791 @guillermooo refactor the arg passing to ex commands
guillermooo authored Oct 1, 2011
76 args=[],
77 wants_plusplus=True,
78 wants_plus=True,
52a64f8 @guillermooo renaming for accuracy
guillermooo authored Oct 3, 2011
79 args_parser=None),
c75e71f @guillermooo (not) implement :ascii commands
guillermooo authored Oct 1, 2011
80 ('ascii', 'as'): ex_cmd_data(
81 command='ex_ascii',
82 args=[],
83 wants_plusplus=False,
84 wants_plus=False,
52a64f8 @guillermooo renaming for accuracy
guillermooo authored Oct 3, 2011
85 args_parser=None),
4077abc @guillermooo initial implementation of :move and :file and :files
guillermooo authored Oct 1, 2011
86 ('file', 'f'): ex_cmd_data(
87 command='ex_file',
88 args=[],
89 wants_plusplus=False,
90 wants_plus=False,
52a64f8 @guillermooo renaming for accuracy
guillermooo authored Oct 3, 2011
91 args_parser=None),
4077abc @guillermooo initial implementation of :move and :file and :files
guillermooo authored Oct 1, 2011
92 ('move', 'move'): ex_cmd_data(
93 command='ex_move',
94 args=['address'],
95 wants_plusplus=False,
96 wants_plus=False,
52a64f8 @guillermooo renaming for accuracy
guillermooo authored Oct 3, 2011
97 args_parser=None),
32ca65a @guillermooo tentative implementation of :copy command
guillermooo authored Oct 2, 2011
98 ('copy', 'co'): ex_cmd_data(
99 command='ex_copy',
100 args=['address'],
101 wants_plusplus=False,
102 wants_plus=False,
52a64f8 @guillermooo renaming for accuracy
guillermooo authored Oct 3, 2011
103 args_parser=None),
32ca65a @guillermooo tentative implementation of :copy command
guillermooo authored Oct 1, 2011
104 ('t', 't'): ex_cmd_data(
105 command='ex_copy',
106 args=['address'],
107 wants_plusplus=False,
108 wants_plus=False,
52a64f8 @guillermooo renaming for accuracy
guillermooo authored Oct 3, 2011
109 args_parser=None),
b363796 @guillermooo reorganized parser-related code
guillermooo authored Sep 24, 2011
110 }
111
112
113 def find_command(cmd_name):
8237a48 @guillermooo add arg data to the command data
guillermooo authored Sep 28, 2011
114 partial_matches = [name for name in EX_COMMANDS.keys()
f39dc82 @guillermooo improve detection of range-only commands
guillermooo authored Sep 26, 2011
115 if name[0].startswith(cmd_name)]
8237a48 @guillermooo add arg data to the command data
guillermooo authored Sep 28, 2011
116 if not partial_matches: return None
117 full_match = [(ln, sh) for (ln, sh) in partial_matches
118 if cmd_name in (ln, sh)]
b363796 @guillermooo reorganized parser-related code
guillermooo authored Sep 24, 2011
119 if full_match:
120 return full_match[0]
121 else:
8237a48 @guillermooo add arg data to the command data
guillermooo authored Sep 28, 2011
122 return partial_matches[0]
b363796 @guillermooo reorganized parser-related code
guillermooo authored Sep 24, 2011
123
124
3198727 @guillermooo add tests for range only detection
guillermooo authored Sep 25, 2011
125 def is_only_range(cmd_line):
f5a3d65 @guillermooo improve range-only command detection
guillermooo authored Sep 25, 2011
126 try:
127 return EX_ONLY_RANGE_REGEXP.search(cmd_line) and \
128 EX_RANGE_REGEXP.search(cmd_line).span()[1] == len(cmd_line)
129 except AttributeError:
130 return EX_ONLY_RANGE_REGEXP.search(cmd_line)
3198727 @guillermooo add tests for range only detection
guillermooo authored Sep 24, 2011
131
132
b363796 @guillermooo reorganized parser-related code
guillermooo authored Sep 24, 2011
133 def get_cmd_line_range(cmd_line):
134 try:
135 start, end = EX_RANGE_REGEXP.search(cmd_line).span()
136 except AttributeError:
137 return None
138 return cmd_line[start:end]
139
140
a47e384 @guillermooo abstract out parsing of command name and options
guillermooo authored Sep 28, 2011
141 def extract_command_name(cmd_line):
142 return ''.join(takewhile(lambda c: c.isalpha(), cmd_line))
143
144
d734791 @guillermooo refactor the arg passing to ex commands
guillermooo authored Oct 1, 2011
145 def extract_write_args(args):
146 if '>>' in args:
147 left, operator, right = args.partition('>>')
148 elif '!' in args and not ('! ' in args or args.endswith('!')):
149 left, operator, right = args.partition('!')
150 else:
151 left = args
152 operator = None
153 right = ''
154
155 if operator:
156 if operator == '!':
157 return {
158 'operator': operator,
159 'plusplus_args': left,
160 'subcmd': right
161 }
162 else:
163 return {
164 'operator': operator,
165 'plusplus_args': left,
166 'target_redirect': right
167 }
168
169 left_tokens = left.split(' ')
170 return {'file_name': ' '.join(dropwhile(lambda x: x.startswith('++') or
171 not x, left_tokens)),
172 'plusplus_args': ' '.join(takewhile(lambda x: x.startswith('++')
173 or not x, left_tokens))
174 }
175
176
a47e384 @guillermooo abstract out parsing of command name and options
guillermooo authored Sep 28, 2011
177 def extract_args(cmd_line):
178 plus_args = []
179 plusplus_args = []
180 args = []
181 for i, token in enumerate(cmd_line.split(' ')):
182 if token and token.startswith('++'):
183 plusplus_args.append(token)
184 elif token and token.startswith('+'):
185 plus_args = cmd_line.split(' ')[i:]
186 break
187 elif not token.startswith('!'):
188 args.append(token)
189 else:
91b278a @guillermooo code cleanup
guillermooo authored Oct 3, 2011
190 raise RuntimeError("Unexpected condition.")
a47e384 @guillermooo abstract out parsing of command name and options
guillermooo authored Sep 28, 2011
191
192 return ' '.join(plus_args), ' '.join(plusplus_args), \
91b278a @guillermooo code cleanup
guillermooo authored Oct 3, 2011
193 ' '.join(args).strip()
a47e384 @guillermooo abstract out parsing of command name and options
guillermooo authored Sep 28, 2011
194
195
b363796 @guillermooo reorganized parser-related code
guillermooo authored Sep 24, 2011
196 def parse_command(cmd):
197 # strip :
198 cmd_name = cmd[1:]
199
b263bdf @guillermooo make range only commands work (:0, :1, :/foo/+10, etc)
guillermooo authored Sep 25, 2011
200 # first the odd commands
201 if is_only_range(cmd_name):
202 return EX_CMD(name=':',
203 command='ex_goto',
204 forced=False,
205 range=cmd_name,
e7a57ed @guillermooo fix :<range> and close : prompt on bad command
guillermooo authored Oct 2, 2011
206 args={},
b263bdf @guillermooo make range only commands work (:0, :1, :/foo/+10, etc)
guillermooo authored Sep 24, 2011
207 )
208
b363796 @guillermooo reorganized parser-related code
guillermooo authored Sep 24, 2011
209 if cmd_name.startswith('!'):
210 cmd_name = '!'
211 args = cmd[2:]
212 return EX_CMD(name=cmd_name,
213 command=None,
214 forced=False,
215 range=None,
e7a57ed @guillermooo fix :<range> and close : prompt on bad command
guillermooo authored Oct 1, 2011
216 args={'shell_cmd': args},
b363796 @guillermooo reorganized parser-related code
guillermooo authored Sep 24, 2011
217 )
218
f39dc82 @guillermooo improve detection of range-only commands
guillermooo authored Sep 26, 2011
219 range_ = get_cmd_line_range(cmd_name)
220 if range_: cmd_name = cmd_name[len(range_):]
b363796 @guillermooo reorganized parser-related code
guillermooo authored Sep 24, 2011
221
a47e384 @guillermooo abstract out parsing of command name and options
guillermooo authored Sep 28, 2011
222 command = extract_command_name(cmd_name)
223 args = cmd_name[len(command):]
b363796 @guillermooo reorganized parser-related code
guillermooo authored Sep 24, 2011
224
a47e384 @guillermooo abstract out parsing of command name and options
guillermooo authored Sep 28, 2011
225 bang = False
226 if args.startswith('!'):
b363796 @guillermooo reorganized parser-related code
guillermooo authored Sep 24, 2011
227 bang = True
a47e384 @guillermooo abstract out parsing of command name and options
guillermooo authored Sep 28, 2011
228 args = args[1:]
229
230 cmd_data = find_command(command)
b363796 @guillermooo reorganized parser-related code
guillermooo authored Sep 24, 2011
231 if not cmd_data: return None
232 cmd_data = EX_COMMANDS[cmd_data]
233
d734791 @guillermooo refactor the arg passing to ex commands
guillermooo authored Oct 1, 2011
234 if cmd_data.wants_plusplus or cmd_data.wants_plus:
91b278a @guillermooo code cleanup
guillermooo authored Oct 3, 2011
235 plus_args, plusplus_args, args = extract_args(args)
d734791 @guillermooo refactor the arg passing to ex commands
guillermooo authored Oct 1, 2011
236 else:
237 plus_args = '',
238 plusplus_args= '',
239
b363796 @guillermooo reorganized parser-related code
guillermooo authored Sep 24, 2011
240 cmd_args = {}
1a42487 @guillermooo improve :r! command
guillermooo authored Oct 1, 2011
241 if cmd_data.wants_plus:
242 cmd_args['plus_args'] = plus_args
243 if cmd_data.wants_plusplus:
244 cmd_args['plusplus_args'] = plusplus_args
5c83ca8 @guillermooo improve :r! command
guillermooo authored Oct 1, 2011
245
52a64f8 @guillermooo renaming for accuracy
guillermooo authored Oct 3, 2011
246 if cmd_data.args_parser:
247 func = globals()[cmd_data.args_parser]
d734791 @guillermooo refactor the arg passing to ex commands
guillermooo authored Oct 1, 2011
248 cmd_args = func(args)
249 else:
250 if cmd_data.args and args:
5c83ca8 @guillermooo improve :r! command
guillermooo authored Oct 1, 2011
251 cmd_args = dict(zip(cmd_data.args, [args]))
b363796 @guillermooo reorganized parser-related code
guillermooo authored Sep 24, 2011
252
a47e384 @guillermooo abstract out parsing of command name and options
guillermooo authored Sep 28, 2011
253 return EX_CMD(name=command,
caefef2 @guillermooo use namedtuples for the command data
guillermooo authored Sep 28, 2011
254 command=cmd_data.command,
b363796 @guillermooo reorganized parser-related code
guillermooo authored Sep 24, 2011
255 forced=bang,
f39dc82 @guillermooo improve detection of range-only commands
guillermooo authored Sep 26, 2011
256 range=range_,
4a1eabe @guillermooo messily accept long commands for :r!, like :r! dir /D
guillermooo authored Sep 26, 2011
257 args=cmd_args,
b363796 @guillermooo reorganized parser-related code
guillermooo authored Sep 24, 2011
258 )
Something went wrong with that request. Please try again.