├── .travis.yml ├── COPYING ├── _unittest ├── test.py └── testdata │ ├── CodeParser.test.md │ ├── ConstantParser.test.md │ ├── FigureParser.test.md │ ├── IncludeParser.test.md │ ├── TOCParser.test.md │ ├── TableParser.test.md │ └── includes │ ├── PythonParser.test.md │ ├── code.py │ ├── figure.png │ ├── include.txt │ └── table.csv ├── academicmarkdown ├── HTMLFilter.py ├── MDFilter.py ├── _BaseParser.py ├── _CodeParser.py ├── _ConstantParser.py ├── _ExecParser.py ├── _FigureParser.py ├── _GitHubParser.py ├── _IncludeParser.py ├── _ODTFixer.py ├── _Pandoc.py ├── _PythonParser.py ├── _TOCParser.py ├── _TableParser.py ├── _VideoParser.py ├── _WcParser.py ├── _WkHtmlToPdf.py ├── _YAMLParser.py ├── _ZoteroParser.py ├── __init__.py ├── build.py ├── constants.py ├── git.py ├── py3compat.py ├── styles │ ├── apa │ │ ├── citation-style.csl │ │ ├── reference.docx │ │ ├── reference.odt │ │ ├── stylesheet.css │ │ └── template.html │ └── modern │ │ ├── citation-style.csl │ │ ├── reference.docx │ │ ├── reference.odt │ │ ├── stylesheet.css │ │ └── template.html └── tools.py ├── debian ├── changelog ├── compat ├── control ├── copyright └── rules ├── example ├── compile.py ├── example.html ├── example.pdf └── src │ ├── example.md │ ├── foveal_acuity.png │ └── gullvision.png ├── readme.md ├── readme.py └── setup.py /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | python: 3 | - "2.7" 4 | - "3.4" 5 | install: 6 | - pip install pyyaml 7 | - python setup.py install 8 | script: _unittest/test.py 9 | -------------------------------------------------------------------------------- /_unittest/test.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | #-*- coding:utf-8 -*- 3 | 4 | """ 5 | This file is part of pseudorandom. 6 | 7 | pseudorandom is free software: you can redistribute it and/or modify 8 | it under the terms of the GNU General Public License as published by 9 | the Free Software Foundation, either version 3 of the License, or 10 | (at your option) any later version. 11 | 12 | pseudorandom is distributed in the hope that it will be useful, 13 | but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | GNU General Public License for more details. 16 | 17 | You should have received a copy of the GNU General Public License 18 | along with pseudorandom. If not, see . 19 | """ 20 | 21 | import os 22 | import unittest 23 | import academicmarkdown 24 | from academicmarkdown.py3compat import * 25 | import importlib 26 | 27 | class AcadamicMarkdownTest(unittest.TestCase): 28 | 29 | """ 30 | desc: 31 | Basic unit testing for academicmarkdown. 32 | """ 33 | 34 | def setUp(self): 35 | 36 | academicmarkdown.build.path += [os.path.join(self.dataFolder(), 37 | u'includes')] 38 | 39 | def dataFolder(self): 40 | 41 | return os.path.join(os.path.dirname(__file__), u'testdata') 42 | 43 | def getTestData(self, fname): 44 | 45 | with open(os.path.join(self.dataFolder(), fname)) as fd: 46 | s = fd.read() 47 | return safe_decode(s) 48 | 49 | def singleTest(self, path): 50 | 51 | clsName = path[:-8] 52 | print(u'testing %s' % clsName) 53 | cls = getattr(academicmarkdown, clsName) 54 | md = self.getTestData(path) 55 | l = md.split(u'===') 56 | inp = l[0] 57 | predOut = l[1].strip() 58 | realOut = cls().parse(inp).strip() 59 | self.assertEqual(predOut, realOut) 60 | 61 | def test_all(self): 62 | 63 | for path in os.listdir(self.dataFolder()): 64 | if not path.endswith(u'.test.md'): 65 | continue 66 | self.singleTest(path) 67 | 68 | if __name__ == '__main__': 69 | unittest.main() 70 | -------------------------------------------------------------------------------- /_unittest/testdata/CodeParser.test.md: -------------------------------------------------------------------------------- 1 | %-- 2 | code: 3 | id: Test 4 | source: code.py 5 | syntax: python 6 | caption: | 7 | Test! 8 | --% 9 | === 10 | ~~~ .python 11 | print('test') 12 | ~~~ 13 | -------------------------------------------------------------------------------- /_unittest/testdata/ConstantParser.test.md: -------------------------------------------------------------------------------- 1 | %-- 2 | constant: 3 | test: "This is a test" 4 | --% 5 | %test 6 | === 7 | This is a test 8 | -------------------------------------------------------------------------------- /_unittest/testdata/FigureParser.test.md: -------------------------------------------------------------------------------- 1 | %-- 2 | figure: 3 | id: test 4 | source: figure.png 5 | caption: | 6 | This is a test! 7 | --% 8 | === 9 |
10 | This is a <b>test</b>!
11 |
Figure 1. This is a <b>test</b>!
12 |
13 | -------------------------------------------------------------------------------- /_unittest/testdata/IncludeParser.test.md: -------------------------------------------------------------------------------- 1 | %-- 2 | include: "include.txt" 3 | --% 4 | === 5 | This is a test 6 | -------------------------------------------------------------------------------- /_unittest/testdata/TOCParser.test.md: -------------------------------------------------------------------------------- 1 | %-- 2 | toc: 3 | excludes: [Level 2A] 4 | mindepth: 2 5 | maxdepth: 4 6 | --% 7 | 8 | # Level 1 9 | 10 | ## Level 2A 11 | 12 | ## Level 2B 13 | 14 | ### Level 3A 15 | 16 | ## Level.2-é 17 | === 18 | - [Level 2A](#level-2a) 19 | - [Level 2B](#level-2b) 20 | - [Level 3A](#level-3a) 21 | - [Level.2-é](#level2) 22 | 23 | 24 | 25 | # Level 1 26 | 27 | ## Level 2A 28 | 29 | ## Level 2B 30 | 31 | ### Level 3A 32 | 33 | ## Level.2-é 34 | -------------------------------------------------------------------------------- /_unittest/testdata/TableParser.test.md: -------------------------------------------------------------------------------- 1 | %-- 2 | table: 3 | id: test 4 | source: table.csv 5 | caption: | 6 | This is a test 7 | --% 8 | === 9 | | A | B | 10 | | --: | --: | 11 | | 1.0000 | 2.0000 | 12 | | 3.0000 | 4.0000 | 13 | 14 | 15 | __Table 1.__ This is a test 16 | 17 | {: .tbl-caption #test} 18 | -------------------------------------------------------------------------------- /_unittest/testdata/includes/PythonParser.test.md: -------------------------------------------------------------------------------- 1 | %-- 2 | python: | 3 | print('This is a test') 4 | --% 5 | === 6 | This is a test 7 | -------------------------------------------------------------------------------- /_unittest/testdata/includes/code.py: -------------------------------------------------------------------------------- 1 | print('test') 2 | -------------------------------------------------------------------------------- /_unittest/testdata/includes/figure.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smathot/academicmarkdown/696592d035e341e170b6c3e86a0855a8cc4d0f29/_unittest/testdata/includes/figure.png -------------------------------------------------------------------------------- /_unittest/testdata/includes/include.txt: -------------------------------------------------------------------------------- 1 | This is a test 2 | -------------------------------------------------------------------------------- /_unittest/testdata/includes/table.csv: -------------------------------------------------------------------------------- 1 | A,B 2 | 1,2 3 | 3,4 4 | -------------------------------------------------------------------------------- /academicmarkdown/HTMLFilter.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """ 4 | This file is part of zoteromarkdown. 5 | 6 | zoteromarkdown is free software: you can redistribute it and/or modify 7 | it under the terms of the GNU General Public License as published by 8 | the Free Software Foundation, either version 3 of the License, or 9 | (at your option) any later version. 10 | 11 | zoteromarkdown is distributed in the hope that it will be useful, 12 | but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | GNU General Public License for more details. 15 | 16 | You should have received a copy of the GNU General Public License 17 | along with zoteromarkdown. If not, see . 18 | """ 19 | 20 | import re 21 | 22 | def citationGlue(s): 23 | 24 | """ 25 | Glues citations together to allow sorting, like so: 26 | 27 | [@B]+[@A] 28 | 29 | This will put '@B' before '@A'. 30 | 31 | Returns: 32 | A unicode string with all citations glued. 33 | """ 34 | 35 | regexp = \ 36 | r'[\]\)]\+[\[\(]' 37 | for i in re.finditer(regexp, s, re.M): 38 | cite = i.group() 39 | s = s.replace(i.group(), u'') 40 | return s 41 | 42 | def DOI(s): 43 | 44 | """ 45 | Creates hyperlinks from DOI references. 46 | 47 | Arguments: 48 | s -- A unicode string. 49 | 50 | Returns: 51 | A unicode string with all DOIs changed into hyperlinks. 52 | """ 53 | 54 | regexp = r'(doi:10[.][0-9]{4,}[^\s"/<>]*/[^\s"<>]+)' 55 | for i in re.finditer(regexp, s, re.M): 56 | doi = i.group() 57 | s = s.replace(doi, u'%s' % \ 58 | (doi[4:], doi)) 59 | return s 60 | 61 | def headerIndent(s, depth=1, minLevel=1, maxLevel=6): 62 | 63 | """ 64 | Makes all headers jump down one level. For example

becomes

, etc. 65 | 66 | Arguments: 67 | s -- A unicode string. 68 | 69 | Keyword arguments: 70 | depth -- The depth of the extra indentation. For example, a depth of 71 | 2 means that

becomes ' % i, u'' % (i+depth)) \ 83 | .replace(u'' % i, u'' % (i+depth)) 84 | return s 85 | 86 | def quote(s): 87 | 88 | s = s.replace('

—', '

') 89 | s = s.replace('
\n—', '
\n') 90 | return s 91 | -------------------------------------------------------------------------------- /academicmarkdown/MDFilter.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """ 4 | This file is part of zoteromarkdown. 5 | 6 | zoteromarkdown is free software: you can redistribute it and/or modify 7 | it under the terms of the GNU General Public License as published by 8 | the Free Software Foundation, either version 3 of the License, or 9 | (at your option) any later version. 10 | 11 | zoteromarkdown is distributed in the hope that it will be useful, 12 | but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | GNU General Public License for more details. 15 | 16 | You should have received a copy of the GNU General Public License 17 | along with zoteromarkdown. If not, see . 18 | """ 19 | 20 | import re 21 | 22 | def highlight(md): 23 | 24 | """ 25 | desc: 26 | Processes the custom Markdown ++highlight++ syntax. 27 | 28 | arguments: 29 | md: 30 | desc: A Markdown string. 31 | type: unicode 32 | 33 | returns: 34 | desc: A processed Markdown string. 35 | type: unicode 36 | """ 37 | 38 | regexp = r'\+\+(.*?)\+\+' 39 | for i in re.finditer(regexp, md, re.M): 40 | old = i.group(0) 41 | new = u'%s' % i.groups()[0] 42 | md = md.replace(old, new) 43 | return md 44 | 45 | def arrows(md): 46 | 47 | """ 48 | desc: 49 | Converts -> and <- into arrows. 50 | 51 | arguments: 52 | md: 53 | desc: A Markdown string. 54 | type: unicode 55 | 56 | returns: 57 | desc: A processed Markdown string. 58 | type: unicode 59 | """ 60 | 61 | md = md.replace(u' <- ', u' ← ') 62 | md = md.replace(u' -> ', u' → ') 63 | md = md.replace(u' \<- ', u' <- ') 64 | md = md.replace(u' \-> ', u' -> ') 65 | return md 66 | 67 | def autoItalics(md): 68 | 69 | """ 70 | Automatically italicizes certain expressions. For example, 'p = .05', 71 | becomes '*p* = .05'. 72 | 73 | Arguments: 74 | md -- A Markdown string. 75 | 76 | Returns: 77 | A processed Markdown string. 78 | """ 79 | 80 | # M, SE, SD, p, r, t 81 | regexp = r'\b(?P(M|p|r|SE|SD|t|β|z)) *(?P[=><]) *(?P-?\d*\.?\d*)\b' 82 | for i in re.finditer(regexp, md, re.M): 83 | old = i.group(0) 84 | new = u'*%s* %s %s' % (i.group('key'), i.group('opr'), \ 85 | i.group('val')) 86 | md = md.replace(old, new) 87 | 88 | # T tests with degrees of freedom 89 | regexp = r'\bt\((?P\d*\.?\d*)\) *(?P[=><]) *(?P-?\d*\.?\d*)\b' 90 | for i in re.finditer(regexp, md, re.M): 91 | old = i.group(0) 92 | new = u'*t*(%s) %s %s' % (i.group('df'), i.group('opr'), \ 93 | i.group('val')) 94 | md = md.replace(old, new) 95 | 96 | # Chisquare tests with degrees of freedom 97 | regexp = r'\bX2\((?P\d*\.?\d*)\) *(?P[=><]) *(?P-?\d*\.?\d*)\b' 98 | for i in re.finditer(regexp, md, re.M): 99 | old = i.group(0) 100 | new = u'*Χ^2^*(%s) %s %s' % (i.group('df'), i.group('opr'), \ 101 | i.group('val')) 102 | md = md.replace(old, new) 103 | 104 | # F tests 105 | regexp = r'\bF\((?P\d*\.?\d*),(?P\d*\.?\d*)\) *(?P[=><]) *(?P-?\d*\.?\d*)\b' 106 | for i in re.finditer(regexp, md, re.M): 107 | old = i.group(0) 108 | new = u'*F*(%s,%s) %s %s' % (i.group('df1'), i.group('df2'), \ 109 | i.group('opr'), i.group('val')) 110 | md = md.replace(old, new) 111 | 112 | return md 113 | 114 | def magicVars(md): 115 | 116 | """ 117 | Replace magic variables, such as %wc%. 118 | 119 | Arguments: 120 | md -- A Markdown string. 121 | 122 | Returns: 123 | A processed Markdown string. 124 | """ 125 | 126 | md = md.replace(u'%wc%', u'%s' % len(md.split())) 127 | md = md.replace(u'%cc%', u'%s' % len(md)) 128 | return md 129 | 130 | def pageBreak(md): 131 | 132 | """ 133 | Converts '~' paragraphs to HTML5 page breaks. 134 | 135 | Arguments: 136 | md -- A Markdown string. 137 | 138 | Returns: 139 | A processed Markdown string. 140 | """ 141 | 142 | return md.replace(u'\n~\n', \ 143 | u'\n

\n') 144 | 145 | def quote(md): 146 | 147 | return md.replace(u'\n— ', u' \n— ') 148 | -------------------------------------------------------------------------------- /academicmarkdown/_BaseParser.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """ 4 | This file is part of zoteromarkdown. 5 | 6 | zoteromarkdown is free software: you can redistribute it and/or modify 7 | it under the terms of the GNU General Public License as published by 8 | the Free Software Foundation, either version 3 of the License, or 9 | (at your option) any later version. 10 | 11 | zoteromarkdown is distributed in the hope that it will be useful, 12 | but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | GNU General Public License for more details. 15 | 16 | You should have received a copy of the GNU General Public License 17 | along with zoteromarkdown. If not, see . 18 | """ 19 | 20 | import re 21 | import yaml 22 | from academicmarkdown.py3compat import * 23 | 24 | class BaseParser(object): 25 | 26 | def __init__(self, verbose=False): 27 | 28 | """ 29 | Constructor. 30 | 31 | Keyword arguments: 32 | verbose -- Indicates whether verbose output should be generated. 33 | (default=False) 34 | """ 35 | 36 | self.verbose = verbose 37 | 38 | def parse(self, md): 39 | 40 | """ 41 | Parses a MarkDown text. 42 | 43 | Arguments: 44 | md -- The Markdown text. 45 | 46 | Returns: 47 | The parsed Markdown text. 48 | """ 49 | 50 | raise Exception(u'BaseParser.parse() should be overridden.') 51 | 52 | def msg(self, msg): 53 | 54 | """ 55 | Print output in verbose mode. 56 | 57 | Arguments: 58 | msg -- The message to print. 59 | """ 60 | 61 | if self.verbose: 62 | print(safe_encode(u'[%s] %s' % (self.__class__.__name__, msg), 63 | enc=u'ascii', errors=u'ignore')) 64 | 65 | def getPath(self, path): 66 | 67 | """ 68 | Checks whether a path is present in the `srcFolder` and if so fixes it. 69 | URLs are accepted as valid paths. 70 | 71 | Arguments: 72 | path -- A path. 73 | 74 | Returns: 75 | A path. 76 | """ 77 | 78 | import os 79 | from academicmarkdown import build 80 | if path.startswith(u'http://'): 81 | return path 82 | for buildPath in build.path: 83 | _path = os.path.join(buildPath, path) 84 | if os.path.exists(_path): 85 | return _path 86 | if not os.path.exists(path): 87 | raise Exception(u'Cannot find file %s' % path) 88 | return path 89 | -------------------------------------------------------------------------------- /academicmarkdown/_CodeParser.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """ 4 | This file is part of zoteromarkdown. 5 | 6 | zoteromarkdown is free software: you can redistribute it and/or modify 7 | it under the terms of the GNU General Public License as published by 8 | the Free Software Foundation, either version 3 of the License, or 9 | (at your option) any later version. 10 | 11 | zoteromarkdown is distributed in the hope that it will be useful, 12 | but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | GNU General Public License for more details. 15 | 16 | You should have received a copy of the GNU General Public License 17 | along with zoteromarkdown. If not, see . 18 | """ 19 | 20 | import yaml 21 | from academicmarkdown import YAMLParser 22 | import subprocess 23 | 24 | codeTemplate = { 25 | u'pandoc' : u""" 26 | ~~~ {%(syntax)s} 27 | %(code)s 28 | ~~~ 29 | """, 30 | u'kramdown' : u""" 31 | ~~~ %(syntax)s 32 | %(code)s 33 | ~~~ 34 | """, 35 | u'jekyll' : u""" 36 | {%% highlight %(syntax)s %%} 37 | %(code)s 38 | {%% endhighlight %%} 39 | 40 | __Listing %(nCode)d.__ %(caption)s\n{: .lst-caption #%(id)s} 41 | """} 42 | 43 | class CodeParser(YAMLParser): 44 | 45 | """ 46 | The `code` blocks embeds a code listing in the text, quite to similar to the 47 | `figure` block. 48 | 49 | %-- 50 | code: 51 | id: CodeA caption: | 52 | Test! 53 | 54 | source: my_script.py 55 | syntax: python 56 | caption: "A simple Python script" 57 | --% 58 | 59 | The `caption` and `syntax` attributes are optional. 60 | """ 61 | 62 | def __init__(self, style=u'inline', template=u'kramdown', verbose=False): 63 | 64 | """ 65 | Constructor. 66 | 67 | Keyword arguments: 68 | style -- Can be u'inline' or u'below' to indicate whether code 69 | should be placed in or below the text. 70 | (default=u'inline') 71 | template -- Indicates the output format, which can be 'kramdown' or 72 | 'liquid'. (default=u'kramdown') 73 | verbose -- Indicates whether verbose output should be generated. 74 | (default=False) 75 | """ 76 | 77 | self.style = style 78 | self.template = template 79 | super(CodeParser, self).__init__(_object=u'code', required=['id', \ 80 | 'source', 'syntax'], verbose=verbose) 81 | 82 | def parse(self, md): 83 | 84 | """See BaseParser.parse().""" 85 | 86 | self.nCode = 0 87 | return super(CodeParser, self).parse(md) 88 | 89 | def parseObject(self, md, _yaml, d): 90 | 91 | """See YAMLParser.parseObject().""" 92 | 93 | self.nCode += 1 94 | d['nCode'] = self.nCode 95 | self.msg(u'Found code: %s (%d)' % (d['id'], self.nCode)) 96 | d[u'source'] = self.getPath(d[u'source']) 97 | if u'caption' not in d: 98 | d[u'caption'] = u'' 99 | if self.template == u'kramdown': 100 | d[u'syntax'] = u'.' + d[u'syntax'] 101 | with open(self.getPath(d[u'source'])) as fd: 102 | d[u'code'] = fd.read().strip() 103 | code = codeTemplate[self.template] % d 104 | if self.style == u'inline': 105 | md = md.replace(_yaml, code) 106 | else: 107 | md = md.replace(_yaml, u'') 108 | md += code 109 | md = md.replace(u'%%%s' % d[u'id'], u'[Listing %d](#%s)' % (self.nCode, \ 110 | d[u'id'])) 111 | return md 112 | -------------------------------------------------------------------------------- /academicmarkdown/_ConstantParser.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """ 4 | This file is part of zoteromarkdown. 5 | 6 | zoteromarkdown is free software: you can redistribute it and/or modify 7 | it under the terms of the GNU General Public License as published by 8 | the Free Software Foundation, either version 3 of the License, or 9 | (at your option) any later version. 10 | 11 | zoteromarkdown is distributed in the hope that it will be useful, 12 | but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | GNU General Public License for more details. 15 | 16 | You should have received a copy of the GNU General Public License 17 | along with zoteromarkdown. If not, see . 18 | """ 19 | 20 | from academicmarkdown import YAMLParser, MDFilter 21 | from academicmarkdown.constants import * 22 | import os 23 | 24 | class ConstantParser(YAMLParser): 25 | 26 | """ 27 | The `constant` block allows you to define constants. For example, if you 28 | define MyConstant1 (as below), all occurrences of "%MyConstant1" in the text 29 | will be replcated by "You can refer to this as %MyConstant1". 30 | 31 | %-- 32 | constant: 33 | MyConstant1: "You can refer to this as %MyConstant1" 34 | MyConstant2: "You can refer to this as %MyConstant2" 35 | --% 36 | """ 37 | 38 | def __init__(self, verbose=False): 39 | 40 | """See YAMLParser.__init__().""" 41 | 42 | super(ConstantParser, self).__init__(_object=u'constant', 43 | verbose=verbose) 44 | 45 | def parseObject(self, md, _yaml, d): 46 | 47 | """See YAMLParser.parseObject().""" 48 | 49 | # Remove the YAML block 50 | md = md.replace(_yaml, u'') 51 | for key, val in d.items(): 52 | self.msg(key) 53 | md = md.replace(u'%%%s' % key, val.strip()) 54 | return md 55 | 56 | 57 | -------------------------------------------------------------------------------- /academicmarkdown/_ExecParser.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """ 4 | This file is part of zoteromarkdown. 5 | 6 | zoteromarkdown is free software: you can redistribute it and/or modify 7 | it under the terms of the GNU General Public License as published by 8 | the Free Software Foundation, either version 3 of the License, or 9 | (at your option) any later version. 10 | 11 | zoteromarkdown is distributed in the hope that it will be useful, 12 | but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | GNU General Public License for more details. 15 | 16 | You should have received a copy of the GNU General Public License 17 | along with zoteromarkdown. If not, see . 18 | """ 19 | 20 | from academicmarkdown import YAMLParser 21 | from academicmarkdown.py3compat import * 22 | import subprocess 23 | import shlex 24 | 25 | class ExecParser(YAMLParser): 26 | 27 | """ 28 | The `exec` block inserts the return value of an external command in the 29 | text. For example, the following block embeds something like 30 | 'Generated on 10/18/2013': 31 | 32 | %-- exec: "date +'Generated on %x'" --% 33 | """ 34 | 35 | def __init__(self, verbose=False): 36 | 37 | """See YAMLParser.__init__().""" 38 | 39 | super(ExecParser, self).__init__(_object=u'exec', verbose=verbose) 40 | 41 | def parseObject(self, md, _yaml, d): 42 | 43 | """See YAMLParser.parseObject().""" 44 | 45 | if not isinstance(d, basestring): 46 | return u'Expecting a string, not "%s"' % d 47 | self.msg(u'Command: %s' % d) 48 | if not py3: 49 | d = safe_encode(d) 50 | output = safe_decode(subprocess.check_output(shlex.split(d))) 51 | self.msg(u'Returns: %s' % output) 52 | return md.replace(_yaml, output) 53 | -------------------------------------------------------------------------------- /academicmarkdown/_FigureParser.py: -------------------------------------------------------------------------------- 1 | 2 | # -*- coding: utf-8 -*- 3 | 4 | """ 5 | This file is part of zoteromarkdown. 6 | 7 | zoteromarkdown is free software: you can redistribute it and/or modify 8 | it under the terms of the GNU General Public License as published by 9 | the Free Software Foundation, either version 3 of the License, or 10 | (at your option) any later version. 11 | 12 | zoteromarkdown is distributed in the hope that it will be useful, 13 | but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | GNU General Public License for more details. 16 | 17 | You should have received a copy of the GNU General Public License 18 | along with zoteromarkdown. If not, see . 19 | """ 20 | 21 | import os 22 | import yaml 23 | from academicmarkdown import YAMLParser 24 | import subprocess 25 | import sys 26 | 27 | figureTemplate = { 28 | u'html5': u""" 29 |
30 | %(caption)s
31 |
Figure %(nFig)d. %(caption)s
32 |
33 | """, 34 | u'jekyll': u""" 35 | ![%(source)s](%(source)s) 36 | 37 | __Figure %(nFig)d.__ %(caption)s\n{: .fig-caption #%(id)s}\n 38 | """, 39 | u'odt': u""" 40 | ![__Figure %(nFig)d.__ %(caption)s](%(source)s) 41 | 42 | __Figure %(nFig)d.__ *%(caption)s* 43 | """, 44 | u'markdown': u""" 45 | ![__Figure %(nFig)d.__ %(caption)s](%(source)s) 46 | """} 47 | 48 | 49 | class FigureParser(YAMLParser): 50 | 51 | """ 52 | The `figure` block embeds a Figure in the text. Figures are numbered 53 | automatically. The ID can be used to refer to the Figure in the text, using 54 | a `%` character. So the following figure would be referred to as `%FigFA`. 55 | 56 | %-- 57 | figure: 58 | id: FigFA 59 | source: foveal_acuity.svg 60 | caption: "Visual acuity drops of rapidly with distance from the fovea." 61 | width: 100 62 | --% 63 | 64 | The `caption` and `width` attributes are optional. 65 | """ 66 | 67 | def __init__(self, style=u'inline', template=u'html5', convertSVG=True, \ 68 | margins=(30, 20, 30, 20), verbose=False): 69 | 70 | """ 71 | Constructor. 72 | 73 | Keyword arguments: 74 | style -- Can be u'inline' or u'below' to indicate whether figures 75 | should be placed in or below the text. 76 | (default=u'inline') 77 | template -- Indicates the output format, which can be 'odt' or 78 | 'html5'. (default=u'html5') 79 | convertSVG -- Indicates whether .svg files should be converted to .png 80 | for better embedding. (default=True) 81 | margins -- Page margins. (default=30,20,30,20) 82 | verbose -- Indicates whether verbose output should be generated. 83 | (default=False) 84 | """ 85 | 86 | self.style = style 87 | self.template = template 88 | self.convertSVG = convertSVG 89 | self.margins = margins 90 | super(FigureParser, self).__init__(_object=u'figure', required=['id', \ 91 | 'source'], verbose=verbose) 92 | 93 | def parse(self, md): 94 | 95 | """See BaseParser.parse().""" 96 | 97 | self.nFig = 0 98 | return super(FigureParser, self).parse(md) 99 | 100 | def parseObject(self, md, _yaml, d): 101 | 102 | """See YAMLParser.parseObject().""" 103 | 104 | self.nFig += 1 105 | d['nFig'] = self.nFig 106 | self.msg(u'Found figure: %s (%d)' % (d['id'], self.nFig)) 107 | 108 | d[u'source'] = self.getPath(d[u'source']) 109 | 110 | if d[u'source'].lower().endswith(u'.svg') and self.convertSVG: 111 | dest = d[u'source'] + u'.png' 112 | self.msg(u'Converting from SVG') 113 | A4WidthMm = 210 114 | A4WidthPx = 744.09 115 | pxPerMm = A4WidthPx / A4WidthMm 116 | realWidthMm = A4WidthMm - self.margins[1] - self.margins[3] 117 | realWidthPx = realWidthMm * pxPerMm 118 | cmd = [u'inkscape', d[u'source'], '-W'] 119 | figWidthPx = float(subprocess.check_output(cmd)) 120 | d[u'width'] = min(100, 100. * figWidthPx / realWidthPx) 121 | self.msg(u'Width: %.2f (%.2f%%)' % (figWidthPx, d[u'width'])) 122 | if not os.path.exists(dest) or '--clear-svg' in sys.argv: 123 | cmd = [u'inkscape', u'-f', d[u'source'], u'-e', dest, u'-d', \ 124 | '150', u'-b', u'white', u'-y', u'1.0'] 125 | subprocess.call(cmd) 126 | else: 127 | self.msg('"%s" exists, not regenerating' % dest) 128 | d[u'source'] = dest 129 | 130 | if u'caption' not in d: 131 | d[u'caption'] = u'' 132 | replaceList = [ 133 | (u'"', u'"'), 134 | (u'\'', u'''), 135 | (u'<', u'<'), 136 | (u'>', u'>') 137 | ] 138 | for _from, _to in replaceList: 139 | d[u'caption'] = d[u'caption'].replace(_from, _to) 140 | d[u'caption'] = d[u'caption'].strip() 141 | if u'width' not in d: 142 | d[u'width'] = 100 143 | img = figureTemplate[self.template] % d 144 | if self.style == u'inline': 145 | md = md.replace(_yaml, img) 146 | else: 147 | md = md.replace(_yaml, u'') 148 | md += img 149 | # Replace both %MyFigure::a and %MyFigure, to allow for suffixes 150 | md = md.replace(u'%%%s::' % d[u'id'], u'[Figure %d](#%s)' % (self.nFig, \ 151 | d[u'id'])) 152 | md = md.replace(u'%%%s' % d[u'id'], u'[Figure %d](#%s)' % (self.nFig, \ 153 | d[u'id'])) 154 | return md 155 | -------------------------------------------------------------------------------- /academicmarkdown/_GitHubParser.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """ 4 | This file is part of zoteromarkdown. 5 | 6 | zoteromarkdown is free software: you can redistribute it and/or modify 7 | it under the terms of the GNU General Public License as published by 8 | the Free Software Foundation, either version 3 of the License, or 9 | (at your option) any later version. 10 | 11 | zoteromarkdown is distributed in the hope that it will be useful, 12 | but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | GNU General Public License for more details. 15 | 16 | You should have received a copy of the GNU General Public License 17 | along with zoteromarkdown. If not, see . 18 | """ 19 | 20 | import yaml 21 | from datamatrix import functional as fnc 22 | from academicmarkdown import YAMLParser 23 | from academicmarkdown.py3compat import * 24 | from academicmarkdown.constants import * 25 | try: 26 | from urllib.request import urlopen 27 | except ImportError: 28 | from urllib2 import urlopen 29 | 30 | 31 | @fnc.memoize(persistent=True) 32 | def urlget(url): 33 | 34 | print(url) 35 | fd = urlopen(url) 36 | content = fd.read() 37 | fd.close() 38 | return content 39 | 40 | 41 | class GitHubParser(YAMLParser): 42 | 43 | """ 44 | The `github` block includes references to GitHub issues or user profiles. 45 | For example: 46 | 47 | %-- github: { user: smathot } --% 48 | %-- github: { repo: "smathot/academicmarkdown", issue: 1 } --% 49 | """ 50 | 51 | def __init__(self, verbose=False): 52 | 53 | """See YAMLParser.__init__().""" 54 | 55 | super(GitHubParser, self).__init__(_object=u'github', verbose=verbose) 56 | 57 | def parseObject(self, md, _yaml, d): 58 | 59 | """See YAMLParser.parseObject().""" 60 | 61 | if not isinstance(d, dict): 62 | raise Exception(u'Expecting a dict') 63 | if u'user' in d: 64 | username = d['user'] 65 | return md.replace(_yaml, \ 66 | u'[@%s](https://github.com/%s)' % (username, username)) 67 | if u'issue' in d: 68 | url = u'https://api.github.com/repos/%(repo)s/issues/%(issue)d' % d 69 | s = urlget(url) 70 | d = yaml.load(s) 71 | summary = u'[Issue #%(number)s](%(url)s): %(title)s' % d 72 | for label in d[u'labels']: 73 | summary += u' (*%(name)s*) ' % label 74 | return md.replace(_yaml, summary) 75 | raise Exception(u'Invalid GitHub block') 76 | -------------------------------------------------------------------------------- /academicmarkdown/_IncludeParser.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """ 4 | This file is part of zoteromarkdown. 5 | 6 | zoteromarkdown is free software: you can redistribute it and/or modify 7 | it under the terms of the GNU General Public License as published by 8 | the Free Software Foundation, either version 3 of the License, or 9 | (at your option) any later version. 10 | 11 | zoteromarkdown is distributed in the hope that it will be useful, 12 | but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | GNU General Public License for more details. 15 | 16 | You should have received a copy of the GNU General Public License 17 | along with zoteromarkdown. If not, see . 18 | """ 19 | 20 | from academicmarkdown import YAMLParser, MDFilter 21 | from academicmarkdown.py3compat import * 22 | from academicmarkdown.constants import * 23 | import os 24 | 25 | class IncludeParser(YAMLParser): 26 | 27 | """ 28 | The `include` block includes an other Markdown file. For example: 29 | 30 | %-- include: example/intro.md --% 31 | """ 32 | 33 | def __init__(self, verbose=False): 34 | 35 | """See YAMLParser.__init__().""" 36 | 37 | super(IncludeParser, self).__init__(_object=u'include', verbose=verbose) 38 | 39 | def parseObject(self, md, _yaml, d): 40 | 41 | """See YAMLParser.parseObject().""" 42 | 43 | if not isinstance(d, basestring): 44 | return u'Expecting a string, not "%s"' % d 45 | d = self.getPath(d) 46 | self.msg('Include: %s' % d) 47 | _md = safe_decode(open(d).read()) 48 | # Apply pre-processing Markdown Filters 49 | for flt in preMarkdownFilters: 50 | fltFunc = getattr(MDFilter, flt) 51 | _md = fltFunc(_md) 52 | ip = IncludeParser(verbose=self.verbose) 53 | _md = ip.parse(_md) 54 | return md.replace(_yaml, _md) 55 | -------------------------------------------------------------------------------- /academicmarkdown/_ODTFixer.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """ 4 | This file is part of zoteromarkdown. 5 | 6 | zoteromarkdown is free software: you can redistribute it and/or modify 7 | it under the terms of the GNU General Public License as published by 8 | the Free Software Foundation, either version 3 of the License, or 9 | (at your option) any later version. 10 | 11 | zoteromarkdown is distributed in the hope that it will be useful, 12 | but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | GNU General Public License for more details. 15 | 16 | You should have received a copy of the GNU General Public License 17 | along with zoteromarkdown. If not, see . 18 | """ 19 | 20 | import zipfile 21 | from academicmarkdown import BaseParser 22 | from academicmarkdown.py3compat import * 23 | import re 24 | 25 | class ODTFixer(BaseParser): 26 | 27 | def __init__(self, verbose=False): 28 | 29 | super(ODTFixer, self).__init__(verbose=verbose) 30 | 31 | def fix(self, path): 32 | 33 | self.msg(u'Fixing %s' % path) 34 | self.msg(u'Reading ...') 35 | archive = zipfile.ZipFile(path, 'a') 36 | content = safe_decode(archive.read(u'content.xml')) 37 | # Style information is embedded as HTML comments, like so: 38 | # . The style needs to be extracted and placed 39 | # into the tags that open a paragraph. 40 | # We also need to take into account that '<', '>'. and '"' characters 41 | # have been HTML-ized by pandoc. 42 | lines = [] 43 | for line in content.split('\n'): 44 | for toStyle in re.findall( \ 45 | r'<!--odt-style="(\w+)"-->', line): 46 | for fromStyle in re.findall( \ 47 | r'', line): 48 | line = line.replace(fromStyle, toStyle) 49 | line = line.replace(u'<!--odt-style="%s"-->' \ 50 | % toStyle, u'') 51 | self.msg(u'Changing style "%s" to "%s"' % (fromStyle, toStyle)) 52 | lines.append(line) 53 | content = u'\n'.join(lines) 54 | 55 | #print content 56 | self.msg(u'Writing ...') 57 | archive.writestr('content.xml', safe_encode(content)) 58 | archive.close() 59 | self.msg(u'Done') 60 | -------------------------------------------------------------------------------- /academicmarkdown/_Pandoc.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """ 4 | This file is part of zoteromarkdown. 5 | 6 | zoteromarkdown is free software: you can redistribute it and/or modify 7 | it under the terms of the GNU General Public License as published by 8 | the Free Software Foundation, either version 3 of the License, or 9 | (at your option) any later version. 10 | 11 | zoteromarkdown is distributed in the hope that it will be useful, 12 | but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | GNU General Public License for more details. 15 | 16 | You should have received a copy of the GNU General Public License 17 | along with zoteromarkdown. If not, see . 18 | """ 19 | 20 | import subprocess 21 | import os 22 | from academicmarkdown import BaseParser 23 | from academicmarkdown.py3compat import * 24 | 25 | class Pandoc(BaseParser): 26 | 27 | def __init__(self, css=None, csl=None, template=None, standalone=True, \ 28 | verbose=False): 29 | 30 | """ 31 | Constructor. 32 | 33 | Keyword arguments: 34 | css -- A path to a `.css` file or None for no stylesheet. 35 | (default=None) 36 | csl -- A path to a `.csl` file to specify a citation format 37 | or None for a default citation format. (default=None) 38 | template -- The HTML template to be used. (default=None) 39 | standalone -- Indicates whether the --standalone and --self-contained 40 | arguments should be passed to pandoc. (default=True) 41 | verbose -- Indicates whether verbose output should be generated. 42 | (default=False) 43 | """ 44 | 45 | self.css = css 46 | self.csl = csl 47 | self.template = template 48 | self.standalone = standalone 49 | super(Pandoc, self).__init__(verbose=verbose) 50 | 51 | def docx(self, md, output, docxRef=None): 52 | 53 | """ 54 | Generates a .docx document. 55 | 56 | Arguments: 57 | md -- A Markdown string. 58 | output -- The name of the output file. 59 | """ 60 | 61 | self.odt(md, output, odtRef=docxRef) 62 | 63 | def epub(self, md, output): 64 | 65 | """ 66 | Generates an .epub document. 67 | 68 | Arguments: 69 | md -- A Markdown string. 70 | output -- The name of the output file. 71 | """ 72 | 73 | self.msg(u'Invoking pandoc') 74 | cmd = u'pandoc --smart -t epub --toc -o %s' % output 75 | if os.path.exists(u'.bibliography.json'): 76 | cmd += u' --bibliography .bibliography.json' 77 | if self.csl != None: 78 | cmd += u' --csl %s' % self.csl 79 | ps = subprocess.Popen(cmd.split(), stdin=subprocess.PIPE, \ 80 | stdout=subprocess.PIPE) 81 | print(safe_decode(ps.communicate(safe_encode(md)[0]))) 82 | 83 | def html(self, md, output): 84 | 85 | """ 86 | Generates an .html document. 87 | 88 | Argument: 89 | md -- A Markdown string. 90 | output -- The name of the output file. 91 | """ 92 | 93 | open(output, 'w').write(safe_encode(self.parse(md))) 94 | 95 | def odt(self, md, output, odtRef=None): 96 | 97 | """ 98 | Generates an .odt document. 99 | 100 | Arguments: 101 | md -- A Markdown string. 102 | output -- The name of the output file. 103 | 104 | Keyword arguments: 105 | odtRef -- A reference ODT for styling. (default=None) 106 | """ 107 | 108 | self.msg(u'Invoking pandoc') 109 | cmd = u'pandoc --standalone' 110 | if os.path.exists(u'.bibliography.json'): 111 | cmd += u' --bibliography .bibliography.json' 112 | if self.csl != None: 113 | cmd += u' --csl %s' % self.csl 114 | if odtRef != None: 115 | # Since this function is also used to render docx, we should make 116 | # sure that we pass the correct reference argument. 117 | if odtRef.endswith(u'.docx'): 118 | cmd += u' --reference-docx=%s' % odtRef 119 | else: 120 | cmd += u' --reference-odt=%s' % odtRef 121 | cmd += u' -o' 122 | ps = subprocess.Popen(cmd.split() + [output], stdin=subprocess.PIPE, \ 123 | stdout=subprocess.PIPE) 124 | print(safe_decode(ps.communicate(safe_encode(md))[0])) 125 | 126 | def parse(self, md): 127 | 128 | """See BaseParser.parse().""" 129 | 130 | self.msg(u'Invoking pandoc') 131 | cmd = u'pandoc -f markdown+header_attributes+markdown_attribute -t html5' 132 | if self.standalone: 133 | # cmd += u' --standalone --self-contained' 134 | cmd += u' --standalone --self-contained' 135 | if self.css != None: 136 | cmd += u' --css %s' % self.css 137 | if self.template != None: 138 | cmd += u' --template %s' % self.template 139 | if os.path.exists(u'.bibliography.json'): 140 | cmd += u' --bibliography .bibliography.json' 141 | if self.csl != None: 142 | cmd += u' --csl %s' % self.csl 143 | print(cmd) 144 | ps = subprocess.Popen(cmd.split(), stdin=subprocess.PIPE, \ 145 | stdout=subprocess.PIPE) 146 | return safe_decode(ps.communicate(safe_encode(md))[0]) 147 | -------------------------------------------------------------------------------- /academicmarkdown/_PythonParser.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """ 4 | This file is part of zoteromarkdown. 5 | 6 | zoteromarkdown is free software: you can redistribute it and/or modify 7 | it under the terms of the GNU General Public License as published by 8 | the Free Software Foundation, either version 3 of the License, or 9 | (at your option) any later version. 10 | 11 | zoteromarkdown is distributed in the hope that it will be useful, 12 | but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | GNU General Public License for more details. 15 | 16 | You should have received a copy of the GNU General Public License 17 | along with zoteromarkdown. If not, see . 18 | """ 19 | 20 | from academicmarkdown import YAMLParser 21 | from academicmarkdown.py3compat import * 22 | import subprocess 23 | import shlex 24 | 25 | _globals = {} 26 | img_nr = 0 27 | 28 | class PythonParser(YAMLParser): 29 | 30 | """ 31 | The `python` block embeds the output (i.e. whatever is printed to stdout) 32 | of a Python script into your document. For example, the following block 33 | embeds the docstring of the `PythonParser` class (i.e. what you're reading 34 | now): 35 | 36 | %-- 37 | python: | 38 | import inspect 39 | from academicmarkdown import PythonParser 40 | print inspect.getdoc(PythonParser) 41 | --% 42 | 43 | Note that the `|` symbol is YAML syntax, and allows you to have a multiline 44 | string. 45 | """ 46 | 47 | def __init__(self, verbose=False): 48 | 49 | """See YAMLParser.__init__().""" 50 | 51 | super(PythonParser, self).__init__(_object=u'python', verbose=verbose) 52 | 53 | def parseObject(self, md, _yaml, d): 54 | 55 | """See YAMLParser.parseObject().""" 56 | 57 | global img_nr 58 | 59 | if not isinstance(d, basestring): 60 | return u'Expecting a string, not "%s"' % d 61 | 62 | import sys 63 | try: 64 | from io import StringIO # Py 3 65 | except ImportError: 66 | from StringIO import StringIO # Py 2 67 | img = False 68 | code = d 69 | if d.endswith('\nplt.show()\n'): 70 | d = d[:-len('\nplt.show()\n')] 71 | img_nr += 1 72 | d = 'from matplotlib import pyplot as plt\nplt.clf()\n{}\nplt.savefig("img/{}.png")\n'.format( 73 | d, 74 | img_nr 75 | ) 76 | img = True 77 | self.msg(u'Python: %s' % d) 78 | buffer = StringIO() 79 | sys.stdout = buffer 80 | if safe_str(d).startswith('# should-raise\n'): 81 | code = code.replace('# should-raise\n', '') 82 | try: 83 | exec('#-*- coding:utf-8 -*-\n%s' % safe_str(d), _globals) 84 | except Exception as e: 85 | print('{0}: {1}'.format(e.__class__.__name__, e)) 86 | # import traceback 87 | # traceback.print_exc(file=buffer, limit=1) 88 | else: 89 | raise ValueError('Code should raise exception but didn\t') 90 | else: 91 | exec('#-*- coding:utf-8 -*-\n%s' % safe_str(d), _globals) 92 | sys.stdout = sys.__stdout__ 93 | output = buffer.getvalue() 94 | self.msg(u'Returns: %s' % output) 95 | s = u""" 96 | ~~~ .python 97 | %s 98 | ~~~ 99 | """ % safe_str(code) 100 | 101 | if output: 102 | s += """ 103 | __Output:__ 104 | 105 | ~~~ .text 106 | %s 107 | ~~~ 108 | """ % output 109 | if img: 110 | s += '\n![](/img/{}.png)\n'.format(img_nr) 111 | return md.replace(_yaml, s) 112 | -------------------------------------------------------------------------------- /academicmarkdown/_TOCParser.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """ 4 | This file is part of zoteromarkdown. 5 | 6 | zoteromarkdown is free software: you can redistribute it and/or modify 7 | it under the terms of the GNU General Public License as published by 8 | the Free Software Foundation, either version 3 of the License, or 9 | (at your option) any later version. 10 | 11 | zoteromarkdown is distributed in the hope that it will be useful, 12 | but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | GNU General Public License for more details. 15 | 16 | You should have received a copy of the GNU General Public License 17 | along with zoteromarkdown. If not, see . 18 | """ 19 | 20 | from academicmarkdown import YAMLParser 21 | import re 22 | import string 23 | 24 | class TOCParser(YAMLParser): 25 | 26 | """ 27 | The `toc` block automatically generates a table of contents from the 28 | headings, assuming that headings are indicated using the `#` style and not 29 | the underlining style. You can indicate headings to be excluded from the 30 | table of contents as well. 31 | 32 | %-- 33 | toc: 34 | mindepth: 1 35 | maxdepth: 2 36 | exclude: [Contents, Contact] 37 | --% 38 | 39 | All attributes are optional. 40 | """ 41 | 42 | def __init__(self, anchorHeaders=False, appendHeaderRefs=False, 43 | verbose=False): 44 | 45 | """ 46 | Constructor. 47 | 48 | Keyword arguments: 49 | anchorHeaders -- Indicates whether headers should be turned into 50 | clickable anchors, mostly useful for HTML pages. 51 | (default=False) 52 | appendHeaderRefs -- Indicates whether headers references should be 53 | appended to the document, so that you can 54 | directly refer to header links in the text. This 55 | is only necessary when using a Markdown parser 56 | that doesn't do this automatically. 57 | (default=False) 58 | verbose -- Indicates whether verbose output should be 59 | generated. (default=False) 60 | """ 61 | 62 | self.anchorHeaders = anchorHeaders 63 | self.appendHeaderRefs = appendHeaderRefs 64 | self._uniqueId = u'' 65 | super(TOCParser, self).__init__(_object=u'toc', verbose=verbose) 66 | 67 | def parseObject(self, md, _yaml, d): 68 | 69 | """See YAMLParser.parseObject().""" 70 | 71 | if u'exclude' not in d: 72 | d[u'exclude'] = [] 73 | if u'maxdepth' not in d: 74 | d[u'maxdepth'] = 3 75 | if u'mindepth' not in d: 76 | d[u'mindepth'] = 1 77 | headers = [] 78 | appends = [] 79 | # Because script can have hashtags as code comments, we should ignore 80 | # script when searching for headers. 81 | # - Remove standard ~~~ style script blocks 82 | mdNoScript = re.sub(r'~~~(.*?)~~~', u'DUMMY', md, re.M, re.S) 83 | # - Remove jekyll style script blocks 84 | mdNoScript = re.sub(r'{% highlight (\w*) %}(.*?){% endhighlight %}', 85 | u'DUMMY', mdNoScript, re.M, re.S) 86 | for i in re.finditer(r'^#(.*)', mdNoScript, re.M): 87 | h = i.group() 88 | for level in range(100, -1, -1): 89 | if h.startswith(u'#' * level): 90 | break 91 | if level not in range(d[u'mindepth'], d[u'maxdepth']+1): 92 | self.msg(u'Header level not in range: %s' % h) 93 | continue 94 | label = h[level:].strip() 95 | _id = self.labelId(label) 96 | if label not in d[u'exclude']: 97 | headers.append( (level, h, label, _id) ) 98 | self.msg(u'%s {#%s} (%d)' % (h, _id, level)) 99 | if self.appendHeaderRefs: 100 | appends.append(u'[%s]: #%s' % (label, _id)) 101 | _md = u'\n' 102 | lRep = [] 103 | for level, h, label, _id in headers: 104 | print(h) 105 | _md += u'\t' * (level-d[u'mindepth']) # Indent 106 | _md += u'- [%s](#%s)\n' % (label, _id) 107 | if self.anchorHeaders: 108 | md = md.replace(h, u'%s [%s](#%s) {#%s}' % (u'#'*level, label, \ 109 | _id, _id)) 110 | _md += u'\n' 111 | return md.replace(_yaml, _md) + u'\n' + u'\n'.join(appends) 112 | 113 | def labelId(self, label): 114 | 115 | """ 116 | Generates and ID for a label, simulating the IDs generated by Pandoc. 117 | 118 | IDs should match the IDs that generated by Pandoc. The basic 119 | algorithm appears to be that spaces are converted to dashes, dashes are 120 | left in and all non-alphanumeric characters are ignored. Avoid dashes 121 | at the beginning and end of the ID and also avoid double dashes. 122 | 123 | BUG: Pandoc doesn't properly parse (at least) Chinese characters, 124 | so links in Chinese TOCs will be broken. 125 | 126 | Arguments: 127 | label -- A label. 128 | 129 | Returns: 130 | An ID. 131 | """ 132 | 133 | _id = u'' 134 | for ch in label: 135 | if ch in string.ascii_letters + string.digits + u'-_': 136 | _id += ch.lower() 137 | elif ch.isspace() and len(_id) > 0 and _id[-1] != u'-': 138 | _id += u'-' 139 | _id = _id.strip(u'-.') 140 | # Make sure that the ID is not empty and starts with a letter 141 | if len(_id) == 0 or not _id[0].isalpha(): 142 | _id = self.uniqueId() + _id 143 | return _id 144 | 145 | def uniqueId(self): 146 | 147 | """ 148 | Returns: 149 | A unique letter id. 150 | """ 151 | 152 | self._uniqueId += u'a' 153 | return self._uniqueId 154 | -------------------------------------------------------------------------------- /academicmarkdown/_TableParser.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """ 4 | This file is part of zoteromarkdown. 5 | 6 | zoteromarkdown is free software: you can redistribute it and/or modify 7 | it under the terms of the GNU General Public License as published by 8 | the Free Software Foundation, either version 3 of the License, or 9 | (at your option) any later version. 10 | 11 | zoteromarkdown is distributed in the hope that it will be useful, 12 | but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | GNU General Public License for more details. 15 | 16 | You should have received a copy of the GNU General Public License 17 | along with zoteromarkdown. If not, see . 18 | """ 19 | 20 | import os 21 | import yaml 22 | from academicmarkdown import YAMLParser 23 | from academicmarkdown.py3compat import * 24 | import subprocess 25 | import sys 26 | 27 | tableTemplate = {u'html5': u""" 28 | NOT IMPLEMENTED 29 | """, 30 | u'kramdown': u""" 31 | %(table)s 32 | 33 | __Table %(nTbl)d.__ %(caption)s\n{: .tbl-caption #%(id)s} 34 | """, 35 | u'pandoc': u""" 36 | 37 |
38 | 39 | Table %(nTbl)d 40 | 41 | %(caption)s 42 | 43 | %(table)s 44 | 45 |
46 | """} 47 | 48 | 49 | class TableParser(YAMLParser): 50 | 51 | """ 52 | The `table` block reads a table from a `.csv` file and embed it into the 53 | document. The source file needs to be a utf-8 encoded file that is 54 | comma separated and double quoted. 55 | 56 | %-- 57 | table: 58 | id: MyTable 59 | source: my_table.csv 60 | caption: "My table caption." 61 | ndigits: 4 62 | --% 63 | """ 64 | 65 | def __init__(self, style=u'inline', template=u'kramdown', verbose= \ 66 | False): 67 | 68 | """ 69 | Constructor. 70 | 71 | Keyword arguments: 72 | style -- Can be u'inline' or u'below' to indicate whether figures 73 | should be placed in or below the text. 74 | (default=u'inline') 75 | template -- Indicates the output format, which can be 'odt' or 76 | 'html5'. (default=u'html5') 77 | verbose -- Indicates whether verbose output should be generated. 78 | (default=False) 79 | """ 80 | 81 | self.style = style 82 | self.template = template 83 | super(TableParser, self).__init__(_object=u'table', required=['id', \ 84 | 'source'], verbose=verbose) 85 | 86 | def parse(self, md): 87 | 88 | """See BaseParser.parse().""" 89 | 90 | self.nTbl = 0 91 | return super(TableParser, self).parse(md) 92 | 93 | def parseObject(self, md, _yaml, d): 94 | 95 | """See YAMLParser.parseObject().""" 96 | 97 | self.nTbl += 1 98 | d['nTbl'] = self.nTbl 99 | self.msg(u'Found table: %s (%d)' % (d['id'], self.nTbl)) 100 | d[u'source'] = self.getPath(d[u'source']) 101 | if u'caption' not in d: 102 | d[u'caption'] = u'' 103 | if u'ndigits' not in d: 104 | d[u'ndigits'] = 4 105 | # Read table and turn it into a kramdown-style table 106 | s = u'' 107 | import csv 108 | i = 0 109 | with open(d[u'source'], u'r') as csvFile: 110 | csvReader = csv.reader(csvFile, delimiter=',', quotechar='"') 111 | for row in csvReader: 112 | # Pandoc requires a row of alignment indicators below the 113 | # header. See also: 114 | # - 115 | if self.template in (u'pandoc', u'kramdown'): 116 | if i == 1: 117 | alignList = [] 118 | for col in row: 119 | try: 120 | float(col) 121 | alignList.append(u'--:') 122 | except: 123 | alignList.append(u':--') 124 | s += (u'| ' + u' | '.join(alignList) + u' |\n') 125 | _row = [] 126 | for col in row: 127 | try: 128 | # If a value is numeric, we need to round it. If the 129 | # rounde value is 0, we indicated this with a smaller 130 | # than sign. 131 | float(col) 132 | col = round(float(col), d[u'ndigits']) 133 | if col == 0: 134 | col = u'< 0.' + u'0'*(d[u'ndigits']-1) + u'1' 135 | else: 136 | # Use a somethat convoluted string formatting 137 | # operation to make sure that we don't lose trailing 138 | # zeros. 139 | col = (u'%%.%df' % d[u'ndigits']) % col 140 | _row.append(col) 141 | except: 142 | if not col.strip(): 143 | col = u' ' 144 | _row.append(safe_decode(col)) 145 | s += (u'| ' + u' | '.join(_row) + u' |\n') 146 | i += 1 147 | d[u'table'] = s 148 | tbl = tableTemplate[self.template] % d 149 | # Insert/ append table into document 150 | if self.style == u'inline': 151 | md = md.replace(_yaml, tbl) 152 | else: 153 | md = md.replace(_yaml, u'') 154 | md += tbl 155 | # Replace reference to table 156 | md = md.replace(u'%%%s' % d[u'id'], u'[Table %d](#%s)' % (self.nTbl, \ 157 | d[u'id'])) 158 | return md 159 | -------------------------------------------------------------------------------- /academicmarkdown/_VideoParser.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """ 4 | This file is part of zoteromarkdown. 5 | 6 | zoteromarkdown is free software: you can redistribute it and/or modify 7 | it under the terms of the GNU General Public License as published by 8 | the Free Software Foundation, either version 3 of the License, or 9 | (at your option) any later version. 10 | 11 | zoteromarkdown is distributed in the hope that it will be useful, 12 | but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | GNU General Public License for more details. 15 | 16 | You should have received a copy of the GNU General Public License 17 | along with zoteromarkdown. If not, see . 18 | """ 19 | 20 | import os 21 | import yaml 22 | from academicmarkdown import YAMLParser 23 | import subprocess 24 | import sys 25 | 26 | videoTemplate = { 27 | u'vimeo' : u""" 28 | 30 |
Video %(nVid)d. %(caption)s
31 | """, 32 | u'youtube' : u""" 33 | 35 |
Video %(nVid)d. %(caption)s
36 | """ 37 | } 38 | 39 | class VideoParser(YAMLParser): 40 | 41 | """ 42 | Embeds a video. Currently, YouTube and Vimeo sources are supported. The 43 | keywords `width`, `height`, and `caption` are optional. 44 | 45 | %-- 46 | video: 47 | id: VidRefresh 48 | source: vimeo 49 | videoid: 24216910 50 | width: 640 51 | height: 240 52 | caption: "A figure caption" 53 | --% 54 | 55 | """ 56 | 57 | def __init__(self, verbose=False): 58 | 59 | """ 60 | Constructor. 61 | 62 | Keyword arguments: 63 | verbose -- Indicates whether verbose output should be generated. 64 | (default=False) 65 | """ 66 | 67 | super(VideoParser, self).__init__(_object=u'video', required=['id', \ 68 | 'source', 'videoid'], verbose=verbose) 69 | 70 | def parse(self, md): 71 | 72 | """See BaseParser.parse().""" 73 | 74 | self.nVid = 0 75 | return super(VideoParser, self).parse(md) 76 | 77 | def parseObject(self, md, _yaml, d): 78 | 79 | """See YAMLParser.parseObject().""" 80 | 81 | self.nVid += 1 82 | d['nVid'] = self.nVid 83 | self.msg(u'Found video: %s (%d)' % (d['id'], self.nVid)) 84 | if u'caption' not in d: 85 | d[u'caption'] = u'' 86 | if u'width' not in d: 87 | d[u'width'] = 640 88 | if u'height' not in d: 89 | d[u'height'] = 320 90 | vid = videoTemplate[d[u'source']] % d 91 | md = md.replace(_yaml, vid) 92 | md = md.replace(u'%%%s' % d[u'id'], u'[Video %d](#%s)' % (self.nVid, \ 93 | d[u'id'])) 94 | return md 95 | 96 | -------------------------------------------------------------------------------- /academicmarkdown/_WcParser.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """ 4 | This file is part of zoteromarkdown. 5 | 6 | zoteromarkdown is free software: you can redistribute it and/or modify 7 | it under the terms of the GNU General Public License as published by 8 | the Free Software Foundation, either version 3 of the License, or 9 | (at your option) any later version. 10 | 11 | zoteromarkdown is distributed in the hope that it will be useful, 12 | but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | GNU General Public License for more details. 15 | 16 | You should have received a copy of the GNU General Public License 17 | along with zoteromarkdown. If not, see . 18 | """ 19 | 20 | from academicmarkdown.py3compat import * 21 | from academicmarkdown import YAMLParser 22 | import subprocess 23 | import shlex 24 | 25 | class WcParser(YAMLParser): 26 | 27 | """ 28 | The `wc` block insert the word count for a particular document. This is 29 | convenient if you have split the text across multiple documents, and want to 30 | have a separate word count for each document. 31 | 32 | %-- wc: method-section.md --% 33 | """ 34 | 35 | def __init__(self, verbose=False): 36 | 37 | """See YAMLParser.__init__().""" 38 | 39 | super(WcParser, self).__init__(_object=u'wc', verbose=verbose) 40 | 41 | def parseObject(self, md, _yaml, d): 42 | 43 | """See YAMLParser.parseObject().""" 44 | 45 | if not isinstance(d, str): 46 | return u'Expecting a string, not "%s"' % d 47 | s = safe_decode(open(self.getPath(d)).read()) 48 | wc = str(len(s.split())) 49 | self.msg(u'Word count: %s words in %s' % (wc, d)) 50 | return md.replace(_yaml, wc) 51 | -------------------------------------------------------------------------------- /academicmarkdown/_WkHtmlToPdf.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """ 4 | This file is part of zoteromarkdown. 5 | 6 | zoteromarkdown is free software: you can redistribute it and/or modify 7 | it under the terms of the GNU General Public License as published by 8 | the Free Software Foundation, either version 3 of the License, or 9 | (at your option) any later version. 10 | 11 | zoteromarkdown is distributed in the hope that it will be useful, 12 | but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | GNU General Public License for more details. 15 | 16 | You should have received a copy of the GNU General Public License 17 | along with zoteromarkdown. If not, see . 18 | """ 19 | 20 | import re 21 | import shlex 22 | import subprocess 23 | from academicmarkdown import BaseParser 24 | from academicmarkdown.py3compat import * 25 | 26 | # From 27 | feaderTmpl = """ 28 | 29 | 30 | 31 | 43 | %css% 44 | 45 |
%feader%
46 | """ 47 | 48 | class WkHtmlToPdf(BaseParser): 49 | 50 | def __init__(self, css=None, fix00=False, margins=(20, 20, 30, 20), 51 | spacing=(10, 10), header=None, footer=u'%page% / %topage%', 52 | verbose=False, args=''): 53 | 54 | """ 55 | Constructor. 56 | 57 | Keyword arguments: 58 | css -- A path to a css stylesheet or None. (default=None) 59 | fix00 -- Indicates whether #00 corruptions should be fixed. 60 | (default=True) 61 | margins -- A T,R,B,L tuple of page margins. (default=20,20,30,20) 62 | spacing -- A (header spacing, footer spacing) tuple. 63 | (default=10,10) 64 | header -- A header text, or None. (default=None) 65 | footer -- A footer text, or None. (default=u'%page% / %topage%') 66 | verbose -- Indicates whether verbose output should be generated. 67 | (default=False) 68 | args -- 69 | """ 70 | 71 | self.css = css 72 | self.args = args 73 | self.fix00 = fix00 74 | self.margins = margins 75 | self.spacing = spacing 76 | self.header = header 77 | self.footer = footer 78 | super(WkHtmlToPdf, self).__init__(verbose=verbose) 79 | 80 | def createFeader(self, s, cssClass): 81 | 82 | """ 83 | Builds a header or footer html file. 84 | 85 | Arguments: 86 | s -- The contents for the header/ footer. 87 | cssClass -- The css class to be used for the div containing the 88 | contents. 89 | 90 | Returns: 91 | An HTML string containing the header/ footer. 92 | """ 93 | 94 | regEx = r'%(?P[a-z]+)%' 95 | for r in re.finditer(regEx, s): 96 | s = s.replace(u'%%%s%%' % r.group('var'), \ 97 | u'' % r.group('var')) 98 | feader = feaderTmpl.replace(u'%feader%', s) 99 | if self.css != None: 100 | feader = feader.replace(u'%css%', \ 101 | u'' % \ 102 | self.css) 103 | else: 104 | feader = feader.replace(u'%css%', u'') 105 | feader = feader.replace(u'%class%', cssClass) 106 | return feader 107 | 108 | def parse(self, html, target): 109 | 110 | """See BaseParser.parse().""" 111 | 112 | self.msg(u'Invoking wkhtmltopdf') 113 | cmd = u'wkhtmltopdf -T %s -R %s -B %s -L %s --dpi 96 --disable-smart-shrinking' % self.margins 114 | if self.header != None: 115 | open('.header.html', 'wb').write(safe_encode(self.createFeader( 116 | self.header, u'header'))) 117 | cmd += u' --header-html .header.html --header-spacing %s' % \ 118 | self.spacing[0] 119 | if self.footer != None: 120 | open('.footer.html', 'wb').write(safe_encode( 121 | self.createFeader(self.footer, u'footer'))) 122 | cmd += u' --footer-html .footer.html --footer-spacing %s' % \ 123 | self.spacing[1] 124 | cmd += u' ' + self.args 125 | cmd += u' %s "%s"' % (html, target) 126 | self.msg(cmd) 127 | if not py3: 128 | cmd = safe_encode(cmd) 129 | subprocess.call(shlex.split(cmd)) 130 | if self.fix00: 131 | # Due to a bug in wkhtmltopdf, the PDF may contain #00 strings, 132 | # which cause Acrobat Reader to choke (but not other PDF readers). 133 | # This happens mostly when filenames are very long, in which case 134 | # anchors are hashed, and the resulting hashes sometimes contain #00 135 | # values. Here we simply replace all #00 strings, which seems to 136 | # work. 137 | self.msg(u'Checking for #00') 138 | pdf = open(target, 'rb').read() 139 | if safe_encode('#00') in pdf: 140 | self.msg(u'Fixing #00!') 141 | pdf = pdf.replace('#00', '#01') 142 | open(target, u'wb').write(pdf) 143 | -------------------------------------------------------------------------------- /academicmarkdown/_YAMLParser.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """ 4 | This file is part of zoteromarkdown. 5 | 6 | zoteromarkdown is free software: you can redistribute it and/or modify 7 | it under the terms of the GNU General Public License as published by 8 | the Free Software Foundation, either version 3 of the License, or 9 | (at your option) any later version. 10 | 11 | zoteromarkdown is distributed in the hope that it will be useful, 12 | but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | GNU General Public License for more details. 15 | 16 | You should have received a copy of the GNU General Public License 17 | along with zoteromarkdown. If not, see . 18 | """ 19 | 20 | import re 21 | import yaml 22 | from academicmarkdown import BaseParser 23 | 24 | class YAMLParser(BaseParser): 25 | 26 | def __init__(self, _object, required=[], verbose=False): 27 | 28 | """ 29 | Constructor. 30 | 31 | Arguments: 32 | _object -- Indicates which objects should be parsed. 33 | 34 | Keyword arguments: 35 | required -- A list of required options in the YAML block. 36 | (default=[]) 37 | verbose -- Indicates whether verbose output should be generated. 38 | (default=False) 39 | """ 40 | 41 | self._object = _object 42 | self.required = required 43 | super(YAMLParser, self).__init__(verbose=verbose) 44 | 45 | def parse(self, md): 46 | 47 | """See BaseParser.parse().""" 48 | 49 | for r in re.finditer(u'%--(.*?)--%', md, re.M|re.S): 50 | try: 51 | d = yaml.load(r.groups()[0]) 52 | except: 53 | self.msg(u'Invalid YAML block: %s' % r.groups()[0]) 54 | continue 55 | if not isinstance(d, dict): 56 | continue 57 | obj = list(d.keys())[0] 58 | if obj.lower() != self._object: 59 | continue 60 | keys = d[obj] 61 | for key in self.required: 62 | if key not in keys: 63 | raise Exception( \ 64 | u'"%s" is a required option for %s objects' % (key, \ 65 | self._object)) 66 | md = self.parseObject(md, r.group(), keys) 67 | return md 68 | 69 | def parseObject(self, md, _yaml, d): 70 | 71 | """ 72 | Parses a specific YAML object found in a Markdown text. 73 | 74 | Arguments: 75 | md -- The full markdown text. 76 | yaml -- The yaml block. 77 | d -- The yaml block already parsed into a dictionary. 78 | 79 | Returns: 80 | The parsed Markdown text. 81 | """ 82 | 83 | return md 84 | -------------------------------------------------------------------------------- /academicmarkdown/_ZoteroParser.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """ 4 | This file is part of zoteromarkdown. 5 | 6 | zoteromarkdown is free software: you can redistribute it and/or modify 7 | it under the terms of the GNU General Public License as published by 8 | the Free Software Foundation, either version 3 of the License, or 9 | (at your option) any later version. 10 | 11 | zoteromarkdown is distributed in the hope that it will be useful, 12 | but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | GNU General Public License for more details. 15 | 16 | You should have received a copy of the GNU General Public License 17 | along with zoteromarkdown. If not, see . 18 | """ 19 | 20 | try: 21 | from pyzotero import zotero 22 | except: 23 | zotero = None 24 | 25 | from academicmarkdown import BaseParser 26 | from academicmarkdown.py3compat import * 27 | import os 28 | import re 29 | import json 30 | import pickle 31 | 32 | 33 | class ZoteroParser(BaseParser): 34 | 35 | cachePath = u'.zoteromarkdown.cache' 36 | 37 | def __init__(self, libraryId, apiKey, libraryType=u'user', 38 | clearCache=False, headerText=u'References', headerLevel=1, 39 | odtStyle=None, fixDOI=True, fixAuthorNames=True, verbose=False, 40 | removeURL=True): 41 | 42 | """ 43 | Constructor. 44 | 45 | Arguments: 46 | libraryId -- The libraryId, available from your Zotero profile. 47 | apiKey -- The API key, available from your Zotero profile. 48 | 49 | Keyword arguments: 50 | libraryType -- The library type. Can be 'user' or 'group'. 51 | (default=u'user') 52 | clearCache -- Indicates whether the cache should be cleared. 53 | (default=False) 54 | headerText -- Indicates the text to be used for the header. 55 | (default=u'References') 56 | headerLevel -- Indicates the header level for the references. 57 | (default=1) 58 | odtStyle -- Indicates the style to be used for ODT output. This 59 | style is indicated as an HTML comment, so that it 60 | does not affect HTML output. (default=None) 61 | fixDOI -- Indicates that the DOI field should be remapped and 62 | cleaned up for proper rendering. (default=True) 63 | fixAuthorNames -- Indicates that first names of authors should be 64 | converted to clean initials, to avoid one author 65 | appearing as multiple. (default=True) 66 | removeURL -- Removes the URL from the references, because some 67 | styles insist on adding it. The URL is only removed 68 | when a journal (i.e. `container-title` field) is 69 | available. (default=True) 70 | verbose -- Indicates whether verbose output should be printed. 71 | (default=False) 72 | """ 73 | 74 | if zotero == None: 75 | raise Exception(u'pyzotero is not available!') 76 | super(ZoteroParser, self).__init__(verbose=verbose) 77 | self.zotero = None 78 | self.libraryId = libraryId 79 | self.apiKey = apiKey 80 | self.libraryType = libraryType 81 | self.headerText = headerText 82 | self.headerLevel = headerLevel 83 | self.odtStyle = odtStyle 84 | self.fixDOI = fixDOI 85 | self.fixAuthorNames = fixAuthorNames 86 | self.removeURL = removeURL 87 | self.refCount = 0 88 | if not os.path.exists(self.cachePath) or clearCache: 89 | self.cache = {} 90 | else: 91 | fd = open(self.cachePath, 'rb') 92 | try: 93 | self.cache = pickle.load(fd) 94 | except: 95 | self.msg(u'Failed to open cache.') 96 | self.cache = {} 97 | fd.close() 98 | 99 | def connect(self): 100 | 101 | """Connects to the Zotero API.""" 102 | 103 | self.msg(u'Connecting to Zotero server.') 104 | self.zotero = zotero.Zotero(self.libraryId, self.libraryType, \ 105 | self.apiKey) 106 | 107 | def getYear(self, s): 108 | 109 | """ 110 | Extracts the year from a string in a clever way. 111 | 112 | Arguments: 113 | s -- A string. 114 | 115 | Returns: 116 | A best guess of the year. 117 | """ 118 | 119 | try: 120 | from dateutil import parser 121 | except: 122 | self.msg(u'dateutil is not available to guess the year.') 123 | return s 124 | try: 125 | return parser.parse(s).year 126 | except: 127 | self.msg(u'failed to parse date %s' % s) 128 | return s 129 | 130 | def parse(self, md): 131 | 132 | """ 133 | Parses pandoc-style citations from the documents and adds a 134 | corresponding bibliography as YAML to the documents. 135 | 136 | Arguments: 137 | md -- A string containing MarkDown text. 138 | 139 | Returns: 140 | The Markdown text with bibliography added. 141 | """ 142 | 143 | items = [] 144 | oldQueries = [] 145 | regexp = r'@([^ ?!,.\t\n\r\f\v\]\[;]+)' 146 | for r in re.finditer(regexp, md): 147 | queryString = r.groups()[0] 148 | self.msg(u'Found citation (#%d) "%s"' % (self.refCount, 149 | queryString)) 150 | if queryString in oldQueries: 151 | continue 152 | self.refCount += 1 153 | matches = self.bestMatch(queryString) 154 | if len(matches) == 0: 155 | self.msg(u'No matches for "%s"!' % queryString) 156 | continue 157 | if len(matches) > 1: 158 | for match in matches: 159 | print(match) 160 | raise Exception( \ 161 | u'Multiple Zotero matches (%d) for "@%s". Be more specific!' % \ 162 | (len(matches), queryString)) 163 | match = matches[0] 164 | if match in items and queryString not in oldQueries: 165 | for _queryString in sorted(oldQueries): 166 | print(u'Ref: %s' % _queryString) 167 | raise Exception( 168 | ('"%s" refers to an existent reference with a different name. Please use consistent references (see list above)!' \ 169 | % queryString)) 170 | match[u'id'] = queryString 171 | if self.odtStyle != None: 172 | match[u'title'] += u'' % self.odtStyle 173 | items.append(match) 174 | oldQueries.append(queryString) 175 | # TODO Placing the citation info in the YAML block doesn't appear to 176 | # work. So for now save it as a JSON file. 177 | fd = open(u'.bibliography.json', u'w') 178 | json.dump(items, fd, indent=1) 179 | fd.close() 180 | if self.headerText == None or self.headerLevel == None: 181 | return md 182 | md = md.replace(u'%rc%', str(self.refCount)) 183 | return md + u'\n\n%s %s\n\n' % (u'#' * self.headerLevel, \ 184 | self.headerText) 185 | 186 | def bestMatch(self, queryString): 187 | 188 | """ 189 | Retrieves a matching item for a given query. Queries 190 | 191 | Arguments: 192 | queryString -- A query string. 193 | 194 | 195 | Returns: 196 | A csljson-style dictionary for the matching item. 197 | """ 198 | 199 | query = self.splitCitation(queryString) 200 | if query[0] in self.cache: 201 | self.msg(u'Retrieving "%s" from cache.' % query[0]) 202 | items = self.cache[query[0]] 203 | else: 204 | self.msg(u'Retrieving "%s" from Zotero API.' % query[0]) 205 | if self.zotero == None: 206 | self.connect() 207 | try: 208 | items = self.zotero.top(q=safe_encode(query[0]), 209 | limit=100, content=u'csljson') 210 | except: 211 | self.msg(u'Failed to query Zotero server!') 212 | return [] 213 | if len(items) == 0: 214 | return [] 215 | self.cache[query[0]] = items 216 | fd = open(self.cachePath, u'wb') 217 | pickle.dump(self.cache, fd) 218 | fd.close() 219 | matches = [] 220 | for item in items: 221 | match = True 222 | matchPhase = 0 223 | for i in range(len(query)): 224 | # Determine whether we are matching a year or an author name 225 | term = query[i].lower() 226 | try: 227 | term = int(term) 228 | matchPhase += 1 229 | except: 230 | pass 231 | # Check authors 232 | if matchPhase == 0: 233 | if 'author' not in item: 234 | break 235 | if i >= len(item[u'author']): 236 | match = False 237 | break 238 | if term not in item[u'author'][i][u'family'].lower(): 239 | match = False 240 | break 241 | # Check year 242 | elif matchPhase == 1: 243 | if u'issued' not in item: 244 | match = False 245 | break 246 | else: 247 | if u'year' in item[u'issued'] and not item[u'issued']['year'] == u'date unknown': 248 | year = item[u'issued'][u'year'] 249 | elif u'raw' in item[u'issued']: 250 | year = item[u'issued'][u'raw'] 251 | elif u'date-parts' in item[u'issued']: 252 | year = item[u'issued'][u'date-parts'][0][0] 253 | elif u'literal' in item[u'issued']: 254 | year = item[u'issued'][u'literal'] 255 | else: 256 | raise Exception(u'Invalid issued field: %s' % item) 257 | try: 258 | year = int(year) 259 | except: 260 | pass 261 | if type(year) == int: 262 | if term != year: 263 | match = False 264 | break 265 | else: 266 | if term != 0: 267 | match = False 268 | break 269 | matchPhase += 1 270 | # Check title or publication 271 | elif matchPhase == 2: 272 | if not (u'title' in item and term in \ 273 | item[u'title'].lower()) and not (u'container-title' in \ 274 | item and term in item[u'container-title'].lower()): 275 | match = False 276 | break 277 | # Sometimes, the year of publication is stored as issued.raw, 278 | # instead of issued.year. Fix this, if this is the case. We need to 279 | # explictly remove the 'raw' entry as well. 280 | if u'issued' in item and u'year' not in item[u'issued']: 281 | if u'raw' in item[u'issued']: 282 | item[u'issued'][u'year'] = self.getYear( 283 | item[u'issued'][u'raw'] 284 | ) 285 | elif u'date-parts' in item[u'issued']: 286 | item[u'issued'][u'year'] = self.getYear( 287 | item[u'issued'][u'date-parts'][0][0] 288 | ) 289 | else: 290 | item[u'issued'][u'year'] = u'date unknown' 291 | # Make sure that both a lowercase (doi) and uppercase (DOI) key is 292 | # present, remove prefixed 'doi:' strings, and lowercase the url. 293 | if self.fixDOI: 294 | if u'DOI' in item: 295 | doi = item[u'DOI'][:] 296 | elif u'doi' in item: 297 | doi = item[u'doi'][:] 298 | else: 299 | doi = None 300 | self.msg('Missing DOI: %s' % item[u'title']) 301 | if doi is not None: 302 | if doi.startswith(u'doi:'): 303 | doi = doi[4:] 304 | doi = doi.lower() 305 | item[u'doi'] = item[u'DOI'] = doi 306 | # Remove URL field 307 | if self.removeURL: 308 | if u'URL' in item.keys() and (u'container-title' in \ 309 | item.keys() or u'publisher' in item.keys()): 310 | del item[u'URL'] 311 | # Convert initials to 'A.B.C.' style to avoid mixups. 312 | if self.fixAuthorNames and u'author' in item: 313 | _author = [] 314 | for author in item[u'author']: 315 | if u'given' not in author or u'family' not in author: 316 | continue 317 | given = author[u'given'] 318 | family = author[u'family'] 319 | # First replace dots by spaces 320 | given = given.replace(u'.', u' ') 321 | # Concatenate a list of capitalized initials 322 | given = u''.join([i[0].upper() for i in given.split()]) 323 | # Add dots after each initial 324 | given = u'. '.join(given) + u'.' 325 | _author.append({u'family' : family, u'given': given}) 326 | item[u'author'] = _author 327 | # Remove empty fields 328 | for field in item: 329 | if isinstance(item[field], str) and \ 330 | item[field].strip() == u'': 331 | self.msg(u'Removing empty field: %s' % field) 332 | del item[field] 333 | if match: 334 | matches.append(item) 335 | return matches 336 | 337 | def splitCitation(self, s): 338 | 339 | """ 340 | Splits a citation string, like Land1999WhyAnimals, and returns each 341 | element of the string in a list. 342 | 343 | Arguments: 344 | s -- The citation string, e.g. 'Land1999WhyAnimals'. 345 | 346 | Returns: 347 | A list of citation elements, e.g. ['land', '1999', 'why', 'animals']. 348 | """ 349 | 350 | # First, split underscore-style citations, like '@land_1999_why_animals' 351 | if u'_' in s: 352 | return s.split(u'_') 353 | regexp = r'([A-Z][^ 0-9A-Z?!,.\t\n\r\f\v\]\[;]*)' 354 | # Otherwise, split camelcase-style citations, like @Land1999WhyAnimals. 355 | l = [] 356 | for t in re.split(regexp, s): 357 | if t != u'': 358 | l.append(t.lower().replace(u'+', u' ')) 359 | return l 360 | -------------------------------------------------------------------------------- /academicmarkdown/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """ 4 | This file is part of zoteromarkdown. 5 | 6 | zoteromarkdown is free software: you can redistribute it and/or modify 7 | it under the terms of the GNU General Public License as published by 8 | the Free Software Foundation, either version 3 of the License, or 9 | (at your option) any later version. 10 | 11 | zoteromarkdown is distributed in the hope that it will be useful, 12 | but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | GNU General Public License for more details. 15 | 16 | You should have received a copy of the GNU General Public License 17 | along with zoteromarkdown. If not, see . 18 | 19 | --- 20 | desc: | 21 | *Who knew writing could be so nerdy?* 22 | 23 | version %-- python: "from academicmarkdown import version; print(version)" --% 24 | 25 | Copyright 2013-2015 Sebastiaan Mathôt 26 | 27 | [![Build Status](https://travis-ci.org/smathot/academicmarkdown.svg?branch=master)](https://travis-ci.org/smathot/academicmarkdown) 28 | 29 | ## Contents 30 | 31 | %-- 32 | toc: 33 | mindepth: 1 34 | maxdepth: 3 35 | exclude: [Contents] 36 | --% 37 | 38 | ## About 39 | 40 | Academic Markdown is a Python module for generating `.md`, `.html`, `.pdf`, 41 | `.docx`, and `.odt` files from Markdown source. [Pandoc] is used for most of 42 | the heavy lifting, so refer to the Pandoc website for detailed information 43 | about writing in Pandoc Markdown. However, Academic Markdown offers some 44 | additional functionality that is useful for writing scientific documents, 45 | such as integration with [Zotero references], and a number of useful 46 | [Academic Markdown extensions]. 47 | 48 | At present, the main target for Academic Markdown is the OpenSesame 49 | documentation site, , although it may in time grow 50 | into a more comprehensive and user-friendly tool. 51 | 52 | ## Examples 53 | 54 | A basic example can be found in the `example` sub folder, included with the source. 55 | 56 | The following manuscripts have been written in Academic Markdown: 57 | 58 | - Mathôt S, Dalmaijer ES, Grainger J, Van der Stigchel S. (2014) The pupillary light response reflects exogenous attention and inhibition of return. *PeerJ PrePrints* 2:e422v1 ([source](https://github.com/smathot/materials_for_P0009.1/tree/master/manuscript)) 59 | - Mathôt S, van der Linden L, Grainger J, Vitu F. (2014) The pupillary light response reflects eye-movement preparation. *PeerJ PrePrints* 2:e238v2 ([source](https://github.com/smathot/materials_for_P0001/tree/master/manuscript)) 60 | 61 | ## Download 62 | 63 | You can download the latest release of Academic Markdown here: 64 | 65 | - 66 | 67 | Ubuntu users can install Academic Markdown from the Cogsci.nl PPA: 68 | 69 | sudo add-apt-repository ppa:smathot/cogscinl 70 | sudo apt-get update 71 | sudo apt-get install python-academicmarkdown 72 | 73 | ## Basic usage 74 | 75 | Academic Markdown assumes that input files are encoded with `utf-8` encoding. 76 | 77 | ~~~ {.python} 78 | from academicmarkdown import build 79 | build.HTML(u'input.md', u'output.html') 80 | build.HTML(u'input.md', u'output.html', standalone=False) 81 | build.PDF(u'input.md', u'output.pdf') 82 | build.DOCX(u'input.md', u'output.docx') 83 | build.ODT(u'input.md', u'output.odt') 84 | ~~~ 85 | 86 | A number of options can be specified by setting attributes of the `build` module, like so 87 | 88 | ~~~ {.python} 89 | build.spacing = 30, 0 90 | ~~~ 91 | 92 | The full list of options is available in `academicmarkdown/constants.py`, or see [academicmarkdown.constants]. 93 | 94 | ## Dependencies 95 | 96 | Academic Markdown has been tested exclusively on Ubuntu Linux. The following dependencies are required: 97 | 98 | - [pandoc] is used for most of the heavy lifting. At the time of writing, the Ubuntu repositories do not contain a sufficiently recent version of Pandoc. Therefore, if you encounter trouble, try installing the latest version of Pandoc manually. 99 | - [pyzotero] is necessary for extracting Zotero references. 100 | - [wkhtmltopdf] is necessary for converting to `.pdf`. For best results, use the latest statically linked release, instead of the version from the Ubuntu repositories. 101 | 102 | ## Zotero references 103 | 104 | ### Pandoc citation style 105 | 106 | Since the basic Markdown conversion is performed by Pandoc, citations should be formatted as described on the Pandoc site: 107 | 108 | - 109 | 110 | ### Zotero API key and library ID 111 | 112 | You can automatically extract references from your Zotero library by setting the `zoteroApiKey` and `zoteroLibraryId` properties. Your references are not extracted from your local Zotero database, but through the web API of . This means that you need to have a Zotero account and synchronize your local database with your account, in order to use this feature. You can find your your API key and library ID online on your Zotero profile page. 113 | 114 | ~~~ {.python} 115 | from academicmarkdown import build 116 | build.zoteroApiKey = u'myapikey' 117 | build.zoteroLibraryId = u'mylibraryid' 118 | build.PDF(u'input.md', u'output.pdf') 119 | ~~~ 120 | 121 | ### Citation identifiers 122 | 123 | Citations are split into separate terms using camelcase or undescore logic. An example of an underscore-style citation is `@bárány_halldén_1948`. And example of a camelcase-style citation is `@Land1999WhyAnimals`. Each citation is interpreted as a series of author names, followed by the year of publication, optionally followed by terms that match either the publication title, or the article title. So the following reference ... 124 | 125 | Land, M., Mennie, N., & Rusted, J. (1999). The roles of vision and eye movements in the control of activities of daily living. *Perception*, *28*(11), 1311–1328. 126 | 127 | ... matches any of the following terms: 128 | 129 | - `@Land1999` 130 | - `@Land1999Roles` 131 | - `@LandMennie1999` 132 | - `@LandRusted1999Percept` 133 | - `@land_rusted_1999_percept` 134 | - etc. 135 | 136 | If a name contains spaces, you can indicate this using a `+` character. So the following reference ... 137 | 138 | Van Zoest, W., & Donk, M. (2005). The effects of salience on saccadic target selection. *Visual Cognition*, *12*(2), 353–375. 139 | 140 | ... matches any of the following terms: 141 | 142 | - `@Van+zoestDonk2005` 143 | - `@van+zoest_donk_2005` 144 | 145 | Note that you must consistently use the same citation to refer to a single reference in one document. If a citation matched multiple references from your Zotero database, one citation will be chosen at random. 146 | 147 | ### Sorting citations 148 | 149 | Pandoc does not allow you to sort your references, which can be annoying. To get around this, Academic Markdown allows you to explicitly sort your citations by linking chains of citations with a `+` character: 150 | 151 | [@Zzz2014]+[@Aaa2014] 152 | 153 | ### Clearing cache 154 | 155 | Previous references will be cached automatically. To refresh, remove the file `.zoteromarkdown.cache` or run your Python script with the command-line argument: `--clear-cache`. 156 | 157 | ## Academic Markdown extensions 158 | 159 | Academic Markdown provides certain extensions to regular Markdown, in the form of YAML blocks embedded in `%-- --%` tags. You can which, and the order in which, extensions are called by settings the `extensions` list: 160 | 161 | ~~~ {.python} 162 | from academicmarkdown import build 163 | # First call the include extension, second call the figure extension 164 | build.extensions = [u'include', u'figure'] 165 | ~~~ 166 | 167 | ### `code`: code listings 168 | 169 | %-- 170 | python: | 171 | import inspect 172 | from academicmarkdown import CodeParser 173 | print inspect.getdoc(CodeParser) 174 | --% 175 | 176 | ### `constant`: define constants 177 | 178 | %-- 179 | python: | 180 | import inspect 181 | from academicmarkdown import ConstantParser 182 | print inspect.getdoc(ConstantParser) 183 | --% 184 | 185 | ### `exec`: external commands 186 | 187 | %-- 188 | python: | 189 | import inspect 190 | from academicmarkdown import ExecParser 191 | print inspect.getdoc(ExecParser) 192 | --% 193 | 194 | ### `figure`: figures 195 | 196 | %-- 197 | python: | 198 | import inspect 199 | from academicmarkdown import FigureParser 200 | print inspect.getdoc(FigureParser) 201 | --% 202 | 203 | ### `include`: include other Markdown files 204 | 205 | %-- 206 | python: | 207 | import inspect 208 | from academicmarkdown import IncludeParser 209 | print inspect.getdoc(IncludeParser) 210 | --% 211 | 212 | ### `python`: python code 213 | 214 | %-- 215 | python: | 216 | import inspect 217 | from academicmarkdown import PythonParser 218 | print inspect.getdoc(PythonParser) 219 | --% 220 | 221 | ### `table`: table 222 | 223 | %-- 224 | python: | 225 | import inspect 226 | from academicmarkdown import TableParser 227 | print inspect.getdoc(TableParser) 228 | --% 229 | 230 | ### `toc`: table of contents 231 | 232 | %-- 233 | python: | 234 | import inspect 235 | from academicmarkdown import TOCParser 236 | print inspect.getdoc(TOCParser) 237 | --% 238 | 239 | ### `wc`: word count 240 | 241 | %-- 242 | python: | 243 | import inspect 244 | from academicmarkdown import WcParser 245 | print inspect.getdoc(WcParser) 246 | --% 247 | 248 | ### Magic variables 249 | 250 | Magic variables are automatically replaced by certain values, and are indicated like this: `%varname%`. The following magic variables are available: 251 | 252 | - `%wc%`: Word count 253 | - `%cc%`: Character count 254 | - `%rc%`: Reference count 255 | 256 | ## License 257 | 258 | Academic Markdown is available under the GNU General Public License 3. For more information, see the included file `COPYING`. 259 | 260 | [pandoc]: http://johnmacfarlane.net/pandoc/ 261 | [pyzotero]: http://pyzotero.readthedocs.org/ 262 | [zotero]: http://www.zotero.org/ 263 | [wkhtmltopdf]: https://code.google.com/p/wkhtmltopdf/ 264 | --- 265 | """ 266 | 267 | version = u'0.9.1' 268 | 269 | from academicmarkdown._BaseParser import BaseParser 270 | from academicmarkdown._YAMLParser import YAMLParser 271 | from academicmarkdown._ZoteroParser import ZoteroParser 272 | from academicmarkdown._FigureParser import FigureParser 273 | from academicmarkdown._CodeParser import CodeParser 274 | from academicmarkdown._ConstantParser import ConstantParser 275 | from academicmarkdown._ExecParser import ExecParser 276 | from academicmarkdown._PythonParser import PythonParser 277 | from academicmarkdown._IncludeParser import IncludeParser 278 | from academicmarkdown._TOCParser import TOCParser 279 | from academicmarkdown._VideoParser import VideoParser 280 | from academicmarkdown._TableParser import TableParser 281 | from academicmarkdown._Pandoc import Pandoc 282 | from academicmarkdown._ODTFixer import ODTFixer 283 | from academicmarkdown._WkHtmlToPdf import WkHtmlToPdf 284 | from academicmarkdown._WcParser import WcParser 285 | from academicmarkdown._GitHubParser import GitHubParser 286 | from academicmarkdown import build, constants 287 | -------------------------------------------------------------------------------- /academicmarkdown/build.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """ 4 | This file is part of zoteromarkdown. 5 | 6 | zoteromarkdown is free software: you can redistribute it and/or modify 7 | it under the terms of the GNU General Public License as published by 8 | the Free Software Foundation, either version 3 of the License, or 9 | (at your option) any later version. 10 | 11 | zoteromarkdown is distributed in the hope that it will be useful, 12 | but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | GNU General Public License for more details. 15 | 16 | You should have received a copy of the GNU General Public License 17 | along with zoteromarkdown. If not, see . 18 | 19 | --- 20 | desc: 21 | Contains functions to build documents from Markdown source. 22 | --- 23 | """ 24 | 25 | import os 26 | import sys 27 | import subprocess 28 | from academicmarkdown import FigureParser, Pandoc, ZoteroParser, ODTFixer, \ 29 | ExecParser, IncludeParser, TOCParser, HTMLFilter, MDFilter, WkHtmlToPdf, \ 30 | CodeParser, WcParser, VideoParser, TableParser, PythonParser, \ 31 | tools, ConstantParser, GitHubParser 32 | from academicmarkdown.constants import * 33 | from academicmarkdown.py3compat import * 34 | 35 | def HTML(src, target=None, standalone=True): 36 | 37 | """ 38 | desc: | 39 | Builds an HTML file from a Markdown source. 40 | 41 | %-- 42 | constant: 43 | arg_src: 44 | The Markdown source. If it is a path to an existing file, the 45 | contents of this file are read. Otherwise, the string itself 46 | it used. Should be in utf-8 encoding. 47 | --% 48 | 49 | arguments: 50 | src: 51 | desc: "%arg_src" 52 | type: [str, unicode] 53 | 54 | keywords: 55 | target: 56 | desc: The name of an HTML target file or None to skip saving. 57 | type: [str, unicode, NoneType] 58 | standalone: 59 | desc: Indicates whether a full HTML5 document should be generated, 60 | which embeds all content, or whether the document should 61 | be rendered without `` and `` tags, etc. 62 | type: bool 63 | 64 | returns: 65 | desc: The HTML file as a unicode string. 66 | type: unicode 67 | """ 68 | 69 | md = MD(src) 70 | # Count words 71 | print(u'Document statistics:') 72 | print(u'Word count: %d' % len(md.split())) 73 | print(u'Character count: %d' % len(md)) 74 | # And finally convert the Markdown to HTML 75 | pd = Pandoc(css=css, csl=csl, template=html5Ref, standalone=standalone, \ 76 | verbose=True) 77 | html = pd.parse(md) 78 | for flt in htmlFilters: 79 | fltFunc = getattr(HTMLFilter, flt) 80 | html = fltFunc(html) 81 | if target != None: 82 | open(target, u'wb').write(safe_encode(html)) 83 | print(u'Done!') 84 | return html 85 | 86 | def MD(src, target=None): 87 | 88 | """ 89 | desc: 90 | Builds a Markdown file from a Markdown source. 91 | 92 | arguments: 93 | src: 94 | desc: "%arg_src" 95 | type: [str, unicode] 96 | 97 | keywords: 98 | target: 99 | desc: The name of a Markdown target file or None to skip saving. 100 | type: [str, unicode, NoneType] 101 | 102 | returns: 103 | desc: The compiled Markdown file as a unicode string. 104 | type: unicode 105 | """ 106 | 107 | if os.path.exists(src): 108 | md = safe_decode(open(src).read()) 109 | print(u'Building %s from %s ...' % (target, src)) 110 | else: 111 | md = src 112 | print(u'Building from string ...') 113 | # Apply pre-processing Markdown Filters 114 | for flt in preMarkdownFilters: 115 | fltFunc = getattr(MDFilter, flt) 116 | md = fltFunc(md) 117 | # Apply all extensions 118 | for ext in extensions: 119 | print(u'Parsing with %s extension ...' % ext) 120 | if u'include' == ext: 121 | md = IncludeParser(verbose=True).parse(md) 122 | elif u'toc' == ext: 123 | md = TOCParser(anchorHeaders=TOCAnchorHeaders, appendHeaderRefs= \ 124 | TOCAppendHeaderRefs, verbose=True).parse(md) 125 | elif u'figure' == ext: 126 | md = FigureParser(verbose=True, style=figureStyle, template= \ 127 | figureTemplate, margins=pdfMargins).parse(md) 128 | elif u'video' == ext: 129 | md = VideoParser(verbose=True).parse(md) 130 | elif u'table' == ext: 131 | md = TableParser(style=tableStyle, template=tableTemplate, verbose= \ 132 | True).parse(md) 133 | elif u'code' == ext: 134 | md = CodeParser(verbose=True, style=codeStyle, template=codeTemplate) \ 135 | .parse(md) 136 | elif u'wc' == ext: 137 | md = WcParser(verbose=True).parse(md) 138 | elif u'exec' == ext: 139 | md = ExecParser(verbose=True).parse(md) 140 | elif u'python' == ext: 141 | md = PythonParser(verbose=True).parse(md) 142 | elif u'constant' == ext: 143 | md = ConstantParser(verbose=True).parse(md) 144 | elif u'github' == ext: 145 | md = GitHubParser(verbose=True).parse(md) 146 | else: 147 | raise Exception(u'Unknown Academic Markdown extension: %s' % ext) 148 | # Parse Zotero references 149 | if zoteroApiKey != None and zoteroLibraryId != None: 150 | clearCache = '--clear-cache' in sys.argv 151 | md = ZoteroParser(verbose=True, apiKey=zoteroApiKey, libraryId= \ 152 | zoteroLibraryId, headerText=zoteroHeaderText, headerLevel= \ 153 | zoteroHeaderLevel, clearCache=clearCache).parse(md) 154 | # Apply post-processing Markdown Filters 155 | for flt in postMarkdownFilters: 156 | fltFunc = getattr(MDFilter, flt) 157 | md = fltFunc(md) 158 | if target != None: 159 | open(target, u'wb').write(safe_encode(md)) 160 | return md 161 | 162 | def PDF(src, target, lineNumbers=False, args=''): 163 | 164 | """ 165 | desc: 166 | Builds a PDF file from a Markdown source. 167 | 168 | arguments: 169 | src: 170 | desc: "%arg_src" 171 | type: [str, unicode] 172 | target: 173 | desc: The name of a PDF target file. 174 | type: [str, unicode] 175 | 176 | keywords: 177 | lineNumbers: 178 | desc: Determines whether line numbers should be added. This is 179 | currently quite a complicated process, which may break. 180 | type: bool 181 | args: 182 | desc: Indicates extra arguments to be passed onto wkhtmltopdf. 183 | type: [str, unicode] 184 | """ 185 | 186 | print(u'Building %s from %s ...' % (target, src)) 187 | HTML(src, u'.tmp.html') 188 | wk = WkHtmlToPdf(css=css, margins=pdfMargins, spacing=pdfSpacing, \ 189 | header=pdfHeader, footer=pdfFooter, verbose=True, args=args) 190 | if lineNumbers: 191 | _target = u'.tmp.pdf' 192 | else: 193 | _target = target 194 | wk.parse(u'.tmp.html', _target) 195 | if lineNumbers: 196 | tools.addLineNumbersToPDF(_target, target) 197 | os.remove(_target) 198 | 199 | def ODT(src, target): 200 | 201 | """ 202 | desc: 203 | Builds an ODT file from a Markdown source. 204 | 205 | arguments: 206 | src: 207 | desc: "%arg_src" 208 | type: [str, unicode] 209 | target: 210 | desc: The name of an ODT target file. 211 | type: [str, unicode] 212 | """ 213 | 214 | global figureTemplate 215 | tmp = figureTemplate 216 | figureTemplate = u'odt' 217 | md = MD(src) 218 | pd = Pandoc(csl=csl, verbose=True) 219 | pd.odt(md, target, odtRef=odtRef) 220 | ODTFixer(verbose=True).fix(target) 221 | figureTemplate = tmp 222 | 223 | def DOC(src, target): 224 | 225 | """ 226 | desc: 227 | Builds a DOC file from a Markdown source. 228 | 229 | arguments: 230 | src: 231 | desc: "%arg_src" 232 | type: [str, unicode] 233 | target: 234 | desc: The name of a DOC target file. 235 | type: [str, unicode] 236 | """ 237 | 238 | # Since pandoc doesn't support DOC output, we convert first to ODT and from 239 | # there use unoconv to convert to DOC. 240 | ODT(src, u'.tmp.odt') 241 | print(u'Converting from .odt to .doc ...') 242 | cmd = [u'unoconv', u'-f', u'doc', u'.tmp.odt'] 243 | subprocess.call(cmd) 244 | print(u'Done!') 245 | os.rename(u'.tmp.doc', target) 246 | 247 | def DOCX(src, target): 248 | 249 | """ 250 | desc: 251 | Builds a DOCX file from a Markdown source. 252 | 253 | arguments: 254 | src: 255 | desc: "%arg_src" 256 | type: [str, unicode] 257 | target: 258 | desc: The name of a DOCX target file. 259 | type: [str, unicode] 260 | """ 261 | 262 | global figureTemplate 263 | tmp = figureTemplate 264 | figureTemplate = u'markdown' 265 | md = MD(src) 266 | pd = Pandoc(csl=csl, verbose=True) 267 | pd.docx(md, target, docxRef=docxRef) 268 | figureTemplate = tmp 269 | 270 | def setStyle(style): 271 | 272 | """ 273 | desc: 274 | Automatically sets a style. 275 | 276 | arguments: 277 | style: 278 | desc: The style name. This should be the name of a folder that 279 | contains style files. See the `academicmarkdown\styles` 280 | subfolder for examples. 281 | type: [str, unicode] 282 | """ 283 | 284 | global css, csl, html5Ref, odtRef, docxRef 285 | moduleFolder = safe_decode(os.path.dirname(__file__), 286 | enc=sys.getfilesystemencoding()) 287 | if os.path.exists(style): 288 | stylePath = style 289 | elif os.path.exists(os.path.join(moduleFolder, u'styles', style)): 290 | stylePath = os.path.join(moduleFolder, u'styles', style) 291 | else: 292 | raise Exception(u'There is no style folder named "%s"' % style) 293 | print(u'Using style folder: %s' % stylePath) 294 | css = os.path.join(stylePath, u'stylesheet.css') 295 | if not os.path.exists(css): 296 | css = None 297 | csl = os.path.join(stylePath, u'citation-style.csl') 298 | if not os.path.exists(csl): 299 | csl = None 300 | html5Ref = os.path.join(stylePath, u'template.html') 301 | if not os.path.exists(html5Ref): 302 | html5Ref = None 303 | odtRef = os.path.join(stylePath, u'reference.odt') 304 | if not os.path.exists(odtRef): 305 | odtRef = None 306 | docxRef = os.path.join(stylePath, u'reference.docx') 307 | if not os.path.exists(docxRef): 308 | docxRef = None 309 | -------------------------------------------------------------------------------- /academicmarkdown/constants.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | This file is part of zoteromarkdown. 4 | 5 | zoteromarkdown is free software: you can redistribute it and/or modify 6 | it under the terms of the GNU General Public License as published by 7 | the Free Software Foundation, either version 3 of the License, or 8 | (at your option) any later version. 9 | 10 | zoteromarkdown is distributed in the hope that it will be useful, 11 | but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | GNU General Public License for more details. 14 | 15 | You should have received a copy of the GNU General Public License 16 | along with zoteromarkdown. If not, see . 17 | 18 | --- 19 | desc: | 20 | Contains the settings, which are imported into `academicmarkdown.build`. You 21 | can change these settings in the `build` module, as shown below. 22 | 23 | __Module source:__ 24 | 25 | %-- 26 | code: 27 | id: LstConstants 28 | syntax: python 29 | source: academicmarkdown/constants.py 30 | --% 31 | 32 | example: | 33 | from academicmarkdown import build 34 | build.pdfHeader = u'A header for my PDF' 35 | --- 36 | """ 37 | 38 | from academicmarkdown.py3compat import * 39 | import os, sys 40 | 41 | # A list of folders that are searched for figures, scripts, etc. 42 | path = [safe_decode(os.getcwd(), enc=sys.getfilesystemencoding())] 43 | 44 | # Parameters for Zotero integration 45 | zoteroApiKey = None 46 | zoteroLibraryId = None 47 | zoteroHeaderText = u'References' 48 | zoteroHeaderLevel = 1 49 | 50 | # Options for the appearance of figures, blocks, and tables 51 | figureTemplate = u'html5' 52 | figureStyle = u'inline' 53 | codeTemplate = u'pandoc' 54 | codeStyle = u'inline' 55 | tableTemplate = u'html5' 56 | tableStyle = u'inline' 57 | 58 | # Indicates whether headers should be turned into clickable anchors by TOCParser 59 | TOCAnchorHeaders = False 60 | # Indicates whether references to header ids should be automatically appended 61 | # to the main text. 62 | TOCAppendHeaderRefs = True 63 | 64 | # Paths to files that determine the document's appearance. For more information, 65 | # see the Pandoc documentation. 66 | css = None # CSS stylesheet 67 | csl = None # CSL citation style 68 | html5Ref = None # HTML5 template 69 | odtRef = None # ODT reference document 70 | docxRef = None # DOCX reference document 71 | 72 | # A list of filters from academicmarkdown.HTMLFilter that should be performed 73 | # after an HTML document has been genertated. 74 | htmlFilters = [u'DOI', u'citationGlue'] 75 | 76 | # A list of filters from academicmarkdown.MDFilter that should be performed 77 | # on the Markdown source, prior to any processing. 78 | preMarkdownFilters = [] 79 | # A list of filters from academicmarkdown.MDFilter that should be performed 80 | # on the Markdown source, after all other processing has been performed 81 | postMarkdownFilters = [u'autoItalics', u'pageBreak', u'magicVars', u'highlight', 82 | u'arrows'] 83 | 84 | # A list of extensions that are enabled. 85 | extensions = [u'include', u'exec', u'python', u'toc', u'code', u'video', \ 86 | u'table', u'figure', u'constant', u'wc', u'github'] 87 | 88 | # The page margins 89 | pdfMargins = 30, 20, 30, 20 90 | 91 | # The spacing between the content and the header and footer 92 | pdfSpacing = 10, 10 93 | 94 | # Header and footer text 95 | pdfHeader = u'%section%' 96 | pdfFooter = u'%page% of %topage%' 97 | -------------------------------------------------------------------------------- /academicmarkdown/git.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | """ 5 | This file is part of academicmarkdown. 6 | 7 | academicmarkdown is free software: you can redistribute it and/or modify 8 | it under the terms of the GNU General Public License as published by 9 | the Free Software Foundation, either version 3 of the License, or 10 | (at your option) any later version. 11 | 12 | academicmarkdown is distributed in the hope that it will be useful, 13 | but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | GNU General Public License for more details. 16 | 17 | You should have received a copy of the GNU General Public License 18 | along with academicmarkdown. If not, see . 19 | """ 20 | 21 | from academicmarkdown import build 22 | from subprocess import check_output, call 23 | import os 24 | import shlex 25 | 26 | exportFolder = u'export' 27 | exportFormats = u'odt', u'pdf', u'doc' 28 | 29 | def commitHash(): 30 | 31 | """ 32 | Gets the latest commit hash. 33 | 34 | Returns: 35 | A unicode string with the latest hash. 36 | """ 37 | 38 | cmd = [u'git', u'log', u'--pretty=format:#%h', u'-1'] 39 | return check_output(cmd) 40 | 41 | def snapshot(src, msg=u'snapshot', pdfArgs={}): 42 | 43 | """ 44 | Commits the current state of the repository and exports a snapshot of the 45 | current documents. 46 | 47 | Arguments: 48 | src -- The source Markdown document. 49 | 50 | Keyword arguments: 51 | msg -- A commit message. (default=u'snapshot') 52 | """ 53 | 54 | cmd = [u'git', u'commit', u'-am', msg] 55 | print(u'Committing (msg: %s)' % msg) 56 | call(cmd) 57 | cmd = [u'git', u'log', u'--pretty=format:[%cd #%h] %s', u'--date=iso', \ 58 | u'-1'] 59 | tag = check_output(cmd).decode() 60 | folder = os.path.join(exportFolder, tag) 61 | print(u'Exporting to %s' % folder) 62 | if os.path.exists(folder): 63 | raise Exception( \ 64 | u'Folder %s already exists! There is probably nothing new to export.' \ 65 | % folder) 66 | os.mkdir(folder) 67 | if u'pdf' in exportFormats: 68 | build.PDF(src, os.path.join(folder, u'export.pdf'), **pdfArgs) 69 | if u'doc' in exportFormats: 70 | build.DOC(src, os.path.join(folder, u'export.doc')) 71 | if u'docx' in exportFormats: 72 | build.DOCX(src, os.path.join(folder, u'export.docx')) 73 | if u'odt' in exportFormats: 74 | build.ODT(src, os.path.join(folder, u'export.odt')) 75 | if u'html' in exportFormats: 76 | build.HTML(src, os.path.join(folder, u'export.html')) 77 | -------------------------------------------------------------------------------- /academicmarkdown/py3compat.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """ 4 | This file is part of zoteromarkdown. 5 | 6 | zoteromarkdown is free software: you can redistribute it and/or modify 7 | it under the terms of the GNU General Public License as published by 8 | the Free Software Foundation, either version 3 of the License, or 9 | (at your option) any later version. 10 | 11 | zoteromarkdown is distributed in the hope that it will be useful, 12 | but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | GNU General Public License for more details. 15 | 16 | You should have received a copy of the GNU General Public License 17 | along with zoteromarkdown. If not, see . 18 | """ 19 | 20 | import sys 21 | 22 | if sys.version_info >= (3,0,0): 23 | py3 = True 24 | basestring = str 25 | else: 26 | bytes = str 27 | str = unicode 28 | py3 = False 29 | 30 | def safe_decode(s, enc='utf-8', errors='strict'): 31 | if isinstance(s, str): 32 | return s 33 | return s.decode(enc, errors) 34 | 35 | def safe_encode(s, enc='utf-8', errors='strict'): 36 | if isinstance(s, bytes): 37 | return s 38 | return s.encode(enc, errors) 39 | 40 | __all__ = ['py3', 'safe_decode', 'safe_encode', 'safe_str'] 41 | if not py3: 42 | safe_str = safe_encode 43 | __all__ += ['str', 'bytes'] 44 | else: 45 | safe_str = safe_decode 46 | __all__ += ['basestring'] 47 | -------------------------------------------------------------------------------- /academicmarkdown/styles/apa/citation-style.csl: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /academicmarkdown/styles/apa/reference.docx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smathot/academicmarkdown/696592d035e341e170b6c3e86a0855a8cc4d0f29/academicmarkdown/styles/apa/reference.docx -------------------------------------------------------------------------------- /academicmarkdown/styles/apa/reference.odt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smathot/academicmarkdown/696592d035e341e170b6c3e86a0855a8cc4d0f29/academicmarkdown/styles/apa/reference.odt -------------------------------------------------------------------------------- /academicmarkdown/styles/apa/stylesheet.css: -------------------------------------------------------------------------------- 1 | * { 2 | font-family: "Times New Roman"; 3 | font-size: 12pt; 4 | line-height: 200%; 5 | text-align: left; 6 | margin: 0px; 7 | } 8 | 9 | p { 10 | text-indent: 32px; 11 | text-align: justify; 12 | } 13 | 14 | a { 15 | text-decoration: none; 16 | } 17 | 18 | a, em, strong { 19 | font-size: 1em; 20 | font-family: inherit; 21 | } 22 | 23 | em, strong { 24 | color: inherit; 25 | } 26 | 27 | code { 28 | font-family: 'Courier New'; 29 | white-space: pre-wrap; 30 | } 31 | 32 | blockquote { 33 | margin: 16px 48px 16px 48px; 34 | font-style: italic; 35 | } 36 | 37 | blockquote p { 38 | text-indent: 0px!important; 39 | } 40 | 41 | sup { 42 | font-size: 0.7em; 43 | } 44 | 45 | h1 { 46 | text-align: center; 47 | font-size: 1.0em; 48 | font-weight: normal; 49 | page-break-after: avoid; 50 | } 51 | 52 | h1, h2, h3 { 53 | color: #204a87; 54 | margin: 15px 0px 5px 0px; 55 | } 56 | 57 | h2 { 58 | font-weight: normal; 59 | font-size: 1.0em; 60 | font-style: italic; 61 | } 62 | 63 | h3 { 64 | font-weight: normal; 65 | font-size: 1.0em; 66 | font-style: italic; 67 | text-indent: 32px; 68 | } 69 | 70 | #titlepage .title, 71 | #titlepage .correspondence, 72 | #titlepage .authornote, 73 | #titlepage .author, 74 | #titlepage .affiliation p { 75 | text-align: center!important; 76 | text-indent: 0px!important; 77 | } 78 | 79 | #titlepage .authornote { 80 | margin-top: 32px; 81 | } 82 | 83 | #titlepage .authornote p, 84 | #titlepage .correspondence p { 85 | text-indent: 0px!important; 86 | } 87 | 88 | #titlepage .runninghead { 89 | text-indent: 0px!important; 90 | margin-bottom: 32px; 91 | } 92 | 93 | #titlepage .affiliation, 94 | #titlepage .title { 95 | margin-bottom: 100px; 96 | } 97 | 98 | #titlepage .author { 99 | margin-bottom: 25px; 100 | } 101 | 102 | figcaption strong { 103 | color: #204a87; 104 | } 105 | 106 | figure { 107 | display: table; 108 | margin: 25px 0px 25px 0px; 109 | page-break-inside: avoid; 110 | caption-side: bottom; 111 | width: 100%; 112 | } 113 | 114 | figure img { 115 | } 116 | 117 | figure figcaption { 118 | display: table-caption; 119 | font-style: italic; 120 | font-size: 0.9em; 121 | width: 100%; 122 | } 123 | 124 | a, 125 | .citation { 126 | color: #204a87; 127 | } 128 | 129 | figure figcaption .citation { 130 | font-size: 0.9em; 131 | } 132 | 133 | *[id^='ref-'], 134 | .references p { 135 | text-align: left; 136 | text-indent: -25px; 137 | margin-left: 25px; 138 | } 139 | 140 | .footer, .header { 141 | text-align: right; 142 | font-size: 12pt; 143 | } 144 | 145 | .footer span, .header span { 146 | font-size: 1em; 147 | } 148 | 149 | .table { 150 | margin: 25px 0px 25px 0px; 151 | page-break-inside: avoid; 152 | } 153 | 154 | .table table th { 155 | border-top: solid 1px; 156 | border-bottom: solid 1px; 157 | font-style: normal; 158 | font-weight: bold; 159 | font-size: 1em; 160 | } 161 | 162 | .table table { 163 | width: 100%; 164 | border-collapse: collapse; 165 | padding: 0px; 166 | } 167 | 168 | .table p { 169 | text-indent: 0px; 170 | } 171 | 172 | .table .table-id { 173 | color: #204a87; 174 | font-size: 0.9em; 175 | } 176 | 177 | .table table td { 178 | font-size: 0.9em; 179 | } 180 | 181 | .table .caption { 182 | font-style: italic; 183 | font-size: 0.9em; 184 | } 185 | 186 | .highlight { 187 | background-color: #ffffaf; 188 | } 189 | -------------------------------------------------------------------------------- /academicmarkdown/styles/apa/template.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | $for(author-meta)$ 7 | 8 | $endfor$ 9 | $if(date-meta)$ 10 | 11 | $endif$ 12 | $if(title-prefix)$$title-prefix$ - $endif$$pagetitle$ 13 | 14 | $if(quotes)$ 15 | 16 | $endif$ 17 | $if(highlighting-css)$ 18 | 21 | $endif$ 22 | $for(css)$ 23 | 24 | $endfor$ 25 | $if(math)$ 26 | $math$ 27 | $endif$ 28 | $for(header-includes)$ 29 | $header-includes$ 30 | $endfor$ 31 | 32 | 33 | 34 | 35 | $if(title)$ 36 |
37 | 38 | $if(runninghead)$ 39 |

Running head: $runninghead$

40 | $endif$ 41 | 42 |

$title$

43 | 44 | $for(author)$ 45 |

$author$

46 | $endfor$ 47 | 48 | $if(affiliation)$ 49 |
50 | $for(affiliation)$ 51 |

$affiliation$

52 | $endfor$ 53 |
54 | $endif$ 55 | 56 | $if(correspondence)$ 57 |
58 |

Address for correspondence:

59 | $for(correspondence)$ 60 |

$correspondence$

61 | $endfor$ 62 |
63 | $endif$ 64 | 65 | $if(authornote)$ 66 |
67 |

Author note:

68 |

$authornote$

69 |
70 | $endif$ 71 | 72 |
73 | $endif$ 74 | 75 |
76 | 77 | $body$ 78 | $for(include-after)$ 79 | $include-after$ 80 | $endfor$ 81 | 82 | 83 | -------------------------------------------------------------------------------- /academicmarkdown/styles/modern/citation-style.csl: -------------------------------------------------------------------------------- 1 | 2 | 444 | -------------------------------------------------------------------------------- /academicmarkdown/styles/modern/reference.docx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smathot/academicmarkdown/696592d035e341e170b6c3e86a0855a8cc4d0f29/academicmarkdown/styles/modern/reference.docx -------------------------------------------------------------------------------- /academicmarkdown/styles/modern/reference.odt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smathot/academicmarkdown/696592d035e341e170b6c3e86a0855a8cc4d0f29/academicmarkdown/styles/modern/reference.odt -------------------------------------------------------------------------------- /academicmarkdown/styles/modern/stylesheet.css: -------------------------------------------------------------------------------- 1 | * { 2 | font-family: "Roboto"; 3 | font-size: 12pt; 4 | line-height: 175%; 5 | text-align: justify; 6 | } 7 | 8 | .highlight {background-color: #ffffaf;} 9 | 10 | p { 11 | } 12 | 13 | a { 14 | color: #204a87; 15 | text-decoration: none; 16 | } 17 | 18 | a, em, strong { 19 | font-size: 1em; 20 | font-family: inherit; 21 | } 22 | 23 | em, strong { 24 | color: inherit; 25 | } 26 | 27 | code { 28 | font-family: 'Courier New'; 29 | } 30 | 31 | sup { 32 | font-size: 0.7em; 33 | } 34 | 35 | h1 { 36 | text-align: left; 37 | font-size: 1.2em; 38 | font-weight: bold; 39 | color: #204a87; 40 | } 41 | 42 | h2 { 43 | font-size: 1.1em; 44 | font-weight: bold; 45 | font-style: normal; 46 | } 47 | 48 | h3 { 49 | font-size: 1.1em; 50 | font-weight: normal; 51 | font-style: italic; 52 | } 53 | 54 | h4 { 55 | font-weight: normal; 56 | font-style: italic; 57 | } 58 | 59 | h1, h2, h3, h4 { 60 | page-break-after: avoid; 61 | } 62 | 63 | 64 | #titlepage { 65 | margin-bottom: 50px; 66 | } 67 | 68 | .correspondence { 69 | margin-bottom: 50px; 70 | } 71 | 72 | figcaption strong { 73 | color: #204a87; 74 | } 75 | 76 | figure { 77 | display: table; 78 | margin-bottom: 25px; 79 | margin-left: 0px; 80 | page-break-inside: avoid; 81 | caption-side: bottom; 82 | width: 100%; 83 | } 84 | 85 | figure img { 86 | } 87 | 88 | figure figcaption { 89 | display: table-caption; 90 | font-style: italic; 91 | font-size: 0.9em; 92 | width: 100%; 93 | } 94 | 95 | figure figcaption .citation { 96 | font-size: 1em; 97 | } 98 | 99 | .citation { 100 | color: #204a87; 101 | } 102 | 103 | .references p { 104 | text-align: left; 105 | text-indent: -25px; 106 | margin-left: 25px; 107 | font-size: 0.9em; 108 | } 109 | 110 | .footer, .header { 111 | text-align: right; 112 | font-size: 0.8em; 113 | } 114 | 115 | .footer span, .header span { 116 | font-size: 1em; 117 | } 118 | 119 | .table { 120 | margin: 25px 0px 25px 0px; 121 | page-break-inside: avoid; 122 | } 123 | 124 | .table table th { 125 | border-top: solid 1px; 126 | border-bottom: solid 1px; 127 | font-style: normal; 128 | font-weight: bold;title 129 | 130 | .table table { 131 | width: 100%; 132 | border-collapse: collapse; 133 | padding: 0px; 134 | } 135 | 136 | .table p { 137 | text-indent: 0px; 138 | } 139 | 140 | .table .table-id { 141 | color: #204a87; 142 | font-size: 0.9em; 143 | } 144 | 145 | .table table td { 146 | font-size: 0.9em; 147 | } 148 | 149 | .table .caption { 150 | font-style: italic; 151 | font-size: 0.9em; 152 | } 153 | -------------------------------------------------------------------------------- /academicmarkdown/styles/modern/template.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | $for(author-meta)$ 7 | 8 | $endfor$ 9 | $if(date-meta)$ 10 | 11 | $endif$ 12 | $if(title-prefix)$$title-prefix$ - $endif$$pagetitle$ 13 | 14 | $if(quotes)$ 15 | 16 | $endif$ 17 | $if(highlighting-css)$ 18 | 21 | $endif$ 22 | $for(css)$ 23 | 24 | $endfor$ 25 | $if(math)$ 26 | $math$ 27 | $endif$ 28 | $for(header-includes)$ 29 | $header-includes$ 30 | $endfor$ 31 | 32 | 33 | 34 | 35 | $if(title)$ 36 |
37 | 38 | $if(correspondence)$ 39 |

$correspondence$

40 | $endif$ 41 | 42 |

$title$

43 | 44 | $for(author)$ 45 |

$author$

46 | $endfor$ 47 | 48 | $for(affiliation)$ 49 |

$affiliation$

50 | $endfor$ 51 | 52 |
53 | $endif$ 54 | 55 | 56 | $body$ 57 | $for(include-after)$ 58 | $include-after$ 59 | $endfor$ 60 | 61 | 62 | -------------------------------------------------------------------------------- /academicmarkdown/tools.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """ 4 | This file is part of zoteromarkdown. 5 | 6 | zoteromarkdown is free software: you can redistribute it and/or modify 7 | it under the terms of the GNU General Public License as published by 8 | the Free Software Foundation, either version 3 of the License, or 9 | (at your option) any later version. 10 | 11 | zoteromarkdown is distributed in the hope that it will be useful, 12 | but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | GNU General Public License for more details. 15 | 16 | You should have received a copy of the GNU General Public License 17 | along with zoteromarkdown. If not, see . 18 | """ 19 | 20 | import re 21 | import os 22 | from academicmarkdown.py3compat import * 23 | 24 | def wordCount(s, excludeYAML=True, clean=True): 25 | 26 | """ 27 | Returns the word count of a file or a string of text. 28 | 29 | Arguments: 30 | s -- A filename or a string of text. This paramater can also 31 | be a list of filenames or a list of strings of text, in 32 | which case the summed word count will be returned. 33 | 34 | Keyword arguments: 35 | excludeYAML -- Indicates whether the contents of %-- --% YAML blocks 36 | should be excluded from the word count. (default=True) 37 | clean -- Indicates whether the text should be cleaned of things 38 | that you probably don't want to count, such as `##` 39 | characters. (default=True) 40 | 41 | Returns: 42 | A word count. 43 | """ 44 | 45 | if isinstance(s, list): 46 | wc = 0 47 | for _s in s: 48 | wc += wordCount(_s, excludeYAML=excludeYAML, clean=clean) 49 | return wc 50 | if os.path.exists(s): 51 | s = safe_decode(open(s).read()) 52 | if excludeYAML: 53 | s = re.sub(u'%--(.*?)--%', lambda x: u'', s, flags=re.M|re.S) 54 | if clean: 55 | s = re.sub(u'^#+\s', lambda x: u'', s, flags=re.M) 56 | l = [] 57 | for w in s.split(): 58 | if clean: 59 | w = re.sub(r'[^a-zA-Z0-9]', u'', w) 60 | if len(w) > 0: 61 | l.append(w) 62 | return len(l) 63 | 64 | def addLineNumbersToPDF(inFile, outFile, color='#d3d7cf'): 65 | 66 | """ 67 | desc: 68 | Adds line numbers to a PDF file. 69 | 70 | arguments: 71 | inFile: 72 | desc: The name of the input PDF. 73 | type: str, unicode 74 | outFile: 75 | desc: The name of the output PDF. 76 | type: str, unicode 77 | 78 | keywords: 79 | color: 80 | desc: An HTML-style color name. 81 | type: str, unicode 82 | """ 83 | 84 | import os 85 | import shutil 86 | import subprocess 87 | try: 88 | from scipy.ndimage import imread 89 | except ImportError: 90 | from imageio import imread 91 | import numpy as np 92 | from PIL import Image, ImageDraw, ImageFont 93 | 94 | #fontFile = '/usr/share/fonts/truetype/msttcorefonts/Times_New_Roman.ttf' 95 | fontFile = '/usr/share/fonts/truetype/freefont/FreeSans.ttf' 96 | fontSize = 20 97 | tmpFolder = u'line-numbers-tmp' 98 | pageFolder = u'%s/page' % tmpFolder 99 | watermarkFolder = u'%s/watermark' % tmpFolder 100 | 101 | try: 102 | shutil.rmtree(tmpFolder) 103 | except: 104 | pass 105 | os.makedirs(pageFolder) 106 | os.makedirs(watermarkFolder) 107 | 108 | print(u'Adding line numbers to PDF') 109 | print(u'Converting ...') 110 | cmd = u'convert -density 150 %s %s' % (inFile, os.path.join(pageFolder, 111 | u'%03d.png')) 112 | subprocess.call(cmd.split()) 113 | print(u'Done!') 114 | # Create watermarks for all pages 115 | for path in os.listdir(pageFolder): 116 | try: 117 | im = imread(os.path.join(pageFolder, path), flatten=True) 118 | except TypeError: 119 | im = imread(os.path.join(pageFolder, path), as_gray=True) 120 | # Create a list of indices that have text on them 121 | nonEmptyRows = np.where(im.mean(axis=1) != 255)[0] 122 | # Store the rows (i.e.) y coordinates of all to-be-numbered-rows 123 | numberRows =[] 124 | firstRow = None 125 | for row in nonEmptyRows: 126 | if im[row-1].mean() == 255: 127 | numberRows.append(row) 128 | print(u'Found %d lines!' % len(numberRows)) 129 | # Create watermark image 130 | print(u'Creating watermark ...') 131 | font = ImageFont.truetype(fontFile, fontSize) 132 | wm = Image.new('RGBA', (im.shape[1], im.shape[0])) 133 | dr = ImageDraw.Draw(wm) 134 | i = 1 135 | for row in numberRows: 136 | dr.text((32, row), '%s' % i, font=font, fill=color) 137 | i += 1 138 | wm.save(os.path.join(watermarkFolder, path)) 139 | print(u'Done!') 140 | 141 | print(u'Creating watermark pdf ...') 142 | cmd = 'convert %s/*.png watermark.pdf' % watermarkFolder 143 | subprocess.call(cmd.split()) 144 | print(u'Done!') 145 | 146 | print(u'Merging watermark and source document ...') 147 | cmd = u'pdftk %s multibackground watermark.pdf output %s' \ 148 | % (inFile, outFile) 149 | subprocess.call(cmd.split()) 150 | print(u'Done!') 151 | 152 | print(u'Cleaning up ...') 153 | shutil.rmtree(tmpFolder) 154 | print(u'Done') 155 | -------------------------------------------------------------------------------- /debian/changelog: -------------------------------------------------------------------------------- 1 | python-academicmarkdown (0.8.1-ubuntu1) trusty; urgency=medium 2 | 3 | * Fix a bug in TOC generation for non-ascii headers 4 | 5 | -- Sebastiaan Mathot Mon, 10 Nov 2014 14:59:44 +0100 6 | 7 | python-academicmarkdown (0.8.0-ubuntu1) trusty; urgency=medium 8 | 9 | * Various improvements and fixes 10 | 11 | -- Sebastiaan Mathot Thu, 30 Oct 2014 12:23:02 +0100 12 | 13 | python-academicmarkdown (0.7.2-ubuntu1) saucy; urgency=low 14 | 15 | * Fix and guess year in ZoteroParser 16 | * Implement pandoc-style table 17 | 18 | -- Sebastiaan Mathot Wed, 19 Mar 2014 15:22:28 +0100 19 | 20 | python-academicmarkdown (0.7.1-ubuntu1) saucy; urgency=low 21 | 22 | * Also format X2(X) = X and t = X style statistics 23 | * Remove URL from book references 24 | 25 | -- Sebastiaan Mathot Mon, 17 Mar 2014 16:26:22 +0100 26 | 27 | python-academicmarkdown (0.7.0-ubuntu1) saucy; urgency=low 28 | 29 | * Add Python parser 30 | * Accept URL paths 31 | 32 | -- Sebastiaan Mathot Thu, 13 Mar 2014 18:09:50 +0100 33 | 34 | python-academicmarkdown (0.6.0-ubuntu1) saucy; urgency=low 35 | 36 | * Add word-count parser (WcParser) 37 | * Print out used references when duplicates are encountered 38 | * Move magicVars to postMarkDownFilers 39 | 40 | -- Sebastiaan Mathot Mon, 20 Jan 2014 13:18:15 +0100 41 | 42 | python-academicmarkdown (0.5.1-ubuntu1) saucy; urgency=low 43 | 44 | * Fix TOC header links 45 | * Fix deep-level headers in TOC 46 | 47 | -- Sebastiaan Mathot Fri, 03 Jan 2014 14:00:13 +0100 48 | 49 | python-academicmarkdown (0.5.0-ubuntu1) saucy; urgency=low 50 | 51 | * Various improvements and bugfixes 52 | 53 | -- Sebastiaan Mathot Sun, 08 Dec 2013 16:23:06 +0100 54 | 55 | python-academicmarkdown (0.4.0-ubuntu4) saucy; urgency=low 56 | 57 | * Various improvements and bugfixes 58 | 59 | -- Sebastiaan Mathot Wed, 06 Nov 2013 17:09:01 +0100 60 | 61 | python-academicmarkdown (0.3.0-ubuntu1) saucy; urgency=low 62 | 63 | * Various improvements and bugfixes 64 | 65 | -- Sebastiaan Mathot Mon, 28 Oct 2013 12:06:07 +0100 66 | 67 | python-academicmarkdown (0.2.0-ubuntu1) saucy; urgency=low 68 | 69 | * Add pageBreak filter 70 | * Various bugfixes 71 | 72 | -- Sebastiaan Mathot Mon, 21 Oct 2013 17:41:12 +0200 73 | 74 | python-academicmarkdown (0.1.1-ubuntu3) raring; urgency=low 75 | 76 | * Initial release 77 | 78 | -- Sebastiaan Mathot Sun, 20 Oct 2013 14:02:10 +0200 79 | -------------------------------------------------------------------------------- /debian/compat: -------------------------------------------------------------------------------- 1 | 7 2 | -------------------------------------------------------------------------------- /debian/control: -------------------------------------------------------------------------------- 1 | Source: python-academicmarkdown 2 | Section: science 3 | Priority: extra 4 | Maintainer: Sebastiaan Mathot 5 | Build-Depends: debhelper (>= 7.0.50~), python-support, python-all, 6 | python-setuptools, python-simplejson, python-yaml 7 | XS-Python-Version: >= 2.6 8 | Standards-Version: 3.9.3 9 | Vcs-Git: git://github.com:smathot/academicmarkdown.git 10 | Homepage: https://github.com/smathot/academicmarkdown 11 | 12 | Package: python-academicmarkdown 13 | Architecture: all 14 | XB-Python-Version: ${python:Versions} 15 | Depends: ${misc:Depends}, ${python:Depends} 16 | Recommends: zotero-standalone, pandoc, wkhtmltopdf, python-pyzotero, unoconv, 17 | python-dateutil 18 | Description: A Python package for generating scientific documents using 19 | Markdown. 20 | 21 | -------------------------------------------------------------------------------- /debian/copyright: -------------------------------------------------------------------------------- 1 | Format: Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ 2 | Upstream-Name: python-academic markdown 3 | Maintainer-Contact: Sebastiaan Mathot 4 | Source: https://github.com/smathot/academicmarkdown 5 | 6 | Files: * 7 | Copyright: 2013, Sebastiaan Mathôt 8 | Licenses: GPL-3 9 | On Debian systems, the full text of the GNU General Public License version 3 10 | can be found in the file `/usr/share/common-licenses/GPL-3'. 11 | -------------------------------------------------------------------------------- /debian/rules: -------------------------------------------------------------------------------- 1 | #!/usr/bin/make -f 2 | %: 3 | dh $@ 4 | -------------------------------------------------------------------------------- /example/compile.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | #-*- coding:utf-8 -*- 3 | 4 | """ 5 | This file is part of zoteromarkdown. 6 | 7 | zoteromarkdown is free software: you can redistribute it and/or modify 8 | it under the terms of the GNU General Public License as published by 9 | the Free Software Foundation, either version 3 of the License, or 10 | (at your option) any later version. 11 | 12 | zoteromarkdown is distributed in the hope that it will be useful, 13 | but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | GNU General Public License for more details. 16 | 17 | You should have received a copy of the GNU General Public License 18 | along with zoteromarkdown. If not, see . 19 | """ 20 | 21 | import academicmarkdown 22 | from academicmarkdown import build 23 | import myZoteroCredentials 24 | build.path.append(u'example/src') 25 | build.zoteroLibraryId = myZoteroCredentials.zoteroLibraryId 26 | build.zoteroApiKey = myZoteroCredentials.zoteroApiKey 27 | build.setStyle('modern') 28 | build.pdfHeader = u'Generated with academicmarkdown %s' \ 29 | % academicmarkdown.version 30 | build.PDF('example/src/example.md', 'example/example.pdf') 31 | build.HTML('example/src/example.md', 'example/example.html') 32 | -------------------------------------------------------------------------------- /example/example.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smathot/academicmarkdown/696592d035e341e170b6c3e86a0855a8cc4d0f29/example/example.pdf -------------------------------------------------------------------------------- /example/src/example.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 3 | A bit about birds looking sideways 4 | author: 5 | Sebastiaan Mathôt^1,\*^ 6 | affiliation: 7 | ^1^Aix-Marseille Université, CNRS, Laboratoire de Psychologie Cognitive 8 | correspondence: 9 | ^\*^Correspondence should be addressed to 10 | --- 11 | 12 | This afternoon I was eating a sub in the Plymouth harbor, finally enjoying a bit of sun, which we haven't seen much of this summer. I was joined by a seagull chick. It was presumably hoping to score a piece of my sub... 13 | 14 | # The fovea 15 | 16 | As you probably know, we see only a small part of our surroundings with high resolution and in color [@Betts2013Anatomy]. This is the part that falls onto our fovea, a small, extra dense part of the retina (see %FigFA). Foveal vision corresponds to about the size of a thumb at arm's length. Yet we feel as though we have a complete and full-color perception of our entire visual field. In large part, this is because our eyes are mobile: If we think about something, we immediately look at it (bring it into foveal vision) to get a crisp view of the object in question. 17 | 18 | %-- 19 | figure: 20 | id: FigFA 21 | source: foveal_acuity.png 22 | caption: "Visual acuity drops of rapidly with distance from the fovea (source: [Wikipedia](http://en.wikipedia.org/wiki/Fovea_centralis))" 23 | width: 30% 24 | --% 25 | 26 | So what does this have to do with the gull chick? Well, for birds it's much the same, but with a twist. Many birds don't have a single fovea (per eye), like we do, but two [@LandNilsson2002]. (The details differ between species, but I believe the following applies to many species except birds of prey.) They have a temporal fovea, which is like ours in the sense that it looks straight ahead and offers binocular vision (i.e. the temporal foveas of both eyes point in the same direction). But birds also have a central fovea, which points sideways and is, obviously, monocular (i.e., the central foveas of both eyes look in opposite directions). 27 | 28 | %-- 29 | figure: 30 | id: FigGV 31 | source: gullvision.png 32 | caption: "Some birds can look in front of them and sideways (Adapted from [Wikipedia](http://en.wikipedia.org/wiki/File:Seagulls_Talking.JPG))" 33 | width: 30% 34 | --% 35 | 36 | So when a bird wants to look at something it has a choice: It can look straight ahead with its temporal foveas, to the left with the central fovea of its left eye, or to the right with the central fovea of its right eye (see %FigGV). And this is not a hypothetical possibility: Birds actually do switch between foveas all the time [@Dawkins2002AnimBehav]! This is why they tend to swing their heads erratically in turns of about 90°, as you can see in the video above. And this is also why, according to Michael F. Land [-@Land1999RolesHead] "it is frustratingly difficult to tell what a bird is actually attending to." (This quote is actually taken a bit out of context, but it applies quite well anyway.) 37 | 38 | # Vision science 39 | 40 | As a vision scientist, I find this intriguing, because it completely goes against our (totally anthropocentric) ideas about vision. Basically all theories about vision assume that there is a single fovea, used to look at one object at a time, and that consequently we attend to only a single object (more or less) at a time. But how well does this translate to birds? Do birds have a more distributed awareness of their environment, as a consequence of their having multiple foveas? There is some research, notably by Robert Cook [-@Cook2000CurrDirPsychSci], that suggests that perception and attention in birds is actually similar to that in humans, but, in general, I believe that this is an open (and interesting!) question. 41 | 42 | *%-- exec: "date +'Generated: %x'" --%* 43 | -------------------------------------------------------------------------------- /example/src/foveal_acuity.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smathot/academicmarkdown/696592d035e341e170b6c3e86a0855a8cc4d0f29/example/src/foveal_acuity.png -------------------------------------------------------------------------------- /example/src/gullvision.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smathot/academicmarkdown/696592d035e341e170b6c3e86a0855a8cc4d0f29/example/src/gullvision.png -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # *module* academicmarkdown 4 | 5 | *Who knew writing could be so nerdy?* 6 | 7 | version 0.8.1 8 | 9 | 10 | Copyright 2013-2014 Sebastiaan Mathôt 11 | 12 | ## Contents 13 | 14 | 15 | - [*module* academicmarkdown](#module-academicmarkdown) 16 | - [About](#about) 17 | - [Examples](#examples) 18 | - [Download](#download) 19 | - [Basic usage](#basic-usage) 20 | - [Dependencies](#dependencies) 21 | - [Zotero references](#zotero-references) 22 | - [Pandoc citation style](#pandoc-citation-style) 23 | - [Zotero API key and library ID](#zotero-api-key-and-library-id) 24 | - [Citation identifiers](#citation-identifiers) 25 | - [Sorting citations](#sorting-citations) 26 | - [Clearing cache](#clearing-cache) 27 | - [Academic Markdown extensions](#academic-markdown-extensions) 28 | - [`code`: code listings](#code-code-listings) 29 | - [`constant`: define constants](#constant-define-constants) 30 | - [`exec`: external commands](#exec-external-commands) 31 | - [`figure`: figures](#figure-figures) 32 | - [`include`: include other Markdown files](#include-include-other-markdown-files) 33 | - [`python`: python code](#python-python-code) 34 | - [`table`: table](#table-table) 35 | - [`toc`: table of contents](#toc-table-of-contents) 36 | - [`wc`: word count](#wc-word-count) 37 | - [Magic variables](#magic-variables) 38 | - [License](#license) 39 | - [*module* academicmarkdown.build](#module-academicmarkdownbuild) 40 | - [function __academicmarkdown\.build\.DOC__\(src, target\)](#function-__academicmarkdownbuilddoc__src-target) 41 | - [function __academicmarkdown\.build\.DOCX__\(src, target\)](#function-__academicmarkdownbuilddocx__src-target) 42 | - [function __academicmarkdown\.build\.HTML__\(src, target=None, standalone=True\)](#function-__academicmarkdownbuildhtml__src-targetnone-standalonetrue) 43 | - [function __academicmarkdown\.build\.MD__\(src, target=None\)](#function-__academicmarkdownbuildmd__src-targetnone) 44 | - [function __academicmarkdown\.build\.ODT__\(src, target\)](#function-__academicmarkdownbuildodt__src-target) 45 | - [function __academicmarkdown\.build\.PDF__\(src, target, args=u'', lineNumbers=False\)](#function-__academicmarkdownbuildpdf__src-target-argsu-linenumbersfalse) 46 | - [function __academicmarkdown\.build\.setStyle__\(style\)](#function-__academicmarkdownbuildsetstyle__style) 47 | - [*module* academicmarkdown.constants](#module-academicmarkdownconstants) 48 | 49 | 50 | 51 | ## About 52 | 53 | Academic Markdown is a Python module for generating `.md`, `.html`, `.pdf`, 54 | `.docx`, and `.odt` files from Markdown source. [Pandoc] is used for most of 55 | the heavy lifting, so refer to the Pandoc website for detailed information 56 | about writing in Pandoc Markdown. However, Academic Markdown offers some 57 | additional functionality that is useful for writing scientific documents, 58 | such as integration with [Zotero references], and a number of useful 59 | [Academic Markdown extensions]. 60 | 61 | At present, the main target for Academic Markdown is the OpenSesame 62 | documentation site, , although it may in time grow 63 | into a more comprehensive and user-friendly tool. 64 | 65 | ## Examples 66 | 67 | A basic example can be found in the `example` sub folder, included with the source. 68 | 69 | The following manuscripts have been written in Academic Markdown: 70 | 71 | - Mathôt S, Dalmaijer ES, Grainger J, Van der Stigchel S. (2014) The pupillary light response reflects exogenous attention and inhibition of return. *PeerJ PrePrints* 2:e422v1 ([source](https://github.com/smathot/materials_for_P0009.1/tree/master/manuscript)) 72 | - Mathôt S, van der Linden L, Grainger J, Vitu F. (2014) The pupillary light response reflects eye-movement preparation. *PeerJ PrePrints* 2:e238v2 ([source](https://github.com/smathot/materials_for_P0001/tree/master/manuscript)) 73 | 74 | ## Download 75 | 76 | You can download the latest release of Academic Markdown here: 77 | 78 | - 79 | 80 | Ubuntu users can install Academic Markdown from the Cogsci.nl PPA: 81 | 82 | sudo add-apt-repository ppa:smathot/cogscinl 83 | sudo apt-get update 84 | sudo apt-get install python-academicmarkdown 85 | 86 | ## Basic usage 87 | 88 | Academic Markdown assumes that input files are encoded with `utf-8` encoding. 89 | 90 | ~~~ {.python} 91 | from academicmarkdown import build 92 | build.HTML(u'input.md', u'output.html') 93 | build.HTML(u'input.md', u'output.html', standalone=False) 94 | build.PDF(u'input.md', u'output.pdf') 95 | build.DOCX(u'input.md', u'output.docx') 96 | build.ODT(u'input.md', u'output.odt') 97 | ~~~ 98 | 99 | A number of options can be specified by setting attributes of the `build` module, like so 100 | 101 | ~~~ {.python} 102 | build.spacing = 30, 0 103 | ~~~ 104 | 105 | The full list of options is available in `academicmarkdown/constants.py`, or see [academicmarkdown.constants]. 106 | 107 | ## Dependencies 108 | 109 | Academic Markdown has been tested exclusively on Ubuntu Linux. The following dependencies are required: 110 | 111 | - [pandoc] is used for most of the heavy lifting. At the time of writing, the Ubuntu repositories do not contain a sufficiently recent version of Pandoc. Therefore, if you encounter trouble, try installing the latest version of Pandoc manually. 112 | - [pyzotero] is necessary for extracting Zotero references. 113 | - [wkhtmltopdf] is necessary for converting to `.pdf`. For best results, use the latest statically linked release, instead of the version from the Ubuntu repositories. 114 | 115 | ## Zotero references 116 | 117 | ### Pandoc citation style 118 | 119 | Since the basic Markdown conversion is performed by Pandoc, citations should be formatted as described on the Pandoc site: 120 | 121 | - 122 | 123 | ### Zotero API key and library ID 124 | 125 | You can automatically extract references from your Zotero library by setting the `zoteroApiKey` and `zoteroLibraryId` properties. Your references are not extracted from your local Zotero database, but through the web API of . This means that you need to have a Zotero account and synchronize your local database with your account, in order to use this feature. You can find your your API key and library ID online on your Zotero profile page. 126 | 127 | ~~~ {.python} 128 | from academicmarkdown import build 129 | build.zoteroApiKey = u'myapikey' 130 | build.zoteroLibraryId = u'mylibraryid' 131 | build.PDF(u'input.md', u'output.pdf') 132 | ~~~ 133 | 134 | ### Citation identifiers 135 | 136 | Citations are split into separate terms using camelcase or undescore logic. An example of an underscore-style citation is `@bárány_halldén_1948`. And example of a camelcase-style citation is `@Land1999WhyAnimals`. Each citation is interpreted as a series of author names, followed by the year of publication, optionally followed by terms that match either the publication title, or the article title. So the following reference ... 137 | 138 | Land, M., Mennie, N., & Rusted, J. (1999). The roles of vision and eye movements in the control of activities of daily living. *Perception*, *28*(11), 1311–1328. 139 | 140 | ... matches any of the following terms: 141 | 142 | - `@Land1999` 143 | - `@Land1999Roles` 144 | - `@LandMennie1999` 145 | - `@LandRusted1999Percept` 146 | - `@land_rusted_1999_percept` 147 | - etc. 148 | 149 | If a name contains spaces, you can indicate this using a `+` character. So the following reference ... 150 | 151 | Van Zoest, W., & Donk, M. (2005). The effects of salience on saccadic target selection. *Visual Cognition*, *12*(2), 353–375. 152 | 153 | ... matches any of the following terms: 154 | 155 | - `@Van+zoestDonk2005` 156 | - `@van+zoest_donk_2005` 157 | 158 | Note that you must consistently use the same citation to refer to a single reference in one document. If a citation matched multiple references from your Zotero database, one citation will be chosen at random. 159 | 160 | ### Sorting citations 161 | 162 | Pandoc does not allow you to sort your references, which can be annoying. To get around this, Academic Markdown allows you to explicitly sort your citations by linking chains of citations with a `+` character: 163 | 164 | [@Zzz2014]+[@Aaa2014] 165 | 166 | ### Clearing cache 167 | 168 | Previous references will be cached automatically. To refresh, remove the file `.zoteromarkdown.cache` or run your Python script with the command-line argument: `--clear-cache`. 169 | 170 | ## Academic Markdown extensions 171 | 172 | Academic Markdown provides certain extensions to regular Markdown, in the form of YAML blocks embedded in `%-- --%` tags. You can which, and the order in which, extensions are called by settings the `extensions` list: 173 | 174 | ~~~ {.python} 175 | from academicmarkdown import build 176 | # First call the include extension, second call the figure extension 177 | build.extensions = [u'include', u'figure'] 178 | ~~~ 179 | 180 | ### `code`: code listings 181 | 182 | The `code` blocks embeds a code listing in the text, quite to similar to the 183 | `figure` block. 184 | 185 | %-- 186 | code: 187 | id: CodeA 188 | source: my_script.py 189 | syntax: python 190 | caption: "A simple Python script" 191 | --% 192 | 193 | The `caption` and `syntax` attributes are optional. 194 | 195 | 196 | ### `constant`: define constants 197 | 198 | The `constant` block allows you to define constants. For example, if you 199 | define MyConstant1 (as below), all occurrences of "%MyConstant1" in the text 200 | will be replcated by "You can refer to this as %MyConstant1". 201 | 202 | %-- 203 | constant: 204 | MyConstant1: "You can refer to this as %MyConstant1" 205 | MyConstant2: "You can refer to this as %MyConstant2" 206 | --% 207 | 208 | 209 | ### `exec`: external commands 210 | 211 | The `exec` block inserts the return value of an external command in the 212 | text. For example, the following block embeds something like 213 | 'Generated on 10/18/2013': 214 | 215 | %-- exec: "date +'Generated on %x'" --% 216 | 217 | 218 | ### `figure`: figures 219 | 220 | The `figure` block embeds a Figure in the text. Figures are numbered 221 | automatically. The ID can be used to refer to the Figure in the text, using 222 | a `%` character. So the following figure would be referred to as `%FigFA`. 223 | 224 | %-- 225 | figure: 226 | id: FigFA 227 | source: foveal_acuity.svg 228 | caption: "Visual acuity drops of rapidly with distance from the fovea." 229 | width: 100 230 | --% 231 | 232 | The `caption` and `width` attributes are optional. 233 | 234 | 235 | ### `include`: include other Markdown files 236 | 237 | The `include` block includes an other Markdown file. For example: 238 | 239 | %-- include: example/intro.md --% 240 | 241 | 242 | ### `python`: python code 243 | 244 | The `python` block embeds the output (i.e. whatever is printed to stdout) 245 | of a Python script into your document. For example, the following block 246 | embeds the docstring of the `PythonParser` class (i.e. what you're reading 247 | now): 248 | 249 | %-- 250 | python: | 251 | import inspect 252 | from academicmarkdown import PythonParser 253 | print inspect.getdoc(PythonParser) 254 | --% 255 | 256 | Note that the `|` symbol is YAML syntax, and allows you to have a multiline 257 | string. 258 | 259 | 260 | ### `table`: table 261 | 262 | The `table` block reads a table from a `.csv` file and embed it into the 263 | document. The source file needs to be a utf-8 encoded file that is 264 | comma separated and double quoted. 265 | 266 | %-- 267 | table: 268 | id: MyTable 269 | source: my_table.csv 270 | caption: "My table caption." 271 | ndigits: 4 272 | --% 273 | 274 | 275 | ### `toc`: table of contents 276 | 277 | The `toc` block automatically generates a table of contents from the 278 | headings, assuming that headings are indicated using the `#` style and not 279 | the underlining style. You can indicate headings to be excluded from the 280 | table of contents as well. 281 | 282 | %-- 283 | toc: 284 | mindepth: 1 285 | maxdepth: 2 286 | exclude: [Contents, Contact] 287 | --% 288 | 289 | All attributes are optional. 290 | 291 | 292 | ### `wc`: word count 293 | 294 | The `wc` block insert the word count for a particular document. This is 295 | convenient if you have split the text across multiple documents, and want to 296 | have a separate word count for each document. 297 | 298 | %-- wc: method-section.md --% 299 | 300 | 301 | ### Magic variables 302 | 303 | Magic variables are automatically replaced by certain values, and are indicated like this: `%varname%`. The following magic variables are available: 304 | 305 | - `%wc%`: Word count 306 | - `%cc%`: Character count 307 | - `%rc%`: Reference count 308 | 309 | ## License 310 | 311 | Academic Markdown is available under the GNU General Public License 3. For more information, see the included file `COPYING`. 312 | 313 | [pandoc]: http://johnmacfarlane.net/pandoc/ 314 | [pyzotero]: http://pyzotero.readthedocs.org/ 315 | [zotero]: http://www.zotero.org/ 316 | [wkhtmltopdf]: https://code.google.com/p/wkhtmltopdf/ 317 | 318 | 319 | 320 | ## *module* academicmarkdown.build 321 | 322 | Contains functions to build documents from Markdown source. 323 | 324 | 325 | 326 | ### function __academicmarkdown\.build\.DOC__\(src, target\) 327 | 328 | Builds a DOC file from a Markdown source. 329 | 330 | __Arguments:__ 331 | 332 | - `src` -- The Markdown source. If it is a path to an existing file, the contents of this file are read. Otherwise, the string itself it used. Should be in utf-8 encoding. 333 | - Type: str, unicode 334 | - `target` -- The name of a DOC target file. 335 | - Type: str, unicode 336 | 337 | 338 | 339 | [academicmarkdown.build.DOC]: #academicmarkdown-build-DOC 340 | [build.DOC]: #academicmarkdown-build-DOC 341 | [DOC]: #academicmarkdown-build-DOC 342 | 343 | 344 | 345 | ### function __academicmarkdown\.build\.DOCX__\(src, target\) 346 | 347 | Builds a DOCX file from a Markdown source. 348 | 349 | __Arguments:__ 350 | 351 | - `src` -- The Markdown source. If it is a path to an existing file, the contents of this file are read. Otherwise, the string itself it used. Should be in utf-8 encoding. 352 | - Type: str, unicode 353 | - `target` -- The name of a DOCX target file. 354 | - Type: str, unicode 355 | 356 | 357 | 358 | [academicmarkdown.build.DOCX]: #academicmarkdown-build-DOCX 359 | [build.DOCX]: #academicmarkdown-build-DOCX 360 | [DOCX]: #academicmarkdown-build-DOCX 361 | 362 | 363 | 364 | ### function __academicmarkdown\.build\.HTML__\(src, target=None, standalone=True\) 365 | 366 | Builds an HTML file from a Markdown source. 367 | 368 | 369 | 370 | __Arguments:__ 371 | 372 | - `src` -- The Markdown source. If it is a path to an existing file, the contents of this file are read. Otherwise, the string itself it used. Should be in utf-8 encoding. 373 | - Type: str, unicode 374 | 375 | __Keywords:__ 376 | 377 | - `target` -- The name of an HTML target file or None to skip saving. 378 | - Default: None 379 | - Type: str, unicode, NoneType 380 | - `standalone` -- Indicates whether a full HTML5 document should be generated, which embeds all content, or whether the document should be rendered without `` and `` tags, etc. 381 | - Default: True 382 | - Type: bool 383 | 384 | __Returns:__ 385 | 386 | The HTML file as a unicode string. 387 | 388 | - Type: unicode 389 | 390 | 391 | 392 | [academicmarkdown.build.HTML]: #academicmarkdown-build-HTML 393 | [build.HTML]: #academicmarkdown-build-HTML 394 | [HTML]: #academicmarkdown-build-HTML 395 | 396 | 397 | 398 | ### function __academicmarkdown\.build\.MD__\(src, target=None\) 399 | 400 | Builds a Markdown file from a Markdown source. 401 | 402 | __Arguments:__ 403 | 404 | - `src` -- The Markdown source. If it is a path to an existing file, the contents of this file are read. Otherwise, the string itself it used. Should be in utf-8 encoding. 405 | - Type: str, unicode 406 | 407 | __Keywords:__ 408 | 409 | - `target` -- The name of a Markdown target file or None to skip saving. 410 | - Default: None 411 | - Type: str, unicode, NoneType 412 | 413 | __Returns:__ 414 | 415 | The compiled Markdown file as a unicode string. 416 | 417 | - Type: unicode 418 | 419 | 420 | 421 | [academicmarkdown.build.MD]: #academicmarkdown-build-MD 422 | [build.MD]: #academicmarkdown-build-MD 423 | [MD]: #academicmarkdown-build-MD 424 | 425 | 426 | 427 | ### function __academicmarkdown\.build\.ODT__\(src, target\) 428 | 429 | Builds an ODT file from a Markdown source. 430 | 431 | __Arguments:__ 432 | 433 | - `src` -- The Markdown source. If it is a path to an existing file, the contents of this file are read. Otherwise, the string itself it used. Should be in utf-8 encoding. 434 | - Type: str, unicode 435 | - `target` -- The name of an ODT target file. 436 | - Type: str, unicode 437 | 438 | 439 | 440 | [academicmarkdown.build.ODT]: #academicmarkdown-build-ODT 441 | [build.ODT]: #academicmarkdown-build-ODT 442 | [ODT]: #academicmarkdown-build-ODT 443 | 444 | 445 | 446 | ### function __academicmarkdown\.build\.PDF__\(src, target, args=u'', lineNumbers=False\) 447 | 448 | Builds a PDF file from a Markdown source. 449 | 450 | __Arguments:__ 451 | 452 | - `src` -- The Markdown source. If it is a path to an existing file, the contents of this file are read. Otherwise, the string itself it used. Should be in utf-8 encoding. 453 | - Type: str, unicode 454 | - `target` -- The name of a PDF target file. 455 | - Type: str, unicode 456 | 457 | __Keywords:__ 458 | 459 | - `args` -- Indicates extra arguments to be passed onto wkhtmltopdf. 460 | - Default: '' 461 | - Type: str, unicode 462 | - `lineNumbers` -- Determines whether line numbers should be added. This is currently quite a complicated process, which may break. 463 | - Default: False 464 | - Type: bool 465 | 466 | 467 | 468 | [academicmarkdown.build.PDF]: #academicmarkdown-build-PDF 469 | [build.PDF]: #academicmarkdown-build-PDF 470 | [PDF]: #academicmarkdown-build-PDF 471 | 472 | 473 | 474 | ### function __academicmarkdown\.build\.setStyle__\(style\) 475 | 476 | Automatically sets a style. 477 | 478 | __Arguments:__ 479 | 480 | - `style` -- The style name. This should be the name of a folder that contains style files. See the `academicmarkdown\styles` subfolder for examples. 481 | - Type: str, unicode 482 | 483 | 484 | 485 | [academicmarkdown.build.setStyle]: #academicmarkdown-build-setStyle 486 | [build.setStyle]: #academicmarkdown-build-setStyle 487 | [setStyle]: #academicmarkdown-build-setStyle 488 | 489 | 490 | 491 | [academicmarkdown.build]: #academicmarkdown-build 492 | [build]: #academicmarkdown-build 493 | 494 | 495 | 496 | ## *module* academicmarkdown.constants 497 | 498 | Contains the settings, which are imported into `academicmarkdown.build`. You 499 | can change these settings in the `build` module, as shown below. 500 | 501 | __Module source:__ 502 | 503 | 504 | ~~~ {.python} 505 | # -*- coding: utf-8 -*- 506 | """ 507 | This file is part of zoteromarkdown. 508 | 509 | zoteromarkdown is free software: you can redistribute it and/or modify 510 | it under the terms of the GNU General Public License as published by 511 | the Free Software Foundation, either version 3 of the License, or 512 | (at your option) any later version. 513 | 514 | zoteromarkdown is distributed in the hope that it will be useful, 515 | but WITHOUT ANY WARRANTY; without even the implied warranty of 516 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 517 | GNU General Public License for more details. 518 | 519 | You should have received a copy of the GNU General Public License 520 | along with zoteromarkdown. If not, see . 521 | 522 | --- 523 | desc: | 524 | Contains the settings, which are imported into `academicmarkdown.build`. You 525 | can change these settings in the `build` module, as shown below. 526 | 527 | __Module source:__ 528 | 529 | %-- 530 | code: 531 | id: LstConstants 532 | syntax: python 533 | source: academicmarkdown/constants.py 534 | --% 535 | 536 | example: | 537 | from academicmarkdown import build 538 | build.pdfHeader = u'A header for my PDF' 539 | --- 540 | """ 541 | 542 | import os, sys 543 | 544 | # A list of folders that are searched for figures, scripts, etc. 545 | path = [os.getcwd().decode(sys.getfilesystemencoding())] 546 | 547 | # Parameters for Zotero integration 548 | zoteroApiKey = None 549 | zoteroLibraryId = None 550 | zoteroHeaderText = u'References' 551 | zoteroHeaderLevel = 1 552 | 553 | # Options for the appearance of figures, blocks, and tables 554 | figureTemplate = u'html5' 555 | figureStyle = u'inline' 556 | codeTemplate = u'pandoc' 557 | codeStyle = u'inline' 558 | tableTemplate = u'html5' 559 | tableStyle = u'inline' 560 | 561 | # Indicates whether headers should be turned into clickable anchors by TOCParser 562 | TOCAnchorHeaders = False 563 | # Indicates whether references to header ids should be automatically appended 564 | # to the main text. 565 | TOCAppendHeaderRefs = True 566 | 567 | # Paths to files that determine the document's appearance. For more information, 568 | # see the Pandoc documentation. 569 | css = None # CSS stylesheet 570 | csl = None # CSL citation style 571 | html5Ref = None # HTML5 template 572 | odtRef = None # ODT reference document 573 | docxRef = None # DOCX reference document 574 | 575 | # A list of filters from academicmarkdown.HTMLFilter that should be performed 576 | # after an HTML document has been genertated. 577 | htmlFilters = [u'DOI', u'citationGlue'] 578 | 579 | # A list of filters from academicmarkdown.MDFilter that should be performed 580 | # on the Markdown source, prior to any processing. 581 | preMarkdownFilters = [] 582 | # A list of filters from academicmarkdown.MDFilter that should be performed 583 | # on the Markdown source, after all other processing has been performed 584 | postMarkdownFilters = [u'autoItalics', u'pageBreak', u'magicVars', u'highlight'] 585 | 586 | # A list of extensions that are enabled. 587 | extensions = [u'include', u'exec', u'python', u'toc', u'code', u'video', \ 588 | u'table', u'figure', u'constant', u'wc'] 589 | 590 | # The page margins 591 | pdfMargins = 30, 20, 30, 20 592 | 593 | # The spacing between the content and the header and footer 594 | pdfSpacing = 10, 10 595 | 596 | # Header and footer text 597 | pdfHeader = u'%section%' 598 | pdfFooter = u'%page% of %topage%' 599 | ~~~ 600 | 601 | 602 | __Example:__ 603 | 604 | ~~~ .python 605 | from academicmarkdown import build 606 | build.pdfHeader = u'A header for my PDF' 607 | ~~~ 608 | 609 | 610 | 611 | [academicmarkdown.constants]: #academicmarkdown-constants 612 | [constants]: #academicmarkdown-constants 613 | 614 | 615 | 616 | [academicmarkdown]: #academicmarkdown 617 | 618 | 619 | [*module* academicmarkdown]: #module-academicmarkdown 620 | [Contents]: #contents 621 | [About]: #about 622 | [Examples]: #examples 623 | [Download]: #download 624 | [Basic usage]: #basic-usage 625 | [Dependencies]: #dependencies 626 | [Zotero references]: #zotero-references 627 | [Pandoc citation style]: #pandoc-citation-style 628 | [Zotero API key and library ID]: #zotero-api-key-and-library-id 629 | [Citation identifiers]: #citation-identifiers 630 | [Sorting citations]: #sorting-citations 631 | [Clearing cache]: #clearing-cache 632 | [Academic Markdown extensions]: #academic-markdown-extensions 633 | [`code`: code listings]: #code-code-listings 634 | [`constant`: define constants]: #constant-define-constants 635 | [`exec`: external commands]: #exec-external-commands 636 | [`figure`: figures]: #figure-figures 637 | [`include`: include other Markdown files]: #include-include-other-markdown-files 638 | [`python`: python code]: #python-python-code 639 | [`table`: table]: #table-table 640 | [`toc`: table of contents]: #toc-table-of-contents 641 | [`wc`: word count]: #wc-word-count 642 | [Magic variables]: #magic-variables 643 | [License]: #license 644 | [*module* academicmarkdown.build]: #module-academicmarkdownbuild 645 | [function __academicmarkdown\.build\.DOC__\(src, target\)]: #function-__academicmarkdownbuilddoc__src-target 646 | [function __academicmarkdown\.build\.DOCX__\(src, target\)]: #function-__academicmarkdownbuilddocx__src-target 647 | [function __academicmarkdown\.build\.HTML__\(src, target=None, standalone=True\)]: #function-__academicmarkdownbuildhtml__src-targetnone-standalonetrue 648 | [function __academicmarkdown\.build\.MD__\(src, target=None\)]: #function-__academicmarkdownbuildmd__src-targetnone 649 | [function __academicmarkdown\.build\.ODT__\(src, target\)]: #function-__academicmarkdownbuildodt__src-target 650 | [function __academicmarkdown\.build\.PDF__\(src, target, args=u'', lineNumbers=False\)]: #function-__academicmarkdownbuildpdf__src-target-argsu-linenumbersfalse 651 | [function __academicmarkdown\.build\.setStyle__\(style\)]: #function-__academicmarkdownbuildsetstyle__style 652 | [*module* academicmarkdown.constants]: #module-academicmarkdownconstants -------------------------------------------------------------------------------- /readme.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | """ 5 | This file is part of academicmarkdown. 6 | 7 | academicmarkdown is free software: you can redistribute it and/or modify 8 | it under the terms of the GNU General Public License as published by 9 | the Free Software Foundation, either version 3 of the License, or 10 | (at your option) any later version. 11 | 12 | academicmarkdown is distributed in the hope that it will be useful, 13 | but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | GNU General Public License for more details. 16 | 17 | You should have received a copy of the GNU General Public License 18 | along with academicmarkdown. If not, see . 19 | """ 20 | 21 | import yamldoc 22 | import academicmarkdown 23 | from academicmarkdown.py3compat import * 24 | 25 | df = yamldoc.DocFactory(academicmarkdown) 26 | academicmarkdown.build.extensions = [u'toc', u'exec', u'code', u'constant', 27 | u'python'] 28 | academicmarkdown.build.postMarkdownFilters = [] 29 | academicmarkdown.build.MD(str(df), u'readme.md') 30 | academicmarkdown.build.PDF(str(df), u'readme.pdf') 31 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | """ 5 | This file is part of zoteromarkdown. 6 | 7 | zoteromarkdown is free software: you can redistribute it and/or modify 8 | it under the terms of the GNU General Public License as published by 9 | the Free Software Foundation, either version 3 of the License, or 10 | (at your option) any later version. 11 | 12 | zoteromarkdown is distributed in the hope that it will be useful, 13 | but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | GNU General Public License for more details. 16 | 17 | You should have received a copy of the GNU General Public License 18 | along with zoteromarkdown. If not, see . 19 | """ 20 | 21 | from academicmarkdown import version 22 | from setuptools import setup, find_packages 23 | 24 | setup( 25 | name=u'python-academicmarkdown', 26 | version=version, 27 | description= \ 28 | u'A Python package for generating scientific documents using Markdown.', 29 | author=u'Sebastiaan Mathôt', 30 | author_email=u's.mathot@cogsci.nl', 31 | license=u'GNU GPL Version 3', 32 | url=u'https://github.com/smathot/academicmarkdown', 33 | packages=find_packages('.') 34 | ) 35 | --------------------------------------------------------------------------------