diff --git a/lexicon.cfg b/lexicon.cfg index 5869544..5b16ee7 100644 --- a/lexicon.cfg +++ b/lexicon.cfg @@ -17,25 +17,16 @@ Lexicon Title logo.png <<>>SIDEBAR_CONTENT>>> -# Sidebar +# The prompt for the Lexicon. Will be read as HTML and inserted into the +# header directly. +>>>PROMPT>>> +Prompt goes here +<<>>SESSION_PAGE>>> -# Lexicon Title - -**You are [...]**. The index grouping is **[...]**. - -For the first turn, give the //Rules// page a read-through. Then come up with the character of your scholar, write an article that's about 100-300 words within your assigned index, and send it to the host. - -Consider putting session information here, like the index grouping and turn count, where to send completed entries, index assignments, turn schedule, and so on. +

Put session information here, like the index grouping and turn count, where to send completed entries, index assignments, turn schedule, and so on.

<<, :, ", ', /, \, |, ?, and * - s = re.sub(r"[<>:\"'/\\|?*]", '', s) - # Strip out Unicode for - - s = re.sub(r"[^\x00-\x7F]+", '-', s) - # Strip out whitespace for _ - s = re.sub(r"\s+", '_', s) +def titleescape(s): + """Makes an article title filename-safe.""" + s = s.strip() + s = re.sub(r"\s+", '_', s) # Replace whitespace with _ + s = parse.quote(s) # Encode all other characters + s = re.sub(r"%", "", s) # Strip encoding %s + if len(s) > 64: # If the result is unreasonably long, + s = hex(abs(hash(s)))[2:] # Replace it with a hex hash return s def titlestrip(s): @@ -32,130 +34,207 @@ def titlestrip(s): if s.startswith("A "): return s[2:] return s -def link_formatter(written_articles): +# Main article class + +class LexiconArticle: """ - Creates a lambda that formats citation links and handles the phantom class. - Input: written_articles, a list of article titles to format as live links - Output: a lambda (fid, alias, title) -> link_string + A Lexicon article and its metadata. + + Members: + author string: the author of the article + turn integer: the turn the article was written for + title string: the article title + title_filesafe string: the title, escaped, used for filenames + content string: the HTML content, with citations replaced by format hooks + citations dict from format hook string to tuple of link alias and link target title + wcites list: titles of written articles cited + pcites list: titles of phantom articles cited + citedby list: titles of articles that cite this + The last three are filled in by populate(). """ - return lambda fid, alias, title: "{0}".format( - alias, as_filename(title), - "" if title in written_articles else " class=\"phantom\"" - ) + + def __init__(self, author, turn, title, content, citations): + """ + Creates a LexiconArticle object with the given parameters. + """ + self.author = author + self.turn = turn + self.title = title + self.title_filesafe = titleescape(title) + self.content = content + self.citations = citations + self.wcites = set() + self.pcites = set() + self.citedby = set() + + @staticmethod + def from_file_raw(raw_content): + """ + Parses the contents of a Lexipython source file into a LexiconArticle + object. If the source file is malformed, returns None. + """ + headers = raw_content.split('\n', 3) + if len(headers) != 4: + print("Header read error") + return None + author_header, turn_header, title_header, content_raw = headers + # Validate and sanitize the author header + if not author_header.startswith("# Author:"): + print("Author header missing") + return None + author = author_header[9:].strip() + # Validate and sanitize the turn header + if not turn_header.startswith("# Turn:"): + print("Turn header missing") + return None + turn = None + try: + turn = int(turn_header[7:].strip()) + except: + print("Turn header error") + return None + # Validate and sanitize the title header + if not title_header.startswith("# Title:"): + print("Title header missing") + return None + title = titlecase(title_header[8:]) + # Parse the content and extract citations + paras = re.split("\n\n+", content_raw.strip()) + content = "" + citations = {} + format_id = 1 + if not paras: + print("No content") + for para in paras: + # Escape angle brackets + para = re.sub("<", "<", para) + para = re.sub(">", ">", para) + # Replace bold and italic marks with tags + para = re.sub(r"//([^/]+)//", r"\1", para) + para = re.sub(r"\*\*([^*]+)\*\*", r"\1", para) + # Replace \\LF with
LF + para = re.sub(r"\\\\\n", "
\n", para) + # Abstract citations into the citation record + link_match = re.search(r"\[\[(([^|\[\]]+)\|)?([^|\[\]]+)\]\]", para) + while link_match: + # Identify the citation text and cited article + cite_text = link_match.group(2) if link_match.group(2) else link_match.group(3) + cite_title = titlecase(link_match.group(3)) + # Record the citation + citations["c"+str(format_id)] = (cite_text, cite_title) + # Stitch the format id in place of the citation + para = para[:link_match.start(0)] + "{c"+str(format_id)+"}" + para[link_match.end(0):] + format_id += 1 # Increment to the next format citation + link_match = re.search(r"\[\[(([^|\[\]]+)\|)?([^|\[\]]+)\]\]", para) + # Convert signature to right-aligned + if para[:1] == '~': + para = "

" + para[1:] + "

\n" + else: + para = "

" + para + "

\n" + content += para + return LexiconArticle(author, turn, title, content, citations) + + def build_page_content(self): + """ + Formats citations into the article content as normal HTML links + and returns the result. + """ + format_map = { + format_id: "{0}".format( + cite_tuple[0], titleescape(cite_tuple[1]), + "" if cite_tuple[1] in self.wcites else " class=\"phantom\"") + for format_id, cite_tuple in self.citations.items() + } + return self.content.format(**format_map) + + def build_page_citeblock(self, prev_target, next_target): + """ + Builds the citeblock content HTML for use in regular article pages. + For each defined target, links the target page as Previous or Next. + """ + citeblock = "
\n" + # Prev/next links + if next_target is not None: + citeblock += "

Next →

\n".format(titleescape(next_target)) + if prev_target is not None: + citeblock += "

← Previous

\n".format(titleescape(prev_target)) + elif next_target is not None: + citeblock += "

 

\n" + # Citations + cites_links = [ + "{0}".format( + title, titleescape(title), + "" if title in self.wcites else " class=\"phantom\"") + for title in sorted(self.wcites | self.pcites)] + cites_str = " | ".join(cites_links) + if len(cites_str) < 1: cites_str = "--" + citeblock += "

Citations: {}

\n".format(cites_str) + # Citedby + citedby_links = [ + "{0}".format( + title, titleescape(title)) + for title in self.citedby] + citedby_str = " | ".join(citedby_links) + if len(citedby_str) < 1: citedby_str = "--" + citeblock += "

Cited by: {}

\n
\n".format(citedby_str) + return citeblock # Parsing functions for source intake -def parse_lex_header(header_para): +def parse_from_directory(directory): """ - Parses the header paragraph of a lex file. - Input: header_para, raw header paragraph from the lex file - Output: {"error": } if there was an error, otherwise - {"title":
, "filename":
} - """ - # The title, which is also translated to the filename, heads the article after the # - title_match = re.match("#(.+)", header_para) - if not title_match: - return {"error": "No match for title"} - title = titlecase(title_match.group(1).strip()) - if not title: - return {"error": "Could not parse header as title"} - return {"title": title, "filename": as_filename(title)} - -def parse_lex_content(paras): - """ - Parses the content paragraphs of a lex file. - Input: paras, a list of raw paragraphs from the lex file - Output: {"error": } if there was an error, otherwise - {"content":
, - "citations": {: (link text, link target)}} - """ - parsed = {"content": "", "citations": {}} - format_id = 1 # Each citation will be ID'd by {c#} for formatting later - for para in paras: - # Escape angle brackets - para = re.sub("<", "<", para) - para = re.sub(">", ">", para) - # Replace bold and italic marks with tags - para = re.sub(r"\*\*([^*]+)\*\*", r"\1", para) - para = re.sub(r"\/\/([^\/]+)\/\/", r"\1", para) - # Replace \\LF with
LF - para = re.sub(r"\\\\\n", "
\n", para) - # Abstract citations into the citation record - link_match = re.search(r"\[\[(([^|\[\]]+)\|)?([^|\[\]]+)\]\]", para) - while link_match: - # Identify the citation text and cited article - cite_text = link_match.group(2) if link_match.group(2) else link_match.group(3) - cite_title = titlecase(link_match.group(3).strip()) - # Record the citation - parsed["citations"]["c"+str(format_id)] = (cite_text, cite_title) - # Stitch the format id in place of the citation - para = para[:link_match.start(0)] + "{c"+str(format_id)+"}" + para[link_match.end(0):] - format_id += 1 # Increment to the next format citation - link_match = re.search(r"\[\[(([^|\[\]]+)\|)?([^|\[\]]+)\]\]", para) - # Convert signature to right-aligned - if para[:1] == '~': - para = "

" + para[1:] + "

\n" - else: - para = "

" + para + "

\n" - parsed["content"] += para - if not parsed["content"]: - return {"error": "No content parsed"} - return parsed - -def parse_lex(lex_contents): - """ - Parses the contents of a lex file into HTML and abstracts citations. - Input: lex_contents, the read contents of a lex file - Output: A dictionary in the following format: - {"title":
, "filename":
, - "content":
, - "citations": {: (link text, link target)}} - """ - parsed_article = {} - # Split the file into paragraphs - paras = re.split("\n\n+", lex_contents) - # Parse the title from the header - title_parsed = parse_lex_header(paras.pop(0)) - if "error" in title_parsed: - return title_parsed - parsed_article.update(title_parsed) - # Parse each paragraph - content_parsed = parse_lex_content(paras) - if "error" in content_parsed: - return content_parsed - parsed_article.update(content_parsed) - # Return the fully abstracted article - return parsed_article - -def parse_lex_from_directory(directory): - """ - Reads and parses each lex file in the given directory. + Reads and parses each source file in the given directory. Input: directory, the path to the folder to read - Output: a list of parsed lex file structures + Output: a list of parsed articles """ - lexes = [] - print("Reading lex files from", directory) + articles = [] + print("Reading source files from", directory) for filename in os.listdir(directory): path = directory + filename - # Read only .lex files - if path[-4:] == ".lex": - print(" Parsing", path) - with open(path, "r", encoding="utf8") as lex_file: - lex_raw = lex_file.read() - parsed_lex = parse_lex(lex_raw) - if "error" in parsed_lex: - print(" ERROR:", parsed_lex["error"]) + # Read only .txt files + if filename[-4:] == ".txt": + print(" Parsing", filename) + with open(path, "r", encoding="utf8") as src_file: + raw = src_file.read() + article = LexiconArticle.from_file_raw(raw) + if article is None: + print(" ERROR") else: - print(" success:", parsed_lex["title"]) - lexes.append(parsed_lex) - return lexes + print(" success:", article.title) + articles.append(article) + return articles + +def populate(lexicon_articles): + """ + Given a list of lexicon articles, fills out citation information + for each article and creates phantom pages for missing articles. + """ + article_by_title = {article.title : article for article in lexicon_articles} + # Determine all articles that exist or should exist + extant_titles = set([citation[1] for article in lexicon_articles for citation in article.citations]) + # Interlink all citations + for article in lexicon_articles: + for cite_tuple in article.citations.values(): + target = cite_tuple[1] + # Create article objects for phantom citations + if target not in article_by_title: + article_by_title[target] = LexiconArticle(None, sys.maxsize, target, "

This entry hasn't been written yet.

", {}) + # Interlink citations + if article_by_title[target].author is None: + article.pcites.add(target) + else: + article.wcites.add(target) + article_by_title[target].citedby.add(article.title) + return list(article_by_title.values()) def load_resource(filename, cache={}): + """Loads files from the resources directory with caching.""" if filename not in cache: cache[filename] = open("resources/" + filename, "r", encoding="utf8").read() return cache[filename] def load_config(): + """Loads values from the config file.""" config = {} with open("lexicon.cfg", "r", encoding="utf8") as f: line = f.readline() @@ -178,193 +257,72 @@ def load_config(): raise SystemExit("Reached EOF while reading config value {}".format(conf)) config[conf] = conf_value.strip() # Check that all necessary values were configured - for config_value in ['LEXICON_TITLE', 'SIDEBAR_CONTENT', 'SESSION_PAGE', "INDEX_LIST"]: + for config_value in ['LEXICON_TITLE', 'PROMPT', 'SESSION_PAGE', "INDEX_LIST"]: if config_value not in config: raise SystemExit("Error: {} not set in lexipython.cfg".format(config_value)) return config -# Building functions for output +# Build functions -def make_cite_map(lex_list): +def build_contents_page(articles, config): """ - Compiles all citation information into a single map. - Input: lex_list, a list of lex structures - Output: a map from article titles to cited titles + Builds the full HTML of the contents page. """ - cite_map = {} - for lex in lex_list: - cited_titles = [cite_tuple[1] for format_id, cite_tuple in lex["citations"].items()] - cite_map[lex["title"]] = sorted(set(cited_titles), key=titlestrip) - return cite_map - -def format_content(lex, format_func): - """ - Formats citations into the lex content according to the provided function. - Input: lex, a lex structure - formatted_key, the key to store the formatted content under - format_func, a function matching (fid, alias, dest) -> citation HTML - Output: lex content formatted according to format_func - """ - format_map = { - format_id: format_func(format_id, cite_tuple[0], cite_tuple[1]) - for format_id, cite_tuple in lex["citations"].items() - } - return lex["content"].format(**format_map) - -def citation_lists(title, cite_map): - """ - Returns the citation lists for an article. - Input: title, an article title - cite_map, generated by make_cite_map - Output: a list of cited article titles - a list of titles of article citing this article - """ - citers = [citer_title - for citer_title, cited_titles in cite_map.items() - if title in cited_titles] - return cite_map[title] if title in cite_map else [], citers - -def build_article_page(lex, cite_map, config): - """ - Builds the full HTML of an article page. - Input: lex, a lex structure - cite_map, generated by make_cite_map - config, a dict of config values - Output: the full HTML as a string - """ - lf = link_formatter(cite_map.keys()) - # Build the article content - content = format_content(lex, lf) - # Build the article citeblock - cites, citedby = citation_lists(lex["title"], cite_map) - cites_str = " | ".join([lf(None, title, title) for title in cites]) - if len(cites_str) < 1: cites_str = "--" - citedby_str = " | ".join([lf(None, title, title) for title in citedby]) - if len(citedby_str) < 1: citedby_str = "--" - citeblock = ""\ - "
\n"\ - "

Citations: {cites}

\n"\ - "

Cited by: {citedby}

\n"\ - "
\n".format( - cites=cites_str, - citedby=citedby_str) - # Fill in the entry skeleton - entry_skeleton = load_resource("entry-page.html") - css = load_resource("lexicon.css") - return entry_skeleton.format( - title=lex["title"], - lexicon=config["LEXICON_TITLE"], - css=css, - logo=config["LOGO_FILENAME"], - sidebar=config["SIDEBAR_HTML"], - content=content, - citeblock=citeblock) - -def build_phantom_page(title, cite_map, config): - """ - Builds the full HTML of a phantom page. - Input: title, the phantom title - cite_map, generated by make_cite_map - config, a dict of config values - Output: the full HTML as a string - """ - lf = link_formatter(cite_map.keys()) - # Fill in the content with filler - content = "

This entry hasn't been written yet.

" - # Build the stub citeblock - cites, citedby = citation_lists(title, cite_map) - citedby_str = " | ".join([lf(None, title, title) for title in citedby]) - citeblock = ""\ - "
\n"\ - "

Cited by: {citedby}

\n"\ - "
\n".format( - citedby=citedby_str) - # Fill in the entry skeleton - entry_skeleton = load_resource("entry-page.html") - css = load_resource("lexicon.css") - return entry_skeleton.format( - title=title, - lexicon=config["LEXICON_TITLE"], - css=css, - logo=config["LOGO_FILENAME"], - sidebar=config["SIDEBAR_HTML"], - content=content, - citeblock=citeblock) - -def build_stub_page(title, cite_map, config): - """ - Builds the full HTML of a stub page. - Input: title, the stub title - cite_map, generated by make_cite_map - config, a dict of config values - Output: the full HTML as a string - """ - lf = link_formatter(cite_map.keys()) - # Fill in the content with filler - content = "

[The handwriting is completely illegible.]

\n"\ - "

Ersatz Scrivener

\n" - # Build the stub citeblock - citedby = [citer_title - for citer_title, cited_titles in cite_map.items() - if title in cited_titles] - citedby_str = " | ".join([lf(None, title, title) for title in citedby]) - citeblock = ""\ - "
\n"\ - "

Citations: [Illegible]

\n"\ - "

Cited by: {citedby}

\n"\ - "
\n".format( - citedby=citedby_str) - # Fill in the entry skeleton - entry_skeleton = load_resource("entry-page.html") - css = load_resource("lexicon.css") - return entry_skeleton.format( - title=title, - lexicon=config["LEXICON_TITLE"], - css=css, - logo=config["LOGO_FILENAME"], - sidebar=config["SIDEBAR_HTML"], - content=content, - citeblock=citeblock) - -def build_index_page(cite_map, config): - """ - Builds the full HTML of the index page. - Input: cite_map, generated by make_cite_map - config, a dict of config values - Output: the HTML of the index page - """ - # Count up all the titles - titles = set(cite_map.keys()) | set([title for cited_titles in cite_map.values() for title in cited_titles]) - titles = sorted(set(titles), key=titlestrip) content = "" - if len(titles) == len(cite_map.keys()): - content = "

There are {0} entries in this lexicon.

\n
    \n".format(len(titles)) + # Article counts + phantom_count = len([article for article in articles if article.author is None]) + if phantom_count == 0: + content = "

    There are {0} entries in this lexicon.

    \n
      \n".format(len(articles)) else: - content = "

      There are {0} entries, {1} written and {2} phantom.

      \n
        \n".format( - len(titles), len(cite_map.keys()), len(titles) - len(cite_map.keys())) - # Write all of the entries out as links under their indices - lf = link_formatter(cite_map.keys()) + content = "

        There are {0} entries, {1} written and {2} phantom.

        \n".format( + len(articles), len(articles) - phantom_count, phantom_count) + # Prepare article links + link_by_title = {article.title : "{0}".format( + article.title, article.title_filesafe, + "" if article.author is not None else " class=\"phantom\"") + for article in articles} + # Write the articles in alphabetical order + content += load_resource("contents.html") + content += "
        \n
          \n" indices = config["INDEX_LIST"].split("\n") + alphabetical_order = sorted(articles, key=lambda a: a.title) + check_off = list(alphabetical_order) for index_str in indices: - content += "

          {0}

          ".format(index_str) - index_titles = [] - for c in index_str.upper(): - for title in titles: - if (titlestrip(title)[0] == c): - index_titles.append(title) - for title in index_titles: - titles.remove(title) + content += "

          {0}

          \n".format(index_str) + for article in alphabetical_order: + if (titlestrip(article.title)[0].upper() in index_str): + check_off.remove(article) + content += "
        • " + content += link_by_title[article.title] + content += "
        • \n" + if len(check_off) > 0: + content += "

          &c.

          \n".format(index_str) + for article in check_off: content += "
        • " - content += lf(None, title, title) + content += link_by_title[article.title] content += "
        • \n" - if len(titles) > 0: - content += "

          &c.

          ".format(index_str) - for title in titles: + content += "
        \n
        \n" + # Write the articles in turn order + content += "
        \n
          \n" + latest_turn = max([article.turn for article in articles if article.author is not None]) + turn_order = sorted(articles, key=lambda a: (a.turn, a.title)) + check_off = list(turn_order) + for turn_num in range(1, latest_turn + 1): + content += "

          Turn {0}

          \n".format(turn_num) + for article in turn_order: + if article.turn == turn_num: + check_off.remove(article) + content += "
        • " + content += link_by_title[article.title] + content += "
        • \n" + if len(check_off) > 0: + content += "

          Unwritten

          \n" + for article in check_off: content += "
        • " - content += lf(None, title, title) + content += link_by_title[article.title] content += "
        • \n" - content += "
        \n" - # Fill in the entry skeleton + content += "
      \n\n" + # Fill in the page skeleton entry_skeleton = load_resource("entry-page.html") css = load_resource("lexicon.css") return entry_skeleton.format( @@ -372,15 +330,13 @@ def build_index_page(cite_map, config): lexicon=config["LEXICON_TITLE"], css=css, logo=config["LOGO_FILENAME"], - sidebar=config["SIDEBAR_HTML"], + prompt=config["PROMPT"], content=content, citeblock="") def build_rules_page(config): """ Builds the full HTML of the rules page. - Input: config, a dict of config values - Output: the HTML of the rules page """ content = load_resource("rules.html") # Fill in the entry skeleton @@ -391,15 +347,13 @@ def build_rules_page(config): lexicon=config["LEXICON_TITLE"], css=css, logo=config["LOGO_FILENAME"], - sidebar=config["SIDEBAR_HTML"], + prompt=config["PROMPT"], content=content, citeblock="") def build_formatting_page(config): """ Builds the full HTML of the formatting page. - Input: config, a dict of config values - Output: the HTML of the formatting page """ content = load_resource("formatting.html") # Fill in the entry skeleton @@ -410,40 +364,35 @@ def build_formatting_page(config): lexicon=config["LEXICON_TITLE"], css=css, logo=config["LOGO_FILENAME"], - sidebar=config["SIDEBAR_HTML"], + prompt=config["PROMPT"], content=content, citeblock="") def build_session_page(config): """ Builds the full HTML of the session page. - Input: config, a dict of config values - Output: the HTML of the session page """ - session_lex = parse_lex(config["SESSION_PAGE"]) - content = format_content(session_lex, lambda fid,alias,dest: "" + alias + "") # Fill in the entry skeleton entry_skeleton = load_resource("entry-page.html") css = load_resource("lexicon.css") return entry_skeleton.format( - title=session_lex["title"], + title=config["LEXICON_TITLE"], lexicon=config["LEXICON_TITLE"], css=css, logo=config["LOGO_FILENAME"], - sidebar=config["SIDEBAR_HTML"], - content=content, + prompt=config["PROMPT"], + content=config["SESSION_PAGE"], citeblock="") -def build_statistics_page(cite_map, config): +def build_statistics_page(articles, config): """ Builds the full HTML of the statistics page. - Input: citation_map, a dictionary returned by make_cite_map - config, a dict of config values - Output: the HTML of the statistics page """ content = "" - # Compute the pagerank - content += "

      Top 10 by page rank:
      \n" + cite_map = {article.title : [cite_tuple[1] for cite_tuple in article.citations.values()] for article in articles} + # Pages by pagerank + content += "

      \n" + content += "

      Top 10 pages by page rank:
      \n" G = networkx.Graph() for citer, citeds in cite_map.items(): for cited in citeds: @@ -451,18 +400,22 @@ def build_statistics_page(cite_map, config): ranks = networkx.pagerank(G) sranks = sorted(ranks.items(), key=lambda x: x[1], reverse=True) ranking = list(enumerate(map(lambda x: x[0], sranks))) - content += "
      \n".join(map(lambda x: "{0} - {1}".format(x[0]+1, x[1]), ranking[:10])) + content += "
      \n".join(map(lambda x: "{0} – {1}".format(x[0]+1, x[1]), ranking[:10])) content += "

      \n" - # Count the top number of citations made from + content += "
      \n" + # Top numebr of citations made + content += "
      \n" content += "

      Most citations made from:
      \n" citation_tally = [(kv[0], len(kv[1])) for kv in cite_map.items()] citation_count = defaultdict(list) for title, count in citation_tally: citation_count[count].append(title) content += "
      \n".join(map( - lambda kv: "{0} - {1}".format(kv[0], "; ".join(kv[1])), + lambda kv: "{0} – {1}".format(kv[0], "; ".join(kv[1])), sorted(citation_count.items(), reverse=True)[:3])) content += "

      \n" - # Count the top number of citations made to + content += "
      \n" + # Top number of times cited + content += "
      \n" content += "

      Most citations made to:
      \n" all_cited = set([title for cites in cite_map.values() for title in cites]) cited_by_map = { cited: [citer for citer in cite_map.keys() if cited in cite_map[citer]] for cited in all_cited } @@ -470,13 +423,43 @@ def build_statistics_page(cite_map, config): cited_count = defaultdict(list) for title, count in cited_tally: cited_count[count].append(title) content += "
      \n".join(map( - lambda kv: "{0} - {1}".format(kv[0], "; ".join(kv[1])), + lambda kv: "{0} – {1}".format(kv[0], "; ".join(kv[1])), sorted(cited_count.items(), reverse=True)[:3])) - #cited_count = map(lambda kv: (kv[0], len(kv[1])), cited_by_map.items()) - #cited_count_sort = sorted(cited_count, key=lambda x: x[1], reverse=True) - #top_cited_count = [kv for kv in cited_count_sort if kv[1] >= cited_count_sort[:5][-1][1]] - #content += "
      \n".join(map(lambda x: "{0} - {1}".format(x[1], x[0]), top_cited_count)) content += "

      \n" + content += "
      \n" + # Author pageranks + content += "
      \n" + content += "

      Author total page rank:
      \n" + authors = sorted(set([article.author for article in articles if article.author is not None])) + articles_by = {author : [a for a in articles if a.author == author] for author in authors} + author_rank = {author : sum(map(lambda a: ranks[a.title], articles)) for author, articles in articles_by.items()} + content += "
      \n".join(map( + lambda kv: "{0} – {1}".format(kv[0], round(kv[1], 3)), + sorted(author_rank.items(), key=lambda t:-t[1]))) + content += "

      \n" + content += "
      \n" + # Author citations made + content += "
      \n" + content += "

      Citations made by author
      \n" + author_cite_count = {author : sum(map(lambda a:len(a.wcites | a.pcites), articles)) for author, articles in articles_by.items()} + content += "
      \n".join(map( + lambda kv: "{0} – {1}".format(kv[0], kv[1]), + sorted(author_cite_count.items(), key=lambda t:-t[1]))) + content += "

      \n" + content += "
      \n" + # Author cited count + content += "
      \n" + content += "

      Citations made to author
      \n" + cited_times = {author : 0 for author in authors} + for article in articles: + if article.author is not None: + cited_times[article.author] += len(article.citedby) + content += "
      \n".join(map( + lambda kv: "{0} – {1}".format(kv[0], kv[1]), + sorted(cited_times.items(), key=lambda t:-t[1]))) + content += "

      \n" + content += "
      \n" + # Fill in the entry skeleton entry_skeleton = load_resource("entry-page.html") css = load_resource("lexicon.css") @@ -485,78 +468,100 @@ def build_statistics_page(cite_map, config): lexicon=config["LEXICON_TITLE"], css=css, logo=config["LOGO_FILENAME"], - sidebar=config["SIDEBAR_HTML"], + prompt=config["PROMPT"], content=content, citeblock="") +def build_graphviz_file(cite_map): + """ + Builds a citation graph in dot format for Graphviz. + """ + result = [] + result.append("digraph G {\n") + # Node labeling + written_entries = list(cite_map.keys()) + phantom_entries = set([title for cites in cite_map.values() for title in cites if title not in written_entries]) + node_labels = [title[:20] for title in written_entries + list(phantom_entries)] + node_names = [hash(i) for i in node_labels] + for i in range(len(node_labels)): + result.append("{} [label=\"{}\"];\n".format(node_names[i], node_labels[i])) + # Edges + for citer in written_entries: + for cited in cite_map[citer]: + result.append("{}->{};\n".format(hash(citer[:20]), hash(cited[:20]))) + # Return result + result.append("overlap=false;\n}\n") + return "".join(result)#"…" + # Summative functions def command_build(argv): if len(argv) >= 3 and (argv[2] != "partial" and argv[2] != "full"): print("unknown build type: " + argv[2]) return - # Set up the entries + # Load content config = load_config() - sidebar_parsed = parse_lex(config["SIDEBAR_CONTENT"]) - config["SIDEBAR_HTML"] = format_content(sidebar_parsed, lambda fid,alias,dest: alias) - lexes = parse_lex_from_directory("raw/") - cite_map = make_cite_map(lexes) - written_entries = cite_map.keys() - phantom_entries = set([title for cites in cite_map.values() for title in cites if title not in written_entries]) - # Clear the folder - print("Clearing old HTML files") - for filename in os.listdir("out/"): - if filename[-5:] == ".html": - os.remove("out/" + filename) - # Write the written entries - print("Writing written articles...") - for lex in lexes: - page = build_article_page(lex, cite_map, config) - with open("out/" + lex["filename"] + ".html", "w", encoding="utf8") as f: - f.write(page) - print(" Wrote " + lex["title"]) - # Write the unwritten entries - if len(phantom_entries) > 0: - if len(argv) < 3 or argv[2] == "partial": - print("Writing phantom articles...") - for title in phantom_entries: - page = build_phantom_page(title, cite_map, config) - with open("out/" + as_filename(title) + ".html", "w", encoding="utf8") as f: - f.write(page) - print(" Wrote " + title) - elif argv[2] == "full": - print("Writing stub articles...") - for title in phantom_entries: - page = build_stub_page(title, cite_map, config) - with open("out/" + as_filename(title) + ".html", "w", encoding="utf8") as f: - f.write(page) - print(" Wrote " + title) - else: - print("ERROR: build type was " + argv[2]) - return - # Write the default pages - print("Writing default pages") - page = build_rules_page(config) - with open("out/rules.html", "w", encoding="utf8") as f: - f.write(page) - print(" Wrote Rules") - page = build_formatting_page(config) - with open("out/formatting.html", "w", encoding="utf8") as f: - f.write(page) - print(" Wrote Formatting") - page = build_index_page(cite_map, config) + entry_skeleton = load_resource("entry-page.html") + css = load_resource("lexicon.css") + articles = [article for article in parse_from_directory("raw/") if article is not None] + written_titles = [article.title for article in articles] + articles = sorted(populate(articles), key=lambda a: (a.turn, a.title)) + #print(articles[13].title_filesafe) + #return + phantom_titles = [article.title for article in articles if article.title not in written_titles] + + # Write the redirect page + print("Writing redirect page...") with open("out/index.html", "w", encoding="utf8") as f: - f.write(page) - print(" Wrote Index") - page = build_session_page(config) - with open("out/session.html", "w", encoding="utf8") as f: - f.write(page) + f.write(load_resource("redirect.html").format(lexicon=config["LEXICON_TITLE"])) + + # Write the article pages + print("Deleting old article pages...") + for filename in os.listdir("out/article/"): + if filename[-5:] == ".html": + os.remove("out/article/" + filename) + print("Writing article pages...") + l = len(articles) + for idx in range(l): + article = articles[idx] + with open("out/article/" + article.title_filesafe + ".html", "w", encoding="utf8") as f: + content = article.build_page_content() + citeblock = article.build_page_citeblock( + None if idx == 0 else articles[idx - 1].title, + None if idx == l-1 else articles[idx + 1].title) + article_html = entry_skeleton.format( + title = article.title, + lexicon = config["LEXICON_TITLE"], + css = css, + logo = config["LOGO_FILENAME"], + prompt = config["PROMPT"], + content = content, + citeblock = citeblock) + f.write(article_html) + print(" Wrote " + article.title) + + # Write default pages + print("Writing default pages...") + with open("out/contents/index.html", "w", encoding="utf8") as f: + f.write(build_contents_page(articles, config)) + print(" Wrote Contents") + with open("out/rules/index.html", "w", encoding="utf8") as f: + f.write(build_rules_page(config)) + print(" Wrote Rules") + with open("out/formatting/index.html", "w", encoding="utf8") as f: + f.write(build_formatting_page(config)) + print(" Wrote Formatting") + with open("out/session/index.html", "w", encoding="utf8") as f: + f.write(build_session_page(config)) print(" Wrote Session") - page = build_statistics_page(cite_map, config) - with open("out/stats.html", "w", encoding="utf8") as f: - f.write(page) + with open("out/statistics/index.html", "w", encoding="utf8") as f: + f.write(build_statistics_page(articles, config)) print(" Wrote Statistics") + # Write auxiliary files + # TODO: write graphviz file + # TODO: write compiled lexicon page + def main(): if len(sys.argv) < 2: print("Available commands:") diff --git a/out/Example_page.html b/out/Example_page.html deleted file mode 100644 index 91ce278..0000000 --- a/out/Example_page.html +++ /dev/null @@ -1,42 +0,0 @@ - - -Example page | Lexicon Title - - - - - -
      -

      Example page

      -

      This is an example page.

      -

      Signature

      -
      -
      -

      Citations:

      -

      Cited by:

      -
      - - \ No newline at end of file diff --git a/out/article/Example_page.html b/out/article/Example_page.html new file mode 100644 index 0000000..575b4d5 --- /dev/null +++ b/out/article/Example_page.html @@ -0,0 +1,49 @@ + + +Example page | Lexicon Title + + + + + +
      +

      Example page

      +

      This is an example page. +Some words are italicized, +and some words are bolded. +All of these sentences are part of the same paragraph.

      +

      This is a new paragraph.
      +Unlike the last paragraph, this line will be after a line break within the paragraph.

      +

      This is an example citation. You can also cite a phantom page with just the title.

      +

      Dr. X. Amplepage

      +
      +
      +

      Next →

      +

       

      +

      Citations: Phantom page

      +

      Cited by: --

      +
      + + \ No newline at end of file diff --git a/out/article/Phantom_page.html b/out/article/Phantom_page.html new file mode 100644 index 0000000..91530aa --- /dev/null +++ b/out/article/Phantom_page.html @@ -0,0 +1,40 @@ + + +Phantom page | Lexicon Title + + + + + +
      +

      Phantom page

      +

      This entry hasn't been written yet.

      +
      +

      ← Previous

      +

      Citations: --

      +

      Cited by: Example page

      +
      + + \ No newline at end of file diff --git a/out/contents/index.html b/out/contents/index.html new file mode 100644 index 0000000..e8121da --- /dev/null +++ b/out/contents/index.html @@ -0,0 +1,75 @@ + + +Index of Lexicon Title | Lexicon Title + + + + + +
      +

      Index of Lexicon Title

      +

      There are 2 entries, 1 written and 1 phantom.

      + + +
      + +
      + +
      + + \ No newline at end of file diff --git a/out/formatting.html b/out/formatting.html deleted file mode 100644 index a386021..0000000 --- a/out/formatting.html +++ /dev/null @@ -1,52 +0,0 @@ - - -Formatting | Lexicon Title - - - - - -
      -

      Formatting

      -

      Lexipython provides support for a limited amount of Markdown-esque formatting. The parsing rules that will be applied are as follows:

      -

      Entries must begin with a header declaring the entry title. Format this header as "# Title", e.g. "# Formatting". This is the name that citations to your entry will use.

      -

      Two line breaks begin a new paragraph. Lines separated by a single line break will be part of the same paragraph, unless the line is ended by a double backslash:
      -  Sample \\
      -  text
      -  here
      -in your markdown produces
      -  Sample
      -  text -here
      -in the generated page.

      -

      Text bounded by ** will be bolded: **bold** produces bold. Text bounded by // will be italicized: //italics// produces italics.

      -

      To cite another Lexicon entry, use double brackets. Text in double brackets will cite and link to the entry of the same name: [[Example page]] produces Example page. Text in double brackets split with a | will alias the link as the left text and link to the entry with the name of the right text: [[this text|Example page]] produces this text. You must be precise in the entry title you cite to. Citations to "Example" vs. "The Example" will point to different entries and create different phantoms, and your GM will probably have to clean up after you. -

      Entries should end with a footer signing the entry with its author. Format this footer as "~ Signature". Signature lines will be set apart by a horizotal rule and right-aligned:

      - -

      Signature

      -
      - - \ No newline at end of file diff --git a/out/formatting/index.html b/out/formatting/index.html new file mode 100644 index 0000000..d20e31e --- /dev/null +++ b/out/formatting/index.html @@ -0,0 +1,58 @@ + + +Formatting | Lexicon Title + + + + + +
      +

      Formatting

      +

      Lexipython provides support for a limited amount of Markdown-esque formatting.

      +
      +# Author: Authorname
      +# Turn: 1
      +# Title: Example page
      +
      +This is an example page.
      +Some words are //italicized//,
      +and some words are **bolded**.
      +All of these sentences are part of the same paragraph.
      +
      +This is a new paragraph.\\
      +Unlike the last paragraph, this line will be after a line break within the paragraph.
      +
      +This is an [[example citation|Phantom page]]. You can also cite a [[phantom page]] with just the title.
      +
      +~Dr. X. Amplepage
      +
      +

      Each turn, fill out the header with your author information, the current turn, and the title of your entry. It doesn't really matter what the Author field is, except that it must be the same across all articles you write.

      +

      Two line breaks begins a new paragraph. A single line break does nothing, unless the line is neded by a double backslash (\\).

      +

      Text bounded by ** will be bolded: **bold** produces bold. Text bounded by // will be italicized: //italics// produces italics.

      +

      To cite another Lexicon entry, use double brackets. Text in double brackets will cite and link to the entry of the same name: [[Example page]] produces Example page. Text in double brackets split with a | will alias the link as the left text and link to the entry with the name of the right text: [[this text|Example page]] produces this text. You must be precise in the entry title you cite to. Citations to "Example" vs. "The Example" will point to different entries and create different phantoms, and your GM will probably have to clean up after you.

      +

      Beginning a paragraph with ~ will right-align it and place a horizontal line above it. Use this for signing your entry with your scholar's name.

      +
      + + \ No newline at end of file diff --git a/out/index.html b/out/index.html index bbc04e8..b676732 100644 --- a/out/index.html +++ b/out/index.html @@ -1,40 +1,9 @@ -Index of Lexicon Title | Lexicon Title - +Lexicon Title + - - -
      -

      Index of Lexicon Title

      -

      There are 1 entries in this lexicon.

      - -
      +

      Redirecting to Lexicon Title...

      \ No newline at end of file diff --git a/out/rules.html b/out/rules/index.html similarity index 75% rename from out/rules.html rename to out/rules/index.html index e5a242e..48b2004 100644 --- a/out/rules.html +++ b/out/rules/index.html @@ -1,33 +1,32 @@ Rules | Lexicon Title + -

      Rules

      diff --git a/out/session.html b/out/session.html deleted file mode 100644 index 1ce79c2..0000000 --- a/out/session.html +++ /dev/null @@ -1,39 +0,0 @@ - - -Lexicon Proximum | Lexicon Title - - - - - -
      -

      Lexicon Proximum

      -

      You are [...]. The index grouping is [...].

      -

      For the first turn, give the Rules page a read-through. Then come up with the character of your scholar, write an article that's about 100-300 words within your assigned index, and send it to the host.

      -

      Consider putting session information here, like the index grouping and turn count, where to send completed entries, index assignments, turn schedule, and so on.

      -
      - - \ No newline at end of file diff --git a/out/session/index.html b/out/session/index.html new file mode 100644 index 0000000..87556ed --- /dev/null +++ b/out/session/index.html @@ -0,0 +1,35 @@ + + +Lexicon Title | Lexicon Title + + + + + +
      +

      Lexicon Title

      +

      Put session information here, like the index grouping and turn count, where to send completed entries, index assignments, turn schedule, and so on.

      + + \ No newline at end of file diff --git a/out/statistics/index.html b/out/statistics/index.html new file mode 100644 index 0000000..148e62f --- /dev/null +++ b/out/statistics/index.html @@ -0,0 +1,61 @@ + + +Statistics | Lexicon Title + + + + + +
      +

      Statistics

      +
      +

      Top 10 pages by page rank:
      +1 – Example page
      +2 – Phantom page

      +
      +
      +

      Most citations made from:
      +2 – Example page
      +0 – Phantom page

      +
      +
      +

      Most citations made to:
      +1 – Phantom page

      +
      +
      +

      Author total page rank:
      +Authorname – 0.5

      +
      +
      +

      Citations made by author
      +Authorname – 1

      +
      +
      +

      Citations made to author
      +Authorname – 0

      +
      +
      + + \ No newline at end of file diff --git a/out/stats.html b/out/stats.html deleted file mode 100644 index 73b89fc..0000000 --- a/out/stats.html +++ /dev/null @@ -1,42 +0,0 @@ - - -Statistics | Lexicon Title - - - - - -
      -

      Statistics

      -

      Top 10 by page rank:
      -

      -

      Most citations made from:
      -0 - Example page

      -

      Most citations made to:
      -

      -
      - - \ No newline at end of file diff --git a/raw/example-page.lex b/raw/example-page.lex deleted file mode 100644 index c381990..0000000 --- a/raw/example-page.lex +++ /dev/null @@ -1,6 +0,0 @@ -# Example page - -This is an example **page**.\\ -[[This is an example //citation//.|Phantom page]] - -~Signature diff --git a/raw/example-page.txt b/raw/example-page.txt new file mode 100644 index 0000000..092d315 --- /dev/null +++ b/raw/example-page.txt @@ -0,0 +1,15 @@ +# Author: Authorname +# Turn: 1 +# Title: Example page + +This is an example page. +Some words are //italicized//, +and some words are **bolded**. +All of these sentences are part of the same paragraph. + +This is a new paragraph.\\ +Unlike the last paragraph, this line will be after a line break within the paragraph. + +This is an [[example citation|Phantom page]]. You can also cite a [[phantom page]] with just the title. + +~Dr. X. Amplepage \ No newline at end of file diff --git a/resources/contents.html b/resources/contents.html new file mode 100644 index 0000000..c7a31a1 --- /dev/null +++ b/resources/contents.html @@ -0,0 +1,17 @@ + + diff --git a/resources/entry-page.html b/resources/entry-page.html index e3bbd76..59619d3 100644 --- a/resources/entry-page.html +++ b/resources/entry-page.html @@ -1,23 +1,23 @@ {title} | {lexicon} + -

      {title}

      diff --git a/resources/formatting.html b/resources/formatting.html index fbd49e0..9521423 100644 --- a/resources/formatting.html +++ b/resources/formatting.html @@ -1,27 +1,23 @@ -

      Lexipython provides support for a limited amount of Markdown-esque formatting. The parsing rules that will be applied are as follows:

      -

      Entries must begin with a header declaring the entry title. Format this header as "# Title", e.g. "# Formatting". This is the name that citations to your entry will use.

      -

      Two line breaks begin a new paragraph. Lines separated by a single line break will be part of the same paragraph, unless the line is ended by a double backslash:
      -  Sample \\
      -  text
      -  here
      -in your markdown produces
      -  Sample
      -  text -here
      -in the generated page.

      -

      Text bounded by ** will be bolded: **bold** produces bold. Text bounded by // will be italicized: //italics// produces italics.

      -

      To cite another Lexicon entry, use double brackets. Text in double brackets will cite and link to the entry of the same name: [[Example page]] produces Example page. Text in double brackets split with a | will alias the link as the left text and link to the entry with the name of the right text: [[this text|Example page]] produces this text. You must be precise in the entry title you cite to. Citations to "Example" vs. "The Example" will point to different entries and create different phantoms, and your GM will probably have to clean up after you. -

      Entries should end with a footer signing the entry with its author. Format this footer as "~ Signature". Signature lines will be set apart by a horizotal rule and right-aligned:

      - -

      Signature

      - -

      Here is an example lexicon file:

      +

      Lexipython provides support for a limited amount of Markdown-esque formatting.

      -# Example page
      +# Author: Authorname
      +# Turn: 1
      +# Title: Example page
       
      -This is an example **page**.\\
      -[[This is an example //citation//.|Phantom page]]
      +This is an example page.
      +Some words are //italicized//,
      +and some words are **bolded**.
      +All of these sentences are part of the same paragraph.
       
      -~Signature
      +This is a new paragraph.\\
      +Unlike the last paragraph, this line will be after a line break within the paragraph.
       
      +This is an [[example citation|Phantom page]]. You can also cite a [[phantom page]] with just the title.
      +
      +~Dr. X. Amplepage
       
      +

      Each turn, fill out the header with your author information, the current turn, and the title of your entry. It doesn't really matter what the Author field is, except that it must be the same across all articles you write.

      +

      Two line breaks begins a new paragraph. A single line break does nothing, unless the line is neded by a double backslash (\\).

      +

      Text bounded by ** will be bolded: **bold** produces bold. Text bounded by // will be italicized: //italics// produces italics.

      +

      To cite another Lexicon entry, use double brackets. Text in double brackets will cite and link to the entry of the same name: [[Example page]] produces Example page. Text in double brackets split with a | will alias the link as the left text and link to the entry with the name of the right text: [[this text|Example page]] produces this text. You must be precise in the entry title you cite to. Citations to "Example" vs. "The Example" will point to different entries and create different phantoms, and your GM will probably have to clean up after you.

      +

      Beginning a paragraph with ~ will right-align it and place a horizontal line above it. Use this for signing your entry with your scholar's name.

      diff --git a/resources/lexicon.css b/resources/lexicon.css index 74b66d5..2babead 100644 --- a/resources/lexicon.css +++ b/resources/lexicon.css @@ -1,10 +1,10 @@ body { background-color: #eeeeee; margin: 10px; } -div#header { background-color: #ffffff; margin: 10px 0; padding: 8px; box-shadow: 2px 2px 10px #888888; } -div.header-option { display:inline-block; margin-right: 20px; } -div#sidebar-outer { width: 140px; background-color: #ffffff; float:left; box-shadow: 2px 2px 10px #888888; padding: 5px; } -div#sidebar-inner { padding: 5px; } -img#logo { width: 140px; } -div.content { margin-left: 160px; margin-top: 10px; background-color: #ffffff; padding: 10px; box-shadow: 2px 2px 10px #888888; } +div#header { background-color: #ffffff; margin: 10px 0; box-shadow: 2px 2px 10px #888888; overflow: hidden; } +img#logo { float:left; margin:8px; max-width: 140px; } +div#header p { margin:10px; } +div.content { margin-top: 10px; background-color: #ffffff; padding: 10px; box-shadow: 2px 2px 10px #888888; overflow: hidden; } a.phantom { color: #cc2200; } div.citeblock a.phantom { font-style: italic; } -span.signature { text-align: right; } \ No newline at end of file +span.signature { text-align: right; } +div.moveable { float: left; margin: 8px; } +div.moveable p { margin: 0px; } \ No newline at end of file diff --git a/resources/redirect.html b/resources/redirect.html new file mode 100644 index 0000000..e20197a --- /dev/null +++ b/resources/redirect.html @@ -0,0 +1,9 @@ + + +{lexicon} + + + +

      Redirecting to {lexicon}...

      + + \ No newline at end of file