Exemple #1
0
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))
Exemple #2
0
    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
Exemple #3
0
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))
Exemple #4
0
    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
Exemple #5
0
    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)
Exemple #6
0
    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]
Exemple #7
0
 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)
Exemple #8
0
    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]
Exemple #9
0
    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)
Exemple #10
0
    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)
Exemple #11
0
 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)
Exemple #12
0
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)
Exemple #13
0
    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
Exemple #14
0
    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
Exemple #15
0
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))