예제 #1
0
    def gen_tasks(self):
        """Generate CSS out of LESS sources."""

        kw = {
            'cache_folder': self.site.config['CACHE_FOLDER'],
            'themes': self.site.THEMES,
        }

        # Find where in the theme chain we define the LESS targets
        # There can be many *.less in the folder, but we only will build
        # the ones listed in less/targets
        targets_path = utils.get_asset_path(
            os.path.join(self.sources_folder, "targets"), self.site.THEMES)
        try:
            with codecs.open(targets_path, "rb", "utf-8") as inf:
                targets = [x.strip() for x in inf.readlines()]
        except Exception:
            targets = []

        for theme_name in kw['themes']:
            src = os.path.join(utils.get_theme_path(theme_name),
                               self.sources_folder)
            for task in utils.copy_tree(
                    src, os.path.join(kw['cache_folder'],
                                      self.sources_folder)):
                #task['basename'] = self.name
                yield task

        # Build targets and write CSS files
        base_path = utils.get_theme_path(self.site.THEMES[0])
        dst_dir = os.path.join(self.site.config['OUTPUT_FOLDER'], 'assets',
                               'css')
        # Make everything depend on all sources, rough but enough
        deps = glob.glob(
            os.path.join(base_path, self.sources_folder,
                         "*{0}".format(self.sources_ext)))

        def compile_target(target, dst):
            if not os.path.isdir(dst_dir):
                os.makedirs(dst_dir)
            src = os.path.join(kw['cache_folder'], self.sources_folder, target)
            compiled = subprocess.check_output([self.compiler_name, src])
            with open(dst, "wb+") as outf:
                outf.write(compiled)

        for target in targets:
            dst = os.path.join(dst_dir, target.replace(self.sources_ext,
                                                       ".css"))
            yield {
                'basename': self.name,
                'name': dst,
                'targets': [dst],
                'file_dep': deps,
                'actions': ((compile_target, [target, dst]), ),
                'uptodate': [utils.config_changed(kw)],
                'clean': True
            }

        if not targets:
            yield {'basename': self.name, 'actions': []}
예제 #2
0
    def gen_tasks(self):
        """Generate CSS out of LESS sources."""

        kw = {
            'cache_folder': self.site.config['CACHE_FOLDER'],
            'themes': self.site.THEMES,
        }

        # Find where in the theme chain we define the LESS targets
        # There can be many *.less in the folder, but we only will build
        # the ones listed in less/targets
        targets_path = utils.get_asset_path(os.path.join(self.sources_folder, "targets"), self.site.THEMES)
        try:
            with codecs.open(targets_path, "rb", "utf-8") as inf:
                targets = [x.strip() for x in inf.readlines()]
        except Exception:
            targets = []

        for theme_name in kw['themes']:
            src = os.path.join(utils.get_theme_path(theme_name), self.sources_folder)
            for task in utils.copy_tree(src, os.path.join(kw['cache_folder'], self.sources_folder)):
                task['basename'] = 'prepare_less_sources'
                yield task

        # Build targets and write CSS files
        base_path = utils.get_theme_path(self.site.THEMES[0])
        dst_dir = os.path.join(self.site.config['OUTPUT_FOLDER'], 'assets', 'css')
        # Make everything depend on all sources, rough but enough
        deps = glob.glob(os.path.join(
            base_path,
            self.sources_folder,
            "*{0}".format(self.sources_ext)))

        def compile_target(target, dst):
            utils.makedirs(dst_dir)
            src = os.path.join(kw['cache_folder'], self.sources_folder, target)
            try:
                compiled = subprocess.check_output([self.compiler_name, src])
            except OSError:
                utils.req_missing([self.compiler_name],
                                  'build LESS files (and use this theme)',
                                  False, False)
            with open(dst, "wb+") as outf:
                outf.write(compiled)

        yield self.group_task()

        for target in targets:
            dst = os.path.join(dst_dir, target.replace(self.sources_ext, ".css"))
            yield {
                'basename': self.name,
                'name': dst,
                'targets': [dst],
                'file_dep': deps,
                'task_dep': ['prepare_less_sources'],
                'actions': ((compile_target, [target, dst]), ),
                'uptodate': [utils.config_changed(kw)],
                'clean': True
            }
예제 #3
0
    def _execute(self, options, args):
        """Install theme into current site."""
        listing = options['list']
        url = options['url']
        if args:
            name = args[0]
        else:
            name = None

        if options['getpath'] and name:
            path = utils.get_theme_path(name)
            if path:
                print(path)
            else:
                print('not installed')
            return 0

        if name is None and not listing:
            LOGGER.error(
                "This command needs either a theme name or the -l option.")
            return False
        try:
            data = requests.get(url).json()
        except requests.exceptions.SSLError:
            LOGGER.warning(
                "SSL error, using http instead of https (press ^C to abort)")
            time.sleep(1)
            url = url.replace('https', 'http', 1)
            data = requests.get(url).json()
        if listing:
            print("Themes:")
            print("-------")
            for theme in sorted(data.keys()):
                print(theme)
            return True
        else:
            # `name` may be modified by the while loop.
            origname = name
            installstatus = self.do_install(name, data)
            # See if the theme's parent is available. If not, install it
            while True:
                parent_name = utils.get_parent_theme_name(name)
                if parent_name is None:
                    break
                try:
                    utils.get_theme_path(parent_name)
                    break
                except:  # Not available
                    self.do_install(parent_name, data)
                    name = parent_name
            if installstatus:
                LOGGER.notice(
                    'Remember to set THEME="{0}" in conf.py to use this theme.'
                    .format(origname))
예제 #4
0
    def _execute(self, options, args):
        """Install theme into current site."""
        listing = options['list']
        url = options['url']
        if args:
            name = args[0]
        else:
            name = None

        if options['getpath'] and name:
            path = utils.get_theme_path(name)
            if path:
                print(path)
            else:
                print('not installed')
            return 0

        if name is None and not listing:
            LOGGER.error("This command needs either a theme name or the -l option.")
            return False
        try:
            data = requests.get(url).json()
        except requests.exceptions.SSLError:
            LOGGER.warning("SSL error, using http instead of https (press ^C to abort)")
            time.sleep(1)
            url = url.replace('https', 'http', 1)
            data = requests.get(url).json()
        if listing:
            print("Themes:")
            print("-------")
            for theme in sorted(data.keys()):
                print(theme)
            return True
        else:
            # `name` may be modified by the while loop.
            origname = name
            installstatus = self.do_install(name, data)
            # See if the theme's parent is available. If not, install it
            while True:
                parent_name = utils.get_parent_theme_name(name)
                if parent_name is None:
                    break
                try:
                    utils.get_theme_path(parent_name)
                    break
                except:  # Not available
                    self.do_install(parent_name, data)
                    name = parent_name
            if installstatus:
                LOGGER.notice('Remember to set THEME="{0}" in conf.py to use this theme.'.format(origname))
예제 #5
0
    def gen_tasks(self):
        """Generate CSS out of LESS sources."""

        kw = {"cache_folder": self.site.config["CACHE_FOLDER"], "themes": self.site.THEMES}

        # Find where in the theme chain we define the LESS targets
        # There can be many *.less in the folder, but we only will build
        # the ones listed in less/targets
        targets_path = utils.get_asset_path(os.path.join(self.sources_folder, "targets"), self.site.THEMES)
        try:
            with codecs.open(targets_path, "rb", "utf-8") as inf:
                targets = [x.strip() for x in inf.readlines()]
        except Exception:
            targets = []

        for theme_name in kw["themes"]:
            src = os.path.join(utils.get_theme_path(theme_name), self.sources_folder)
            for task in utils.copy_tree(src, os.path.join(kw["cache_folder"], self.sources_folder)):
                # task['basename'] = self.name
                yield task

        # Build targets and write CSS files
        base_path = utils.get_theme_path(self.site.THEMES[0])
        dst_dir = os.path.join(self.site.config["OUTPUT_FOLDER"], "assets", "css")
        # Make everything depend on all sources, rough but enough
        deps = glob.glob(os.path.join(base_path, self.sources_folder, "*{0}".format(self.sources_ext)))

        def compile_target(target, dst):
            if not os.path.isdir(dst_dir):
                os.makedirs(dst_dir)
            src = os.path.join(kw["cache_folder"], self.sources_folder, target)
            compiled = subprocess.check_output([self.compiler_name, src])
            with open(dst, "wb+") as outf:
                outf.write(compiled)

        for target in targets:
            dst = os.path.join(dst_dir, target.replace(self.sources_ext, ".css"))
            yield {
                "basename": self.name,
                "name": dst,
                "targets": [dst],
                "file_dep": deps,
                "actions": ((compile_target, [target, dst]),),
                "uptodate": [utils.config_changed(kw)],
                "clean": True,
            }

        if not targets:
            yield {"basename": self.name, "actions": []}
예제 #6
0
    def _execute(self, options, args):
        """Install theme into current site."""
        listing = options['list']
        url = options['url']
        if args:
            name = args[0]
        else:
            name = None

        if options['getpath'] and name:
            path = utils.get_theme_path(name)
            if path:
                print(path)
            else:
                print('not installed')
            exit(0)

        if name is None and not listing:
            LOGGER.error(
                "This command needs either a theme name or the -l option.")
            return False
        data = requests.get(url).text
        data = json.loads(data)
        if listing:
            print("Themes:")
            print("-------")
            for theme in sorted(data.keys()):
                print(theme)
            return True
        else:
            # `name` may be modified by the while loop.
            origname = name
            installstatus = self.do_install(name, data)
            # See if the theme's parent is available. If not, install it
            while True:
                parent_name = utils.get_parent_theme_name(name)
                if parent_name is None:
                    break
                try:
                    utils.get_theme_path(parent_name)
                    break
                except:  # Not available
                    self.do_install(parent_name, data)
                    name = parent_name
            if installstatus:
                LOGGER.notice(
                    'Remember to set THEME="{0}" in conf.py to use this theme.'
                    .format(origname))
예제 #7
0
        def generate_css():
            # Compass compile
            for theme_name in self.site.THEMES:

                theme_root = os.path.abspath(utils.get_theme_path(theme_name))
                compass_root = os.path.abspath(os.path.join(theme_root, 'style'))
                tmp_dir = os.path.abspath(os.path.join(theme_root, '_tmp'))

                if os.path.exists(compass_root):

                    LOGGER.notice("PYGMENTS CSS CODE")
                    create_code_css(self.site.config['CODE_COLOR_SCHEME'],
                                    os.path.join(compass_root, 'css', 'code.css'))


                    LOGGER.notice("COMPASS COMPILE")
                    run('compass clean', cwd=compass_root)
                    run('compass compile', cwd=compass_root)

                    LOGGER.notice("AUTOPREFIXER")
                    LOGGER.notice("CWD: {}".format(theme_root))
                    run('autoprefixer -o _tmp/all.pre.css _tmp/all.css', cwd=theme_root)

                    LOGGER.notice("CSSO (CSS optimizer)")
                    LOGGER.notice("CWD: {}".format(theme_root))
                    run('csso _tmp/all.pre.css _tmp/all.min.css', cwd=theme_root)


                    LOGGER.notice("Move CSS to output")
                    css_output_dir = os.path.join(os.path.abspath(self.site.config['OUTPUT_FOLDER']), 'assets', 'css')
                    utils.makedirs(css_output_dir)
                    shutil.copy2(os.path.join(tmp_dir, 'all.min.css'), css_output_dir)
예제 #8
0
    def do_install(self, name, data):
        if name in data:
            utils.makedirs(self.output_dir)
            LOGGER.notice('Downloading: ' + data[name])
            zip_file = BytesIO()
            zip_file.write(requests.get(data[name]).content)
            LOGGER.notice('Extracting: {0} into themes'.format(name))
            utils.extract_all(zip_file)
            dest_path = os.path.join('themes', name)
        else:
            try:
                theme_path = utils.get_theme_path(name)
            except:
                LOGGER.error("Can't find theme " + name)
                return False

            utils.makedirs(self.output_dir)
            dest_path = os.path.join(self.output_dir, name)
            if os.path.exists(dest_path):
                LOGGER.error("{0} is already installed".format(name))
                return False

            LOGGER.notice('Copying {0} into themes'.format(theme_path))
            shutil.copytree(theme_path, dest_path)
        confpypath = os.path.join(dest_path, 'conf.py.sample')
        if os.path.exists(confpypath):
            LOGGER.notice('This plugin has a sample config file.')
            print('Contents of the conf.py.sample file:\n')
            with codecs.open(confpypath, 'rb', 'utf-8') as fh:
                print(indent(pygments.highlight(
                    fh.read(), PythonLexer(), TerminalFormatter()), 4 * ' '))
            return True
예제 #9
0
    def do_install(self, name, data):
        if name in data:
            utils.makedirs(self.output_dir)
            LOGGER.info("Downloading '{0}'".format(data[name]))
            zip_file = io.BytesIO()
            zip_file.write(requests.get(data[name]).content)
            LOGGER.info("Extracting '{0}' into themes/".format(name))
            utils.extract_all(zip_file)
            dest_path = os.path.join(self.output_dir, name)
        else:
            dest_path = os.path.join(self.output_dir, name)
            try:
                theme_path = utils.get_theme_path(name)
                LOGGER.error("Theme '{0}' is already installed in {1}".format(
                    name, theme_path))
            except Exception:
                LOGGER.error("Can't find theme {0}".format(name))

            return False

        confpypath = os.path.join(dest_path, 'conf.py.sample')
        if os.path.exists(confpypath):
            LOGGER.notice(
                'This theme has a sample config file.  Integrate it with yours in order to make this theme work!'
            )
            print('Contents of the conf.py.sample file:\n')
            with io.open(confpypath, 'r', encoding='utf-8') as fh:
                if self.site.colorful:
                    print(
                        indent(
                            pygments.highlight(fh.read(), PythonLexer(),
                                               TerminalFormatter()), 4 * ' '))
                else:
                    print(indent(fh.read(), 4 * ' '))
        return True
예제 #10
0
    def do_install(self, name, data):
        if name in data:
            utils.makedirs(self.output_dir)
            LOGGER.info("Downloading '{0}'".format(data[name]))
            zip_file = io.BytesIO()
            zip_file.write(requests.get(data[name]).content)
            LOGGER.info("Extracting '{0}' into themes/".format(name))
            utils.extract_all(zip_file)
            dest_path = os.path.join(self.output_dir, name)
        else:
            dest_path = os.path.join(self.output_dir, name)
            try:
                theme_path = utils.get_theme_path(name)
                LOGGER.error("Theme '{0}' is already installed in {1}".format(name, theme_path))
            except Exception:
                LOGGER.error("Can't find theme {0}".format(name))

            return False

        confpypath = os.path.join(dest_path, 'conf.py.sample')
        if os.path.exists(confpypath):
            LOGGER.notice('This theme has a sample config file.  Integrate it with yours in order to make this theme work!')
            print('Contents of the conf.py.sample file:\n')
            with io.open(confpypath, 'r', encoding='utf-8') as fh:
                if self.site.colorful:
                    print(utils.indent(pygments.highlight(
                        fh.read(), PythonLexer(), TerminalFormatter()),
                        4 * ' '))
                else:
                    print(utils.indent(fh.read(), 4 * ' '))
        return True
예제 #11
0
    def copy_template(self, template):
        """Copy the named template file from the parent to a local theme or to templates/."""
        # Find template
        t = self.site.template_system.get_template_path(template)
        if t is None:
            LOGGER.error(
                "Cannot find template {0} in the lookup.".format(template))
            return 2

        # Figure out where to put it.
        # Check if a local theme exists.
        theme_path = utils.get_theme_path(self.site.THEMES[0])
        if theme_path.startswith('themes' + os.sep):
            # Theme in local themes/ directory
            base = os.path.join(theme_path, 'templates')
        else:
            # Put it in templates/
            base = 'templates'

        if not os.path.exists(base):
            os.mkdir(base)
            LOGGER.info("Created directory {0}".format(base))

        try:
            out = shutil.copy(t, base)
            LOGGER.info("Copied template from {0} to {1}".format(t, out))
        except shutil.SameFileError:
            LOGGER.error(
                "This file already exists in your templates directory ({0}).".
                format(base))
            return 3
예제 #12
0
    def gen_tasks(self):
        """Create tasks to copy the assets of the whole theme chain.

        If a file is present on two themes, use the version
        from the "youngest" theme.
        """

        kw = {
            "themes":
            self.site.THEMES,
            "output_folder":
            self.site.config['OUTPUT_FOLDER'],
            "filters":
            self.site.config['FILTERS'],
            "code_color_scheme":
            self.site.config['CODE_COLOR_SCHEME'],
            "code.css_selectors": ['pre.code', 'div.code pre'],
            "code.css_close":
            "\ntable.codetable { width: 100%;} td.linenos {text-align: right; width: 4em;}\n",
        }
        has_code_css = False
        tasks = {}
        code_css_path = os.path.join(kw['output_folder'], 'assets', 'css',
                                     'code.css')

        yield self.group_task()

        for theme_name in kw['themes']:
            src = os.path.join(utils.get_theme_path(theme_name), 'assets')
            dst = os.path.join(kw['output_folder'], 'assets')
            for task in utils.copy_tree(src, dst):
                if task['name'] in tasks:
                    continue
                has_code_css = task['targets'][0] == code_css_path
                tasks[task['name']] = task
                task['uptodate'] = [utils.config_changed(kw)]
                task['basename'] = self.name
                yield utils.apply_filters(task, kw['filters'])

        if not has_code_css:  # Generate it

            def create_code_css():
                from pygments.formatters import get_formatter_by_name
                formatter = get_formatter_by_name(
                    'html', style=kw["code_color_scheme"])
                utils.makedirs(os.path.dirname(code_css_path))
                with codecs.open(code_css_path, 'wb+', 'utf8') as outf:
                    outf.write(
                        formatter.get_style_defs(kw["code.css_selectors"]))
                    outf.write(kw["code.css_close"])

            task = {
                'basename': self.name,
                'name': code_css_path,
                'targets': [code_css_path],
                'uptodate': [utils.config_changed(kw)],
                'actions': [(create_code_css, [])],
                'clean': True,
            }
            yield utils.apply_filters(task, kw['filters'])
예제 #13
0
    def gen_tasks(self):
        """Create tasks to copy the assets of the whole theme chain.

        If a file is present on two themes, use the version
        from the "youngest" theme.
        """

        kw = {
            "themes": self.site.THEMES,
            "output_folder": self.site.config['OUTPUT_FOLDER'],
            "filters": self.site.config['FILTERS'],
        }
        flag = True
        tasks = {}
        for theme_name in kw['themes']:
            src = os.path.join(utils.get_theme_path(theme_name), 'assets')
            dst = os.path.join(kw['output_folder'], 'assets')
            for task in utils.copy_tree(src, dst):
                if task['name'] in tasks:
                    continue
                tasks[task['name']] = task
                task['uptodate'] = [utils.config_changed(kw)]
                task['basename'] = self.name
                flag = False
                yield utils.apply_filters(task, kw['filters'])

        if flag:
            yield {
                'basename': self.name,
                'name': 'None',
                'uptodate': [True],
                'actions': [],
            }
예제 #14
0
    def gen_tasks(self):
        """Create tasks to copy the assets of the whole theme chain.

        If a file is present on two themes, use the version
        from the "youngest" theme.
        """

        kw = {
            "themes": self.site.THEMES,
            "output_folder": self.site.config['OUTPUT_FOLDER'],
            "filters": self.site.config['FILTERS'],
        }

        tasks = {}
        for theme_name in kw['themes']:
            src = os.path.join(utils.get_theme_path(theme_name), 'assets')
            dst = os.path.join(kw['output_folder'], 'assets')
            for task in utils.copy_tree(src, dst):
                if task['name'] in tasks:
                    continue
                tasks[task['name']] = task
                task['uptodate'] = task.get('uptodate', []) + \
                    [utils.config_changed(kw)]
                task['basename'] = self.name
                yield utils.apply_filters(task, kw['filters'])
예제 #15
0
    def copy_template(self, template):
        """Copy the named template file from the parent to a local theme or to templates/."""
        # Find template
        t = self.site.template_system.get_template_path(template)
        if t is None:
            LOGGER.error("Cannot find template {0} in the lookup.".format(template))
            return 2

        # Figure out where to put it.
        # Check if a local theme exists.
        theme_path = utils.get_theme_path(self.site.THEMES[0])
        if theme_path.startswith('themes' + os.sep):
            # Theme in local themes/ directory
            base = os.path.join(theme_path, 'templates')
        else:
            # Put it in templates/
            base = 'templates'

        if not os.path.exists(base):
            os.mkdir(base)
            LOGGER.info("Created directory {0}".format(base))

        try:
            out = shutil.copy(t, base)
            LOGGER.info("Copied template from {0} to {1}".format(t, out))
        except shutil.SameFileError:
            LOGGER.error("This file already exists in your templates directory ({0}).".format(base))
            return 3
예제 #16
0
파일: theme.py 프로젝트: Cadair/nikola
 def get_path(self, name):
     """Get path for an installed theme."""
     try:
         path = utils.get_theme_path(name)
         print(path)
     except Exception:
         print("not installed")
     return 0
예제 #17
0
파일: theme.py 프로젝트: withshunli/nikola
 def get_path(self, name):
     """Get path for an installed theme."""
     try:
         path = utils.get_theme_path(name)
         print(path)
     except Exception:
         print("not installed")
     return 0
예제 #18
0
    def gen_tasks(self):
        """Create tasks to copy the assets of the whole theme chain.

        If a file is present on two themes, use the version
        from the "youngest" theme.
        """

        kw = {
            "themes": self.site.THEMES,
            "output_folder": self.site.config['OUTPUT_FOLDER'],
            "filters": self.site.config['FILTERS'],
            "code_color_scheme": self.site.config['CODE_COLOR_SCHEME'],
        }
        flag = True
        has_code_css = False
        tasks = {}
        code_css_path = os.path.join(kw['output_folder'], 'assets', 'css', 'code.css')
        for theme_name in kw['themes']:
            src = os.path.join(utils.get_theme_path(theme_name), 'assets')
            dst = os.path.join(kw['output_folder'], 'assets')
            for task in utils.copy_tree(src, dst):
                if task['name'] in tasks:
                    continue
                if task['targets'][0] == code_css_path:
                    has_code_css = True
                tasks[task['name']] = task
                task['uptodate'] = [utils.config_changed(kw)]
                task['basename'] = self.name
                flag = False
                yield utils.apply_filters(task, kw['filters'])

        if flag:
            yield {
                'basename': self.name,
                'name': 'None',
                'uptodate': [True],
                'actions': [],
            }

        if not has_code_css:  # Generate it

            def create_code_css():
                from pygments.formatters import get_formatter_by_name
                formatter = get_formatter_by_name('html', style=kw["code_color_scheme"])
                utils.makedirs(os.path.dirname(code_css_path))
                with codecs.open(code_css_path, 'wb+', 'utf8') as outf:
                    outf.write(formatter.get_style_defs('.code'))
                    outf.write("table.codetable { width: 100%;} td.linenos {text-align: right; width: 4em;}")

            task = {
                'basename': self.name,
                'name': code_css_path,
                'targets': [code_css_path],
                'uptodate': [utils.config_changed(kw)],
                'actions': [(create_code_css, [])],
                'clean': True,
            }
            yield utils.apply_filters(task, kw['filters'])
예제 #19
0
    def _execute(self, options, args):
        """Install theme into current site."""
        listing = options['list']
        url = options['url']
        if args:
            name = args[0]
        else:
            name = None

        if options['getpath'] and name:
            path = utils.get_theme_path(name)
            if path:
                print(path)
            else:
                print('not installed')
            exit(0)

        if name is None and not listing:
            LOGGER.error("This command needs either a theme name or the -l option.")
            return False
        data = requests.get(url).text
        data = json.loads(data)
        if listing:
            print("Themes:")
            print("-------")
            for theme in sorted(data.keys()):
                print(theme)
            return True
        else:
            # `name` may be modified by the while loop.
            origname = name
            installstatus = self.do_install(name, data)
            # See if the theme's parent is available. If not, install it
            while True:
                parent_name = utils.get_parent_theme_name(name)
                if parent_name is None:
                    break
                try:
                    utils.get_theme_path(parent_name)
                    break
                except:  # Not available
                    self.do_install(parent_name, data)
                    name = parent_name
            if installstatus:
                LOGGER.notice('Remember to set THEME="{0}" in conf.py to use this theme.'.format(origname))
예제 #20
0
파일: theme.py 프로젝트: Cadair/nikola
 def do_install_deps(self, url, name):
     """Install themes and their dependencies."""
     data = self.get_json(url)
     # `name` may be modified by the while loop.
     origname = name
     installstatus = self.do_install(name, data)
     # See if the theme's parent is available. If not, install it
     while True:
         parent_name = utils.get_parent_theme_name(name)
         if parent_name is None:
             break
         try:
             utils.get_theme_path(parent_name)
             break
         except:  # Not available
             self.do_install(parent_name, data)
             name = parent_name
     if installstatus:
         LOGGER.notice('Remember to set THEME="{0}" in conf.py to use this theme.'.format(origname))
예제 #21
0
파일: theme.py 프로젝트: withshunli/nikola
 def do_install_deps(self, url, name):
     """Install themes and their dependencies."""
     data = self.get_json(url)
     # `name` may be modified by the while loop.
     origname = name
     installstatus = self.do_install(name, data)
     # See if the theme's parent is available. If not, install it
     while True:
         parent_name = utils.get_parent_theme_name(name)
         if parent_name is None:
             break
         try:
             utils.get_theme_path(parent_name)
             break
         except:  # Not available
             self.do_install(parent_name, data)
             name = parent_name
     if installstatus:
         LOGGER.notice(
             'Remember to set THEME="{0}" in conf.py to use this theme.'.
             format(origname))
예제 #22
0
    def gen_tasks(self):
        """Create tasks to copy the assets of the whole theme chain.

        If a file is present on two themes, use the version
        from the "youngest" theme.
        """

        kw = {
            "themes": self.site.THEMES,
            "output_folder": self.site.config["OUTPUT_FOLDER"],
            "filters": self.site.config["FILTERS"],
            "code_color_scheme": self.site.config["CODE_COLOR_SCHEME"],
        }
        has_code_css = False
        tasks = {}
        code_css_path = os.path.join(kw["output_folder"], "assets", "css", "code.css")

        yield self.group_task()

        for theme_name in kw["themes"]:
            src = os.path.join(utils.get_theme_path(theme_name), "assets")
            dst = os.path.join(kw["output_folder"], "assets")
            for task in utils.copy_tree(src, dst):
                if task["name"] in tasks:
                    continue
                if task["targets"][0] == code_css_path:
                    has_code_css = True
                tasks[task["name"]] = task
                task["uptodate"] = [utils.config_changed(kw)]
                task["basename"] = self.name
                yield utils.apply_filters(task, kw["filters"])

        if not has_code_css:  # Generate it

            def create_code_css():
                from pygments.formatters import get_formatter_by_name

                formatter = get_formatter_by_name("html", style=kw["code_color_scheme"])
                utils.makedirs(os.path.dirname(code_css_path))
                with codecs.open(code_css_path, "wb+", "utf8") as outf:
                    outf.write(formatter.get_style_defs(["pre.code", "div.code pre"]))
                    outf.write("\ntable.codetable { width: 100%;} td.linenos {text-align: right; width: 4em;}\n")

            task = {
                "basename": self.name,
                "name": code_css_path,
                "targets": [code_css_path],
                "uptodate": [utils.config_changed(kw)],
                "actions": [(create_code_css, [])],
                "clean": True,
            }
            yield utils.apply_filters(task, kw["filters"])
예제 #23
0
def get_theme_bundles(themes):
    """Given a theme chain, return the bundle definitions."""
    bundles = {}
    for theme_name in themes:
        bundles_path = os.path.join(utils.get_theme_path(theme_name), "bundles")
        if os.path.isfile(bundles_path):
            with open(bundles_path) as fd:
                for line in fd:
                    name, files = line.split("=")
                    files = [f.strip() for f in files.split(",")]
                    bundles[name.strip()] = files
                break
    return bundles
예제 #24
0
def get_theme_bundles(themes):
    """Given a theme chain, return the bundle definitions."""
    bundles = {}
    for theme_name in themes:
        bundles_path = os.path.join(utils.get_theme_path(theme_name),
                                    'bundles')
        if os.path.isfile(bundles_path):
            with open(bundles_path) as fd:
                for line in fd:
                    name, files = line.split('=')
                    files = [f.strip() for f in files.split(',')]
                    bundles[name.strip().replace('/', os.sep)] = files
                break
    return bundles
예제 #25
0
    def _execute(self, options, args):
        """Install theme into current site."""
        if requests is None:
            utils.LOGGER.error('This command requires the requests package be installed.')
            return False

        listing = options['list']
        url = options['url']
        if args:
            name = args[0]
        else:
            name = None

        if name is None and not listing:
            utils.LOGGER.error("This command needs either a theme name or the -l option.")
            return False
        data = requests.get(url).text
        data = json.loads(data)
        if listing:
            print("Themes:")
            print("-------")
            for theme in sorted(data.keys()):
                print(theme)
            return True
        else:
            self.do_install(name, data)
        # See if the theme's parent is available. If not, install it
        while True:
            parent_name = utils.get_parent_theme_name(name)
            if parent_name is None:
                break
            try:
                utils.get_theme_path(parent_name)
                break
            except:  # Not available
                self.do_install(parent_name, data)
                name = parent_name
예제 #26
0
    def _execute(self, options, args):
        """Install theme into current site."""
        if requests is None:
            utils.req_missing(['requests'], 'install themes')

        listing = options['list']
        url = options['url']
        if args:
            name = args[0]
        else:
            name = None

        if name is None and not listing:
            LOGGER.error(
                "This command needs either a theme name or the -l option.")
            return False
        data = requests.get(url).text
        data = json.loads(data)
        if listing:
            print("Themes:")
            print("-------")
            for theme in sorted(data.keys()):
                print(theme)
            return True
        else:
            self.do_install(name, data)
        # See if the theme's parent is available. If not, install it
        while True:
            parent_name = utils.get_parent_theme_name(name)
            if parent_name is None:
                break
            try:
                utils.get_theme_path(parent_name)
                break
            except:  # Not available
                self.do_install(parent_name, data)
                name = parent_name
예제 #27
0
파일: theme.py 프로젝트: withshunli/nikola
 def do_uninstall(self, name):
     """Uninstall a theme."""
     try:
         path = utils.get_theme_path(name)
     except Exception:
         LOGGER.error('Unknown theme: {0}'.format(name))
         return 1
     LOGGER.warning('About to uninstall theme: {0}'.format(name))
     LOGGER.warning('This will delete {0}'.format(path))
     sure = utils.ask_yesno('Are you sure?')
     if sure:
         LOGGER.warning('Removing {0}'.format(path))
         shutil.rmtree(path)
         return 0
     return 1
예제 #28
0
파일: theme.py 프로젝트: Cadair/nikola
 def do_uninstall(self, name):
     """Uninstall a theme."""
     try:
         path = utils.get_theme_path(name)
     except Exception:
         LOGGER.error('Unknown theme: {0}'.format(name))
         return 1
     LOGGER.warning('About to uninstall theme: {0}'.format(name))
     LOGGER.warning('This will delete {0}'.format(path))
     sure = utils.ask_yesno('Are you sure?')
     if sure:
         LOGGER.warning('Removing {0}'.format(path))
         shutil.rmtree(path)
         return 0
     return 1
예제 #29
0
파일: bundles.py 프로젝트: getnikola/nikola
def get_theme_bundles(themes):
    """Given a theme chain, return the bundle definitions."""
    for theme_name in themes:
        bundles_path = os.path.join(
            utils.get_theme_path(theme_name), 'bundles')
        if os.path.isfile(bundles_path):
            config = configparser.ConfigParser()
            header = io.StringIO('[bundles]\n')
            with open(bundles_path, 'rt') as fd:
                config.read_file(itertools.chain(header, fd))
            bundles = {}
            for name, files in config['bundles'].items():
                name = name.strip().replace('/', os.sep)
                files = [f.strip() for f in files.split(',') if f.strip()]
                bundles[name] = files
            return bundles
예제 #30
0
파일: bundles.py 프로젝트: thuvh/nikola
def get_theme_bundles(themes):
    """Given a theme chain, return the bundle definitions."""
    for theme_name in themes:
        bundles_path = os.path.join(utils.get_theme_path(theme_name),
                                    'bundles')
        if os.path.isfile(bundles_path):
            config = configparser.ConfigParser()
            header = io.StringIO('[bundles]\n')
            with open(bundles_path, 'rt') as fd:
                config.read_file(itertools.chain(header, fd))
            bundles = {}
            for name, files in config['bundles'].items():
                name = name.strip().replace('/', os.sep)
                files = [f.strip() for f in files.split(',') if f.strip()]
                bundles[name] = files
            return bundles
예제 #31
0
파일: theme.py 프로젝트: withshunli/nikola
    def do_install(self, name, data):
        """Download and install a theme."""
        if name in data:
            utils.makedirs(self.output_dir)
            url = data[name]
            LOGGER.info("Downloading '{0}'".format(url))
            try:
                zip_data = requests.get(url).content
            except requests.exceptions.SSLError:
                LOGGER.warning(
                    "SSL error, using http instead of https (press ^C to abort)"
                )
                time.sleep(1)
                url = url.replace('https', 'http', 1)
                zip_data = requests.get(url).content

            zip_file = io.BytesIO()
            zip_file.write(zip_data)
            LOGGER.info("Extracting '{0}' into themes/".format(name))
            utils.extract_all(zip_file)
            dest_path = os.path.join(self.output_dir, name)
        else:
            dest_path = os.path.join(self.output_dir, name)
            try:
                theme_path = utils.get_theme_path(name)
                LOGGER.error("Theme '{0}' is already installed in {1}".format(
                    name, theme_path))
            except Exception:
                LOGGER.error("Can't find theme {0}".format(name))

            return False

        confpypath = os.path.join(dest_path, 'conf.py.sample')
        if os.path.exists(confpypath):
            LOGGER.notice(
                'This theme has a sample config file.  Integrate it with yours in order to make this theme work!'
            )
            print('Contents of the conf.py.sample file:\n')
            with io.open(confpypath, 'r', encoding='utf-8') as fh:
                if self.site.colorful:
                    print(
                        utils.indent(
                            pygments.highlight(fh.read(), PythonLexer(),
                                               TerminalFormatter()), 4 * ' '))
                else:
                    print(utils.indent(fh.read(), 4 * ' '))
        return True
예제 #32
0
파일: bundles.py 프로젝트: andredias/nikola
def get_theme_bundles(themes):
    """Given a theme chain, return the bundle definitions."""
    bundles = {}
    for theme_name in themes:
        bundles_path = os.path.join(
            utils.get_theme_path(theme_name), 'bundles')
        if os.path.isfile(bundles_path):
            with open(bundles_path) as fd:
                for line in fd:
                    try:
                        name, files = line.split('=')
                        files = [f.strip() for f in files.split(',')]
                        bundles[name.strip().replace('/', os.sep)] = files
                    except ValueError:
                        # for empty lines
                        pass
                break
    return bundles
예제 #33
0
    def gen_tasks(self):
        """Tweak theming by generating a LESS definition file."""

        template = self.site.config.get("LESS_THEME_TEMPLATE", "")
        if not template:
            print ("No less theme template found... exiting.")
            yield {"basename": self.name, "actions": []}
            return
        timeout_time = self.site.config.get("THEME_TIMEOUT", datetime.timedelta(days=1))

        kw = {"cache_folder": self.site.config["CACHE_FOLDER"], "themes": self.site.THEMES, "template": template}

        # Build targets and write CSS files
        base_path = utils.get_theme_path(self.site.THEMES[0])
        dst_dir = os.path.join(base_path, self.sources_folder)
        target = os.path.join(dst_dir, "define.less")
        json_target = os.path.join(self.site.config["OUTPUT_FOLDER"], "assets", "js", "background_image_data.json")

        def write_theme_define():
            """ Write the theme file and json data file.
            """
            try:
                image_data = get_random_image()
                bg_url = image_data["url"].strip()
                thumbnail = image_data["thumbnail_url"].strip()
                base_color = color_from_url(thumbnail)
            except Exception as e:
                print "Failed to change image."
                print e
                return {"basename": self.name, "actions": []}
            with codecs.open(target, "w", "utf-8") as f:
                f.write(template % (base_color, bg_url))
            with codecs.open(json_target, "w", "utf-8") as f:
                json.dump(image_data, f, indent=2)

        yield {
            "basename": self.name,
            "name": target,
            "targets": [target, json_target],
            "actions": [(write_theme_define, [])],
            "uptodate": [timeout(timeout_time), os.path.exists(target), utils.config_changed(kw)],
            "clean": True,
            "verbosity": 2,
        }
예제 #34
0
    def do_install(self, name, data):
        """Download and install a theme."""
        if name in data:
            utils.makedirs(self.output_dir)
            url = data[name]
            LOGGER.info("Downloading '{0}'".format(url))
            try:
                zip_data = requests.get(url).content
            except requests.exceptions.SSLError:
                LOGGER.warning("SSL error, using http instead of https (press ^C to abort)")
                time.sleep(1)
                url = url.replace('https', 'http', 1)
                zip_data = requests.get(url).content

            zip_file = io.BytesIO()
            zip_file.write(zip_data)
            LOGGER.info("Extracting '{0}' into themes/".format(name))
            utils.extract_all(zip_file)
            dest_path = os.path.join(self.output_dir, name)
        else:
            dest_path = os.path.join(self.output_dir, name)
            try:
                theme_path = utils.get_theme_path(name)
                LOGGER.error("Theme '{0}' is already installed in {1}".format(name, theme_path))
            except Exception:
                LOGGER.error("Can't find theme {0}".format(name))

            return False

        confpypath = os.path.join(dest_path, 'conf.py.sample')
        if os.path.exists(confpypath):
            LOGGER.notice('This theme has a sample config file.  Integrate it with yours in order to make this theme work!')
            print('Contents of the conf.py.sample file:\n')
            with io.open(confpypath, 'r', encoding='utf-8') as fh:
                if self.site.colorful:
                    print(utils.indent(pygments.highlight(
                        fh.read(), PythonLexer(), TerminalFormatter()),
                        4 * ' '))
                else:
                    print(utils.indent(fh.read(), 4 * ' '))
        return True
  def gen_tasks(self):
      """Copy static files into the output folder."""

      kw = {
          "themes": self.site.THEMES,
          "output_folder": self.site.config['OUTPUT_FOLDER'],
          "filters": self.site.config['FILTERS'],
      }

      flag = True
      
      for theme_name in kw['themes']:
          src = os.path.join(utils.get_theme_path(theme_name), 'assets', 'css')
          for root, dirs, files in os.walk(src):
            for src_name in files:
                if src_name.endswith('.less'):
                  flag=False
                  
                  src_file = os.path.join(root, src_name)
                  
                  dst_file_name = src_name.replace('.less', '.css')      
                  dst_file = os.path.join(root, dst_file_name)
                  
                  yield {
                      'basename' : self.name,
                      'name': str(dst_file),
                      'file_dep': [src_file],
                      'targets': [dst_file],
                      'actions': [(lessify, (src_file, dst_file))],
                      'clean': True,
                  }

      if flag:
          yield {
              'basename': self.name,
              'name': 'None',
              'uptodate': [True],
              'actions': [],
          }
예제 #36
0
def get_asset_path(path, themes, files_folders={'files': ''}):
    """Checks which theme provides the path with the given asset,
    and returns the "real" path to the asset, relative to the
    current directory.

    If the asset is not provided by a theme, then it will be checked for
    in the FILES_FOLDERS

    >>> get_asset_path('assets/css/rst.css', ['site', 'default'])
    'nikola/data/themes/default/assets/css/rst.css'

    >>> get_asset_path('assets/css/theme.css', ['site', 'default'])
    'nikola/data/themes/site/assets/css/theme.css'

    >>> get_asset_path('nikola.py', ['site', 'default'], {'nikola': ''})
    'nikola/nikola.py'

    >>> get_asset_path('nikola/nikola.py', ['site', 'default'],
    ... {'nikola':'nikola'})
    'nikola/nikola.py'

    """
    for theme_name in themes:
        candidate = os.path.join(
            utils.get_theme_path(theme_name),
            path
        )
        if os.path.isfile(candidate):
            return os.path.relpath(candidate, os.getcwd())
    for src, rel_dst in files_folders.items():
        candidate = os.path.join(
            src,
            os.path.relpath(path, rel_dst)
        )
        if os.path.isfile(candidate):
            return os.path.relpath(candidate, os.getcwd())

    # whatever!
    return None
예제 #37
0
    def do_install(self, name, data):
        if name in data:
            utils.makedirs(self.output_dir)
            LOGGER.info("Downloading: " + data[name])
            zip_file = io.BytesIO()
            zip_file.write(requests.get(data[name]).content)
            LOGGER.info("Extracting: {0} into themes".format(name))
            utils.extract_all(zip_file)
            dest_path = os.path.join("themes", name)
        else:
            try:
                theme_path = utils.get_theme_path(name)
            except:
                LOGGER.error("Can't find theme " + name)
                return False

            utils.makedirs(self.output_dir)
            dest_path = os.path.join(self.output_dir, name)
            if os.path.exists(dest_path):
                LOGGER.error("{0} is already installed".format(name))
                return False

            LOGGER.info("Copying {0} into themes".format(theme_path))
            shutil.copytree(theme_path, dest_path)
        confpypath = os.path.join(dest_path, "conf.py.sample")
        if os.path.exists(confpypath):
            LOGGER.notice(
                "This theme has a sample config file.  Integrate it with yours in order to make this theme work!"
            )
            print("Contents of the conf.py.sample file:\n")
            with io.open(confpypath, "r", encoding="utf-8") as fh:
                if self.site.colorful:
                    print(indent(pygments.highlight(fh.read(), PythonLexer(), TerminalFormatter()), 4 * " "))
                else:
                    print(indent(fh.read(), 4 * " "))
        return True
예제 #38
0
    def do_install(self, name, data):
        if name in data:
            utils.makedirs(self.output_dir)
            utils.LOGGER.notice('Downloading: ' + data[name])
            zip_file = BytesIO()
            zip_file.write(requests.get(data[name]).content)
            utils.LOGGER.notice('Extracting: {0} into themes'.format(name))
            utils.extract_all(zip_file)
        else:
            try:
                theme_path = utils.get_theme_path(name)
            except:
                utils.LOGGER.error("Can't find theme " + name)
                return False

            utils.makedirs(self.output_dir)
            dest_path = os.path.join(self.output_dir, name)
            if os.path.exists(dest_path):
                utils.LOGGER.error("{0} is already installed".format(name))
                return False

            utils.LOGGER.notice('Copying {0} into themes'.format(theme_path))
            shutil.copytree(theme_path, dest_path)
            return True
예제 #39
0
    def _execute(self, options, args):
        """Start the watcher."""

        self.logger = get_logger('auto', STDERR_HANDLER)
        LRSocket.logger = self.logger

        if WebSocket is object and watchdog is None:
            req_missing(['ws4py', 'watchdog'], 'use the "auto" command')
        elif WebSocket is object:
            req_missing(['ws4py'], 'use the "auto" command')
        elif watchdog is None:
            req_missing(['watchdog'], 'use the "auto" command')

        self.cmd_arguments = ['nikola', 'build']
        if self.site.configuration_filename != 'conf.py':
            self.cmd_arguments = ['--conf=' + self.site.configuration_filename] + self.cmd_arguments

        # Run an initial build so we are up-to-date
        subprocess.call(self.cmd_arguments)

        port = options and options.get('port')
        self.snippet = '''<script>document.write('<script src="http://'
            + (location.host || 'localhost').split(':')[0]
            + ':{0}/livereload.js?snipver=1"></'
            + 'script>')</script>
        </head>'''.format(port)

        # Do not duplicate entries -- otherwise, multiple rebuilds are triggered
        watched = set([
            'templates/',
        ] + [get_theme_path(name) for name in self.site.THEMES])
        for item in self.site.config['post_pages']:
            watched.add(os.path.dirname(item[0]))
        for item in self.site.config['FILES_FOLDERS']:
            watched.add(item)
        for item in self.site.config['GALLERY_FOLDERS']:
            watched.add(item)
        for item in self.site.config['LISTINGS_FOLDERS']:
            watched.add(item)

        out_folder = self.site.config['OUTPUT_FOLDER']
        if options and options.get('browser'):
            browser = True
        else:
            browser = False

        if options['ipv6']:
            dhost = '::'
        else:
            dhost = None

        host = options['address'].strip('[').strip(']') or dhost

        # Instantiate global observer
        observer = Observer()
        # Watch output folders and trigger reloads
        observer.schedule(OurWatchHandler(self.do_refresh), out_folder, recursive=True)

        # Watch input folders and trigger rebuilds
        for p in watched:
            if os.path.exists(p):
                observer.schedule(OurWatchHandler(self.do_rebuild), p, recursive=True)

        # Watch config file (a bit of a hack, but we need a directory)
        _conf_fn = os.path.abspath(self.site.configuration_filename or 'conf.py')
        _conf_dn = os.path.dirname(_conf_fn)
        observer.schedule(ConfigWatchHandler(_conf_fn, self.do_rebuild), _conf_dn, recursive=False)

        try:
            observer.start()
        except KeyboardInterrupt:
            pass

        parent = self

        class Mixed(WebSocketWSGIApplication):
            """A class that supports WS and HTTP protocols in the same port."""
            def __call__(self, environ, start_response):
                if environ.get('HTTP_UPGRADE') is None:
                    return parent.serve_static(environ, start_response)
                return super(Mixed, self).__call__(environ, start_response)

        ws = make_server(
            host, port, server_class=WSGIServer,
            handler_class=WebSocketWSGIRequestHandler,
            app=Mixed(handler_cls=LRSocket)
        )
        ws.initialize_websockets_manager()
        self.logger.info("Serving HTTP on {0} port {1}...".format(host, port))
        if browser:
            if options['ipv6'] or '::' in host:
                server_url = "http://[{0}]:{1}/".format(host, port)
            else:
                server_url = "http://{0}:{1}/".format(host, port)

            self.logger.info("Opening {0} in the default web browser...".format(server_url))
            # Yes, this is racy
            webbrowser.open('http://{0}:{1}'.format(host, port))

        try:
            ws.serve_forever()
        except KeyboardInterrupt:
            self.logger.info("Server is shutting down.")
            observer.stop()
            observer.join()
예제 #40
0
    def gen_tasks(self):
        """Generate CSS out of Sass sources."""
        self.logger = utils.get_logger('build_sass', self.site.loghandlers)
        self.compiler_name = self.site.config['SASS_COMPILER']
        self.compiler_options = self.site.config['SASS_OPTIONS']

        kw = {
            'cache_folder': self.site.config['CACHE_FOLDER'],
            'themes': self.site.THEMES,
        }
        tasks = {}

        # Find where in the theme chain we define the Sass targets
        # There can be many *.sass/*.scss in the folder, but we only
        # will build the ones listed in sass/targets
        if os.path.isfile(os.path.join(self.sources_folder, "targets")):
            targets_path = os.path.join(self.sources_folder, "targets")
        else:
            targets_path = utils.get_asset_path(os.path.join(self.sources_folder, "targets"), self.site.THEMES)
        try:
            with codecs.open(targets_path, "rb", "utf-8") as inf:
                targets = [x.strip() for x in inf.readlines()]
        except Exception:
            targets = []

        for task in utils.copy_tree(self.sources_folder, os.path.join(kw['cache_folder'], self.sources_folder)):
            if task['name'] in tasks:
                continue
            task['basename'] = 'prepare_sass_sources'
            tasks[task['name']] = task
            yield task

        for theme_name in kw['themes']:
            src = os.path.join(utils.get_theme_path(theme_name), self.sources_folder)
            for task in utils.copy_tree(src, os.path.join(kw['cache_folder'], self.sources_folder)):
                if task['name'] in tasks:
                    continue
                task['basename'] = 'prepare_sass_sources'
                tasks[task['name']] = task
                yield task

        # Build targets and write CSS files
        base_path = utils.get_theme_path(self.site.THEMES[0])
        dst_dir = os.path.join(self.site.config['OUTPUT_FOLDER'], 'assets', 'css')
        # Make everything depend on all sources, rough but enough
        deps = []
        for ext in self.sources_ext:
            if os.path.isfile(os.path.join(self.sources_folder, "targets")):
                deps += glob.glob(os.path.join(kw['cache_folder'], self.sources_folder,
                                               '*{0}'.format(ext)))
            else:
                deps += glob.glob(os.path.join(base_path, self.sources_folder,
                                               '*{0}'.format(ext)))

        def compile_target(target, dst):
            utils.makedirs(dst_dir)
            run_in_shell = sys.platform == 'win32'
            src = os.path.join(kw['cache_folder'], self.sources_folder, target)
            try:
                compiled = subprocess.check_output([self.compiler_name] + self.compiler_options + [src], shell=run_in_shell)
            except OSError:
                utils.req_missing([self.compiler_name],
                                  'build Sass files (and use this theme)',
                                  False, False)
            with open(dst, "wb+") as outf:
                outf.write(compiled)

        yield self.group_task()

        # We can have file conflicts.  This is a way to prevent them.
        # I orignally wanted to use sets and their cannot-have-duplicates
        # magic, but I decided not to do this so we can show the user
        # what files were problematic.
        # If we didn’t do this, there would be a cryptic message from doit
        # instead.
        seennames = {}
        for target in targets:
            base = os.path.splitext(target)[0]
            dst = os.path.join(dst_dir, base + ".css")

            if base in seennames:
                self.logger.error(
                    'Duplicate filenames for Sass compiled files: {0} and '
                    '{1} (both compile to {2})'.format(
                        seennames[base], target, base + ".css"))
            else:
                seennames.update({base: target})

            yield {
                'basename': self.name,
                'name': dst,
                'targets': [dst],
                'file_dep': deps,
                'task_dep': ['prepare_sass_sources'],
                'actions': ((compile_target, [target, dst]), ),
                'uptodate': [utils.config_changed(kw)],
                'clean': True
            }
예제 #41
0
    def gen_tasks(self):
        """Create tasks to copy the assets of the whole theme chain.

        If a file is present on two themes, use the version
        from the "youngest" theme.
        """
        kw = {
            "themes": self.site.THEMES,
            "files_folders": self.site.config['FILES_FOLDERS'],
            "output_folder": self.site.config['OUTPUT_FOLDER'],
            "filters": self.site.config['FILTERS'],
            "code_color_scheme": self.site.config['CODE_COLOR_SCHEME'],
            "code.css_selectors": 'pre.code',
            "code.css_head": '/* code.css file generated by Nikola */\n',
            "code.css_close": "\ntable.codetable { width: 100%;} td.linenos {text-align: right; width: 4em;}\n",
        }
        tasks = {}
        code_css_path = os.path.join(kw['output_folder'], 'assets', 'css', 'code.css')
        code_css_input = utils.get_asset_path('assets/css/code.css',
                                              themes=kw['themes'],
                                              files_folders=kw['files_folders'], output_dir=None)
        yield self.group_task()

        for theme_name in kw['themes']:
            src = os.path.join(utils.get_theme_path(theme_name), 'assets')
            dst = os.path.join(kw['output_folder'], 'assets')
            for task in utils.copy_tree(src, dst):
                if task['name'] in tasks:
                    continue
                tasks[task['name']] = task
                task['uptodate'] = [utils.config_changed(kw, 'nikola.plugins.task.copy_assets')]
                task['basename'] = self.name
                if code_css_input:
                    if 'file_dep' not in task:
                        task['file_dep'] = []
                    task['file_dep'].append(code_css_input)
                yield utils.apply_filters(task, kw['filters'])

        # Check whether or not there is a code.css file around.
        if not code_css_input and kw['code_color_scheme']:
            def create_code_css():
                from pygments.formatters import get_formatter_by_name
                formatter = get_formatter_by_name('html', style=kw["code_color_scheme"])
                utils.makedirs(os.path.dirname(code_css_path))
                with io.open(code_css_path, 'w+', encoding='utf8') as outf:
                    outf.write(kw["code.css_head"])
                    outf.write(formatter.get_style_defs(kw["code.css_selectors"]))
                    outf.write(kw["code.css_close"])

            if os.path.exists(code_css_path):
                with io.open(code_css_path, 'r', encoding='utf-8') as fh:
                    testcontents = fh.read(len(kw["code.css_head"])) == kw["code.css_head"]
            else:
                testcontents = False

            task = {
                'basename': self.name,
                'name': code_css_path,
                'targets': [code_css_path],
                'uptodate': [utils.config_changed(kw, 'nikola.plugins.task.copy_assets'), testcontents],
                'actions': [(create_code_css, [])],
                'clean': True,
            }
            yield utils.apply_filters(task, kw['filters'])
예제 #42
0
    def _execute(self, options, args):
        """Start the watcher."""
        self.sockets = []
        self.rebuild_queue = asyncio.Queue()
        self.last_rebuild = datetime.datetime.now()

        if aiohttp is None and Observer is None:
            req_missing(['aiohttp', 'watchdog'], 'use the "auto" command')
        elif aiohttp is None:
            req_missing(['aiohttp'], 'use the "auto" command')
        elif Observer is None:
            req_missing(['watchdog'], 'use the "auto" command')

        if sys.argv[0].endswith('__main__.py'):
            self.nikola_cmd = [sys.executable, '-m', 'nikola', 'build']
        else:
            self.nikola_cmd = [sys.argv[0], 'build']

        if self.site.configuration_filename != 'conf.py':
            self.nikola_cmd.append('--conf=' + self.site.configuration_filename)

        # Run an initial build so we are up-to-date (synchronously)
        self.logger.info("Rebuilding the site...")
        subprocess.call(self.nikola_cmd)

        port = options and options.get('port')
        self.snippet = '''<script>document.write('<script src="http://'
            + (location.host || 'localhost').split(':')[0]
            + ':{0}/livereload.js?snipver=1"></'
            + 'script>')</script>
        </head>'''.format(port)

        # Deduplicate entries by using a set -- otherwise, multiple rebuilds are triggered
        watched = set([
            'templates/'
        ] + [get_theme_path(name) for name in self.site.THEMES])
        for item in self.site.config['post_pages']:
            watched.add(os.path.dirname(item[0]))
        for item in self.site.config['FILES_FOLDERS']:
            watched.add(item)
        for item in self.site.config['GALLERY_FOLDERS']:
            watched.add(item)
        for item in self.site.config['LISTINGS_FOLDERS']:
            watched.add(item)
        for item in self.site.config['IMAGE_FOLDERS']:
            watched.add(item)
        for item in self.site._plugin_places:
            watched.add(item)
        # Nikola itself (useful for developers)
        watched.add(pkg_resources.resource_filename('nikola', ''))

        out_folder = self.site.config['OUTPUT_FOLDER']
        if options and options.get('browser'):
            browser = True
        else:
            browser = False

        if options['ipv6']:
            dhost = '::'
        else:
            dhost = '0.0.0.0'

        host = options['address'].strip('[').strip(']') or dhost

        # Set up asyncio server
        webapp = web.Application()
        webapp.router.add_get('/livereload.js', self.serve_livereload_js)
        webapp.router.add_get('/robots.txt', self.serve_robots_txt)
        webapp.router.add_route('*', '/livereload', self.websocket_handler)
        resource = IndexHtmlStaticResource(True, self.snippet, '', out_folder)
        webapp.router.register_resource(resource)

        # Prepare asyncio event loop
        # Required for subprocessing to work
        loop = asyncio.get_event_loop()

        # Set debug setting
        loop.set_debug(self.site.debug)

        # Server can be disabled (Issue #1883)
        self.has_server = not options['no-server']

        if self.has_server:
            handler = webapp.make_handler()
            srv = loop.run_until_complete(loop.create_server(handler, host, port))

        self.wd_observer = Observer()
        # Watch output folders and trigger reloads
        if self.has_server:
            self.wd_observer.schedule(NikolaEventHandler(self.reload_page, loop), 'output/', recursive=True)

        # Watch input folders and trigger rebuilds
        for p in watched:
            if os.path.exists(p):
                self.wd_observer.schedule(NikolaEventHandler(self.run_nikola_build, loop), p, recursive=True)

        # Watch config file (a bit of a hack, but we need a directory)
        _conf_fn = os.path.abspath(self.site.configuration_filename or 'conf.py')
        _conf_dn = os.path.dirname(_conf_fn)
        self.wd_observer.schedule(ConfigEventHandler(_conf_fn, self.run_nikola_build, loop), _conf_dn, recursive=False)
        self.wd_observer.start()

        if not self.has_server:
            self.logger.info("Watching for changes...")
            # Run the event loop forever (no server mode).
            try:
                # Run rebuild queue
                loop.run_until_complete(self.run_rebuild_queue())

                loop.run_forever()
            except KeyboardInterrupt:
                pass
            finally:
                self.wd_observer.stop()
                self.wd_observer.join()
            loop.close()
            return

        host, port = srv.sockets[0].getsockname()

        self.logger.info("Serving HTTP on {0} port {1}...".format(host, port))
        if browser:
            if options['ipv6'] or '::' in host:
                server_url = "http://[{0}]:{1}/".format(host, port)
            else:
                server_url = "http://{0}:{1}/".format(host, port)

            self.logger.info("Opening {0} in the default web browser...".format(server_url))
            webbrowser.open('http://{0}:{1}'.format(host, port))

        # Run the event loop forever and handle shutdowns.
        try:
            # Run rebuild queue
            loop.run_until_complete(self.run_rebuild_queue())

            self.dns_sd = dns_sd(port, (options['ipv6'] or '::' in host))
            loop.run_forever()
        except KeyboardInterrupt:
            pass
        finally:
            self.logger.info("Server is shutting down.")
            if self.dns_sd:
                self.dns_sd.Reset()
            srv.close()
            self.rebuild_queue.put((None, None))
            loop.run_until_complete(srv.wait_closed())
            loop.run_until_complete(webapp.shutdown())
            loop.run_until_complete(handler.shutdown(5.0))
            loop.run_until_complete(webapp.cleanup())
            self.wd_observer.stop()
            self.wd_observer.join()
        loop.close()
예제 #43
0
    def gen_tasks(self):
        """Generate CSS out of Sass sources."""
        self.logger = utils.get_logger('build_sass', utils.STDERR_HANDLER)
        self.compiler_name = self.site.config['SASS_COMPILER']
        self.compiler_options = self.site.config['SASS_OPTIONS']

        kw = {
            'cache_folder': self.site.config['CACHE_FOLDER'],
            'themes': self.site.THEMES,
        }
        tasks = {}

        # Find where in the theme chain we define the Sass targets
        # There can be many *.sass/*.scss in the folder, but we only
        # will build the ones listed in sass/targets
        if os.path.isfile(os.path.join(self.sources_folder, "targets")):
            targets_path = os.path.join(self.sources_folder, "targets")
        else:
            targets_path = utils.get_asset_path(
                os.path.join(self.sources_folder, "targets"), self.site.THEMES)
        try:
            with codecs.open(targets_path, "rb", "utf-8") as inf:
                targets = [x.strip() for x in inf.readlines()]
        except Exception:
            targets = []

        for task in utils.copy_tree(
                self.sources_folder,
                os.path.join(kw['cache_folder'], self.sources_folder)):
            if task['name'] in tasks:
                continue
            task['basename'] = 'prepare_sass_sources'
            tasks[task['name']] = task
            yield task

        for theme_name in kw['themes']:
            src = os.path.join(utils.get_theme_path(theme_name),
                               self.sources_folder)
            for task in utils.copy_tree(
                    src, os.path.join(kw['cache_folder'],
                                      self.sources_folder)):
                if task['name'] in tasks:
                    continue
                task['basename'] = 'prepare_sass_sources'
                tasks[task['name']] = task
                yield task

        # Build targets and write CSS files
        dst_dir = os.path.join(self.site.config['OUTPUT_FOLDER'], 'assets',
                               'css')
        # Make everything depend on all sources, rough but enough
        deps = []
        for task in tasks.keys():
            for ext in self.sources_ext:
                if task.endswith(ext):
                    deps.append(task)

        def compile_target(target, dst):
            utils.makedirs(dst_dir)
            run_in_shell = sys.platform == 'win32'
            src = os.path.join(kw['cache_folder'], self.sources_folder, target)
            try:
                compiled = subprocess.check_output(
                    [self.compiler_name] + self.compiler_options + [src],
                    shell=run_in_shell)
            except OSError:
                utils.req_missing([self.compiler_name],
                                  'build Sass files (and use this theme)',
                                  False, False)
            with open(dst, "wb+") as outf:
                outf.write(compiled)

        yield self.group_task()

        # We can have file conflicts.  This is a way to prevent them.
        # I orignally wanted to use sets and their cannot-have-duplicates
        # magic, but I decided not to do this so we can show the user
        # what files were problematic.
        # If we didn’t do this, there would be a cryptic message from doit
        # instead.
        seennames = {}
        for target in targets:
            base = os.path.splitext(target)[0]
            dst = os.path.join(dst_dir, base + ".css")

            if base in seennames:
                self.logger.error(
                    'Duplicate filenames for Sass compiled files: {0} and '
                    '{1} (both compile to {2})'.format(seennames[base], target,
                                                       base + ".css"))
            else:
                seennames.update({base: target})

            yield {
                'basename': self.name,
                'name': dst,
                'targets': [dst],
                'file_dep': deps,
                'task_dep': ['prepare_sass_sources'],
                'actions': ((compile_target, [target, dst]), ),
                'uptodate': [utils.config_changed(kw)],
                'clean': True
            }
예제 #44
0
    def gen_tasks(self):
        """Generate CSS out of LESS sources."""
        self.compiler_name = self.site.config['LESS_COMPILER']
        self.compiler_options = self.site.config['LESS_OPTIONS']

        kw = {
            'cache_folder': self.site.config['CACHE_FOLDER'],
            'themes': self.site.THEMES,
        }
        tasks = {}

        # Find where in the theme chain we define the LESS targets
        # There can be many *.less in the folder, but we only will build
        # the ones listed in less/targets
        if os.path.isfile(os.path.join(self.sources_folder, "targets")):
            targets_path = os.path.join(self.sources_folder, "targets")
        else:
            targets_path = utils.get_asset_path(os.path.join(self.sources_folder, "targets"), self.site.THEMES)
        try:
            with codecs.open(targets_path, "rb", "utf-8") as inf:
                targets = [x.strip() for x in inf.readlines()]
        except Exception:
            targets = []

        for task in utils.copy_tree(self.sources_folder, os.path.join(kw['cache_folder'], self.sources_folder)):
            if task['name'] in tasks:
                continue
            task['basename'] = 'prepare_less_sources'
            tasks[task['name']] = task
            yield task

        for theme_name in kw['themes']:
            src = os.path.join(utils.get_theme_path(theme_name), self.sources_folder)
            for task in utils.copy_tree(src, os.path.join(kw['cache_folder'], self.sources_folder)):
                if task['name'] in tasks:
                    continue
                task['basename'] = 'prepare_less_sources'
                tasks[task['name']] = task
                yield task

        # Build targets and write CSS files
        dst_dir = os.path.join(self.site.config['OUTPUT_FOLDER'], 'assets', 'css')
        # Make everything depend on all sources, rough but enough
        deps = []
        for task in tasks.keys():
            if task.endswith(self.sources_ext):
                deps.append(task)

        def compile_target(target, dst):
            utils.makedirs(dst_dir)
            src = os.path.join(kw['cache_folder'], self.sources_folder, target)
            run_in_shell = sys.platform == 'win32'
            try:
                compiled = subprocess.check_output([self.compiler_name] + self.compiler_options + [src], shell=run_in_shell)
            except OSError:
                utils.req_missing([self.compiler_name],
                                  'build LESS files (and use this theme)',
                                  False, False)
            with open(dst, "wb+") as outf:
                outf.write(compiled)

        yield self.group_task()

        for target in targets:
            dst = os.path.join(dst_dir, target.replace(self.sources_ext, ".css"))
            yield {
                'basename': self.name,
                'name': dst,
                'targets': [dst],
                'file_dep': deps,
                'task_dep': ['prepare_less_sources'],
                'actions': ((compile_target, [target, dst]), ),
                'uptodate': [utils.config_changed(kw)],
                'clean': True
            }
예제 #45
0
    def gen_tasks(self):
        """Create tasks to copy the assets of the whole theme chain.

        If a file is present on two themes, use the version
        from the "youngest" theme.
        """
        kw = {
            "themes":
            self.site.THEMES,
            "translations":
            self.site.translations,
            "files_folders":
            self.site.config['FILES_FOLDERS'],
            "output_folder":
            self.site.config['OUTPUT_FOLDER'],
            "filters":
            self.site.config['FILTERS'],
            "code_color_scheme":
            self.site.config['CODE_COLOR_SCHEME'],
            "code.css_selectors":
            ['pre.code', '.code .codetable', '.highlight pre'],
            "code.css_wrappers": ['.highlight', '.code'],
            "code.css_head":
            '/* code.css file generated by Nikola */\n',
            "code.css_close":
            ("\ntable.codetable, table.highlighttable { width: 100%;}\n"
             ".codetable td.linenos, td.linenos { text-align: right; width: 3.5em; "
             "padding-right: 0.5em; background: rgba(127, 127, 127, 0.2) }\n"
             ".codetable td.code, td.code { padding-left: 0.5em; }\n"),
        }
        tasks = {}
        code_css_path = os.path.join(kw['output_folder'], 'css', 'code.css')
        code_css_input = utils.get_asset_path(
            'assets/css/code.css',
            themes=kw['themes'],
            files_folders=kw['files_folders'],
            output_dir=None)
        yield self.group_task()

        main_theme = utils.get_theme_path(kw['themes'][0])
        theme_ini = utils.parse_theme_meta(main_theme)
        if theme_ini:
            ignored_assets = theme_ini.get("Nikola",
                                           "ignored_assets",
                                           fallback='').split(',')
            ignored_assets = [
                os.path.normpath(asset_name.strip())
                for asset_name in ignored_assets
            ]
        else:
            ignored_assets = []

        for theme_name in kw['themes']:
            src = os.path.join(utils.get_theme_path(theme_name), 'assets')
            dst = kw['output_folder']
            for task in utils.copy_tree(src, dst):
                asset_name = os.path.relpath(task['name'], dst)
                if task['name'] in tasks or asset_name in ignored_assets:
                    continue
                tasks[task['name']] = task
                task['uptodate'] = [
                    utils.config_changed(kw, 'nikola.plugins.task.copy_assets')
                ]
                task['basename'] = self.name
                if code_css_input:
                    if 'file_dep' not in task:
                        task['file_dep'] = []
                    task['file_dep'].append(code_css_input)
                yield utils.apply_filters(task, kw['filters'])

        # Check whether or not there is a code.css file around.
        if not code_css_input and kw['code_color_scheme']:

            def create_code_css():
                formatter = BetterHtmlFormatter(style=kw["code_color_scheme"])
                utils.makedirs(os.path.dirname(code_css_path))
                with io.open(code_css_path, 'w+', encoding='utf-8') as outf:
                    outf.write(kw["code.css_head"])
                    outf.write(
                        formatter.get_style_defs(kw["code.css_selectors"],
                                                 kw["code.css_wrappers"]))
                    outf.write(kw["code.css_close"])

            if os.path.exists(code_css_path):
                with io.open(code_css_path, 'r', encoding='utf-8-sig') as fh:
                    testcontents = fh.read(len(
                        kw["code.css_head"])) == kw["code.css_head"]
            else:
                testcontents = False

            task = {
                'basename':
                self.name,
                'name':
                code_css_path,
                'targets': [code_css_path],
                'uptodate': [
                    utils.config_changed(kw,
                                         'nikola.plugins.task.copy_assets'),
                    testcontents
                ],
                'actions': [(create_code_css, [])],
                'clean':
                True,
            }
            yield utils.apply_filters(task, kw['filters'])
예제 #46
0
    def gen_tasks(self):
        """Create tasks to copy the assets of the whole theme chain.

        If a file is present on two themes, use the version
        from the "youngest" theme.
        """
        kw = {
            "themes":
            self.site.THEMES,
            "translations":
            self.site.translations,
            "files_folders":
            self.site.config['FILES_FOLDERS'],
            "output_folder":
            self.site.config['OUTPUT_FOLDER'],
            "filters":
            self.site.config['FILTERS'],
            "code_color_scheme":
            self.site.config['CODE_COLOR_SCHEME'],
            "code.css_selectors": ['pre.code', '.highlight pre'],
            "code.css_head":
            '/* code.css file generated by Nikola */\n',
            "code.css_close":
            "\ntable.codetable { width: 100%;} td.linenos {text-align: right; width: 4em;}\n",
        }
        tasks = {}
        code_css_path = os.path.join(kw['output_folder'], 'assets', 'css',
                                     'code.css')
        code_css_input = utils.get_asset_path(
            'assets/css/code.css',
            themes=kw['themes'],
            files_folders=kw['files_folders'],
            output_dir=None)
        yield self.group_task()

        main_theme = utils.get_theme_path(kw['themes'][0])
        theme_ini = utils.parse_theme_meta(main_theme)
        if theme_ini:
            ignored_assets = theme_ini.get("Nikola",
                                           "ignored_assets",
                                           fallback='').split(',')
            ignore_colorbox_i18n = theme_ini.get("Nikola",
                                                 "ignore_colorbox_i18n",
                                                 fallback="unused")
        else:
            ignored_assets = []
            ignore_colorbox_i18n = "unused"

        if ignore_colorbox_i18n == "unused":
            # Check what colorbox languages we need so we can ignore the rest
            needed_colorbox_languages = [
                LEGAL_VALUES['COLORBOX_LOCALES'][i] for i in kw['translations']
            ]
            needed_colorbox_languages = [
                i for i in needed_colorbox_languages if i
            ]  # remove '' for en
            # ignored_filenames is passed to copy_tree to avoid creating
            # directories. Since ignored_assets are full paths, and copy_tree
            #  works on single filenames, we can’t use that here.
            if not needed_colorbox_languages:
                ignored_filenames = set(["colorbox-i18n"])
            else:
                ignored_filenames = set()

        for theme_name in kw['themes']:
            src = os.path.join(utils.get_theme_path(theme_name), 'assets')
            dst = os.path.join(kw['output_folder'], 'assets')
            for task in utils.copy_tree(src,
                                        dst,
                                        ignored_filenames=ignored_filenames):
                asset_name = os.path.relpath(task['name'], dst)
                if task['name'] in tasks or asset_name in ignored_assets:
                    continue
                elif asset_name.startswith(_COLORBOX_PREFIX):
                    if ignore_colorbox_i18n == "all" or ignore_colorbox_i18n is True:
                        continue

                    colorbox_lang = asset_name[_COLORBOX_SLICE]
                    if ignore_colorbox_i18n == "unused" and colorbox_lang not in needed_colorbox_languages:
                        continue
                tasks[task['name']] = task
                task['uptodate'] = [
                    utils.config_changed(kw, 'nikola.plugins.task.copy_assets')
                ]
                task['basename'] = self.name
                if code_css_input:
                    if 'file_dep' not in task:
                        task['file_dep'] = []
                    task['file_dep'].append(code_css_input)
                yield utils.apply_filters(task, kw['filters'])

        # Check whether or not there is a code.css file around.
        if not code_css_input and kw['code_color_scheme']:

            def create_code_css():
                from pygments.formatters import get_formatter_by_name
                formatter = get_formatter_by_name(
                    'html', style=kw["code_color_scheme"])
                utils.makedirs(os.path.dirname(code_css_path))
                with io.open(code_css_path, 'w+', encoding='utf8') as outf:
                    outf.write(kw["code.css_head"])
                    outf.write(
                        formatter.get_style_defs(kw["code.css_selectors"]))
                    outf.write(kw["code.css_close"])

            if os.path.exists(code_css_path):
                with io.open(code_css_path, 'r', encoding='utf-8') as fh:
                    testcontents = fh.read(len(
                        kw["code.css_head"])) == kw["code.css_head"]
            else:
                testcontents = False

            task = {
                'basename':
                self.name,
                'name':
                code_css_path,
                'targets': [code_css_path],
                'uptodate': [
                    utils.config_changed(kw,
                                         'nikola.plugins.task.copy_assets'),
                    testcontents
                ],
                'actions': [(create_code_css, [])],
                'clean':
                True,
            }
            yield utils.apply_filters(task, kw['filters'])
예제 #47
0
    def _execute(self, options, args):
        """Start the watcher."""
        self.logger = get_logger('auto', STDERR_HANDLER)
        LRSocket.logger = self.logger

        if WebSocket is object and watchdog is None:
            req_missing(['ws4py', 'watchdog'], 'use the "auto" command')
        elif WebSocket is object:
            req_missing(['ws4py'], 'use the "auto" command')
        elif watchdog is None:
            req_missing(['watchdog'], 'use the "auto" command')

        self.cmd_arguments = ['nikola', 'build']
        if self.site.configuration_filename != 'conf.py':
            self.cmd_arguments.append('--conf=' +
                                      self.site.configuration_filename)

        # Run an initial build so we are up-to-date
        subprocess.call(self.cmd_arguments)

        port = options and options.get('port')
        self.snippet = '''<script>document.write('<script src="http://'
            + (location.host || 'localhost').split(':')[0]
            + ':{0}/livereload.js?snipver=1"></'
            + 'script>')</script>
        </head>'''.format(port)

        # Do not duplicate entries -- otherwise, multiple rebuilds are triggered
        watched = set([
            'templates/',
            'plugins/',
        ] + [get_theme_path(name) for name in self.site.THEMES])
        for item in self.site.config['post_pages']:
            watched.add(os.path.dirname(item[0]))
        for item in self.site.config['FILES_FOLDERS']:
            watched.add(item)
        for item in self.site.config['GALLERY_FOLDERS']:
            watched.add(item)
        for item in self.site.config['LISTINGS_FOLDERS']:
            watched.add(item)

        out_folder = self.site.config['OUTPUT_FOLDER']
        if options and options.get('browser'):
            browser = True
        else:
            browser = False

        if options['ipv6']:
            dhost = '::'
        else:
            dhost = None

        host = options['address'].strip('[').strip(']') or dhost

        # Server can be disabled (Issue #1883)
        self.has_server = not options['no-server']

        # Instantiate global observer
        observer = Observer()
        if self.has_server:
            # Watch output folders and trigger reloads
            observer.schedule(OurWatchHandler(self.do_refresh),
                              out_folder,
                              recursive=True)

        # Watch input folders and trigger rebuilds
        for p in watched:
            if os.path.exists(p):
                observer.schedule(OurWatchHandler(self.do_rebuild),
                                  p,
                                  recursive=True)

        # Watch config file (a bit of a hack, but we need a directory)
        _conf_fn = os.path.abspath(self.site.configuration_filename
                                   or 'conf.py')
        _conf_dn = os.path.dirname(_conf_fn)
        observer.schedule(ConfigWatchHandler(_conf_fn, self.do_rebuild),
                          _conf_dn,
                          recursive=False)

        try:
            self.logger.info("Watching files for changes...")
            observer.start()
        except KeyboardInterrupt:
            pass

        parent = self

        class Mixed(WebSocketWSGIApplication):
            """A class that supports WS and HTTP protocols on the same port."""
            def __call__(self, environ, start_response):
                if environ.get('HTTP_UPGRADE') is None:
                    return parent.serve_static(environ, start_response)
                return super(Mixed, self).__call__(environ, start_response)

        if self.has_server:
            ws = make_server(host,
                             port,
                             server_class=WSGIServer,
                             handler_class=WebSocketWSGIRequestHandler,
                             app=Mixed(handler_cls=LRSocket))
            ws.initialize_websockets_manager()
            self.logger.info("Serving HTTP on {0} port {1}...".format(
                host, port))
            if browser:
                if options['ipv6'] or '::' in host:
                    server_url = "http://[{0}]:{1}/".format(host, port)
                else:
                    server_url = "http://{0}:{1}/".format(host, port)

                self.logger.info(
                    "Opening {0} in the default web browser...".format(
                        server_url))
                # Yes, this is racy
                webbrowser.open('http://{0}:{1}'.format(host, port))

            try:
                self.dns_sd = dns_sd(port, (options['ipv6'] or '::' in host))
                ws.serve_forever()
            except KeyboardInterrupt:
                self.logger.info("Server is shutting down.")
                if self.dns_sd:
                    self.dns_sd.Reset()
                # This is a hack, but something is locking up in a futex
                # and exit() doesn't work.
                os.kill(os.getpid(), 15)
        else:
            # Workaround: can’t have nothing running (instant exit)
            #    but also can’t join threads (no way to exit)
            # The joys of threading.
            try:
                while True:
                    time.sleep(1)
            except KeyboardInterrupt:
                self.logger.info("Shutting down.")
                # This is a hack, but something is locking up in a futex
                # and exit() doesn't work.
                os.kill(os.getpid(), 15)
예제 #48
0
    def gen_tasks(self):
        """Create tasks to copy the assets of the whole theme chain.

        If a file is present on two themes, use the version
        from the "youngest" theme.
        """
        kw = {
            "themes": self.site.THEMES,
            "files_folders": self.site.config['FILES_FOLDERS'],
            "output_folder": self.site.config['OUTPUT_FOLDER'],
            "filters": self.site.config['FILTERS'],
            "code_color_scheme": self.site.config['CODE_COLOR_SCHEME'],
            "code.css_selectors": 'pre.code',
            "code.css_head": '/* code.css file generated by Nikola */\n',
            "code.css_close": "\ntable.codetable { width: 100%;} td.linenos {text-align: right; width: 4em;}\n",
        }
        tasks = {}
        code_css_path = os.path.join(kw['output_folder'], 'assets', 'css', 'code.css')
        code_css_input = utils.get_asset_path('assets/css/code.css',
                                              themes=kw['themes'],
                                              files_folders=kw['files_folders'], output_dir=None)
        yield self.group_task()

        for theme_name in kw['themes']:
            src = os.path.join(utils.get_theme_path(theme_name), 'assets')
            dst = os.path.join(kw['output_folder'], 'assets')
            for task in utils.copy_tree(src, dst):
                if task['name'] in tasks:
                    continue
                tasks[task['name']] = task
                task['uptodate'] = [utils.config_changed(kw, 'nikola.plugins.task.copy_assets')]
                task['basename'] = self.name
                if code_css_input:
                    if 'file_dep' not in task:
                        task['file_dep'] = []
                    task['file_dep'].append(code_css_input)
                yield utils.apply_filters(task, kw['filters'])

        # Check whether or not there is a code.css file around.
        if not code_css_input and kw['code_color_scheme']:
            def create_code_css():
                from pygments.formatters import get_formatter_by_name
                formatter = get_formatter_by_name('html', style=kw["code_color_scheme"])
                utils.makedirs(os.path.dirname(code_css_path))
                with io.open(code_css_path, 'w+', encoding='utf8') as outf:
                    outf.write(kw["code.css_head"])
                    outf.write(formatter.get_style_defs(kw["code.css_selectors"]))
                    outf.write(kw["code.css_close"])

            if os.path.exists(code_css_path):
                with io.open(code_css_path, 'r', encoding='utf-8') as fh:
                    testcontents = fh.read(len(kw["code.css_head"])) == kw["code.css_head"]
            else:
                testcontents = False

            task = {
                'basename': self.name,
                'name': code_css_path,
                'targets': [code_css_path],
                'uptodate': [utils.config_changed(kw, 'nikola.plugins.task.copy_assets'), testcontents],
                'actions': [(create_code_css, [])],
                'clean': True,
            }
            yield utils.apply_filters(task, kw['filters'])
예제 #49
0
    def gen_tasks(self):
        """Generate CSS out of LESS sources."""
        self.compiler_name = self.site.config['LESS_COMPILER']
        self.compiler_options = self.site.config['LESS_OPTIONS']

        kw = {
            'cache_folder': self.site.config['CACHE_FOLDER'],
            'themes': self.site.THEMES,
        }
        tasks = {}

        # Find where in the theme chain we define the LESS targets
        # There can be many *.less in the folder, but we only will build
        # the ones listed in less/targets
        if os.path.isfile(os.path.join(self.sources_folder, "targets")):
            targets_path = os.path.join(self.sources_folder, "targets")
        else:
            targets_path = utils.get_asset_path(os.path.join(self.sources_folder, "targets"), self.site.THEMES)
        try:
            with codecs.open(targets_path, "rb", "utf-8") as inf:
                targets = [x.strip() for x in inf.readlines()]
        except Exception:
            targets = []

        for task in utils.copy_tree(self.sources_folder, os.path.join(kw['cache_folder'], self.sources_folder)):
            if task['name'] in tasks:
                continue
            task['basename'] = 'prepare_less_sources'
            tasks[task['name']] = task
            yield task

        for theme_name in kw['themes']:
            src = os.path.join(utils.get_theme_path(theme_name), self.sources_folder)
            for task in utils.copy_tree(src, os.path.join(kw['cache_folder'], self.sources_folder)):
                task['basename'] = 'prepare_less_sources'
                yield task

        # Build targets and write CSS files
        base_path = utils.get_theme_path(self.site.THEMES[0])
        dst_dir = os.path.join(self.site.config['OUTPUT_FOLDER'], 'assets', 'css')
        # Make everything depend on all sources, rough but enough
        deps = []
        if os.path.isfile(os.path.join(self.sources_folder, "targets")):
            deps += glob.glob(os.path.join(kw['cache_folder'], self.sources_folder,
                                           '*{0}'.format(self.sources_ext)))
        else:
            deps += glob.glob(os.path.join(base_path, self.sources_folder,
                                           '*{0}'.format(self.sources_ext)))

        def compile_target(target, dst):
            utils.makedirs(dst_dir)
            src = os.path.join(kw['cache_folder'], self.sources_folder, target)
            run_in_shell = sys.platform == 'win32'
            try:
                compiled = subprocess.check_output([self.compiler_name] + self.compiler_options + [src], shell=run_in_shell)
            except OSError:
                utils.req_missing([self.compiler_name],
                                  'build LESS files (and use this theme)',
                                  False, False)
            with open(dst, "wb+") as outf:
                outf.write(compiled)

        yield self.group_task()

        for target in targets:
            dst = os.path.join(dst_dir, target.replace(self.sources_ext, ".css"))
            yield {
                'basename': self.name,
                'name': dst,
                'targets': [dst],
                'file_dep': deps,
                'task_dep': ['prepare_less_sources'],
                'actions': ((compile_target, [target, dst]), ),
                'uptodate': [utils.config_changed(kw)],
                'clean': True
            }
예제 #50
0
    def gen_tasks(self):
        """Create tasks to copy the assets of the whole theme chain.

        If a file is present on two themes, use the version
        from the "youngest" theme.
        """
        kw = {
            "themes": self.site.THEMES,
            "translations": self.site.translations,
            "files_folders": self.site.config['FILES_FOLDERS'],
            "output_folder": self.site.config['OUTPUT_FOLDER'],
            "filters": self.site.config['FILTERS'],
            "code_color_scheme": self.site.config['CODE_COLOR_SCHEME'],
            "code.css_selectors": ['pre.code', '.highlight pre'],
            "code.css_head": '/* code.css file generated by Nikola */\n',
            "code.css_close": "\ntable.codetable { width: 100%;} td.linenos {text-align: right; width: 4em;}\n",
        }
        tasks = {}
        code_css_path = os.path.join(kw['output_folder'], 'assets', 'css', 'code.css')
        code_css_input = utils.get_asset_path('assets/css/code.css',
                                              themes=kw['themes'],
                                              files_folders=kw['files_folders'], output_dir=None)
        yield self.group_task()

        main_theme = utils.get_theme_path(kw['themes'][0])
        theme_ini = utils.parse_theme_meta(main_theme)
        if theme_ini:
            ignored_assets = theme_ini.get("Nikola", "ignored_assets", fallback='').split(',')
            ignore_colorbox_i18n = theme_ini.get("Nikola", "ignore_colorbox_i18n", fallback="unused")
        else:
            ignored_assets = []
            ignore_colorbox_i18n = "unused"

        if ignore_colorbox_i18n == "unused":
            # Check what colorbox languages we need so we can ignore the rest
            needed_colorbox_languages = [LEGAL_VALUES['COLORBOX_LOCALES'][i] for i in kw['translations']]
            needed_colorbox_languages = [i for i in needed_colorbox_languages if i]  # remove '' for en
            # ignored_filenames is passed to copy_tree to avoid creating
            # directories. Since ignored_assets are full paths, and copy_tree
            #  works on single filenames, we can’t use that here.
            if not needed_colorbox_languages:
                ignored_filenames = set(["colorbox-i18n"])
            else:
                ignored_filenames = set()

        for theme_name in kw['themes']:
            src = os.path.join(utils.get_theme_path(theme_name), 'assets')
            dst = os.path.join(kw['output_folder'], 'assets')
            for task in utils.copy_tree(src, dst, ignored_filenames=ignored_filenames):
                asset_name = os.path.relpath(task['name'], dst)
                if task['name'] in tasks or asset_name in ignored_assets:
                    continue
                elif asset_name.startswith(_COLORBOX_PREFIX):
                    if ignore_colorbox_i18n == "all" or ignore_colorbox_i18n is True:
                        continue

                    colorbox_lang = asset_name[_COLORBOX_SLICE]
                    if ignore_colorbox_i18n == "unused" and colorbox_lang not in needed_colorbox_languages:
                        continue
                tasks[task['name']] = task
                task['uptodate'] = [utils.config_changed(kw, 'nikola.plugins.task.copy_assets')]
                task['basename'] = self.name
                if code_css_input:
                    if 'file_dep' not in task:
                        task['file_dep'] = []
                    task['file_dep'].append(code_css_input)
                yield utils.apply_filters(task, kw['filters'])

        # Check whether or not there is a code.css file around.
        if not code_css_input and kw['code_color_scheme']:
            def create_code_css():
                from pygments.formatters import get_formatter_by_name
                formatter = get_formatter_by_name('html', style=kw["code_color_scheme"])
                utils.makedirs(os.path.dirname(code_css_path))
                with io.open(code_css_path, 'w+', encoding='utf8') as outf:
                    outf.write(kw["code.css_head"])
                    outf.write(formatter.get_style_defs(kw["code.css_selectors"]))
                    outf.write(kw["code.css_close"])

            if os.path.exists(code_css_path):
                with io.open(code_css_path, 'r', encoding='utf-8') as fh:
                    testcontents = fh.read(len(kw["code.css_head"])) == kw["code.css_head"]
            else:
                testcontents = False

            task = {
                'basename': self.name,
                'name': code_css_path,
                'targets': [code_css_path],
                'uptodate': [utils.config_changed(kw, 'nikola.plugins.task.copy_assets'), testcontents],
                'actions': [(create_code_css, [])],
                'clean': True,
            }
            yield utils.apply_filters(task, kw['filters'])
예제 #51
0
    def _execute(self, options, args):
        """Start the watcher."""
        self.sockets = []
        self.rebuild_queue = asyncio.Queue()
        self.last_rebuild = datetime.datetime.now()

        if aiohttp is None and Observer is None:
            req_missing(['aiohttp', 'watchdog'], 'use the "auto" command')
        elif aiohttp is None:
            req_missing(['aiohttp'], 'use the "auto" command')
        elif Observer is None:
            req_missing(['watchdog'], 'use the "auto" command')

        if sys.argv[0].endswith('__main__.py'):
            self.nikola_cmd = [sys.executable, '-m', 'nikola', 'build']
        else:
            self.nikola_cmd = [sys.argv[0], 'build']

        if self.site.configuration_filename != 'conf.py':
            self.nikola_cmd.append('--conf=' + self.site.configuration_filename)

        # Run an initial build so we are up-to-date (synchronously)
        self.logger.info("Rebuilding the site...")
        subprocess.call(self.nikola_cmd)

        port = options and options.get('port')
        self.snippet = '''<script>document.write('<script src="http://'
            + (location.host || 'localhost').split(':')[0]
            + ':{0}/livereload.js?snipver=1"></'
            + 'script>')</script>
        </head>'''.format(port)

        # Deduplicate entries by using a set -- otherwise, multiple rebuilds are triggered
        watched = set([
            'templates/'
        ] + [get_theme_path(name) for name in self.site.THEMES])
        for item in self.site.config['post_pages']:
            watched.add(os.path.dirname(item[0]))
        for item in self.site.config['FILES_FOLDERS']:
            watched.add(item)
        for item in self.site.config['GALLERY_FOLDERS']:
            watched.add(item)
        for item in self.site.config['LISTINGS_FOLDERS']:
            watched.add(item)
        for item in self.site.config['IMAGE_FOLDERS']:
            watched.add(item)
        for item in self.site._plugin_places:
            watched.add(item)
        # Nikola itself (useful for developers)
        watched.add(pkg_resources.resource_filename('nikola', ''))

        out_folder = self.site.config['OUTPUT_FOLDER']
        if options and options.get('browser'):
            browser = True
        else:
            browser = False

        if options['ipv6']:
            dhost = '::'
        else:
            dhost = '0.0.0.0'

        host = options['address'].strip('[').strip(']') or dhost

        # Set up asyncio server
        webapp = web.Application()
        webapp.router.add_get('/livereload.js', self.serve_livereload_js)
        webapp.router.add_get('/robots.txt', self.serve_robots_txt)
        webapp.router.add_route('*', '/livereload', self.websocket_handler)
        resource = IndexHtmlStaticResource(True, self.snippet, '', out_folder)
        webapp.router.register_resource(resource)

        # Prepare asyncio event loop
        # Required for subprocessing to work
        loop = asyncio.get_event_loop()

        # Set debug setting
        loop.set_debug(self.site.debug)

        # Server can be disabled (Issue #1883)
        self.has_server = not options['no-server']

        if self.has_server:
            handler = webapp.make_handler()
            srv = loop.run_until_complete(loop.create_server(handler, host, port))

        self.wd_observer = Observer()
        # Watch output folders and trigger reloads
        if self.has_server:
            self.wd_observer.schedule(NikolaEventHandler(self.reload_page, loop), out_folder, recursive=True)

        # Watch input folders and trigger rebuilds
        for p in watched:
            if os.path.exists(p):
                self.wd_observer.schedule(NikolaEventHandler(self.run_nikola_build, loop), p, recursive=True)

        # Watch config file (a bit of a hack, but we need a directory)
        _conf_fn = os.path.abspath(self.site.configuration_filename or 'conf.py')
        _conf_dn = os.path.dirname(_conf_fn)
        self.wd_observer.schedule(ConfigEventHandler(_conf_fn, self.run_nikola_build, loop), _conf_dn, recursive=False)
        self.wd_observer.start()

        win_sleeper = None
        # https://bugs.python.org/issue23057 (fixed in Python 3.8)
        if sys.platform == 'win32' and sys.version_info < (3, 8):
            win_sleeper = asyncio.ensure_future(windows_ctrlc_workaround())

        if not self.has_server:
            self.logger.info("Watching for changes...")
            # Run the event loop forever (no server mode).
            try:
                # Run rebuild queue
                loop.run_until_complete(self.run_rebuild_queue())

                loop.run_forever()
            except KeyboardInterrupt:
                pass
            finally:
                if win_sleeper:
                    win_sleeper.cancel()
                self.wd_observer.stop()
                self.wd_observer.join()
            loop.close()
            return

        host, port = srv.sockets[0].getsockname()

        if options['ipv6'] or '::' in host:
            server_url = "http://[{0}]:{1}/".format(host, port)
        else:
            server_url = "http://{0}:{1}/".format(host, port)
        self.logger.info("Serving on {0} ...".format(server_url))

        if browser:
            self.logger.info("Opening {0} in the default web browser...".format(server_url))
            webbrowser.open('http://{0}:{1}'.format(host, port))

        # Run the event loop forever and handle shutdowns.
        try:
            # Run rebuild queue
            queue_future = asyncio.ensure_future(self.run_rebuild_queue())

            self.dns_sd = dns_sd(port, (options['ipv6'] or '::' in host))
            loop.run_forever()
        except KeyboardInterrupt:
            pass
        finally:
            self.logger.info("Server is shutting down.")
            if win_sleeper:
                win_sleeper.cancel()
            if self.dns_sd:
                self.dns_sd.Reset()
            queue_future.cancel()
            srv.close()
            loop.run_until_complete(srv.wait_closed())
            loop.run_until_complete(webapp.shutdown())
            loop.run_until_complete(handler.shutdown(5.0))
            loop.run_until_complete(webapp.cleanup())
            self.wd_observer.stop()
            self.wd_observer.join()
        loop.close()
예제 #52
0
    def gen_tasks(self):
        """Generate CSS out of Sass sources."""
        self.logger = utils.get_logger('build_sass', self.site.loghandlers)

        kw = {
            'cache_folder': self.site.config['CACHE_FOLDER'],
            'themes': self.site.THEMES,
        }

        # Find where in the theme chain we define the Sass targets
        # There can be many *.sass/*.scss in the folder, but we only
        # will build the ones listed in sass/targets
        targets_path = utils.get_asset_path(
            os.path.join(self.sources_folder, "targets"), self.site.THEMES)
        try:
            with codecs.open(targets_path, "rb", "utf-8") as inf:
                targets = [x.strip() for x in inf.readlines()]
        except Exception:
            targets = []

        for theme_name in kw['themes']:
            src = os.path.join(utils.get_theme_path(theme_name),
                               self.sources_folder)
            for task in utils.copy_tree(
                    src, os.path.join(kw['cache_folder'],
                                      self.sources_folder)):
                task['basename'] = 'prepare_sass_sources'
                yield task

        # Build targets and write CSS files
        base_path = utils.get_theme_path(self.site.THEMES[0])
        dst_dir = os.path.join(self.site.config['OUTPUT_FOLDER'], 'assets',
                               'css')
        # Make everything depend on all sources, rough but enough
        deps = glob.glob(
            os.path.join(base_path, self.sources_folder,
                         *("*{0}".format(ext) for ext in self.sources_ext)))

        def compile_target(target, dst):
            utils.makedirs(dst_dir)
            src = os.path.join(kw['cache_folder'], self.sources_folder, target)
            compiled = subprocess.check_output([self.compiler_name, src])
            with open(dst, "wb+") as outf:
                outf.write(compiled)

        yield self.group_task()

        # We can have file conflicts.  This is a way to prevent them.
        # I orignally wanted to use sets and their cannot-have-duplicates
        # magic, but I decided not to do this so we can show the user
        # what files were problematic.
        # If we didn’t do this, there would be a cryptic message from doit
        # instead.
        seennames = {}
        for target in targets:
            base = os.path.splitext(target)[0]
            dst = os.path.join(dst_dir, base + ".css")

            if base in seennames:
                self.logger.error(
                    'Duplicate filenames for SASS compiled files: {0} and '
                    '{1} (both compile to {2})'.format(seennames[base], target,
                                                       base + ".css"))
            else:
                seennames.update({base: target})

            yield {
                'basename': self.name,
                'name': dst,
                'targets': [dst],
                'file_dep': deps,
                'task_dep': ['prepare_sass_sources'],
                'actions': ((compile_target, [target, dst]), ),
                'uptodate': [utils.config_changed(kw)],
                'clean': True
            }