Exemple #1
0
class Parser(object):
    """Parse ReStructuredText source files."""

    def __init__(self, config):
        self.config = config
        self.path = Path(config)
        #self.source_dir = "%s/%s" % (config.project_dir, config.source_folder)

    def get_fragment(self, source_abspath):
        source = self.get_document_source(source_abspath)
        parts = self.get_document_parts(source) 
        return parts['fragment']

    def get_data(self, source_abspath):
        source = self.get_document_source(source_abspath)
        parts = self.get_document_parts(source) 

        data = dict()
        data['title'] = parts['title']
        data['subtitle'] = parts['subtitle']
        data['fragment'] = parts['fragment']
        
        # Extra metadata: docid, author, date, tags
        meta = self._get_metadata(source, source_abspath) 
        data.update(meta)  

        # Derived parts: slug, fragment_path, source_path 
        slug = self.get_slug(source_abspath)
        data['slug'] = slug
        data['fragment_path'] = self.path.get_fragment_path(source_abspath)
        data['source_path'] = self.path.get_source_path(source_abspath)

        return data

    def get_document_source(self, source_abspath):
        def_source = self.get_substitution_definitions()
        doc_source = self.read_source_file(source_abspath)
        source = "\n".join([def_source, doc_source])
        return source
                
    def get_document_parts(self, source):
        # http://docutils.sourceforge.net/docs/api/publisher.html#publish-parts-details
        writer_name = self.config.writer_name
        settings = dict(initial_header_level=2) # do we need this?
        options = dict(source=source, writer_name=writer_name, settings_overrides=settings)
        parts = docutils.core.publish_parts(**options)
        return parts
    
    def get_substitution_definitions(self):
        # Standard substitution definitions
        # http://docutils.sourceforge.net/docs/ref/rst/definitions.html
        module_abspath = os.path.abspath(__file__)
        module_dir = os.path.dirname(module_abspath)
        source = self.read_source_file("%s/etc/substitutions.rst" % module_dir)
        return source

    def read_source_file(self, file_path):
        fin = open(file_path, "r")
        source = fin.read().decode('utf-8')
        return source       

    def get_slug(self, source_abspath):
        start = self.path.get_source_dir()
        #relative_path = file_name.rpartition(source_dir)[-1].lstrip("/") 
        relative_path = os.path.relpath(source_abspath, start)
        slug = os.path.splitext(relative_path)[0]
        return slug

    def _get_metadata(self, source, source_abspath):
        doctree = docutils.core.publish_doctree(source)
        docinfo = doctree.traverse(docutils.nodes.docinfo)
        try:
            meta = self._process_standard_fields(docinfo)
            meta = self._process_custom_fields(meta)
        except IndexError:
            print "ERROR: Source file is missing data: %s" % source_abspath
            raise
        for key, value in meta.items():
            meta[key] = value.astext()
        return meta

    def _process_standard_fields(self,docinfo):
        # Standard fields: date, author, etc.
        meta = {}
        for node in docinfo[0].children:
            key = node.tagname.lower()
            value = node.children[0]
            meta[key] = value
        return meta

    def _process_custom_fields(self, meta):
        # http://repo.or.cz/w/wrigit.git/blob/f045e5e7766e767c0b56bcb7a1ba0582a6f4f176:/rst.py
        field = meta['field']
        meta['tags'] = field.parent.children[1]
        meta['docid'] = field.parent.parent.children[0].children[1]
        del meta['field']
        return meta
        
    def get_all_files(self):
        source_dir = self.path.get_source_dir()
        for root, dirs, files in os.walk(source_dir):
            for filename in files:
                # Ignore pattern: emacs autosave files. TODO: generalize this
                if fnmatch(filename, "*.rst") and not fnmatch(filename, "*.#*"):
                    source_abspath = os.path.join(root, filename)
                    yield source_abspath
Exemple #2
0
class ChangeLog(PickleDB):

    db_name = "changelog"

    def initialize(self, config):
        self.config = config
        self.path = Path(config)
        assert self.db_abspath == self.path.get_changelog_abspath()

    def update(self):
        # File exists so go ahead and read/write to the changlog
        if self.exists() is False:
            print "CHANGELOG NOT FOUND: Will add/update all entries in database on push."
            # Remove the old changelog from git so it doesn't persist on the server
            self._remove_changelog()
            return 
        
        diff = self._get_diff()   
        if not diff:
            return

        self._write_diff(diff)
        self._display()
        
        return self.data

    def _display(self):
        print "CHANGELOG"
        for filename in self.data:
            status, timestamp = self.data[filename]
            print timestamp, status, filename 
        print
        
    def _write_diff(self, diff):
        source_dir = self.path.get_source_dir()
        start = self.path.get_working_dir()
        source_folder = os.path.relpath(source_dir, start)

        for status, filename in self._split_diff(diff):
            # filter out files that don't include the source_dir
            if re.search(source_folder, filename) and filename.endswith(self.config.source_ext):
                # Git diff is NOT sorted by modified time.
                # We need it ordered by time so use timestamp instead
                timestamp = self._current_timestamp()
                # remove it from the dict and add it back so more recent entries are always last
                self.data.pop(filename, None)
                self.data[filename] = (status, timestamp)
        self.write()

        # Add the changelog to git now that it has been updated.
        self._add_changelog()

        return self.data

    def _current_timestamp(self):
        return int(time.time())

    def _split_diff(self, diff):
        lines = [line.split('\t') for line in diff.strip().split('\n')]
        return lines

    def _get_diff(self):
        # git diff is NOT sorted by modified time
        #command = "git diff --cached --name-only"
        git_dir = self.path.get_git_dir()
        working_dir = self.path.get_working_dir()
        command = "git  diff --cached --name-status"
        return self._execute(command)

    def _add_changelog(self):
        # Add the changelog to git after it has been updated.
        command = "git add %s" % self.path.get_changelog_abspath()
        self._execute(command)

    def _remove_changelog(self):
        # Doing this so the old changelog doesn't persist on the server
        command = "git rm %s" % self.path.get_changelog_abspath()
        print self._execute(command)
        
    def _execute(self, command):
        # Setting Git env vars to ensure proper paths when running outside of working dir
        os.putenv("GIT_DIR", self.path.get_git_dir())
        os.putenv("GIT_WORK_TREE", self.path.get_working_dir()) 
        return execute(command)
Exemple #3
0
class Command(object):

    def __init__(self, config, graph):
        self.config = config
        self.graph = graph
        self.path = Path(config)
        self.parser = Parser(config)
        self.writer = Writer(config)
        self.loader = Loader(config, graph)

    # Public methods
       
    def new(self, filename):
        # TODO: parse out docid, maybe sign docid

        try:
            assert filename.endswith(self.config.source_ext)
        except AssertionError as e:
            print "File name must end with %s" % self.config.source_ext
            sys.exit(1)

        source_dir = self.path.get_source_dir()
        source_abspath = os.path.join(source_dir, filename)
        content = self._build_initial_source(filename)

        print "Creating file:  %s" % source_abspath
        self._create_file(source_abspath, content)

        return source_abspath

    def edit(self, filename):
        # Open new file in the editor specified in the yaml config file
        editor = self.config.editor
        source_path = self.new(filename)
        process = "%s %s" % (editor, source_path)
        return subprocess.call(process.split())

    def init(self):
        # Make sure author is pre-loaded in the database
        data = dict(username=self.config.username, name=self.config.name)
        author = self.graph.people.get_or_create("username", self.config.username, data)

    def build(self):
        # Create HTML fragments
        self.writer.run()

    def update(self):
        # Update blog entries
        self.loader.update_entries()

    # Execute one of the above methods

    def _execute(self, command_name, command_args):
        command = getattr(self, command_name)
        return command(*command_args)

    # Private methods

    def _create_file(self, source_abspath, content):
        self._make_dir(source_abspath)
        with open(source_abspath, "w") as fout:
            fout.writelines(content)
                                     
    def _build_initial_source(self, filename):
        # generat the source from template
        template_path = self.path.get_rst_template_path()
        template = get_template(template_path)
        params = self._get_params(filename)
        source = template.substitute(params)
        return source

    def _get_params(self, filename):
        # Get template params
        docid = uuid.uuid4().hex
        date = datetime.datetime.now().strftime("%Y-%m-%d")
        username = self.config.username or getpass.getuser()
        title = self._get_title(filename)
        title_line = "=" * len(title)
        params = dict(title=title, title_line=title_line, docid=docid, author=username, date=date)
        return params

    def _get_title(self, filename):
        stub = os.path.splitext(filename)[0]
        word_list = stub.split(self.config.separator)
        words = " ".join(word_list)
        title = titlecase(words)
        return title
        
    def _write_file(self, file_path, content):
        with open(file_path, "w") as fout:
            fout.write(content.encode('utf-8') + '\n')

    def _make_dir(self, path):
        # mkpath
        dirname = os.path.dirname(path)
        if not os.path.isdir(dirname):
            print "Creating dir:   %s" % dirname
            os.makedirs(dirname)