3 # Copyright (C) 2011-2013 W. Trevor King <wking@tremily.us>
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Lesser General Public License as
7 # published by the Free Software Foundation, either version 3 of the
8 # License, or (at your option) any later version.
10 # This program is distributed in the hope that it will be useful, but
11 # WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 # Lesser General Public License for more details.
15 # You should have received a copy of the GNU Lesser General Public
16 # License along with this program. If not, see
17 # <http://www.gnu.org/licenses/>.
19 """View Maple worksheets (.mw) from the command line (without X).
21 ./mw2txt.py -c input.mw | less
22 ./mw2txt.py -m input.mw | /opt/maple15/bin/maple | less
25 import logging as _logging
28 import lxml.etree as _lxml_etree
31 from pygments.console import colorize as _colorize
32 except ImportError as e:
33 _sys.stderr.write(str(e) + '\n')
34 def _color_string(string, color=None):
37 def _color_string(string, color=None):
43 return _colorize(color_key=color, text=string)
48 LOG = _logging.getLogger(__name__)
49 LOG.addHandler(_logging.StreamHandler())
50 LOG.setLevel(_logging.ERROR)
53 def _write_color(string, color=None, stream=None):
56 stream.write(_color_string(string=string, color=color))
59 class Writer (object):
60 def __init__(self, color=None, stream=None, use_color=False):
63 self.use_color = use_color
66 def __call__(self, text, color=None):
67 if not self.use_color:
71 if text == '\n' and self.last_char == '\n':
72 return # don't add lots of blank lines
73 _write_color(string=text, color=color, stream=self.stream)
74 self.last_char = text[-1]
77 def mw2txt(path, writer, filter_math=False):
78 xml = _lxml_etree.parse(path)
81 match=lambda node: node.tag == 'Text-field',
82 match_action=lambda node: top_text_node2txt(
83 node=node, writer=writer, filter_math=filter_math),
84 match_tail=lambda node:writer(text='\n'))
86 def top_text_node2txt(node, writer, filter_math=False):
90 match_action = lambda node: other_in_text_node2txt(
91 node=node, writer=writer)
94 match=lambda node: node.tag not in ['Text-field', 'Font', 'Hyperlink'],
95 match_action=match_action,
96 other_action=lambda node: text_node2txt(
97 node=node, writer=writer, filter_math=filter_math),
98 match_tail=lambda node:tail_node2txt(
99 node=node, writer=writer, filter_math=filter_math),
100 other_tail=lambda node:tail_node2txt(
101 node=node, writer=writer, filter_math=filter_math))
103 def other_in_text_node2txt(node, writer):
104 if node.tag in ['Drawing-Root']:
105 # ignore missing content
107 elif node.tag in ['Equation', 'Image', 'Plot']:
108 # warn about missing content
109 writer(text=node.tag, color='yellow')
111 # warn about wierd tag
112 writer(text=node.tag, color='magenta')
114 def text_node2txt(node, writer, filter_math=False):
115 if node.tag not in ['Text-field', 'Font', 'Hyperlink'] and not filter_math:
116 # warn about wierd tag
117 writer(text=node.tag, color='magenta')
119 node=node, text=node.text, writer=writer, filter_math=filter_math)
121 def tail_node2txt(node, writer, filter_math=False):
122 if node.tag != 'Text-field':
124 node=node.getparent(), text=node.tail, writer=writer,
125 filter_math=filter_math)
127 def write_text(node, text, writer, filter_math=False):
130 style = node_style(node)
132 if style == 'Maple Input':
135 prompt = node.get('prompt', None)
137 t = '\n'.join(prompt+line for line in text.splitlines())
138 if text.endswith('\n'):
139 t += '\n' # '\n'.join('a\nb\n'.splitlines()) == 'a\nb'
140 if writer.last_char not in [None, '\n']:
141 t = t[len(prompt):] # no initial prompt
143 if style == 'Maple Input':
147 writer(text=text, color=color)
149 def node_style(node):
152 style = p.get('style', None)
158 def pruned_iteration(root, match, match_action=None, match_tail=None,
159 other_action=None, other_tail=None):
162 'start pruned iteration from {}'.format(root), color='blue'))
165 while len(stack) > 0:
172 _pruned_iteration_handle_tail(
173 node=n, match=match, match_tail=match_tail,
174 other_tail=other_tail)
176 LOG.debug(color_node(node, color='cyan'))
183 stack = list(node.getchildren()) + stack
188 _pruned_iteration_handle_tail(
189 node=n, match=match, match_tail=match_tail, other_tail=other_tail)
192 'end pruned iteration from {}'.format(root), color='blue'))
194 def _pruned_iteration_handle_tail(node, match, match_tail, other_tail):
195 LOG.debug(color_node(node, color='magenta', tail=True))
203 def node_depth(node):
211 def color_node(node, color=None, tail=False):
212 depth = node_depth(node)
213 string = ' '*depth + node.tag
216 return _color_string(string, color)
219 if __name__ == '__main__':
220 import argparse as _argparse
222 parser = _argparse.ArgumentParser(
224 formatter_class=_argparse.RawDescriptionHelpFormatter)
226 '-v', '--version', action='version',
227 version='%(prog)s {}'.format(__version__),
228 help='print the program version and exit')
230 '-V', '--verbose', action='count', default=0,
231 help='increment log verbosity')
233 '-c', '--color', action='store_const', const=True,
234 help='use ANSI escape sequences to color output')
236 '-m', '--maple', action='store_const', const=True,
237 help='output text suitable for piping into `maple`')
239 'path', metavar='PATH',
240 help='path to a Maple worksheet (.mw)')
242 args = parser.parse_args()
245 LOG.setLevel(max(_logging.DEBUG, LOG.level - 10*args.verbose))
247 filter_math = args.maple
248 writer = Writer(use_color=args.color)
251 raise ValueError("maple doesn't understand ANSI color")
252 mw2txt(path=args.path, writer=writer, filter_math=filter_math)