5 from pygments import formatters, util, lexers
6 import blogofile_bf as bf
8 config = {"name": "Syntax Highlighter",
9 "description": "Highlights blocks of code based on syntax",
10 "author": "Ryan McGuire",
16 #This filter normally only loads pygments styles when needed.
17 #This will force a particular style to get loaded at startup.
18 for style in bf.config.filters.syntax_highlight.preload_styles:
19 css_class = "pygments_{0}".format(style)
20 formatter = pygments.formatters.HtmlFormatter(
21 linenos=False, cssclass=css_class, style=style)
22 write_pygments_css(style, formatter)
29 The following is a python code block:
34 prices = {'apple' : 0.50, #Prices of fruit
39 for fruit, price in prices.items():
40 print "An %s costs %s" % (fruit, price)
43 This is a ruby code block:
47 attr_reader :name, :age
48 def initialize(name, age)
49 @name, @age = name, age
51 def <=>(person) # Comparison operator for sorting
60 Person.new("Bob", 33),
61 Person.new("Chris", 16),
65 puts group.sort.reverse
71 css_files_written = set()
73 code_block_re = re.compile(
74 r"(?:^|\s)" # $$code Must start as a new word
75 r"\$\$code" # $$code is the start of the block
76 r"(?P<args>\([^\r\n]*\))?" # optional arguments are passed in brackets
77 r"[^\r\n]*\r?\n" # ignore everything else on the 1st line
78 r"(?P<code>.*?)\s\$\$/code" # code block continues until $$/code
81 argument_re = re.compile(
82 r"[ ]*" # eat spaces at the beginning
83 "(?P<arg>" # start of argument
84 ".*?" # the name of the argument
86 r"""(?:(?:[^"']*?)""" # a non-quoted value
87 r"""|(?:"[^"]*")""" # or, a double-quoted value
88 r"""|(?:'[^']*')))""" # or, a single-quoted value
89 "[ ]*" # eat spaces at the end
90 "[,\r\n]" # ends in a comma or newline
94 def highlight_code(code, language, formatter):
96 lexer = pygments.lexers.get_lexer_by_name(language)
97 except pygments.util.ClassNotFound:
98 lexer = pygments.lexers.get_lexer_by_name("text")
99 #Highlight with pygments and surround by blank lines
100 #(blank lines required for markdown syntax)
101 highlighted = "\n\n{0}\n\n".format(
102 pygments.highlight(code, lexer, formatter))
106 def parse_args(args):
107 #Make sure the args are newline terminated (req'd by regex)
111 args = args.lstrip("(").rstrip(")")
114 for m in argument_re.finditer(args):
115 arg = m.group('arg').split('=')
116 opts[arg[0]] = arg[1]
120 def write_pygments_css(style, formatter,
121 location=bf.config.filters.syntax_highlight.css_dir):
122 path = bf.util.path_join("_site", bf.util.fs_site_path_helper(location))
124 css_file = "pygments_{0}.css".format(style)
125 css_path = os.path.join(path, css_file)
126 css_site_path = css_path.replace("_site", "")
127 if css_site_path in css_files_written:
128 return #already written, no need to overwrite it.
129 f = open(css_path, "w")
130 css_class = ".pygments_{0}".format(style)
131 f.write(formatter.get_style_defs(css_class))
133 css_files_written.add(css_site_path)
138 for m in code_block_re.finditer(src):
139 args = parse_args(m.group('args'))
141 if args.has_key('lang'):
143 elif args.has_key('language'):
144 lang = args['language']
148 if args.has_key('linenums'):
149 linenums = args['linenums']
150 elif args.has_key("linenos"):
151 linenums = args['linenos']
152 if linenums.lower().strip() == "true":
159 style = args['style']
161 style = bf.config.filters.syntax_highlight.style
163 css_class = args['cssclass']
165 css_class = "pygments_{0}".format(style)
166 formatter = pygments.formatters.HtmlFormatter(
167 linenos=linenums, cssclass=css_class, style=style)
168 write_pygments_css(style, formatter)
169 substitutions[m.group()] = highlight_code(
170 m.group('code'), lang, formatter)
171 if len(substitutions) > 0:
172 p = re.compile('|'.join(map(re.escape, substitutions)))
173 src = p.sub(lambda x: substitutions[x.group(0)], src)