Skip to content

Instantly share code, notes, and snippets.

@sublimator
Created August 18, 2011 04:04
Show Gist options
  • Save sublimator/1153273 to your computer and use it in GitHub Desktop.
Save sublimator/1153273 to your computer and use it in GitHub Desktop.
#!/usr/bin/env python
#coding: utf8
#################################### IMPORTS ###################################
# Std Libs
import cgi
import os
import pprint
import string
import time
import webbrowser
from os.path import split, join, normpath, splitext
# Sublime Modules
import sublime
import sublime_plugin
# APP Libs
from plist import parse_plist
################################# SETTINGS #####################################
settings = sublime.load_settings('html-export.sublime-settings')
defaults = {
"open_html_in_editor" : False,
"open_html_in_browser" : True,
"copy_css_to_clipboard" : True,
"copy_html_to_clipboard" : True,
"encode_as" : "utf-8",
"add_line_numbers" : True
}
class Settings(object):
def __init__(self, kw):
self.kw = kw
def __getattr__(self, attr):
if attr in getattr(self, 'kw'): return self.kw.get(attr)
if settings.has(attr): return settings.get(attr)
else: return defaults.get(attr)
################################### TODO #######################################
"""
clean up 4 000,000 line function
multiple selections, for posting snippets with foo() ... bar() = DONE =
contract selections so there are no empty lines 4 mult-sel
pre tag + css to clipboard = almost DONE =
compress clipboard css = DONE =
embedded style="bla" attributes, no messing around with style
sheets for quick posts
in <head> css styles
line numbers in float:left pre tag so can easily copy paste
"""
################################# TEMPLATES ####################################
HTML_TEMPLATE = """<html>
<head>
<title> %s </title>
<link rel="stylesheet" href="%s.css" type="text/css" charset="utf-8" />
<style type='text/css'>
body {margin:0; padding:0;}
pre {padding: 1em; font: 12px "DejaVu Sans Mono", monospace;}
</style>
</head>
<body>
%s
</body>
</html>
"""
LINE_NUMBER = '<span class="lineNumber">%s</span>'
################################### BUILD CSS ##################################
CSSMAP = {
'background': 'background-color',
'caret': None,
'fontStyle': None,
'foreground': 'color',
'invisibles': None,
'lineHighlight': None,
'selection': None,
}
################################################################################
def camelize_string(to_camel):
to_camel = [(l if l in string.ascii_letters else ' ') for l in to_camel]
to_camel = [w.capitalize() for w in "".join(to_camel).split(' ')]
to_camel = "".join(to_camel)
return to_camel[:1].lower() + to_camel[1:] #sometimes to_camel will be ''
def create_title(input_dict):
name, author = input_dict['name'], input_dict['author']
return "Theme: %s\nAuthor: %s" % (name, author)
def get_css_from_tm_settings(settings):
css = {}
for rule in settings:
if rule in CSSMAP and CSSMAP[rule]:
css[CSSMAP[rule]] = settings[rule]
return sorted(css.items())
def create_rule(rule_starter, listing):
rule = rule_starter
css = get_css_from_tm_settings(listing['settings'])
for property_, value in css:
if property_ in ("background-color", 'color'):
value = value[:7]
rule.append(" %s: %s;" % (property_, value))
rule.append("}")
return "\n".join(rule)
def create_main_rule(listing, theme_name):
return create_rule(["pre.%s {" % camelize_string(theme_name)], listing)
def create_scope_rule(listing, theme_name):
name = camelize_string(listing['name'])
return create_rule(["pre.%s .%s {" % (camelize_string(theme_name), name)],
listing)
def get_css_from_theme_dict(theme):
name = theme['name']
settings = theme['settings']
main_settings = settings[0]
css = [create_main_rule(main_settings, name)]
for scope_rule in settings[1:]:
css.append(create_scope_rule(scope_rule, name))
css+=["pre.%s .lineNumber {\n color: #7f909f;\n}" % camelize_string(name)]
return "\n\n".join(css)
def get_scopes(theme):
scopes = {}
for scope in theme["settings"][1:]:
if 'scope' in scope:
scopes[scope['scope']] = camelize_string(scope['name'])
return tuple(scopes.items())
#################################### HELPERS ###################################
def memoize(func):
"Implementation taken from python test suite"
saved = {}
def call(*args):
try:
return saved[args]
except KeyError:
res = func(*args)
saved[args] = res
return res
except TypeError:
# Unhashable argument
return func(*args)
call.func_name = func.func_name
return call
def write_html(html, fn, theme):
with open('%s.html' % fn, 'w') as fh:
html = HTML_TEMPLATE % (fn, camelize_string(theme), html)
fh.write(html.encode(settings.get('encode_as')))
return '%s.html' % fn #TODO: refactor
def get_theme_name(color_scheme):
return splitext(split(color_scheme)[1])[0]
def get_theme_abs_path(color_scheme):
appdata_path = split(sublime.packages_path())[0]
return normpath(join(appdata_path, color_scheme))
@memoize
def get_css_rules(color_scheme):
rules = get_scopes(parse_plist(get_theme_abs_path(color_scheme)))
sublime.set_clipboard(pprint.pformat(rules))
return rules
def get_view_css(view):
color_scheme = view.settings().get('color_scheme')
# theme = get_theme_name(color_scheme)
return get_css_rules(color_scheme)
def write_css(color_scheme, wd=None):
theme = camelize_string(get_theme_name(color_scheme))
theme_p_list = get_theme_abs_path(color_scheme)
css = get_css_from_theme_dict(parse_plist(theme_p_list))
with open(os.path.join(wd, "%s.css" % theme), 'w') as fh:
fh.write(css.encode(settings.get('encode_as')))
return css #TODO fix this
def selections_or_full_buffer_if_empty(view):
if view.has_non_empty_selection_region():
return [view.line(s) for s in view.sel()]
else:
return [sublime.Region(0, view.size())]
################################# COMMANDS #####################################
def scope_events(view, rng=None):
rng = rng or xrange(0, view.size())
opened = []
for pt in rng:
scope = view.scope_name(pt).rstrip()
ch = view.substr(pt)
for i, (os, ss) in enumerate(map(None, opened, scope.split())):
if not ss or os != ss:
for ev in opened[i:]:
yield (pt, 'close', os)
del opened[i:]
if ss and (not os or os != ss):
opened.append(ss)
yield (pt, 'open', ' '.join(opened))
yield (pt, 'char', ch)
# close up shop
for p in opened:
yield (pt, 'close', None)
def sort_by_selector ( scope,
to_sort,
selector_index=0,
keep_non_matches=True ):
candidates =[]
for i, item in enumerate(to_sort):
selector = item[selector_index]
specificity = sublime.score_selector(scope, selector )
if specificity or keep_non_matches:
candidates.append((specificity, i ))
return [to_sort[i[1]] for i in sorted( candidates,
key=lambda c: c[0], reverse=True)]
@memoize
def css_class_for_scope(scope, color_scheme):
css_rules = get_css_rules(color_scheme)
candidates = sort_by_selector( scope, css_rules, keep_non_matches=False)
if candidates:
return candidates[0][1]
class HtmlExportCommand(sublime_plugin.TextCommand):
def run(self, edit, **kw):
view = self.view
t = time.time()
s = Settings(kw)
# Perf boost?
add_line_numbers = s.add_line_numbers
copy_css_to_clipboard = s.copy_css_to_clipboard
copy_html_to_clipboard = s.copy_html_to_clipboard
open_html_in_browser = s.open_html_in_browser
open_html_in_editor = s.open_html_in_editor
color_scheme = view.settings().get('color_scheme')
theme = get_theme_name(color_scheme)
html = ["<pre class='%s'>" % camelize_string(theme)]
selections = selections_or_full_buffer_if_empty(view)
ln_cols = len(`view.rowcol(selections[-1].end()-1)[0]`)
for i, sel in enumerate(selections):
if i > 0: html += [LINE_NUMBER % ("\n\n%s\n\n" % (ln_cols * '.'))]
if add_line_numbers:
current_line_number = view.rowcol(sel.begin())[0] + 1
line_numbers_template = LINE_NUMBER % ("%"+ `ln_cols` + "d ")
html += [line_numbers_template % current_line_number]
for pt, event, value in scope_events(view, xrange(sel.begin(), sel.end())):
if event == 'open':
kls = css_class_for_scope(value, color_scheme)
html.append("<span class='%s'>" % kls)
elif event == 'close':
html.append("</span>")
else: # event == 'char'
html.append(cgi.escape(value))
if add_line_numbers and value == '\n':
current_line_number += 1
html += [line_numbers_template % current_line_number]
html = "".join(html + ["</pre>"])
sublime.status_message (
"HTML and CSS conversion complete: %s " % (time.time() -t) )
write_out_html = open_html_in_editor or open_html_in_browser
if write_out_html:
html_file = write_html(html, view.file_name(), theme) # HACK TODO
css_string = write_css(color_scheme, wd=os.path.dirname(
str(view.file_name())))
if open_html_in_browser: webbrowser.open(html_file)
if open_html_in_editor: view.window().open_file(html_file)
clipboard = ''
if copy_css_to_clipboard:
# compress the css
css = ' '.join([l.strip('\n') for l in css_string.split('\n')])
clipboard += ' '.join([w.strip(' ') for w in css.split(' ')]) + '\n'
if copy_html_to_clipboard: clipboard += html
if clipboard:
sublime.set_clipboard(clipboard)
################################################################################
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment