Пример #1
0
    def init_env(self):
        """Initialize the environment for generating the Web site to deploy.

        This function reads the Web site configuration, sets up the template
        environment and sets object properties.
        """

        self.dir_content = self.get_path('content', required=True)
        self.config = Config(self.get_path('site.yaml', required=True))

        self.dir_templates = self.get_path('templates', required=True)
        self.template = Template(self)

        self.dir_bin = self.get_path('bin')

        # make all settings in site section available to templates
        for key, val in list(self.config.get_section('site').items()):
            self.template.add_var(key, val)

        # optional directory with static files like css, js and images
        self.dir_static = self.get_path('static')

        self.dir_dst = self.get_path('deploy')

        # feeds are only generated if base_url is set in site section of config
        self.base_url = self.config.get('site', 'base_url')

        if self.base_url is False:
            raise Exception('base_url not set in site config.')
Пример #2
0
    def init_env(self):
        """Initialize the environment for generating the Web site to deploy.

        This function reads the Web site configuration, sets up the template
        environment and sets object properties.
        """

        cwd = self.dir_site
        self.dir_content = path.join(cwd, 'content', required=True)
        self.config = config.load(path.join(cwd, 'site.yaml', required=True))

        self.dir_templates = path.join(cwd, 'templates', required=True)
        self.template = Template(self)

        self.dir_bin = path.join(cwd, 'bin')

        # make all settings in site section available to templates
        self.template.vars.update(self.config['site'])

        # Optional directory with static files like css, js and images.
        self.dir_static = path.join(cwd, 'static')

        # Directory is created by the generate command.
        self.dir_deploy = path.join(cwd, 'deploy')

        self.base_url = self.config['site'].get('base_url')
        # base_url must be defined in settings
        if not self.base_url:
            raise Exception('base_url not set in site config.')

        # A dictionary of parsed documents indexed by resource paths.
        self.docs = {}

        # A dictionary of document collections.
        self.index = {}

        # Set default templates once for a Logya instance.
        self.templates = {
            'doc': self.config['content']['doc']['template'],
            'index': self.config['content']['index']['template'],
            'rss': self.config['content']['rss']['template']
        }

        # Map collection paths to config variables (vars) to make collecion
        # settings accessible via index URLs.
        self.collection_index = {
            v['path']: k
            for k, v in self.config['collections'].items()
        }
Пример #3
0
    def init_env(self):
        """Initialize the environment for generating the Web site to deploy.

        This function reads the Web site configuration, sets up the template
        environment and sets object properties.
        """

        self.dir_content = self.get_path('content', required=True)
        self.config = Config(self.get_path('site.yaml', required=True))

        self.dir_templates = self.get_path('templates', required=True)
        self.template = Template(self)

        self.dir_bin = self.get_path('bin')

        # make all settings in site section available to templates
        for key, val in list(self.config.get_section('site').items()):
            self.template.add_var(key, val)

        # optional directory with static files like css, js and images
        self.dir_static = self.get_path('static')

        self.dir_dst = self.get_path('deploy')

        # feeds are only generated if base_url is set in site section of config
        self.base_url = self.config.get('site', 'base_url')

        if self.base_url is False:
            raise Exception('base_url not set in site config.')
Пример #4
0
Файл: core.py Проект: yaph/logya
    def init_env(self):
        """Initialize the environment for generating the Web site to deploy.

        This function reads the Web site configuration, sets up the template
        environment and sets object properties.
        """

        cwd = self.dir_site
        self.dir_content = path.join(cwd, 'content', required=True)
        self.config = config.load(path.join(cwd, 'site.yaml', required=True))

        self.dir_templates = path.join(cwd, 'templates', required=True)
        self.template = Template(self)

        self.dir_bin = path.join(cwd, 'bin')

        # make all settings in site section available to templates
        self.template.vars.update(self.config['site'])

        # Optional directory with static files like css, js and images.
        self.dir_static = path.join(cwd, 'static')

        # Directory is created by the generate command.
        self.dir_deploy = path.join(cwd, 'deploy')

        self.base_url = self.config['site'].get('base_url')
        # base_url must be defined in settings
        if not self.base_url:
            raise Exception('base_url not set in site config.')

        # A dictionary of parsed documents indexed by resource paths.
        self.docs = {}

        # A dictionary of document collections.
        self.index = {}

        # Set default templates once for a Logya instance.
        self.templates = {
            'doc': self.config['content']['doc']['template'],
            'index': self.config['content']['index']['template'],
            'rss': self.config['content']['rss']['template']
        }

        # Set languages if specified.
        if 'languages' in self.config:
            self.languages = self.config['languages']

        # Map collection paths to config variables (vars) to make collecion
        # settings accessible via index URLs.
        self.collection_index = {
            v['path']: k for k, v in self.config['collections'].items()}
Пример #5
0
class Logya(object):
    """Main logic for creating, building and serving a static site."""

    index_filename = 'index.html'

    def __init__(self, **kwargs):
        """Set required logya object properties."""

        self.verbose = kwargs.get('verbose', False)

        # dir_site is an optional argument of the generate command, which may
        # instantiate the class with a value of None for dir_site.
        dir_site = kwargs.get('dir_site')
        self.dir_site = dir_site if dir_site else os.getcwd()

    def init_env(self):
        """Initialize the environment for generating the Web site to deploy.

        This function reads the Web site configuration, sets up the template
        environment and sets object properties.
        """

        cwd = self.dir_site
        self.dir_content = path.join(cwd, 'content', required=True)
        self.config = config.load(path.join(cwd, 'site.yaml', required=True))

        self.dir_templates = path.join(cwd, 'templates', required=True)
        self.template = Template(self)

        self.dir_bin = path.join(cwd, 'bin')

        # make all settings in site section available to templates
        self.template.vars.update(self.config['site'])

        # Optional directory with static files like css, js and images.
        self.dir_static = path.join(cwd, 'static')

        # Directory is created by the generate command.
        self.dir_deploy = path.join(cwd, 'deploy')

        self.base_url = self.config['site'].get('base_url')
        # base_url must be defined in settings
        if not self.base_url:
            raise Exception('base_url not set in site config.')

        # A dictionary of parsed documents indexed by resource paths.
        self.docs = {}

        # A dictionary of document collections.
        self.index = {}

        # Set default templates once for a Logya instance.
        self.templates = {
            'doc': self.config['content']['doc']['template'],
            'index': self.config['content']['index']['template'],
            'rss': self.config['content']['rss']['template']
        }

        # Map collection paths to config variables (vars) to make collecion
        # settings accessible via index URLs.
        self.collection_index = {
            v['path']: k for k, v in self.config['collections'].items()}

    def info(self, msg):
        """Print message if in verbose mode."""

        if self.verbose:
            print(msg)

    def get_doc_template(self, doc):
        """Get template setting from doc otherwise from configuration."""

        return doc.get('template', self.templates['doc'])

    def get_index_template(self, fullpath):
        """Get template file name for given fullpath.

        If a collection is set in site.yaml for fullpath, use the corresponding
        template if set. Otherwise return the default index template.
        """

        template = self.templates['index']
        var = get_collection_var(fullpath, self.collection_index)
        if var:
            template = self.config['collections'][var].get('template', template)
        return template

    def _update_index(self, doc, url=None):
        """Add a doc to index determined from given url.

        For each directory in the URL except for the one containing the content
        file itself a collection is created, if it doesn't exist, and the
        document is added to it.
        """

        if url is None:
            url = doc['url']

        dirs = path.parent_dirs(url)
        for fullpath in path.parent_paths(dirs):
            if fullpath not in self.index:
                template = self.get_index_template(fullpath)
                self.index[fullpath] = Collection(docs=[], template=template, urls=set())

            # Don't add documents more than once to a collection. Important to
            # check and add doc['url'], because url can be an index url like tags.
            if doc['url'] not in self.index[fullpath].urls:
                self.index[fullpath].urls.add(doc['url'])
                self.index[fullpath].docs.append(doc)

    def _update_doc_index(self, doc, var, basepath):
        """Add the doc to the index defined for the header variable (var)."""

        for val in doc[var]:
            url = '/{}/{}/'.format(basepath, path.slugify(val))

            links = var + '_links'
            doc[links] = doc.get(links, []) + [(url, val)]

            # Must append file name to url to create subdir.
            self._update_index(doc, url + self.index_filename)

    def update_index(self, doc):
        """Add document to index based on index headers."""

        # Don't index documents with noindex set to a true value.
        if doc.get('noindex'):
            return

        self._update_index(doc)

        # Add to special __index__ for RSS generation.
        self._update_index(doc, '__index__/index/')

        for url, collection in self.config['collections'].items():
            if url in doc:
                self._update_doc_index(doc, url, collection['path'])

    def build_index(self, mode=None):
        """Build index of documents for content directories to be created.

        The mode argument hints the Logya command that was executed."""

        msg_duplicate = 'The URL {} is already used and will be overwritten.'

        for doc in DocReader(self.dir_content).parsed:
            url = doc['url']
            # Warn user about duplicate URLs when not in serve mode.
            if 'serve' != mode and url in self.docs:
                print(msg_duplicate.format(url))
            self.docs[url] = doc
            self.update_index(doc)

        # For each collection sort docs by creation dates in descending order.
        for url, collection in self.index.items():
            collection.docs.sort(key=itemgetter('created'), reverse=True)

        # Make index available to templates.
        self.template.vars['index'] = self.index

    def index_title(self, s):
        """Title for index pages, usually created from directory paths."""

        return ' » '.join(s.split('/')).replace('-', ' ').title()

    def write_rss(self, feed_title, url, docs):
        """Write RSS 2.0 XML file in target directory"""

        self.template.vars['url'] = self.base_url
        self.template.vars['title'] = feed_title
        self.template.vars['description'] = feed_title
        self.template.vars['last_build'] = datetime.datetime.now()
        self.template.vars['docs'] = docs

        page = self.template.get_page(self.template.vars, self.templates['rss'])
        content = page.render(self.template.vars)

        filename = path.target_file(
            self.dir_deploy, path.join(url, 'rss.xml'))
        write(filename, content)

    def write_index(self, url, collection):
        """Write an auto-generated index.html file."""

        check_doc_url = '/{}'.format(path.join(url, self.index_filename))
        # make sure there exists no document at the index url
        if check_doc_url not in self.docs:
            # Ugly fix for issue #32: delete description var. This is called
            # for every index, instead of once for all, because write_index is
            # called in serve mode. Also there may remain other vars causing
            # future problems.
            # Emptying the vars dict does not work either, because the index
            # key is set in build_index and needed.
            if 'description' in self.template.vars:
                del self.template.vars['description']

            title = self.index_title(url)

            self.template.vars['docs'] = collection.docs
            self.template.vars['title'] = title
            self.template.vars['canonical'] = '{:s}/{:s}/'.format(self.base_url, url)

            page = self.template.get_page(self.template.vars, collection.template)
            content = page.render(self.template.vars)

            filename = path.target_file(self.dir_deploy, url)
            write(filename, content)

            # write directory RSS file
            self.write_rss(title, url, collection.docs)

    def write_index_files(self):
        """Write index.html files to deploy directories where non exists.

        If no template is specified in configuration index won't be written.
        """

        feed_title = self.config['site'].get('feed_title', 'RSS Feed')

        for url, collection in self.index.items():
            if '__index__' != url:
                self.write_index(url, collection)

        # write root RSS file
        if '__index__' in self.index:
            self.write_rss(feed_title, '', self.index['__index__'].docs)
Пример #6
0
class Logya(object):
    """Main logic for creating, building and serving a static site."""

    re_url_replace = re.compile(r'[\/\s_]+')

    def __init__(self, **kwargs):
        """Set required logya object properties."""

        if 'verbose' in kwargs and kwargs['verbose']:
            self.verbose = True
        else:
            self.verbose = False

        self.dir_current = os.getcwd()

        self.index_filename = 'index.html'

        self.feed_limit = 10

        # a dictionary of parsed documents indexed by resource paths
        self.docs_parsed = {}

    def init_env(self):
        """Initialize the environment for generating the Web site to deploy.

        This function reads the Web site configuration, sets up the template
        environment and sets object properties.
        """

        self.dir_content = self.get_path('content', required=True)
        self.config = Config(self.get_path('site.yaml', required=True))

        self.dir_templates = self.get_path('templates', required=True)
        self.template = Template(self)

        self.dir_bin = self.get_path('bin')

        # make all settings in site section available to templates
        for key, val in list(self.config.get_section('site').items()):
            self.template.add_var(key, val)

        # optional directory with static files like css, js and images
        self.dir_static = self.get_path('static')

        self.dir_dst = self.get_path('deploy')

        # feeds are only generated if base_url is set in site section of config
        self.base_url = self.config.get('site', 'base_url')

        if self.base_url is False:
            raise Exception('base_url not set in site config.')

    def info(self, msg):
        """Print message if in verbose mode."""

        if self.verbose:
            print(msg)

    def set_dir_current(self, dir_current):
        """Called from tests."""

        self.dir_current = dir_current

    def get_path(self, name, required=False):
        """Get path relative to current working directory for given name.

        Raises an exception if resource is required and doesn't exist.
        """

        path = os.path.join(self.dir_current, name)
        if required and not os.path.exists(path):
            raise Exception('Resource at path "%s" does not exist.' % path)
        return path

    def get_doc_template(self, doc):
        """Get template setting from doc otherwise from configuration."""

        template = self.config.get_item('templates', 'doc', 'content_type',
                                        'template')

        return doc.get('template', template)

    def get_dirs_from_path(self, url):
        """Returns a list of directories from given url.

        The last directory is omitted as it contains an index.html file
        containing the content of the appropriate document."""

        dirs = [f for f in url.strip('/').split('/') if f]
        if dirs:
            dirs = dirs[:-1]
        return dirs

    def update_index(self, doc, path):
        """Add a doc to given path index."""

        self.indexes[path] = self.indexes.get(path, []) + [doc]

    def _update_indexes(self, doc, url=None):
        """Add a doc to indexes determined from given url."""

        if url is None:
            url = doc['url']

        dirs = self.get_dirs_from_path(url)
        last = 0
        for d in dirs:
            last += 1
            self.update_index(doc, '/'.join(dirs[:last]))

    def update_indexes(self, doc):
        """Add all indexes for doc determined from headers."""

        # don't index documents with noindex set
        if 'noindex' in doc and doc['noindex']:
            return
        self._update_indexes(doc)
        # add to special __index__ for RSS generation
        self._update_indexes(doc, '__index__/index/')

        doc_indexes = self.config.get_section('indexes')
        for idx in doc_indexes:
            if idx['var'] in doc:
                self.update_doc_index(doc, idx['var'], idx['path'])

    def update_doc_index(self, doc, var, path):
        """Update doc index."""

        for val in doc[var]:
            var_path = re.sub(self.re_url_replace, '-', val).lower()
            url = '/%s/%s/' % (path, var_path)
            links = var + '_links'
            doc[links] = doc.get(links, []) + [(url, val)]
            # must append path after tag string to create subdir
            self._update_indexes(doc, url + self.index_filename)

    def build_indexes(self, mode=None):
        """Build indexes of documents for content directories to be created.

        The mode argument hints the Logya command that was executed."""

        # a dictionary of indexes with parsed documents
        self.indexes = {}

        docs = DocReader(self.dir_content, DocParser()).get_docs()
        for doc in docs:
            # ignore empty documents and those without url
            if doc is None or 'url' not in doc:
                continue
            url = doc['url']
            # warn user about duplicate URLs when not in serve mode
            if 'serve' != mode and url in self.docs_parsed:
                print(('The URL %s is already used and will be overwritten.' %
                       url))
            self.docs_parsed[url] = doc

        for doc in list(self.docs_parsed.values()):
            self.update_indexes(doc)

        # sort indexes by descending docs creation dates
        for idx in self.indexes:
            self.indexes[idx] = sorted(self.indexes[idx],
                                       key=itemgetter('created'),
                                       reverse=True)

        # make indexes available to templates
        self.template.add_var('indexes', self.indexes)

    def index_title(self, s):
        """Title for index pages, usually created from directory paths."""

        if not is3:
            s = s.encode('utf-8')
        return ' » '.join(s.split('/')).replace('-', ' ').title()

    def write_rss(self, feed_title, directory, docs):
        """Write RSS 2.0 XML file in target directory"""

        self.template.add_var('url', self.base_url)
        self.template.add_var('title', feed_title)
        self.template.add_var('description', directory)
        self.template.add_var('last_build', datetime.datetime.now())
        self.template.add_var('items', docs[0:self.feed_limit])

        writer = FileWriter()
        page = self.template.env.get_template('rss2.xml')
        fh = writer.getfile(self.dir_dst, os.path.join(directory, 'rss.xml'))
        writer.write(fh, page.render(self.template.get_vars()))

    def write_index(self, filewriter, directory, template):
        """Write an auto-generated index.html file."""

        url_path = '/%s' % os.path.join(directory, self.index_filename)
        # make sure there exists no document at the index path
        if url_path not in self.docs_parsed:
            # remove the file name part if it's index.html
            url = url_path.replace(self.index_filename, '')

            docs = self.indexes[directory]

            # Ugly fix for issue #32: delete description var. This is called
            # for every index, instead of once for all, because write_index is
            # called in serve mode. Also there may remain other vars causing
            # future problems.
            # Emptying the vars dict does not work either, because the indexes
            # key is set in build_indexes and needed.
            if 'description' in self.template.vars:
                del self.template.vars['description']

            title = self.index_title(directory)
            if not is3:
                title = title.decode('utf-8')

            self.template.add_var('url', url)
            self.template.add_var('canonical', self.base_url + url)
            self.template.add_var('index', docs)
            self.template.add_var('title', title)
            self.template.add_var('directory', directory)

            page = self.template.env.get_template(template)
            filewriter.write(filewriter.getfile(self.dir_dst, directory),
                             page.render(self.template.get_vars()))

            # write directory RSS file
            if self.base_url:
                self.write_rss(title, directory, docs)

    def write_indexes(self):
        """Write index.html files to deploy directories where non exists.

        If no template file is specified in configuration indexes won't be
        written.
        """

        template = self.config.get_item('templates', 'index', 'content_type',
                                        'template')
        if not template:
            return

        feed_title = self.config.get('site', 'feed_title')
        if not feed_title:
            feed_title = 'RSS Feed'

        for directory in list(self.indexes.keys()):
            self.write_index(FileWriter(), directory, template)

        # write root RSS file
        if self.base_url and '__index__' in self.indexes:
            docs = sorted(self.indexes['__index__'],
                          key=itemgetter('created'),
                          reverse=True)
            self.write_rss(feed_title, '', docs)

    # TODO remove in 4.0
    def get_execs(self, path):
        """Generator yielding paths to executable files in given directory."""

        for p in os.listdir(path):
            fpath = os.path.join(path, p)
            if os.path.isfile(fpath) and os.access(fpath, os.X_OK):
                yield fpath

    # TODO remove in 4.0
    def exec_bin(self):
        """Execute binary files in bin dir."""

        dir_bin = self.get_path('bin')
        if os.path.exists(dir_bin):
            args = {'logya': self}
            for exe in self.get_execs(dir_bin):
                execfile(exe, args)
Пример #7
0
class Logya(object):
    """Main logic for creating, building and serving a static site."""

    re_url_replace = re.compile(r'[\/\s_]+')

    def __init__(self, **kwargs):
        """Set required logya object properties."""

        if 'verbose' in kwargs and kwargs['verbose']:
            self.verbose = True
        else:
            self.verbose = False

        self.dir_current = os.getcwd()

        self.index_filename = 'index.html'

        self.feed_limit = 10

        # a dictionary of parsed documents indexed by resource paths
        self.docs_parsed = {}

    def init_env(self):
        """Initialize the environment for generating the Web site to deploy.

        This function reads the Web site configuration, sets up the template
        environment and sets object properties.
        """

        self.dir_content = self.get_path('content', required=True)
        self.config = Config(self.get_path('site.yaml', required=True))

        self.dir_templates = self.get_path('templates', required=True)
        self.template = Template(self)

        self.dir_bin = self.get_path('bin')

        # make all settings in site section available to templates
        for key, val in list(self.config.get_section('site').items()):
            self.template.add_var(key, val)

        # optional directory with static files like css, js and images
        self.dir_static = self.get_path('static')

        self.dir_dst = self.get_path('deploy')

        # feeds are only generated if base_url is set in site section of config
        self.base_url = self.config.get('site', 'base_url')

        if self.base_url is False:
            raise Exception('base_url not set in site config.')

    def info(self, msg):
        """Print message if in verbose mode."""

        if self.verbose:
            print(msg)

    def set_dir_current(self, dir_current):
        """Called from tests."""

        self.dir_current = dir_current

    def get_path(self, name, required=False):
        """Get path relative to current working directory for given name.

        Raises an exception if resource is required and doesn't exist.
        """

        path = os.path.join(self.dir_current, name)
        if required and not os.path.exists(path):
            raise Exception('Resource at path "%s" does not exist.' % path)
        return path

    def get_doc_template(self, doc):
        """Get template setting from doc otherwise from configuration."""

        template = self.config.get_item(
            'templates', 'doc', 'content_type', 'template')

        return doc.get('template', template)

    def get_dirs_from_path(self, url):
        """Returns a list of directories from given url.

        The last directory is omitted as it contains an index.html file
        containing the content of the appropriate document."""

        dirs = [f for f in url.strip('/').split('/') if f]
        if dirs:
            dirs = dirs[:-1]
        return dirs

    def update_index(self, doc, path):
        """Add a doc to given path index."""

        self.indexes[path] = self.indexes.get(path, []) + [doc]

    def _update_indexes(self, doc, url=None):
        """Add a doc to indexes determined from given url."""

        if url is None:
            url = doc['url']

        dirs = self.get_dirs_from_path(url)
        last = 0
        for d in dirs:
            last += 1
            self.update_index(doc, '/'.join(dirs[:last]))

    def update_indexes(self, doc):
        """Add all indexes for doc determined from headers."""

        # don't index documents with noindex set
        if 'noindex' in doc and doc['noindex']:
            return
        self._update_indexes(doc)
        # add to special __index__ for RSS generation
        self._update_indexes(doc, '__index__/index/')

        doc_indexes = self.config.get_section('indexes')
        for idx in doc_indexes:
            if idx['var'] in doc:
                self.update_doc_index(doc, idx['var'], idx['path'])

    def update_doc_index(self, doc, var, path):
        """Update doc index."""

        for val in doc[var]:
            var_path = re.sub(self.re_url_replace, '-', val).lower()
            url = '/%s/%s/' % (path, var_path)
            links = var + '_links'
            doc[links] = doc.get(links, []) + [(url, val)]
            # must append path after tag string to create subdir
            self._update_indexes(doc, url + self.index_filename)

    def build_indexes(self, mode=None):
        """Build indexes of documents for content directories to be created.

        The mode argument hints the Logya command that was executed."""

        # a dictionary of indexes with parsed documents
        self.indexes = {}

        docs = DocReader(self.dir_content, DocParser()).get_docs()
        for doc in docs:
            # ignore empty documents and those without url
            if doc is None or 'url' not in doc:
                continue
            url = doc['url']
            # warn user about duplicate URLs when not in serve mode
            if 'serve' != mode and url in self.docs_parsed:
                print(('The URL %s is already used and will be overwritten.'
                    % url))
            self.docs_parsed[url] = doc

        for doc in list(self.docs_parsed.values()):
            self.update_indexes(doc)

        # sort indexes by descending docs creation dates
        for idx in self.indexes:
            self.indexes[idx] = sorted(self.indexes[idx],
                                       key=itemgetter('created'),
                                       reverse=True)

        # make indexes available to templates
        self.template.add_var('indexes', self.indexes)

    def index_title(self, s):
        """Title for index pages, usually created from directory paths."""

        if not is3:
            s = s.encode('utf-8')
        return ' » '.join(s.split('/')).replace('-', ' ').title()

    def write_rss(self, feed_title, directory, docs):
        """Write RSS 2.0 XML file in target directory"""

        self.template.add_var('url', self.base_url)
        self.template.add_var('title', feed_title)
        self.template.add_var('description', directory)
        self.template.add_var('last_build', datetime.datetime.now())
        self.template.add_var('items', docs[0:self.feed_limit])

        writer = FileWriter()
        page = self.template.env.get_template('rss2.xml')
        fh = writer.getfile(self.dir_dst, os.path.join(directory, 'rss.xml'))
        writer.write(fh, page.render(self.template.get_vars()))

    def write_index(self, filewriter, directory, template):
        """Write an auto-generated index.html file."""

        url_path = '/%s' % os.path.join(directory, self.index_filename)
        # make sure there exists no document at the index path
        if url_path not in self.docs_parsed:
            # remove the file name part if it's index.html
            url = url_path.replace(self.index_filename, '')

            docs = self.indexes[directory]

            # Ugly fix for issue #32: delete description var. This is called
            # for every index, instead of once for all, because write_index is
            # called in serve mode. Also there may remain other vars causing
            # future problems.
            # Emptying the vars dict does not work either, because the indexes
            # key is set in build_indexes and needed.
            if 'description' in self.template.vars:
                del self.template.vars['description']

            title = self.index_title(directory)
            if not is3:
                title = title.decode('utf-8')

            self.template.add_var('url', url)
            self.template.add_var('canonical', self.base_url + url)
            self.template.add_var('index', docs)
            self.template.add_var('title', title)
            self.template.add_var('directory', directory)

            page = self.template.env.get_template(template)
            filewriter.write(filewriter.getfile(self.dir_dst, directory),
                             page.render(self.template.get_vars()))

            # write directory RSS file
            if self.base_url:
                self.write_rss(title, directory, docs)

    def write_indexes(self):
        """Write index.html files to deploy directories where non exists.

        If no template file is specified in configuration indexes won't be
        written.
        """

        template = self.config.get_item(
            'templates', 'index', 'content_type', 'template')
        if not template:
            return

        feed_title = self.config.get('site', 'feed_title')
        if not feed_title:
            feed_title = 'RSS Feed'

        for directory in list(self.indexes.keys()):
            self.write_index(FileWriter(), directory, template)

        # write root RSS file
        if self.base_url and '__index__' in self.indexes:
            docs = sorted(self.indexes['__index__'],
                          key=itemgetter('created'),
                          reverse=True)
            self.write_rss(feed_title, '', docs)

    # TODO remove in 4.0
    def get_execs(self, path):
        """Generator yielding paths to executable files in given directory."""

        for p in os.listdir(path):
            fpath = os.path.join(path, p)
            if os.path.isfile(fpath) and os.access(fpath, os.X_OK):
                yield fpath

    # TODO remove in 4.0
    def exec_bin(self):
        """Execute binary files in bin dir."""

        dir_bin = self.get_path('bin')
        if os.path.exists(dir_bin):
            args = {'logya': self}
            for exe in self.get_execs(dir_bin):
                execfile(exe, args)
Пример #8
0
class Logya(object):
    """Main logic for creating, building and serving a static site."""

    index_filename = 'index.html'

    def __init__(self, **kwargs):
        """Set required logya object properties."""

        self.verbose = kwargs.get('verbose', False)

        # dir_site is an optional argument of the generate command, which may
        # instantiate the class with a value of None for dir_site.
        dir_site = kwargs.get('dir_site')
        self.dir_site = dir_site if dir_site else os.getcwd()

    def init_env(self):
        """Initialize the environment for generating the Web site to deploy.

        This function reads the Web site configuration, sets up the template
        environment and sets object properties.
        """

        cwd = self.dir_site
        self.dir_content = path.join(cwd, 'content', required=True)
        self.config = config.load(path.join(cwd, 'site.yaml', required=True))

        self.dir_templates = path.join(cwd, 'templates', required=True)
        self.template = Template(self)

        self.dir_bin = path.join(cwd, 'bin')

        # make all settings in site section available to templates
        self.template.vars.update(self.config['site'])

        # Optional directory with static files like css, js and images.
        self.dir_static = path.join(cwd, 'static')

        # Directory is created by the generate command.
        self.dir_deploy = path.join(cwd, 'deploy')

        self.base_url = self.config['site'].get('base_url')
        # base_url must be defined in settings
        if not self.base_url:
            raise Exception('base_url not set in site config.')

        # A dictionary of parsed documents indexed by resource paths.
        self.docs = {}

        # A dictionary of document collections.
        self.index = {}

        # Set default templates once for a Logya instance.
        self.templates = {
            'doc': self.config['content']['doc']['template'],
            'index': self.config['content']['index']['template'],
            'rss': self.config['content']['rss']['template']
        }

        # Map collection paths to config variables (vars) to make collecion
        # settings accessible via index URLs.
        self.collection_index = {
            v['path']: k
            for k, v in self.config['collections'].items()
        }

    def info(self, msg):
        """Print message if in verbose mode."""

        if self.verbose:
            print(msg)

    def get_doc_template(self, doc):
        """Get template setting from doc otherwise from configuration."""

        return doc.get('template', self.templates['doc'])

    def get_index_template(self, fullpath):
        """Get template file name for given fullpath.

        If a collection is set in site.yaml for fullpath, use the corresponding
        template if set. Otherwise return the default index template.
        """

        template = self.templates['index']
        var = get_collection_var(fullpath, self.collection_index)
        if var:
            template = self.config['collections'][var].get(
                'template', template)
        return template

    def _update_index(self, doc, url=None):
        """Add a doc to index determined from given url.

        For each directory in the URL except for the one containing the content
        file itself a collection is created, if it doesn't exist, and the
        document is added to it.
        """

        if url is None:
            url = doc['url']

        dirs = path.parent_dirs(url)
        for fullpath in path.parent_paths(dirs):
            if fullpath not in self.index:
                template = self.get_index_template(fullpath)
                self.index[fullpath] = Collection(docs=[],
                                                  template=template,
                                                  urls=set())

            # Don't add documents more than once to a collection. Important to
            # check and add doc['url'], because url can be an index url like tags.
            if doc['url'] not in self.index[fullpath].urls:
                self.index[fullpath].urls.add(doc['url'])
                self.index[fullpath].docs.append(doc)

    def _update_doc_index(self, doc, var, basepath):
        """Add the doc to the index defined for the header variable (var)."""

        for val in doc[var]:
            url = '/{}/{}/'.format(basepath, path.slugify(val))

            links = var + '_links'
            doc[links] = doc.get(links, []) + [(url, val)]

            # Must append file name to url to create subdir.
            self._update_index(doc, url + self.index_filename)

    def update_index(self, doc):
        """Add document to index based on index headers."""

        # Don't index documents with noindex set to a true value.
        if doc.get('noindex'):
            return

        self._update_index(doc)

        # Add to special __index__ for RSS generation.
        self._update_index(doc, '__index__/index/')

        for url, collection in self.config['collections'].items():
            if url in doc:
                self._update_doc_index(doc, url, collection['path'])

    def build_index(self, mode=None):
        """Build index of documents for content directories to be created.

        The mode argument hints the Logya command that was executed."""

        msg_duplicate = 'The URL {} is already used and will be overwritten.'

        for doc in DocReader(self.dir_content).parsed:
            url = doc['url']
            # Warn user about duplicate URLs when not in serve mode.
            if 'serve' != mode and url in self.docs:
                print(msg_duplicate.format(url))
            self.docs[url] = doc
            self.update_index(doc)

        # For each collection sort docs by creation dates in descending order.
        for url, collection in self.index.items():
            collection.docs.sort(key=itemgetter('created'), reverse=True)

        # Make index available to templates.
        self.template.vars['index'] = self.index

    def index_title(self, s):
        """Title for index pages, usually created from directory paths."""

        return ' » '.join(s.split('/')).replace('-', ' ').title()

    def write_rss(self, feed_title, url, docs):
        """Write RSS 2.0 XML file in target directory"""

        self.template.vars['url'] = self.base_url
        self.template.vars['title'] = feed_title
        self.template.vars['description'] = feed_title
        self.template.vars['last_build'] = datetime.datetime.now()
        self.template.vars['docs'] = docs

        page = self.template.get_page(self.template.vars,
                                      self.templates['rss'])
        content = page.render(self.template.vars)

        filename = path.target_file(self.dir_deploy, path.join(url, 'rss.xml'))
        write(filename, content)

    def write_index(self, url, collection):
        """Write an auto-generated index.html file."""

        check_doc_url = '/{}'.format(path.join(url, self.index_filename))
        # make sure there exists no document at the index url
        if check_doc_url not in self.docs:
            # Ugly fix for issue #32: delete description var. This is called
            # for every index, instead of once for all, because write_index is
            # called in serve mode. Also there may remain other vars causing
            # future problems.
            # Emptying the vars dict does not work either, because the index
            # key is set in build_index and needed.
            if 'description' in self.template.vars:
                del self.template.vars['description']

            title = self.index_title(url)

            self.template.vars['docs'] = collection.docs
            self.template.vars['title'] = title
            self.template.vars['canonical'] = '{:s}/{:s}/'.format(
                self.base_url, url)

            page = self.template.get_page(self.template.vars,
                                          collection.template)
            content = page.render(self.template.vars)

            filename = path.target_file(self.dir_deploy, url)
            write(filename, content)

            # write directory RSS file
            self.write_rss(title, url, collection.docs)

    def write_index_files(self):
        """Write index.html files to deploy directories where non exists.

        If no template is specified in configuration index won't be written.
        """

        feed_title = self.config['site'].get('feed_title', 'RSS Feed')

        for url, collection in self.index.items():
            if '__index__' != url:
                self.write_index(url, collection)

        # write root RSS file
        if '__index__' in self.index:
            self.write_rss(feed_title, '', self.index['__index__'].docs)