def do_build(options, args): if options.output is not None: out = open(options.output, 'wb') else: out = sys.stdout # Get the unencoded stream on Python 3 out = getattr(out, 'buffer', out) css = Scss(scss_opts={ 'style': options.style, 'debug_info': options.debug_info, }) if args: source_files = [ SourceFile.from_file(sys.stdin, "<stdin>", is_sass=options.is_sass) if path == '-' else SourceFile.from_filename( path, is_sass=options.is_sass) for path in args ] else: source_files = [ SourceFile.from_file(sys.stdin, "<stdin>", is_sass=options.is_sass) ] encodings = set(source.encoding for source in source_files) if len(encodings) > 1: sys.stderr.write("Can't combine these files! " "They have different encodings: {0}\n".format( ', '.join(encodings))) sys.exit(3) output = css.compile(source_files=source_files) out.write(output.encode(source_files[0].encoding)) for f, t in profiling.items(): sys.stderr.write("%s took %03fs" % (f, t))
def __call__(self, scss, system): request = system.get('request') response = request.response response.cache_control.max_age = 3600 ct = response.content_type if ct == response.default_content_type: response.content_type = 'text/css' css = self.cache.get(request.url) if css is not None: return css asset_path = self.info.settings['scss.asset_path'].split() parser = OscadScss(scss_opts=self.options, search_paths=asset_path) dirname, filename = os.path.split(scss) try: source_file = SourceFile('split', '$icon-font-path: "{0}";\n' '@import "{1}"'.format( request.static_path('bootstrap/fonts/'), filename)) source_file.parent_dir = dirname css = parser.compile(source_file=source_file) except SassError as e: raise HTTPNotFound(e) self.cache[request.url] = css return css
def do_build(options, args): if options.output is not None: out = open(options.output, 'wb') else: out = sys.stdout # Get the unencoded stream on Python 3 out = getattr(out, 'buffer', out) css = Scss(scss_opts={ 'style': options.style, 'debug_info': options.debug_info, }) if args: source_files = [ SourceFile.from_file(sys.stdin, "<stdin>", is_sass=options.is_sass) if path == '-' else SourceFile.from_filename(path, is_sass=options.is_sass) for path in args ] else: source_files = [ SourceFile.from_file(sys.stdin, "<stdin>", is_sass=options.is_sass)] encodings = set(source.encoding for source in source_files) if len(encodings) > 1: sys.stderr.write( "Can't combine these files! " "They have different encodings: {0}\n" .format(', '.join(encodings)) ) sys.exit(3) output = css.compile(source_files=source_files) out.write(output.encode(source_files[0].encoding)) for f, t in profiling.items(): sys.stderr.write("%s took %03fs" % (f, t))
def Compilation(self, scss_string=None, scss_file=None, super_selector=None, filename=None, is_sass=None, line_numbers=True, source_file=None): if super_selector: self.super_selector = super_selector + ' ' self.reset() if scss_string is not None: source_file = SourceFile.from_string(scss_string, filename, is_sass, line_numbers) elif scss_file is not None: source_file = SourceFile.from_filename(scss_file, filename, is_sass, line_numbers) if source_file is not None: # Clear the existing list of files self.source_files = [] self.source_file_index = dict() self.source_files.append(source_file) self.source_file_index[source_file.filename] = source_file # this will compile and manage rule: child objects inside of a node self.parse_children() # this will manage @extends self.apply_extends() rules_by_file, css_files = self.parse_properties() all_rules = 0 all_selectors = 0 exceeded = '' final_cont = '' files = len(css_files) for source_file in css_files: rules = rules_by_file[source_file] fcont, total_rules, total_selectors = self.create_css(rules) all_rules += total_rules all_selectors += total_selectors if files > 1 and self.scss_opts.get('debug_info', False): if source_file.is_string: final_cont += "/* %s %s generated add up to a total of %s %s accumulated%s */\n" % ( total_selectors, 'selector' if total_selectors == 1 else 'selectors', all_selectors, 'selector' if all_selectors == 1 else 'selectors', exceeded) else: final_cont += "/* %s %s generated from '%s' add up to a total of %s %s accumulated%s */\n" % ( total_selectors, 'selector' if total_selectors == 1 else 'selectors', source_file.filename, all_selectors, 'selector' if all_selectors == 1 else 'selectors', exceeded) final_cont += fcont return final_cont
def _do_import(self, rule, scope, block): """ Implements @import Load and import mixins and functions and rules """ # Protect against going to prohibited places... if any(scary_token in block.argument for scary_token in ('..', '://', 'url(')): rule.properties.append((block.prop, None)) return names = block.argument.split(',') for name in names: name = dequote(name.strip()) full_name, relpath, content, seen_paths = self._load_file(rule, name) if full_name is None: load_paths_msg =\ "\nLoad paths:\n\t%s" % "\n\t".join(seen_paths) raise ScssNotFound( "File to import not found or unreadable: '%s' (%s)%s", name, rule.file_and_line, load_paths_msg) source_file = SourceFile(full_name, content) source_file.parent_dir = relpath import_key = (name, source_file.parent_dir) if rule.namespace.has_import(import_key): # If already imported in this scope, skip continue _rule = SassRule( source_file=source_file, lineno=block.lineno, import_key=import_key, unparsed_contents=source_file.contents, # rule options=rule.options, properties=rule.properties, extends_selectors=rule.extends_selectors, ancestry=rule.ancestry, namespace=rule.namespace, ) rule.namespace.add_import(import_key, rule.import_key, rule.file_and_line) self.manage_children(_rule, scope)
def _find_source_file(self, filename, relative_to=None): paths = self.get_possible_import_paths(filename, relative_to) log.debug("Searching for %s in %s", filename, paths) for name in paths: full_filename, storage = self.get_file_and_storage(name) if full_filename: if full_filename not in self.source_file_index: with storage.open(full_filename) as f: source = f.read() source_file = SourceFile(full_filename, source) # SourceFile.__init__ calls os.path.realpath on this, we don't want # that, we want them to remain relative. source_file.parent_dir = os.path.dirname(name) self.source_files.append(source_file) self.source_file_index[full_filename] = source_file return self.source_file_index[full_filename]
def __init__(self, is_sass=False): self.css = Scss() self.namespace = self.css.root_namespace self.options = self.css.scss_opts self.source_file = SourceFile.from_string('', '<shell>', line_numbers=False, is_sass=is_sass) self.calculator = Calculator(self.namespace)
def _find_source_file(self, filename, relative_to=None): paths = self.get_possible_import_paths(filename, relative_to) log.debug('Searching for %s in %s', filename, paths) for name in paths: full_filename, storage = self.get_file_and_storage(name) if full_filename: if full_filename not in self.source_file_index: with storage.open(full_filename) as f: source = f.read() source_file = SourceFile( full_filename, source, ) # SourceFile.__init__ calls os.path.realpath on this, we don't want # that, we want them to remain relative. source_file.parent_dir = os.path.dirname(name) self.source_files.append(source_file) self.source_file_index[full_filename] = source_file return self.source_file_index[full_filename]
def _do_import(self, rule, scope, block): """ Implements @import using the django storages API. """ # Protect against going to prohibited places... if any(scary_token in block.argument for scary_token in ('..', '://', 'url(')): rule.properties.append((block.prop, None)) return full_filename = None names = block.argument.split(',') for name in names: name = dequote(name.strip()) relative_to = rule.source_file.parent_dir source_file = self._find_source_file(name, relative_to) if source_file is None: i_codestr = self._do_magic_import(rule, scope, block) if i_codestr is not None: source_file = SourceFile.from_string(i_codestr) self.source_files.append(source_file) self.source_file_index[full_filename] = source_file if source_file is None: log.warn("File to import not found or unreadable: '%s' (%s)", name, rule.file_and_line) continue import_key = (name, source_file.parent_dir) if rule.namespace.has_import(import_key): # If already imported in this scope, skip continue _rule = SassRule( source_file=source_file, lineno=block.lineno, import_key=import_key, unparsed_contents=source_file.contents, # rule options=rule.options, properties=rule.properties, extends_selectors=rule.extends_selectors, ancestry=rule.ancestry, namespace=rule.namespace, ) rule.namespace.add_import(import_key, rule.import_key, rule.file_and_line) self.manage_children(_rule, scope)
def main(): logging.basicConfig(format="%(levelname)s: %(message)s") from optparse import OptionGroup, OptionParser, SUPPRESS_HELP parser = OptionParser(usage="Usage: %prog [options] [file]", description="Converts Scss files to CSS.", add_help_option=False) parser.add_option("-i", "--interactive", action="store_true", help="Run an interactive Scss shell") parser.add_option("-w", "--watch", metavar="DIR", help="Watch the files in DIR, and recompile when they change") parser.add_option("-r", "--recursive", action="store_true", help="Also watch directories inside of the watch directory") parser.add_option("-o", "--output", metavar="PATH", help="Write output to PATH (a directory if using watch, a file otherwise)") parser.add_option("-s", "--suffix", metavar="STRING", help="If using watch, a suffix added to the output filename (i.e. filename.STRING.css)") parser.add_option("--time", action="store_true", help="Display compliation times") parser.add_option("--debug-info", action="store_true", help="Turns on scss's debugging information") parser.add_option("--no-debug-info", action="store_false", dest="debug_info", default=False, help="Turns off scss's debugging information") parser.add_option("-t", "--test", action="store_true", help=SUPPRESS_HELP) parser.add_option("-C", "--no-compress", action="store_false", dest="compress", default=True, help="Don't minify outputted CSS") parser.add_option("-?", action="help", help=SUPPRESS_HELP) parser.add_option("-h", "--help", action="help", help="Show this message and exit") parser.add_option("-v", "--version", action="store_true", help="Print version and exit") paths_group = OptionGroup(parser, "Resource Paths") paths_group.add_option("-I", "--load-path", metavar="PATH", action="append", dest="load_paths", help="Add a scss import path, may be given multiple times") paths_group.add_option("-S", "--static-root", metavar="PATH", dest="static_root", help="Static root path (Where images and static resources are located)") paths_group.add_option("-A", "--assets-root", metavar="PATH", dest="assets_root", help="Assets root path (Sprite images will be created here)") paths_group.add_option("-a", "--assets-url", metavar="URL", dest="assets_url", help="URL to reach the files in your assets_root") parser.add_option_group(paths_group) (options, args) = parser.parse_args() # General runtime configuration config.VERBOSITY = 0 if options.time: config.VERBOSITY = 2 if options.static_root is not None: config.STATIC_ROOT = options.static_root if options.assets_root is not None: config.ASSETS_ROOT = options.assets_root if options.load_paths is not None: # TODO: Convert global LOAD_PATHS to a list. Use it directly. # Doing the above will break backwards compatibility! if hasattr(config.LOAD_PATHS, 'split'): load_path_list = [p.strip() for p in config.LOAD_PATHS.split(',')] else: load_path_list = list(config.LOAD_PATHS) for path_param in options.load_paths: for p in path_param.replace(os.pathsep, ',').replace(';', ',').split(','): p = p.strip() if p and p not in load_path_list: load_path_list.append(p) # TODO: Remove this once global LOAD_PATHS is a list. if hasattr(config.LOAD_PATHS, 'split'): config.LOAD_PATHS = ','.join(load_path_list) else: config.LOAD_PATHS = load_path_list if options.assets_url is not None: config.ASSETS_URL = options.assets_url # Execution modes if options.test: import doctest doctest.testfile('tests.rst') elif options.version: print BUILD_INFO elif options.interactive: from pprint import pprint try: import atexit import readline histfile = os.path.expanduser('~/.scss-history') try: readline.read_history_file(histfile) except IOError: pass atexit.register(readline.write_history_file, histfile) except ImportError: pass css = Scss() context = css.scss_vars options = css.scss_opts source_file = SourceFile.from_string('', '<shell>') rule = SassRule(source_file, context=context, options=options) print "Welcome to %s interactive shell" % BUILD_INFO while True: try: s = raw_input('>>> ').strip() except EOFError: print break except KeyboardInterrupt: print break if s in ('exit', 'quit'): break for s in s.split(';'): s = source_file.prepare_source(s.strip(), line_numbers=False) if not s: continue elif s.startswith('@'): properties = [] children = deque() SassRule(source_file, context=context, options=options, properties=properties) code, name = (s.split(None, 1) + [''])[:2] if code == '@option': css._settle_options(rule, [''], set(), children, None, None, s, None, code, name) continue elif code == '@import': css._do_import(rule, [''], set(), children, None, None, s, None, code, name) continue elif code == '@include': final_cont = '' css._do_include(rule, [''], set(), children, None, None, s, None, code, name) code = css._print_properties(properties).rstrip('\n') if code: final_cont += code if children: css.children.extendleft(children) css.parse_children() code = css._create_css(css.rules).rstrip('\n') if code: final_cont += code final_cont = css.post_process(final_cont) print final_cont continue elif s == 'ls' or s.startswith('show(') or s.startswith('show ') or s.startswith('ls(') or s.startswith('ls '): m = re.match(r'(?:show|ls)(\()?\s*([^,/\\) ]*)(?:[,/\\ ]([^,/\\ )]+))*(?(1)\))', s, re.IGNORECASE) if m: name = m.group(2) code = m.group(3) name = name and name.strip().rstrip('s') # remove last 's' as in functions code = code and code.strip() if not name: pprint(sorted(['vars', 'options', 'mixins', 'functions'])) elif name in ('v', 'var', 'variable'): if code == '*': d = dict((k, v) for k, v in context.items()) pprint(d) elif code: d = dict((k, v) for k, v in context.items() if code in k) pprint(d) else: d = dict((k, v) for k, v in context.items() if k.startswith('$') and not k.startswith('$__')) pprint(d) elif name in ('o', 'opt', 'option'): if code == '*': d = dict((k, v) for k, v in options.items()) pprint(d) elif code: d = dict((k, v) for k, v in options.items() if code in k) pprint(d) else: d = dict((k, v) for k, v in options.items() if not k.startswith('@')) pprint(d) elif name in ('m', 'mix', 'mixin', 'f', 'func', 'funct', 'function'): if name.startswith('m'): name = 'mixin' elif name.startswith('f'): name = 'function' if code == '*': d = dict((k[len(name) + 2:], v) for k, v in options.items() if k.startswith('@' + name + ' ')) pprint(sorted(d)) elif code: d = dict((k, v) for k, v in options.items() if k.startswith('@' + name + ' ') and code in k) seen = set() for k, mixin in d.items(): mixin = getattr(mixin, 'mixin', mixin) fn_name, _, _ = k.partition(':') if fn_name not in seen: seen.add(fn_name) print fn_name + '(' + ', '.join(p + (': ' + mixin[1].get(p) if p in mixin[1] else '') for p in mixin[0]) + ') {' print ' ' + '\n '.join(l for l in mixin[2].split('\n')) print '}' else: d = dict((k[len(name) + 2:].split(':')[0], v) for k, v in options.items() if k.startswith('@' + name + ' ')) pprint(sorted(d)) continue elif s.startswith('$') and (':' in s or '=' in s): prop, value = [a.strip() for a in _prop_split_re.split(s, 1)] prop = css.calculator.do_glob_math(prop, context, options, rule, True) value = css.calculator.calculate(value, context, rule) context[prop] = value continue s = to_str(css.calculator.calculate(s, context, rule)) s = css.post_process(s) print s print "Bye!" elif options.watch: import time try: from watchdog.observers import Observer from watchdog.events import PatternMatchingEventHandler except ImportError: sys.stderr.write("Using watch functionality requires the `watchdog` library: http://pypi.python.org/pypi/watchdog/") sys.exit(1) if options.output and not os.path.isdir(options.output): sys.stderr.write("watch file output directory is invalid: '%s'" % (options.output)) sys.exit(2) class ScssEventHandler(PatternMatchingEventHandler): def __init__(self, *args, **kwargs): super(ScssEventHandler, self).__init__(*args, **kwargs) self.css = Scss(scss_opts={ 'compress': options.compress, 'debug_info': options.debug_info, }) self.output = options.output self.suffix = options.suffix def is_valid(self, path): return os.path.isfile(path) and path.endswith(".scss") and not os.path.basename(path).startswith("_") def process(self, path): if os.path.isdir(path): for f in os.listdir(path): full = os.path.join(path, f) if self.is_valid(full): self.compile(full) elif self.is_valid(path): self.compile(path) def compile(self, src_path): fname = os.path.basename(src_path) if fname.endswith(".scss"): fname = fname[:-5] if self.suffix: fname += "." + self.suffix fname += ".css" else: # you didn't give me a file of the correct type! return False if self.output: dest_path = os.path.join(self.output, fname) else: dest_path = os.path.join(os.path.dirname(src_path), fname) print "Compiling %s => %s" % (src_path, dest_path) src_file = open(src_path) dest_file = open(dest_path, 'w') dest_file.write(self.css.compile(src_file.read())) def on_moved(self, event): super(ScssEventHandler, self).on_moved(event) self.process(event.dest_path) def on_created(self, event): super(ScssEventHandler, self).on_created(event) self.process(event.src_path) def on_modified(self, event): super(ScssEventHandler, self).on_modified(event) self.process(event.src_path) event_handler = ScssEventHandler(patterns="*.scss") observer = Observer() observer.schedule(event_handler, path=options.watch, recursive=options.recursive) observer.start() try: while True: time.sleep(1) except KeyboardInterrupt: observer.stop() observer.join() else: if options.output is not None: output = open(options.output, 'wt') else: output = sys.stdout css = Scss(scss_opts={ 'compress': options.compress, 'debug_info': options.debug_info, }) if args: for path in args: finput = open(path, 'rt') output.write(css.compile(finput.read())) else: output.write(css.compile(sys.stdin.read())) for f, t in profiling.items(): print >>sys.stderr, "%s took %03fs" % (f, t)
def Compilation(self, scss_string=None, scss_file=None, super_selector=None, filename=None, is_sass=None, line_numbers=True, relative_to=None): """ Overwritten to call _find_source_file instead of SourceFile.from_filename. Also added the relative_to option. """ if not os.path.exists(config.ASSETS_ROOT): os.makedirs(config.ASSETS_ROOT) if super_selector: self.super_selector = super_selector + ' ' self.reset() source_file = None if scss_string is not None: source_file = SourceFile.from_string(scss_string, filename, is_sass, line_numbers) # Set the parent_dir to be something meaningful instead of the # current working directory, which is never correct for DjangoScss. source_file.parent_dir = relative_to elif scss_file is not None: # Call _find_source_file instead of SourceFile.from_filename source_file = self._find_source_file(scss_file) if source_file is not None: # Clear the existing list of files self.source_files = [] self.source_file_index = dict() self.source_files.append(source_file) self.source_file_index[source_file.filename] = source_file # this will compile and manage rule: child objects inside of a node self.parse_children() # this will manage @extends self.apply_extends() rules_by_file, css_files = self.parse_properties() all_rules = 0 all_selectors = 0 exceeded = '' final_cont = '' files = len(css_files) for source_file in css_files: rules = rules_by_file[source_file] fcont, total_rules, total_selectors = self.create_css(rules) all_rules += total_rules all_selectors += total_selectors if not exceeded and all_selectors > 4095: exceeded = " (IE exceeded!)" log.error("Maximum number of supported selectors in Internet Explorer (4095) exceeded!") if files > 1 and self.scss_opts.get('debug_info', False): if source_file.is_string: final_cont += "/* %s %s generated add up to a total of %s %s accumulated%s */\n" % ( total_selectors, 'selector' if total_selectors == 1 else 'selectors', all_selectors, 'selector' if all_selectors == 1 else 'selectors', exceeded) else: final_cont += "/* %s %s generated from '%s' add up to a total of %s %s accumulated%s */\n" % ( total_selectors, 'selector' if total_selectors == 1 else 'selectors', source_file.filename, all_selectors, 'selector' if all_selectors == 1 else 'selectors', exceeded) final_cont += fcont return final_cont
def main(): logging.basicConfig(format="%(levelname)s: %(message)s") from optparse import OptionGroup, OptionParser, SUPPRESS_HELP parser = OptionParser(usage="Usage: %prog [options] [file]", description="Converts Scss files to CSS.", add_help_option=False) parser.add_option("-i", "--interactive", action="store_true", help="Run an interactive Scss shell") parser.add_option( "-w", "--watch", metavar="DIR", help="Watch the files in DIR, and recompile when they change") parser.add_option( "-r", "--recursive", action="store_true", default=False, help="Also watch directories inside of the watch directory") parser.add_option( "-o", "--output", metavar="PATH", help= "Write output to PATH (a directory if using watch, a file otherwise)") parser.add_option( "-s", "--suffix", metavar="STRING", help= "If using watch, a suffix added to the output filename (i.e. filename.STRING.css)" ) parser.add_option("--time", action="store_true", help="Display compliation times") parser.add_option("--debug-info", action="store_true", help="Turns on scss's debugging information") parser.add_option("--no-debug-info", action="store_false", dest="debug_info", default=False, help="Turns off scss's debugging information") parser.add_option("-t", "--test", action="store_true", help=SUPPRESS_HELP) parser.add_option("-C", "--no-compress", action="store_false", dest="compress", default=True, help="Don't minify outputted CSS") parser.add_option("-?", action="help", help=SUPPRESS_HELP) parser.add_option("-h", "--help", action="help", help="Show this message and exit") parser.add_option("-v", "--version", action="store_true", help="Print version and exit") paths_group = OptionGroup(parser, "Resource Paths") paths_group.add_option( "-I", "--load-path", metavar="PATH", action="append", dest="load_paths", help="Add a scss import path, may be given multiple times") paths_group.add_option( "-S", "--static-root", metavar="PATH", dest="static_root", help="Static root path (Where images and static resources are located)" ) paths_group.add_option( "-A", "--assets-root", metavar="PATH", dest="assets_root", help="Assets root path (Sprite images will be created here)") paths_group.add_option("-a", "--assets-url", metavar="URL", dest="assets_url", help="URL to reach the files in your assets_root") paths_group.add_option( "--cache-root", metavar="PATH", dest="cache_root", help="Cache root path (Cache files will be created here)") parser.add_option_group(paths_group) parser.add_option("--sass", action="store_true", dest="is_sass", default=None, help="Sass mode") (options, args) = parser.parse_args() # General runtime configuration config.VERBOSITY = 0 if options.time: config.VERBOSITY = 2 if options.static_root is not None: config.STATIC_ROOT = options.static_root if options.assets_root is not None: config.ASSETS_ROOT = options.assets_root if options.cache_root is not None: config.CACHE_ROOT = options.cache_root if options.load_paths is not None: # TODO: Convert global LOAD_PATHS to a list. Use it directly. # Doing the above will break backwards compatibility! if hasattr(config.LOAD_PATHS, 'split'): load_path_list = [p.strip() for p in config.LOAD_PATHS.split(',')] else: load_path_list = list(config.LOAD_PATHS) for path_param in options.load_paths: for p in path_param.replace(os.pathsep, ',').replace(';', ',').split(','): p = p.strip() if p and p not in load_path_list: load_path_list.append(p) # TODO: Remove this once global LOAD_PATHS is a list. if hasattr(config.LOAD_PATHS, 'split'): config.LOAD_PATHS = ','.join(load_path_list) else: config.LOAD_PATHS = load_path_list if options.assets_url is not None: config.ASSETS_URL = options.assets_url # Execution modes if options.test: import doctest doctest.testfile('tests/tests.rst') elif options.version: print(BUILD_INFO) elif options.interactive: from pprint import pprint try: import atexit import readline histfile = os.path.expanduser('~/.scss-history') try: readline.read_history_file(histfile) except IOError: pass atexit.register(readline.write_history_file, histfile) except ImportError: pass is_sass = options.is_sass css = Scss() context = css.scss_vars options = css.scss_opts source_file = SourceFile.from_string('', '<shell>', line_numbers=False) rule = SassRule(source_file, context=context, options=options, is_sass=is_sass) print("Welcome to %s interactive shell" % (BUILD_INFO, )) while True: try: s = raw_input('>>> ').strip() except EOFError: print break except KeyboardInterrupt: print break if s in ('exit', 'quit'): break for s in s.split(';'): s = source_file.prepare_source(s.strip()) if not s: continue elif s.startswith('@'): properties = [] children = deque() SassRule(source_file, context=context, options=options, properties=properties) code, name = (s.split(None, 1) + [''])[:2] if code == '@option': css._settle_options(rule, [''], set(), children, None, None, s, None, code, name) continue elif code == '@import': css._do_import(rule, [''], set(), children, None, None, s, None, code, name) continue elif code == '@include': final_cont = '' css._do_include(rule, [''], set(), children, None, None, s, None, code, name) code = css._print_properties(properties).rstrip('\n') if code: final_cont += code if children: css.children.extendleft(children) css.parse_children() code = css._create_css(css.rules).rstrip('\n') if code: final_cont += code final_cont = css.post_process(final_cont) print(final_cont) continue elif s == 'ls' or s.startswith('show(') or s.startswith( 'show ') or s.startswith('ls(') or s.startswith('ls '): m = re.match( r'(?:show|ls)(\()?\s*([^,/\\) ]*)(?:[,/\\ ]([^,/\\ )]+))*(?(1)\))', s, re.IGNORECASE) if m: name = m.group(2) code = m.group(3) name = name and name.strip().rstrip( 's') # remove last 's' as in functions code = code and code.strip() if not name: pprint( sorted( ['vars', 'options', 'mixins', 'functions'])) elif name in ('v', 'var', 'variable'): if code == '*': d = dict((k, v) for k, v in context.items()) pprint(d) elif code: d = dict((k, v) for k, v in context.items() if code in k) pprint(d) else: d = dict((k, v) for k, v in context.items() if k.startswith('$') and not k.startswith('$__')) pprint(d) elif name in ('o', 'opt', 'option'): if code == '*': d = dict((k, v) for k, v in options.items()) pprint(d) elif code: d = dict((k, v) for k, v in options.items() if code in k) pprint(d) else: d = dict((k, v) for k, v in options.items() if not k.startswith('@')) pprint(d) elif name in ('m', 'mix', 'mixin', 'f', 'func', 'funct', 'function'): if name.startswith('m'): name = 'mixin' elif name.startswith('f'): name = 'function' if code == '*': d = dict((k[len(name) + 2:], v) for k, v in options.items() if k.startswith('@' + name + ' ')) pprint(sorted(d)) elif code: d = dict((k, v) for k, v in options.items() if k.startswith('@' + name + ' ') and code in k) seen = set() for k, mixin in d.items(): mixin = getattr(mixin, 'mixin', mixin) fn_name, _, _ = k.partition(':') if fn_name not in seen: seen.add(fn_name) print(fn_name + '(' + ', '.join( p + (': ' + mixin[1].get(p) if p in mixin[1] else '') for p in mixin[0]) + ') {') print(' ' + '\n '.join( l for l in mixin[2].split('\n'))) print('}') else: d = dict((k[len(name) + 2:].split(':')[0], v) for k, v in options.items() if k.startswith('@' + name + ' ')) pprint(sorted(d)) continue elif s.startswith('$') and (':' in s or '=' in s): prop, value = [ a.strip() for a in _prop_split_re.split(s, 1) ] prop = css.calculator.do_glob_math(prop, context, options, rule, True) value = css.calculator.calculate(value, context, rule) context[prop] = value continue s = to_str(css.calculator.calculate(s, context, rule)) s = css.post_process(s) print(s) print("Bye!") elif options.watch: import time try: from watchdog.observers import Observer from watchdog.events import PatternMatchingEventHandler except ImportError: sys.stderr.write( "Using watch functionality requires the `watchdog` library: http://pypi.python.org/pypi/watchdog/" ) sys.exit(1) if options.output and not os.path.isdir(options.output): sys.stderr.write("watch file output directory is invalid: '%s'" % (options.output)) sys.exit(2) class ScssEventHandler(PatternMatchingEventHandler): def __init__(self, *args, **kwargs): super(ScssEventHandler, self).__init__(*args, **kwargs) self.css = Scss( scss_opts={ 'compress': options.compress, 'debug_info': options.debug_info, }) self.output = options.output self.suffix = options.suffix def is_valid(self, path): return os.path.isfile(path) and ( path.endswith('.scss') or path.endswith('.sass') ) and not os.path.basename(path).startswith('_') def process(self, path): if os.path.isdir(path): for f in os.listdir(path): full = os.path.join(path, f) if self.is_valid(full): self.compile(full) elif self.is_valid(path): self.compile(path) def compile(self, src_path): fname = os.path.basename(src_path) if fname.endswith('.scss') or fname.endswith('.sass'): fname = fname[:-5] if self.suffix: fname += '.' + self.suffix fname += '.css' else: # you didn't give me a file of the correct type! return False if self.output: dest_path = os.path.join(self.output, fname) else: dest_path = os.path.join(os.path.dirname(src_path), fname) print("Compiling %s => %s" % (src_path, dest_path)) dest_file = open(dest_path, 'w') dest_file.write(self.css.compile(scss_file=src_path)) def on_moved(self, event): super(ScssEventHandler, self).on_moved(event) self.process(event.dest_path) def on_created(self, event): super(ScssEventHandler, self).on_created(event) self.process(event.src_path) def on_modified(self, event): super(ScssEventHandler, self).on_modified(event) self.process(event.src_path) event_handler = ScssEventHandler(patterns=['*.scss', '*.sass']) observer = Observer() observer.schedule(event_handler, path=options.watch, recursive=options.recursive) observer.start() try: while True: time.sleep(1) except KeyboardInterrupt: observer.stop() observer.join() else: if options.output is not None: output = open(options.output, 'wt') else: output = sys.stdout css = Scss(scss_opts={ 'compress': options.compress, 'debug_info': options.debug_info, }) if args: for path in args: output.write( css.compile(scss_file=path, is_sass=options.is_sass)) else: output.write(css.compile(sys.stdin.read(), is_sass=options.is_sass)) for f, t in profiling.items(): sys.stderr.write("%s took %03fs" % (f, t))