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
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)
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)