-
Notifications
You must be signed in to change notification settings - Fork 0
/
section.py
executable file
·256 lines (211 loc) · 8.68 KB
/
section.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
#! /usr/bin/env python3
# -----------------------------------------------------------------------------
# Copyright © 2016 Ben Blazak <benblazak.dev@gmail.com>
# Released under the [MIT License] (http://opensource.org/licenses/MIT)
# Project located at <https://github.com/benblazak/text-processing-utilities>
# -----------------------------------------------------------------------------
'''A script/module to 'section' files -- that is, to take one file and produce
some number of others, where each of the derivative files contains some subset
of the lines in the original.
If run as a script, `input` is called with the arguments passed via the command
line (except for the program name).
The basic syntax for input files is
```
normal text normal text
// SECTION BEGIN section-name
this text will be included in the 'section-name' section
// SECTION END section-name
normal text normal text
```
'''
# -----------------------------------------------------------------------------
import os.path
import re
import sys
import textwrap
# -----------------------------------------------------------------------------
class Section:
class Error(Exception):
pass
class SyntaxError(Exception):
pass
def raiseSyntaxError(self, message):
raise self.SyntaxError(
'"' + self._filename + '"'
+ ' line '
+ str( self._lineno )
+ ': '
+ message
)
# . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
def __init__(self):
# (managed by `self.input`)
self._filename = None # the name of the current file
# (managed by `self.section`)
self._lineno = None # the current line number
# (used by `self.section`)
self._esc = '//' # the string beginning a section command
# .........................................................................
def section(self):
'''Break `self._filename` into sections.
'''
section_re = self._esc + r'\s*SECTION'
ignore_re = r'\s+IGNORE' # ignore line
begin_re = r'\s+BEGIN'
end_re = r'\s+END'
pause_re = r'\s+PAUSE'
resume_re = r'\s+RESUME'
indent_re = r'\s+INDENT' # reset indent
name_re = r'\s+([\w-]+)'
section = re.compile( section_re )
ignore = re.compile( section_re + ignore_re )
begin = re.compile( section_re + begin_re + name_re )
end = re.compile( section_re + end_re + name_re )
pause = re.compile( section_re + pause_re + name_re )
resume = re.compile( section_re + resume_re + name_re )
indent = re.compile( section_re + indent_re + name_re )
sections_lines = { 'all':'' } # the lines of text, for every section
sections_open = { 'all' } # sections currently being processed
sections_indent = { 'all':0 } # number of initial chars to discard
f = open(self._filename)
self._lineno = 0;
for line in f:
self._lineno += 1
if ignore.search(line):
pass
elif begin.search(line):
name = begin.search(line).group(1)
sections_lines[name] = ''
sections_open.add(name)
sections_indent[name] = re.search( r'\S', line ).start()
elif end.search(line):
name = end.search(line).group(1)
if name in sections_open:
sections_open.remove(name)
else:
raiseSyntaxError( 'Illegal SECTION in input file' )
elif pause.search(line):
name = pause.search(line).group(1)
if name in sections_open:
sections_open.remove(name)
else:
raiseSyntaxError( 'Illegal SECTION in input file' )
elif resume.search(line):
name = resume.search(line).group(1)
sections_open.add(name)
elif indent.search(line):
name = indent.search(line).group(1)
sections_indent[name] = re.search( r'\S', line ).start()
else:
if section.search(line):
raiseSyntaxError( 'Illegal SECTION in input file' )
for name in sections_open:
sections_lines[name] += line[sections_indent[name]:] or '\n'
return sections_lines
def input(self, *args):
'''Handle command line arguments.
'''
def wrap(width, indent, text):
return '\n'.join(
textwrap.wrap(
textwrap.dedent( text ).lstrip(),
width = width,
initial_indent = ' ' * indent,
subsequent_indent = ' ' * indent,
)
)
usage = '\n'.join((
'usage: ' + sys.argv[0] + ' [OPTIONS] INPUT_FILE',
'',
wrap( width=79, indent=0, text='''
Options may be placed anywhere in the list of arguments.
''' ),
'',
'-h|--help', wrap( width=79-4, indent=4, text='''
Show this documentation and exit.
''' ),
'',
'-l|--list', wrap( width=79-4, indent=4, text='''
Show the section names contained in the input file as a comma
separated list, and exit.
''' ),
'',
'-e|--escape ESC', wrap( width=79-4, indent=4, text='''
Set the "escape sequence", i.e. the string that begins a
section command in INPUT_FILE. By default, this value is set
based on the filename extension (or '//' if the extension is
not known). Later occurrences of this option override earlier
ones.
''' ),
'',
'-b|--base BASE', wrap( width=79-4, indent=4, text='''
See [-o | --output]. By default, BASE is set to INPUT_FILE.
Later occurrences of this option override earlier ones.
''' ),
'',
'-o|--output SECTIONS', wrap( width=79-4, indent=4, text='''
Write each NAME in SECTIONS (where SECTIONS is a comma
separated list of section names) to BASE.section.NAME. By
default, all possible sections are written. Later occurrences
of this option override earlier ones.
''' ),
))
# vars
self._filename = None
_list = None
base = None
output = None
# helper functions
def error(message):
print( sys.argv[0]+':', message, file=sys.stderr )
print( usage, file=sys.stderr )
exit(1)
# process arguments
it = iter(args)
for arg in it:
if arg in ( '-h', '--help' ):
print(usage)
exit(0)
elif arg in ( '-l', '--list' ):
_list = True
elif arg in ( '-e', '--escape' ):
try:
self._esc = next(it)
except StopIteration:
error( '[-e | --escape] requires a following argument' )
elif arg in ( '-b', '--base' ):
try:
base = next(it)
except StopIteration:
error( '[-b | --base] requires a following argument' )
elif arg in ( '-o', '--output' ):
try:
output = [ s.strip() for s in next(it).split(',') ]
except StopIteration:
error( '[-o | --output] requires a following argument' )
else:
if self._filename is None:
self._filename = arg
else:
error( 'Multiple INPUT_FILEs given' )
# error check
if self._filename is None:
error( 'No INPUT_FILE given' )
# set defaults
if self._filename.endswith('.py'): self._esc = '#'
if self._filename.endswith('.tex'): self._esc = '%'
if self._esc is None: self._esc = '//'
if _list is None: _list = False
if base is None: base = self._filename
# section
sections = self.section()
if _list is True:
print(','.join(sections.keys()))
return
for s,lines in sections.items():
if output is None or s in output:
with open(base + '.section.' + s, 'w') as f:
f.write(lines)
# -----------------------------------------------------------------------------
if __name__ == '__main__':
Section().input( *sys.argv[1:] )