Download code

Jump to: navigation, search

Back to Literate_Programming_Tool_(Python)

Download for Windows: single file, zip

Download for UNIX: single file, zip, tar.gz, tar.bz2

asciilitprog.py

  1 #!/usr/bin/env python
  2 # The authors of this work have released all rights to it and placed it
  3 # in the public domain under the Creative Commons CC0 1.0 waiver
  4 # (http://creativecommons.org/publicdomain/zero/1.0/).
  5 # 
  6 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
  7 # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
  8 # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
  9 # IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
 10 # CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
 11 # TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
 12 # SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 13 # 
 14 # Retrieved from: http://en.literateprograms.org/Literate_Programming_Tool_(Python)?oldid=17020
 15 
 16 """
 17 Literate programming tool for use with AsciiDoc.
 18 
 19 
 20 Copyright (C) 2009 Ed Keith
 21 
 22 Free use of this software is granted under the terms of the 
 23 GNU General Public License (GPL).
 24 """
 25 __version__ 	= "1.0.0a1"
 26 __author__ 	= "Ed Keith"
 27 __copyright__ 	= "Copyright 2009, Ed Keith"
 28 __license__ 	= "GPL"
 29 __email__ 	= "edk@users.berlios.de"
 30 __status__ 	= "Alpha"
 31 __date__ 	= "16 December 2009"
 32 
 33 import re
 34 import logging
 35 
 36 _logging_level = logging.INFO # should be logging.INFO for released code
 37 
 38 def setup_logging():
 39     """
 40     Sets up the logger
 41     """
 42     import ConfigParser # change this to configparser for Python 3
 43     import logging.config
 44     global logger
 45 
 46     try:
 47         logging.config.fileConfig("apllog.conf")
 48     except ConfigParser.NoSectionError: 
 49         # if there is no configuration file setup a default configuration
 50         logging.basicConfig(filename='asciilitprog.log',level= _logging_level,
 51                             format='%(asctime)s %(levelname)s - %(message)s',
 52                             datefmt='%Y %b %d, %a %H:%M:%S'
 53                 )
 54     
 55     logger = logging.getLogger('%s' % __name__)
 56 
 57     logger.debug('logger ready')
 58 def get_options():
 59     """
 60     gets options from the command line and environment.
 61     """
 62     from optparse import OptionParser
 63 
 64     logger.debug('getting options')
 65 	
 66     usage = "usage: %prog [options] [input file]" + \
 67             "if no file is given input is read from stdin."
 68 
 69     version="%prog " + __version__ + " - " + __date__
 70     parser = OptionParser(usage=usage, version=version)
 71 
 72     parser.add_option("-l", "--logging", dest="log_level", default= -1,
 73             help= "logging level, CRITICAL, ERROR, WARNING, INFO, or DEBUG")
 74     parser.add_option("-t", "--tabs", dest="tab_stop", default= 8,
 75             help= "set tab stops for tab expansion")
 76     
 77     (options, args) = parser.parse_args()
 78 
 79     if options.log_level in ("CRITICAL", "ERROR", "WARNING", "INFO", "DEBUG") :
 80 
 81         ll = {"CRITICAL"   : logging.CRITICAL,
 82               "ERROR"	      : logging.ERROR,
 83               "WARNING"    : logging.WARNING,
 84               "INFO"       : logging.INFO,
 85               "DEBUG"      : logging.DEBUG}
 86 
 87         logger.setLevel(ll[options.log_level])
 88 
 89     elif options.log_level != -1:
 90         logger.warning("Logging Level set to illegal value '%s'", 
 91                         options.log_level)
 92 
 93     if len(args) == 1:
 94         options.rfilename = args[0]
 95     elif len(args) == 0:
 96         options.rfilename = ""
 97     else :
 98         options.rfilename = args[0]
 99         logger.warning("Wrong number of argumants")
100  
101     logger.debug('done reading options %s', options)
102       
103     return options
104 tag_start_pattern = '<{4}'
105 tag_end_pattern = '>{4}'
106 
107 
108 def index_of_last(seq, f):
109     """Return the index of the last item in seq where f(item) == True."""
110     return next((i for i in xrange(len(seq)-1,-1,-1) if f(seq[i])), None)
111 
112 def add_chunk(chunks, name, value, mod):
113     """
114     adds a chunk to the collection of chunks
115 
116     Trims trailing blank lines before storing.
117     """
118 
119     # Get index of last non empty line
120     i = index_of_last(value, (lambda x: x.strip() != '') )
121 
122     v = value[:i+1]
123 
124     logger.debug(
125            "Adding chunk named %(name)s " +
126            "with value of '%(value)s' to collection in mod %(mod)s", 
127            {'name':name, 'value':v, 'mod':mod})
128     if mod == '+':
129         chunks[name] = chunks[name] + v
130     else:	    
131         chunks[name] = v
132 
133 def extract_chunks(ins, outs):
134     """
135     The input parameter is an open stream from which the document is read.
136 
137     Returns a dictionary of chunks each made up of a list of the lines of 
138     text in the chunk. 
139     """
140 
141     block_delimiter = re.compile('^(-{4,})\s*$') 
142     start_chunk_definition = re.compile('^' + tag_start_pattern + 
143                                         '(?P<name>[^<>]+)' + 
144                                         tag_end_pattern + 
145                                         '(?P<mod>\+?)=(?P<value>.*)')
146 
147     preceding_line_length = 0
148     cur_chunk_name = ""
149     cur_chunk_value = []
150     chunks = {}
151     mod = ''
152 
153     in_code_block = False
154 
155     while True:
156         line = ins.readline()
157         logger.debug("processing line %s", line)
158         if not line: break
159 
160         m = block_delimiter.match(line)
161 
162         if in_code_block:
163             if m:
164                 logger.debug("Leaving code block")
165 		in_code_block = False
166 		if cur_chunk_name != "":
167 		    add_chunk(chunks, cur_chunk_name, cur_chunk_value, mod)
168 
169 		cur_chunk_name = ''
170 		cur_chunk_value = []
171 		mod = ''
172 
173             else:
174                 # in a code block
175                 c = start_chunk_definition.match(line)
176                 if c: # starting a new chunk
177                     logger.debug('starting ney chunk named "%s"', c.group('name'))
178 		    if cur_chunk_name != "":
179 		        add_chunk(chunks, cur_chunk_name, cur_chunk_value, mod)
180 		        cur_chunk_name = ''
181 		        cur_chunk_value = []
182 		        mod = ''
183 
184 		    cur_chunk_name = c.group('name')
185 		    cur_chunk_value.append(c.group('value').rstrip())
186 		    mod = c.group('mod')
187                 else:
188                     cur_chunk_value.append(line.rstrip())
189 
190         elif m and ((preceding_line_length == 0) 
191                or (abs(len(m.group(1)) - preceding_line_length) > 4)):
192 
193             logger.debug("entering code block")
194             in_code_block = True
195             cur_chunk_name = ''
196             cur_chunk_value = []
197             mod = ''
198 
199         preceding_line_length = len(line.rstrip())
200 
201     return chunks
202 def assemble_chunk(chunks, chunk_name, indent_level):
203     """
204     returns a string made of all the lines of the named chunk.
205 
206     It starts with the beginning of the first line of the chunk(nothing is 
207     prepended)
208     
209     Before each successive line a new line and indent_level spaces are inserted.
210     """
211     logger.debug("entering assemble_chunk")
212     logger.debug("Keys in chunks = '%s'", "".join(chunks.keys()))
213     logger.debug("name = '%s'", chunk_name)
214     logger.debug("indent = %d", indent_level)
215     logger.debug(
216             "Assembling chunk named %(name)s " +
217             "with value of '%(value)s' to collection", 
218              {'name':chunk_name, 
219               'value':chunks[chunk_name]})
220 
221     s = ''
222     tag_pattern = re.compile(tag_start_pattern + '(.+?)' + tag_end_pattern)
223     for c in chunks[chunk_name]:
224         logger.debug('adding "%s" to "%s"', c, s)
225         ce = c.rstrip().expandtabs(tab_stop)
226         padding = len(ce) - len(ce.lstrip())
227 
228         logger.debug('new padding = %d', padding)
229 
230         tags = tag_pattern.findall(ce)
231 
232         for t in tags:
233             p = '^(.*)' + tag_start_pattern  + t + tag_end_pattern + '(.*)$'
234 
235             expansion = assemble_chunk(chunks, t, (indent_level + padding))
236             logger.debug("chunk expanded to '%s'", expansion)
237 
238             # re.sub can not be used because every '\n' 
239             # would be translated into a new line.
240             # ce = re.sub(p, expansion, ce)
241 
242             m = re.match(p, ce)
243             ce = m.group(1) + expansion + m.group(2)
244 
245         if s == '':
246             s = ce
247         else:
248             s = s + "\n" + ''.ljust(indent_level) + ce
249 
250     logger.debug("assemble_chunk is returning '%s'", s)
251     return s
252 
253 
254 def write_files(chunks):
255     """
256     Creates the output files by assembling the chunks
257 
258     The contents of the chunked named '*' will be written to stdout.
259     """
260     logger.debug('writing files')
261 
262     # import re
263     r = re.compile('\*.+\*')
264 
265 
266     # [s[1:-1] for s in l if (s[:1] == s[-1:] == '*')]
267     for n in [x for x in chunks.keys() 
268         if x.startswith("*") and x.endswith("*")]:
269 
270             logger.debug('Writing %s', n)
271 
272             # open the file for writing text
273             c = assemble_chunk(chunks, n, 0)
274 
275             logger.debug("writing '%s' to %s", c , n[1:-1]) 
276 
277             if n[1 : -1] == '':
278                 f = sys.stdout
279             else:
280                 f = open(n[1 : -1], 'wt')
281             f.write(c)
282             f.close()
283 
284 def main():
285     """
286     main entry point. 
287 
288     Makes sure the input and output streams are open.
289 
290     Calls the procedure to process the file.
291 
292     Closes the files if necessary.
293     """
294     import sys  # needed for stdin
295 
296     setup_logging()
297 
298     options = get_options()
299 
300     if options.rfilename == "":
301         i = sys.stdin
302         logger.info("Reading form console")
303     else:
304         i=open(options.rfilename, "rt")
305         logger.info("reading from %s", options.rfilename)
306 
307 
308     chunks = extract_chunks(i, sys.stdout)
309 
310     logger.debug("***** All Cunks Extracted *****")
311     logger.debug("Extracted chunks = %s", chunks)
312 
313     # close the files, if we opened them
314     if i != sys.stdin:
315         i.close()
316 
317     global tab_stop 
318     tab_stop = options.tab_stop
319     write_files(chunks)
320 
321 if __name__ == "__main__":
322     main()


hijacker
hijacker
hijacker
hijacker