Files
2013-04-04 08:54:25 -04:00

463 lines
17 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# -*- coding: utf-8 -*-
import json
import os
import re
import sublime
def import_dir(name, fromlist=()):
PACKAGE_EXT = '.sublime-package'
dirname = os.path.basename(os.path.dirname(os.path.realpath(__file__)))
if dirname.endswith(PACKAGE_EXT):
dirname = dirname[:-len(PACKAGE_EXT)]
return __import__('{0}.{1}'.format(dirname, name), fromlist=fromlist)
try:
get_flat_css = import_dir('css_dict_driver', ('get_flat_css',)).get_flat_css
except ImportError:
from css_dict_driver import get_flat_css
try:
imp = import_dir('probe', ('hayaku_extract', 'sub_string'))
hayaku_extract, sub_string = imp.hayaku_extract, imp.sub_string
except ImportError:
from probe import hayaku_extract, sub_string
COLOR_REGEX = re.compile(r'#([0-9a-fA-F]{3,6})')
COMPLEX_COLOR_REGEX = re.compile(r'^\s*(#?([a-fA-F\d]{3}|[a-fA-F\d]{6})|(rgb|hsl)a?\([^\)]+\))\s*$')
IMAGE_REGEX = re.compile(r'^\s*([^\s]+\.(jpg|jpeg|gif|png))\s*$')
CAPTURING_GROUPS = re.compile(r'(?<!\\)\((?!\?[^<])')
CAPTURES = re.compile(r'(\(\?|\$)(\d+)|^(\d)')
def align_prefix(property_name, prefix_list, no_unprefixed_property, aligned_prefixes, use_only):
"""Если есть префиксы, сделать шаблон с правильными отступами"""
# if no_unprefixed_property:
# prefix_list = ('-{0}-{1}'.format(prefix_list[0], property_name),)
# skip if `use_only` is empty
if use_only:
prefix_list = [p for p in prefix_list if p in use_only]
if prefix_list:
prefix_list = ['-{0}-{1}'.format(p, property_name) for p in prefix_list]
if not no_unprefixed_property:
prefix_list.append(property_name)
if not aligned_prefixes:
return prefix_list
max_length = max(len(p) for p in prefix_list)
# TODO: сделать сортировку по размеру значений в prefix_list
return tuple((' '*(max_length-len(p))) + p for p in prefix_list)
return (property_name,)
def hex_to_coloralpha(hex):
if len(hex) == 1:
hex = hex*2
return round(float(int(hex, 16)) / 255, 2)
def color_expand(color,alpha):
if not color:
return '#'
if len(color) == 1:
if color == '#':
color = ''
else:
color = color * 3
elif len(color) == 2:
if color[0] == '#':
color = color[1] * 3
else:
color = color * 3
elif len(color) == 3:
if color[0] == '#':
color = color[1:] * 3
else:
color = color
elif len(color) == 4:
if color[0] != '#' and alpha == 1:
alpha = hex_to_coloralpha(color[3])
color = color[:3]
else:
return color
elif len(color) == 5:
if color[0] != '#':
alpha = hex_to_coloralpha(color[3:5])
color = color[:3]
else:
alpha = hex_to_coloralpha(color[4]*2)
color = color[1:4]
elif len(color) == 6:
if color[0] != '#':
pass
else:
alpha = hex_to_coloralpha(color[4:5])
color = color[1:4]
elif len(color) == 7:
color = color[1:]
else:
return color
# Convert color to rgba if there is some alpha
if alpha == '.' or float(alpha) < 1:
if alpha == '.':
alpha = '.${1:5}' # adding caret for entering alpha value
if alpha == '.0' or alpha == 0:
alpha = '0'
if len(color) == 3:
color = color[0] * 2 + color[1] * 2 + color[2] * 2
return "rgba({0},{1},{2},{3})".format(int(color[:2],16), int(color[2:4],16), int(color[4:],16), alpha)
return '#{0}'.format(color)
def length_expand(name, value, unit, options=None):
if options is None:
options = {}
if unit and 'percents'.startswith(unit):
unit = '%'
if isinstance(value, float):
full_unit = options.get('CSS_default_unit_decimal', 'em')
else:
full_unit = options.get('CSS_default_unit', 'px')
if '<number>' in [val for prop, val in get_flat_css() if prop == name] and not options.get('CSS_units_for_unitless_numbers'):
full_unit = ''
if value == 0:
return '0'
if value == '':
return ''
if unit:
units = (val[1:] for key, val in get_flat_css() if key == name and val.startswith('.'))
req_units = [u for u in units if sub_string(u, unit)]
PRIORITY = ("em", "ex", "vw", "vh", "vmin", "vmax" "vm", "ch", "rem",
"px", "cm", "mm", "in", "pt", "pc")
full_unit = hayaku_extract(unit, req_units, PRIORITY)
if not full_unit:
return
return '{0}{1}'.format(value, full_unit)
def expand_value(args, options=None):
if 'keyword-value' in args:
return args['keyword-value']
if args['property-name'] in set(p for p, v in get_flat_css() if v == '<color_values>'):
if 'color' in args and not args['color']:
return '#'
return color_expand(args.get('color', ''),args.get('color_alpha', 1))
elif args['property-name'] in set(p for p, v in get_flat_css() if v.startswith('.')) and 'keyword-value' not in args:
ret = length_expand(args['property-name'], args.get('type-value', ''), args.get('type-name', ''), options)
return ret
elif 'type-value' in args:
return str(args['type-value'])
return args.get('keyword-value', '')
def split_for_snippet(values, offset=0):
split_lefts = [[]]
split_rights = [[]]
parts = 0
new_offset = offset
for value in (v for v in values if len(v) > 1):
for i in range(1, len(value)):
if value[:i] not in [item for sublist in split_lefts for item in sublist] + values:
if len(split_lefts[parts]) > 98:
parts += 1
split_lefts.append([])
split_rights.append([])
split_lefts[parts].append(value[:i])
split_rights[parts].append(value[i:])
new_offset += 1
for index in range(0, parts + 1):
split_lefts[index] = ''.join('({0}$)?'.format(re.escape(i)) for i in split_lefts[index])
split_rights[index] = ''.join('(?{0}:{1})'.format(i+1+offset,re.escape(f)) for i,f in enumerate(split_rights[index]))
return (split_lefts, split_rights, new_offset)
def convert_to_parts(parts):
matches = []
inserts = []
parts_count = 1
# Function for offsetting the captured groups in inserts
def offset_captures(match):
if match.group(3):
return '()' + match.group(3)
else:
number = int(match.group(2))
return match.group(1) + str(number + parts_count)
for part in parts:
matches.append(''.join([
'(?=(',
part['match'],
')?)',
]))
inserts.append(''.join([
'(?',
str(parts_count),
':',
CAPTURES.sub(offset_captures, part['insert']),
')',
]))
# Incrementing the counter, adding the number of internal capturing groups
parts_count += 1 + len(CAPTURING_GROUPS.findall(part['match'] ))
return { "matches": matches, "inserts": inserts }
def generate_snippet(data):
value = data.get('value')
before = ''.join([
'_PROPERTY_',
data.get('colon'),
data.get('space'),
])
after = ''
importance = ''
if data.get('important'):
importance = ' !important'
if value:
after = importance + data.get('semicolon')
else:
if not importance:
importance_splitted = split_for_snippet(["!important"])
importance = ''.join([
'${1/.*?',
importance_splitted[0][0],
'$/',
importance_splitted[1][0],
'/}',
])
befores = convert_to_parts(data["before"])
before = ''.join([
'${1/^',
''.join(befores["matches"]),
'.+$|.*/',
before,
''.join(befores["inserts"]),
'/m}',
])
if data.get('semicolon') == '':
data['semicolon'] = ' '
afters = convert_to_parts(data["after"])
after = ''.join([
'${1/^',
''.join(afters["matches"]),
'.+$|.*/',
''.join(afters["inserts"]),
'/m}',
data.get('autovalues'),
importance,
data.get('semicolon'),
])
value = ''.join([
'${1:',
data.get('default'),
'}',
])
return (before + value + after).replace('{','{{').replace('}','}}').replace('_PROPERTY_','{0}')
def make_template(args, options):
whitespace = options.get('CSS_whitespace_after_colon', '')
disable_semicolon = options.get('CSS_syntax_no_semicolons', False)
disable_colon = options.get('CSS_syntax_no_colons', False)
disable_prefixes = options.get('CSS_prefixes_disable', False)
clipboard = sublime.get_clipboard()
if not whitespace and disable_colon:
whitespace = ' '
value = expand_value(args, options)
if value is None:
return
if value.startswith('[') and value.endswith(']'):
value = False
semicolon = ';'
colon = ':'
if disable_semicolon:
semicolon = ''
if disable_colon:
colon = ''
snippet_parts = {
'colon': colon,
'semicolon': semicolon,
'space': whitespace,
'default': args.get('default-value',''),
'important': args.get('important'),
'before': [],
'after': [],
'autovalues': '',
}
# Handling prefixes
property_ = (args['property-name'],)
if not disable_prefixes:
property_ = align_prefix(
args['property-name'],
args.get('prefixes', []),
args.get('no-unprefixed-property', False) or options.get('CSS_prefixes_no_unprefixed', False),
options.get('CSS_prefixes_align', True),
options.get('CSS_prefixes_only', []),
)
# Replace the parens with a tabstop snippet
# TODO: Move the inside snippets to the corresponding snippets dict
if value and '()' in value:
if value.replace('()', '') in ['rotate','rotateX','rotateY','rotateZ','skew','skewX','skewY']:
value = value.replace('()', '($1${1/^((?!0$)-?(\d*.)?\d+)?.*$/(?1:deg)/m})')
else:
value = value.replace('()', '($1)')
# Do things when there is no value expanded
if not value or value == "#":
if not options.get('CSS_disable_postexpand', False):
auto_values = [val for prop, val in get_flat_css() if prop == args['property-name']]
if auto_values:
units = []
values = []
for p_value in (v for v in auto_values if len(v) > 1):
if p_value.startswith('.'):
units.append(p_value[1:])
elif not p_value.startswith('<'):
values.append(p_value)
values_splitted = split_for_snippet(values)
snippet_values = ''
for index in range(0,len(values_splitted[0])):
snippet_values += ''.join([
'${1/^\s*',
values_splitted[0][index],
'.*/',
values_splitted[1][index],
'/m}',
])
snippet_parts['autovalues'] += snippet_values
snippet_units = ''
# TODO: find out when to use units or colors
# TODO: Rewrite using after
if units and value != "#":
units_splitted = split_for_snippet(units, 4)
snippet_parts['before'].append({
"match": "%$",
"insert": "100"
})
# If there can be `number` in value, don't add `em` automatically
optional_unit_for_snippet = '(?2:(?3::0)em:px)'
if '<number>' in auto_values and not options.get('CSS_units_for_unitless_numbers'):
optional_unit_for_snippet = '(?2:(?3::0):)'
snippet_units = ''.join([
'${1/^\s*((?!0$)(?=.)[\d\-]*(\.)?(\d+)?((?=.)',
units_splitted[0][0],
')?$)?.*/(?4:',
units_splitted[1][0],
':(?1:' + optional_unit_for_snippet + '))/m}',
])
snippet_parts['autovalues'] += snippet_units
# Adding snippets for colors
if value == "#":
value = ''
# Insert hash and doubling letters
snippet_parts['before'].append({
"match": "([0-9a-fA-F]{1,6}|[0-9a-fA-F]{3,6}\s*(!\w*\s*)?)$",
"insert": "#"
})
snippet_parts['after'].append({
"match": "#?([0-9a-fA-F]{1,2})$",
"insert": "(?1:$1$1)"
})
# Insert `rgba` thingies
snippet_parts['before'].append({
"match": "(\d{1,3}%?),(\.)?.*$",
"insert": "rgba\((?2:$1,$1,)"
})
snippet_parts['after'].append({
"match": "(\d{1,3}%?),(\.)?(.+)?$",
"insert": "(?2:(?3::5):(?3::$1,$1,1))\)"
})
# Getting the value from the clipboard
# TODO: Move to the whole clipboard2default function
check_clipboard_for_color = COMPLEX_COLOR_REGEX.match(clipboard)
if check_clipboard_for_color and 'colors' in options.get('CSS_clipboard_defaults'):
snippet_parts['default'] = check_clipboard_for_color.group(1)
# TODO: move this out of `if not value`,
# so we could use it for found `url()` values
if '<url>' in auto_values:
snippet_parts['before'].append({
"match": "[^\s]+\.(jpg|jpeg|gif|png)$",
"insert": "url\("
})
snippet_parts['after'].append({
"match": "[^\s]+\.(jpg|jpeg|gif|png)$",
"insert": "\)"
})
check_clipboard_for_image = IMAGE_REGEX.match(clipboard)
if check_clipboard_for_image and 'images' in options.get('CSS_clipboard_defaults'):
quote_symbol = ''
if options.get('CSS_syntax_url_quotes'):
quote_symbol = options.get('CSS_syntax_quote_symbol')
snippet_parts['default'] = 'url(' + quote_symbol + check_clipboard_for_image.group(1) + quote_symbol + ')'
snippet_parts['value'] = value or ''
snippet = generate_snippet(snippet_parts)
# Apply settings to the colors in the values
def restyle_colors(match):
color = match.group(1)
# Change case of the colors in the value
if options.get('CSS_colors_case').lower() in ('uppercase' 'upper'):
color = color.upper()
elif options.get('CSS_colors_case').lower() in ('lowercase' 'lower'):
color = color.lower()
# Make colors short or longhand
if options.get('CSS_colors_length').lower() in ('short' 'shorthand') and len(color) == 6:
if color[0] == color[1] and color[2] == color[3] and color[4] == color[5]:
color = color[0] + color[2] + color[4]
elif options.get('CSS_colors_length').lower() in ('long' 'longhand') and len(color) == 3:
color = color[0] * 2 + color[1] * 2 + color[2] * 2
return '#' + color
snippet = COLOR_REGEX.sub(restyle_colors, snippet)
# Apply setting of the prefered quote symbol
if options.get('CSS_syntax_quote_symbol') == "'" and '"' in snippet:
snippet = snippet.replace('"',"'")
if options.get('CSS_syntax_quote_symbol') == '"' and "'" in snippet:
snippet = snippet.replace("'",'"')
newline_ending = ''
if options.get('CSS_newline_after_expand'):
newline_ending = '\n'
return '\n'.join(snippet.format(prop) for prop in property_) + newline_ending
# TODO
# display: -moz-inline-box;
# display: inline-block;
# background-image: -webkit-linear-gradient(top,rgba(255,255,255,0.6),rgba(255,255,255,0));
# background-image: -moz-linear-gradient(top,rgba(255,255,255,0.6),rgba(255,255,255,0));
# background-image: -o-linear-gradient(top,rgba(255,255,255,0.6),rgba(255,255,255,0));
# background-image: linear-gradient(top,rgba(255,255,255,0.6),rgba(255,255,255,0));