class SassRepl(object): def __init__(self, is_sass=False): # TODO it would be lovely to get these out of here, somehow self.namespace = Namespace(variables=_default_scss_vars) self.compiler = Compiler(namespace=self.namespace) self.compilation = self.compiler.make_compilation() self.legacy_compiler_options = {} self.source_file = SourceFile.from_string('', '<shell>', is_sass=is_sass) self.calculator = Calculator(self.namespace) def __call__(self, s): # TODO this is kind of invasive; surely it's possible to do this # without calling only private methods from pprint import pformat if s in ('exit', 'quit'): raise KeyboardInterrupt for s in s.split(';'): s = self.source_file.prepare_source(s.strip()) if not s: continue elif s.startswith('@'): scope = None properties = [] children = deque() rule = SassRule(self.source_file, namespace=self.namespace, legacy_compiler_options=self.legacy_compiler_options, properties=properties) block = UnparsedBlock(rule, 1, s, None) code, name = (s.split(None, 1) + [''])[:2] if code == '@option': self.compilation._at_options(self.calculator, rule, scope, block) continue elif code == '@import': self.compilation._at_import(self.calculator, rule, scope, block) continue elif code == '@include': final_cont = '' self.compilation._at_include(self.calculator, rule, scope, block) code = self.compilation._print_properties(properties).rstrip('\n') if code: final_cont += code if children: self.compilation.children.extendleft(children) self.compilation.parse_children() code = self.compilation._create_css(self.compilation.rules).rstrip('\n') if code: final_cont += code yield 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() ns = self.namespace if not name: yield pformat(list(sorted(['vars', 'options', 'mixins', 'functions']))) elif name in ('v', 'var', 'variable'): variables = dict(ns._variables) if code == '*': pass elif code: variables = dict((k, v) for k, v in variables.items() if code in k) else: variables = dict((k, v) for k, v in variables.items() if not k.startswith('$--')) yield pformat(variables) elif name in ('o', 'opt', 'option'): opts = self.legacy_compiler_options if code == '*': pass elif code: opts = dict((k, v) for k, v in opts.items() if code in k) else: opts = dict((k, v) for k, v in opts.items()) yield pformat(opts) elif name in ('m', 'mix', 'mixin', 'f', 'func', 'funct', 'function'): if name.startswith('m'): funcs = dict(ns._mixins) elif name.startswith('f'): funcs = dict(ns._functions) if code == '*': pass elif code: funcs = dict((k, v) for k, v in funcs.items() if code in k[0]) else: pass # TODO print source when possible yield pformat(funcs) continue elif s.startswith('$') and (':' in s or '=' in s): prop, value = [a.strip() for a in _prop_split_re.split(s, 1)] prop = self.calculator.do_glob_math(prop) value = self.calculator.calculate(value) self.namespace.set_variable(prop, value) continue # TODO respect compress? try: yield(self.calculator.calculate(s).render()) except (SyntaxError, SassEvaluationError) as e: print("%s" % e, file=sys.stderr)
class SassRepl(object): def __init__(self, is_sass=False): # TODO it would be lovely to get these out of here, somehow self.namespace = Namespace(variables=_default_scss_vars) self.compiler = Compiler(namespace=self.namespace) self.compilation = self.compiler.make_compilation() self.legacy_compiler_options = {} self.source_file = SourceFile.from_string('', '<shell>', is_sass=is_sass) self.calculator = Calculator(self.namespace) def __call__(self, s): # TODO this is kind of invasive; surely it's possible to do this # without calling only private methods from pprint import pformat if s in ('exit', 'quit'): raise KeyboardInterrupt for s in s.split(';'): s = self.source_file.prepare_source(s.strip()) if not s: continue elif s.startswith('@'): scope = None properties = [] children = deque() rule = SassRule( self.source_file, namespace=self.namespace, legacy_compiler_options=self.legacy_compiler_options, properties=properties) block = UnparsedBlock(rule, 1, s, None) code, name = (s.split(None, 1) + [''])[:2] if code == '@option': self.compilation._at_options(self.calculator, rule, scope, block) continue elif code == '@import': self.compilation._at_import(self.calculator, rule, scope, block) continue elif code == '@include': final_cont = '' self.compilation._at_include(self.calculator, rule, scope, block) code = self.compilation._print_properties( properties).rstrip('\n') if code: final_cont += code if children: self.compilation.children.extendleft(children) self.compilation.parse_children() code = self.compilation._create_css( self.compilation.rules).rstrip('\n') if code: final_cont += code yield 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() ns = self.namespace if not name: yield pformat( list( sorted( ['vars', 'options', 'mixins', 'functions']))) elif name in ('v', 'var', 'variable'): variables = dict(ns._variables) if code == '*': pass elif code: variables = dict((k, v) for k, v in variables.items() if code in k) else: variables = dict((k, v) for k, v in variables.items() if not k.startswith('$--')) yield pformat(variables) elif name in ('o', 'opt', 'option'): opts = self.legacy_compiler_options if code == '*': pass elif code: opts = dict( (k, v) for k, v in opts.items() if code in k) else: opts = dict((k, v) for k, v in opts.items()) yield pformat(opts) elif name in ('m', 'mix', 'mixin', 'f', 'func', 'funct', 'function'): if name.startswith('m'): funcs = dict(ns._mixins) elif name.startswith('f'): funcs = dict(ns._functions) if code == '*': pass elif code: funcs = dict((k, v) for k, v in funcs.items() if code in k[0]) else: pass # TODO print source when possible yield pformat(funcs) continue elif s.startswith('$') and (':' in s or '=' in s): prop, value = [a.strip() for a in _prop_split_re.split(s, 1)] prop = self.calculator.do_glob_math(prop) value = self.calculator.calculate(value) self.namespace.set_variable(prop, value) continue # TODO respect compress? try: yield (self.calculator.calculate(s).render()) except (SyntaxError, SassEvaluationError) as e: print("%s" % e, file=sys.stderr)
def compile( self, scss_string=None, scss_file=None, source_files=None, super_selector=None, filename=None, is_sass=None, line_numbers=True): """Compile Sass to CSS. Returns a single CSS string. This method is DEPRECATED; see :mod:`scss.compiler` instead. """ # Derive our root namespace self.scss_vars = _default_scss_vars.copy() if self._scss_vars is not None: self.scss_vars.update(self._scss_vars) root_namespace = Namespace( variables=self.scss_vars, functions=self._library, ) # Figure out search paths. Fall back from provided explicitly to # defined globally to just searching the current directory search_paths = ['.'] if self._search_paths is not None: assert not isinstance(self._search_paths, six.string_types), \ "`search_paths` should be an iterable, not a string" search_paths.extend(self._search_paths) else: if config.LOAD_PATHS: if isinstance(config.LOAD_PATHS, six.string_types): # Back-compat: allow comma-delimited search_paths.extend(config.LOAD_PATHS.split(',')) else: search_paths.extend(config.LOAD_PATHS) search_paths.extend(self._scss_opts.get('load_paths', [])) # Normalize a few old styles of options output_style = self._scss_opts.get('style', config.STYLE) if output_style is True: output_style = 'compressed' elif output_style is False: output_style = 'legacy' # Build the compiler compiler = Compiler( namespace=root_namespace, search_path=search_paths, live_errors=self.live_errors, generate_source_map=self._scss_opts.get('debug_info', False), output_style=output_style, warn_unused_imports=self._scss_opts.get('warn_unused', False), super_selector=super_selector or self.super_selector, ) # Gonna add the source files manually compilation = compiler.make_compilation() # Inject the files we know about # TODO how does this work with the expectation of absoluteness if source_files is not None: for source in source_files: compilation.add_source(source) elif scss_string is not None: source = SourceFile.from_string( scss_string, path=filename, is_sass=is_sass, ) compilation.add_source(source) elif scss_file is not None: source = SourceFile.from_filename( scss_file, path=filename, is_sass=is_sass, ) compilation.add_source(source) # Plus the ones from the constructor if self._scss_files: for name, contents in list(self._scss_files.items()): source = SourceFile.from_string(contents, path=name) compilation.add_source(source) return compiler.call_and_catch_errors(compilation.run)
def compile( self, scss_string=None, scss_file=None, source_files=None, super_selector=None, filename=None, is_sass=None, line_numbers=True, import_static_css=False): """Compile Sass to CSS. Returns a single CSS string. This method is DEPRECATED; see :mod:`scss.compiler` instead. """ # Derive our root namespace self.scss_vars = _default_scss_vars.copy() if self._scss_vars is not None: self.scss_vars.update(self._scss_vars) root_namespace = Namespace( variables=self.scss_vars, functions=self._library, ) # Figure out search paths. Fall back from provided explicitly to # defined globally to just searching the current directory search_paths = ['.'] if self._search_paths is not None: assert not isinstance(self._search_paths, six.string_types), \ "`search_paths` should be an iterable, not a string" search_paths.extend(self._search_paths) else: if config.LOAD_PATHS: if isinstance(config.LOAD_PATHS, six.string_types): # Back-compat: allow comma-delimited search_paths.extend(config.LOAD_PATHS.split(',')) else: search_paths.extend(config.LOAD_PATHS) search_paths.extend(self._scss_opts.get('load_paths', [])) # Normalize a few old styles of options output_style = self._scss_opts.get('style', config.STYLE) if output_style is True: output_style = 'compressed' elif output_style is False: output_style = 'legacy' fixed_search_path = [] for path in search_paths: if isinstance(path, six.string_types): fixed_search_path.append(Path(path)) else: fixed_search_path.append(path) # Build the compiler compiler = Compiler( namespace=root_namespace, extensions=[ CoreExtension, ExtraExtension, FontsExtension, CompassExtension, BootstrapExtension, ], search_path=fixed_search_path, import_static_css=import_static_css, live_errors=self.live_errors, generate_source_map=self._scss_opts.get('debug_info', False), output_style=output_style, warn_unused_imports=self._scss_opts.get('warn_unused', False), ignore_parse_errors=config.DEBUG, loops_have_own_scopes=config.CONTROL_SCOPING, undefined_variables_fatal=config.FATAL_UNDEFINED, super_selector=super_selector or self.super_selector, ) # Gonna add the source files manually compilation = compiler.make_compilation() # Inject the files we know about # TODO how does this work with the expectation of absoluteness if source_files is not None: for source in source_files: compilation.add_source(source) elif scss_string is not None: source = SourceFile.from_string( scss_string, relpath=filename, is_sass=is_sass, ) compilation.add_source(source) elif scss_file is not None: # This is now the only way to allow forcibly overriding the # filename a source "thinks" it is with open(scss_file, 'rb') as f: source = SourceFile.from_file( f, relpath=filename or scss_file, is_sass=is_sass, ) compilation.add_source(source) # Plus the ones from the constructor if self._scss_files: for name, contents in list(self._scss_files.items()): source = SourceFile.from_string(contents, relpath=name) compilation.add_source(source) return compiler.call_and_catch_errors(compilation.run)
def compile(self, scss_string=None, scss_file=None, source_files=None, super_selector=None, filename=None, is_sass=None, line_numbers=True, import_static_css=False): """Compile Sass to CSS. Returns a single CSS string. This method is DEPRECATED; see :mod:`scss.compiler` instead. """ # Derive our root namespace self.scss_vars = _default_scss_vars.copy() if self._scss_vars is not None: self.scss_vars.update(self._scss_vars) root_namespace = Namespace( variables=self.scss_vars, functions=self._library, ) # Figure out search paths. Fall back from provided explicitly to # defined globally to just searching the current directory search_paths = ['.'] if self._search_paths is not None: assert not isinstance(self._search_paths, six.string_types), \ "`search_paths` should be an iterable, not a string" search_paths.extend(self._search_paths) else: if config.LOAD_PATHS: if isinstance(config.LOAD_PATHS, six.string_types): # Back-compat: allow comma-delimited search_paths.extend(config.LOAD_PATHS.split(',')) else: search_paths.extend(config.LOAD_PATHS) search_paths.extend(self._scss_opts.get('load_paths', [])) # Normalize a few old styles of options output_style = self._scss_opts.get('style', config.STYLE) if output_style is True: output_style = 'compressed' elif output_style is False: output_style = 'legacy' fixed_search_path = [] for path in search_paths: if isinstance(path, six.string_types): fixed_search_path.append(Path(path)) else: fixed_search_path.append(path) # Build the compiler compiler = Compiler( namespace=root_namespace, extensions=[ CoreExtension, ExtraExtension, FontsExtension, CompassExtension, BootstrapExtension, ], search_path=fixed_search_path, import_static_css=import_static_css, live_errors=self.live_errors, generate_source_map=self._scss_opts.get('debug_info', False), output_style=output_style, warn_unused_imports=self._scss_opts.get('warn_unused', False), ignore_parse_errors=config.DEBUG, loops_have_own_scopes=config.CONTROL_SCOPING, undefined_variables_fatal=config.FATAL_UNDEFINED, super_selector=super_selector or self.super_selector, ) # Gonna add the source files manually compilation = compiler.make_compilation() # Inject the files we know about # TODO how does this work with the expectation of absoluteness if source_files is not None: for source in source_files: compilation.add_source(source) elif scss_string is not None: source = SourceFile.from_string( scss_string, relpath=filename, is_sass=is_sass, ) compilation.add_source(source) elif scss_file is not None: # This is now the only way to allow forcibly overriding the # filename a source "thinks" it is with open(scss_file, 'rb') as f: source = SourceFile.from_file( f, relpath=filename or scss_file, is_sass=is_sass, ) compilation.add_source(source) # Plus the ones from the constructor if self._scss_files: for name, contents in list(self._scss_files.items()): source = SourceFile.from_string(contents, relpath=name) compilation.add_source(source) return compiler.call_and_catch_errors(compilation.run)