├── README.rst ├── setup.py ├── test └── text_formatting.rst └── nitrile.py /README.rst: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | try: 2 | from setuptools import setup 3 | except: 4 | from distutils.core import setup 5 | 6 | setup(name='nitrile', 7 | version='0.1', 8 | author='matt harrison', 9 | description='utilities to create LaTex', 10 | modules=['nitrile'], 11 | ) 12 | -------------------------------------------------------------------------------- /test/text_formatting.rst: -------------------------------------------------------------------------------- 1 | From http://en.wikibooks.org/wiki/LaTeX/Text_Formatting 2 | 3 | >>> from nitrile import * 4 | >>> env = Environment('spacing', '2.5') 5 | >>> env.write('This paragraph has') 6 | >>> env += LineBreak() 7 | >>> print env 8 | \begin{spacing}{2.5} 9 | This paragraph has\\ 10 | 11 | \end{spacing} 12 | 13 | 14 | >>> txt = Content() 15 | >>> txt += T('D.~') 16 | >>> txt += Command('textsc', 'Knuth') 17 | >>> print txt 18 | D.~\textsc{Knuth} 19 | 20 | 21 | >>> txt = Content() 22 | >>> txt += "Author Name" 23 | >>> txt += Command('hfill') 24 | >>> txt += Command('today') 25 | >>> print txt 26 | Author Name\hfill\today 27 | 28 | >>> txt = Content() 29 | >>> txt += "electromagnetic" 30 | >>> txt += Command('hyp') 31 | >>> txt += 'endioscopy' 32 | >>> print txt 33 | electromagnetic\hyp{}endioscopy 34 | 35 | >>> cmd = Command('hypenpenalty=100000') 36 | >>> print cmd 37 | \hypenpenalty=100000 38 | 39 | >>> q = DQuote('quote') 40 | >>> print q 41 | ``quote'' 42 | -------------------------------------------------------------------------------- /nitrile.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | r""" 3 | >>> doc = Document() 4 | >>> doc += Comment('hello.tex - Our first LaTex example!') 5 | >>> doc += Command('documentclass', 'article') 6 | >>> env = Environment('document') 7 | >>> env += Text('Hello World!') 8 | >>> doc += env 9 | >>> print doc 10 | 11 | % hello.tex - Our first LaTex example! 12 | \documentclass{article}\begin{document} 13 | Hello World! 14 | \end{document} 15 | 16 | 17 | # write to preamble after the body 18 | 19 | >>> doc = Document() 20 | >>> doc += Comment('hello.tex - Our first LaTex example!') 21 | >>> env = Environment('document') 22 | >>> env += Text('Hello World!') 23 | >>> doc += env 24 | >>> preamble = Command('documentclass', 'article') 25 | >>> preamble += Command('frontmatter') 26 | >>> doc.preamble = preamble 27 | >>> print doc 28 | \documentclass{article}\frontmatter 29 | % hello.tex - Our first LaTex example! 30 | \begin{document} 31 | Hello World! 32 | \end{document} 33 | 34 | 35 | 36 | Context Manager style 37 | 38 | >>> doc = Document() 39 | >>> doc += Comment('hello.tex - Our first LaTex example!') 40 | >>> doc += Command('documentclass', 'article') 41 | >>> with doc.Environment('document') as env: 42 | ... env.write('Hello World!') 43 | >>> print doc 44 | % hello.tex - Our first LaTex example! 45 | \documentclass{article}\begin{document} 46 | Hello World! 47 | \end{document} 48 | 49 | 50 | >>> with open('/tmp/foo.tex', 'w') as fout: 51 | ... doc.write(fout) 52 | 53 | >>> c2 = Command('documentclass', 'article', ['11pt', 'twoside', 'a4paper']) 54 | >>> print c2 55 | \documentclass[11pt,twoside,a4paper]{article} 56 | 57 | 58 | >>> c3 = Command('usepackage', 'color') 59 | >>> print c3 60 | \usepackage{color} 61 | 62 | 63 | >>> c4 = Command('usepackage', 'p1,p2,p3') 64 | >>> print c4 65 | \usepackage{p1,p2,p3} 66 | 67 | 68 | >>> c5 = Command('usepackage', 'geometry', 'margin=2cm') 69 | >>> print c5 70 | \usepackage[margin=2cm]{geometry} 71 | 72 | 73 | >>> foo = Content() 74 | >>> foo += T('Andrew Roberts') 75 | >>> foo += LineBreak() 76 | >>> foo += T('School of Computing') 77 | >>> foo += LineBreak() 78 | >>> foo += Command('texttt', 'andy@foo.com') 79 | >>> c6 = Command('author', foo) 80 | >>> print c6 81 | \author{Andrew Roberts\\ 82 | School of Computing\\ 83 | \texttt{andy@foo.com}} 84 | 85 | 86 | >>> c7 = Command('addcontentsline', 'toc,subsection,Preface'.split(',')) 87 | 88 | >>> print c7 89 | \addcontentsline{toc}{subsection}{Preface} 90 | 91 | 92 | >>> c8 = Command('color', 'blue') 93 | >>> content = Content() 94 | >>> content +=c8 + T('Name') + LineBreak() + T('Work') 95 | >>> c9 = Command('author', content) 96 | >>> print c9 97 | \author{\color{blue}Name\\ 98 | Work} 99 | 100 | 101 | >>> c10 = Command('today') 102 | >>> print c10 103 | \today 104 | 105 | """ 106 | RESERVED = """# $ % ^ & _ { } \ ~""".split() 107 | 108 | ESCAPE_MAPPING = { 109 | '~': '\\textasciitilde ', 110 | '^' : '\\textasciicircum', 111 | # handle \ intelligently 112 | } 113 | for char in "& % $ # _ { }".split(): 114 | ESCAPE_MAPPING[char] = '\\' + char 115 | 116 | def escape(txt): 117 | txt = txt.replace('\\', '\\textbackslash|||') 118 | for old, new in ESCAPE_MAPPING.items(): 119 | txt = txt.replace(old, new) 120 | txt = txt.replace('\\textbackslash|||', '\\textbackslash ') 121 | return txt 122 | 123 | class _Node(object): 124 | def __init__(self): 125 | self.children = [] 126 | self.parent = None 127 | 128 | def next_sibling(self, sib): 129 | # get sibling to right of sib 130 | idx = self.children.index(sib) 131 | try: 132 | return self.children[idx + 1] 133 | except IndexError: 134 | return None 135 | 136 | def __call__(self, *args): 137 | if self.name is None: 138 | self.name = args[0] 139 | return self 140 | 141 | def __getattr__(self, name): 142 | if name == 'Environment': 143 | env = Environment(None) 144 | self += env 145 | return env 146 | elif name == 'Command': 147 | cmd = Command(None) 148 | self += cmd 149 | return cmd 150 | raise KeyError 151 | 152 | def __enter__(self): 153 | return self 154 | 155 | def __exit__(self, *args): 156 | pass 157 | 158 | def __iter__(self): 159 | return iter(self.children) 160 | 161 | def __isub__(self, txt): 162 | if unicode(self.children[-1]).endswith(txt): 163 | self.children[-1] = unicode(self.children[-1])[:-len(txt)] 164 | return self 165 | 166 | def __iadd__(self, other): 167 | if isinstance(other, basestring): 168 | other = T(other) 169 | self.children.append(other) 170 | other.parent = self 171 | return self 172 | 173 | def __add__(self, other): 174 | if isinstance(other, basestring): 175 | other = T(other) 176 | self.children.append(other) 177 | other.parent = self 178 | return self 179 | 180 | def write(self, txt): 181 | self += Text(txt) 182 | 183 | def start(self): 184 | return '' 185 | 186 | def end(self): 187 | return '' 188 | 189 | def content(self): 190 | return ''.join(unicode(c) for c in self.children) 191 | 192 | def __str__(self): 193 | return ''.join([self.start(), self.content(), self.end()]) 194 | 195 | 196 | class Content(_Node): 197 | pass 198 | 199 | 200 | class T(_Node): 201 | """Text""" 202 | def __init__(self, txt): 203 | super(T, self).__init__() 204 | self.txt = txt 205 | 206 | def content(self): 207 | return self.txt 208 | 209 | 210 | class LineBreak(_Node): 211 | def content(self): 212 | return u'\\\\\n' 213 | 214 | 215 | class Group(_Node): 216 | def __init__(self, name=None): 217 | super(Group, self).__init__() 218 | self.name = name 219 | 220 | def start(self): 221 | return u'{0}\n'.format( 222 | '\\begin'+self.name if self.name else "{", 223 | ) 224 | 225 | def end(self): 226 | return u'\n{0}\n'.format( 227 | '\\end'+self.name if self.name else "{", 228 | ) 229 | 230 | 231 | class Environment(_Node): 232 | def __init__(self, *args): 233 | super(Environment, self).__init__() 234 | self.name = args[0] if args else None 235 | self.rest = args[1:] 236 | 237 | def start(self): 238 | return u'\\begin{{{0}}}{1}\n'.format( 239 | self.name, 240 | ''.join('{{{0}}}'.format(x) for x in self.rest) 241 | ) 242 | 243 | return 244 | def end(self): 245 | return u'\n\\end{{{0}}}\n'.format(self.name) 246 | 247 | class Raw(_Node): 248 | def __init__(self, txt, escape=False): 249 | self.txt = txt 250 | self.escape=escape 251 | def content(self): 252 | if self.escape: 253 | return escape(self.txt) 254 | else: 255 | return self.txt 256 | 257 | class Command(_Node): 258 | def __init__(self, name, arguments=None, options=None, 259 | add_newline=False): 260 | super(Command, self).__init__() 261 | self.name = name 262 | self.list_type = False 263 | self.str_type = False 264 | 265 | if isinstance(arguments, basestring): 266 | self.str_type = True 267 | self.arguments = [arguments] 268 | elif isinstance(arguments, _Node): 269 | self.arguments = arguments 270 | elif isinstance(arguments, list): 271 | self.list_type = True 272 | self.arguments = arguments 273 | else: 274 | self.arguments = arguments 275 | # self.arguments = [arguments] if isinstance(arguments, basestring) else arguments 276 | self.options = [options] if isinstance(options, basestring) else options 277 | self.add_newline = add_newline 278 | 279 | def start(self): 280 | if self.options: 281 | one = '[{0}]'.format(','.join(self.options)) 282 | else: 283 | one = '' 284 | if self.arguments: 285 | if self.list_type: 286 | two = ''.join('{{{0}}}'.format(str(x)) for x in self.arguments) 287 | elif self.str_type: 288 | two = '{{{0}}}'.format(','.join(str(x) for x in self.arguments)) 289 | else: 290 | two = '{{{0}}}'.format(self.arguments) 291 | else: 292 | two = '' 293 | return u'\{0}{1}{2}{3}{4}'.format( 294 | self.name, 295 | one, 296 | two, 297 | '{}' if self.insert_space() else '', 298 | '\n' if self.add_newline else "" 299 | ) 300 | 301 | def insert_space(self): 302 | if self.parent: 303 | sib = self.parent.next_sibling(self) 304 | return sib and isinstance(sib, T) 305 | 306 | 307 | class Switch(_Node): 308 | def __init__(self, name): 309 | super(Switch, self).__init__() 310 | self.name = name 311 | 312 | def __str__(self): 313 | return u'{0}\{{1}\}'.format( 314 | self.name, 315 | self.content() 316 | ) 317 | 318 | 319 | class DQuote(_Node): 320 | def __init__(self, txt): 321 | super(DQuote, self).__init__() 322 | self.txt = txt 323 | 324 | def content(self): 325 | return self.txt 326 | 327 | def start(self): 328 | return u'``' 329 | 330 | def end(self): 331 | return u"''" 332 | 333 | 334 | class Comment(_Node): 335 | def __init__(self, txt): 336 | super(Comment, self).__init__() 337 | self.txt = txt 338 | 339 | def content(self): 340 | return self.txt 341 | 342 | def start(self): 343 | return u'% ' 344 | 345 | def end(self): 346 | return '\n' 347 | 348 | 349 | class Text(_Node): 350 | def __init__(self, txt): 351 | super(Text, self).__init__() 352 | self.txt = txt 353 | 354 | def content(self): 355 | return self.txt 356 | 357 | def __str__(self): 358 | return u'{0}'.format( 359 | self.content() 360 | ) 361 | 362 | 363 | class Document(object): 364 | def __init__(self): 365 | self.preamble = _Node() 366 | self.document = _Node() 367 | self.images = {} # path to abspath 368 | 369 | def add_image(self, path, abspath): 370 | self.images[path] = abspath 371 | 372 | def __iadd__(self, other): 373 | #return self.document.__iadd__(other) 374 | self.document.__iadd__(other) 375 | return self 376 | 377 | def __isub__(self, other): 378 | #return self.document.__iadd__(other) 379 | self.document.__isub__(other) 380 | return self 381 | 382 | 383 | def __str__(self): 384 | # return u''.join(unicode(c) for c in self.preamble.children +\ 385 | # self.document.children) 386 | return u'\n'.join(unicode(x) for x in [self.preamble, self.document]) 387 | 388 | def write(self, fout): 389 | fout.write(unicode(self.preamble)) 390 | fout.write(unicode(self.document)) 391 | 392 | 393 | class DocumentOLD(_Node): 394 | def __str__(self): 395 | return u'\n'.join(unicode(c) for c in self.children) 396 | 397 | def write(self, fout): 398 | fout.write(str(self)) 399 | 400 | def accent_escape(txt): 401 | return txt 402 | mapping = { u'é':"{\\'e}"} 403 | for k, v in mapping.items(): 404 | txt = txt.replace(k, v) 405 | return txt 406 | 407 | 408 | if __name__ == '__main__': 409 | import doctest 410 | doctest.testmod() 411 | #doctest.testfile('test/text_formatting.rst') 412 | #doctest.testfile('test/memoir.rst') 413 | --------------------------------------------------------------------------------