commit
e42493c255
|
@ -0,0 +1,26 @@
|
||||||
|
# Lexipython
|
||||||
|
|
||||||
|
This document explains the Lexipython suite. For the rules of Lexicon, see the [main readme](README.md).
|
||||||
|
|
||||||
|
To play Lexicon, all you really need is something for the scholars to write their articles on and a place to collect them. However, if people with doctorates are known for anything, it's their terrible handwriting and disorganization. Lexipython therefore provides tools to solve this most ancient of problems. To play Lexicon with Lexipython, appoint a player or a third party as the Editor. The Editor will need to download Lexipython, set up the game, and handle posting the pages somewhere all the players can access them.
|
||||||
|
|
||||||
|
## Functionality
|
||||||
|
|
||||||
|
To aid in playing Lexicon, Lexipython **does** provide the following:
|
||||||
|
* Specialized markdown parsing into formatted Lexicon entries.
|
||||||
|
* HTML page generation and interlinking.
|
||||||
|
* Handy help pages for rules, session information, statistics, and more.
|
||||||
|
|
||||||
|
Lexipython **does not** provide:
|
||||||
|
* Web hosting for the Lexicon pages. The Editor should have a plan for distributing new editions of the Lexicon.
|
||||||
|
* Checks for factual consistency between submitted articles. The Editor is responsible for ensuring scholarly rigor.
|
||||||
|
|
||||||
|
## Using Lexipython
|
||||||
|
|
||||||
|
To run a game of Lexicon with Lexipython, clone this repository out to a new folder:
|
||||||
|
```
|
||||||
|
$ git clone https://github.com/Jaculabilis/Lexipython.git [name]
|
||||||
|
```
|
||||||
|
|
||||||
|
Steps for setup:
|
||||||
|
1. [WIP]
|
101
README.md
101
README.md
|
@ -1,88 +1,93 @@
|
||||||
# Lexipython
|
# Lexipython
|
||||||
|
|
||||||
Lexipython is a Python suite for playing Lexicon, a collaborative, worldbuilding, role-playing game.
|
Lexipython is a Python suite for playing Lexicon, a collaborative, worldbuilding, role-playing game. This document explains the game of Lexicon. For documentation of Lexipython, see the [usage readme](LEXIPYTHON.md).
|
||||||
|
|
||||||
## Summary
|
## Summary: If Wikipedia Were an RPG
|
||||||
|
|
||||||
Lexicon is basically _Wikipedia: The RPG_. Each player takes on the role of a scholar. You are cranky, opinionated, prejudiced, and eccentric. You are also collaborating with a number of your peers -- the other players -- on the construction of an encyclopedia describing some bounded space, such as a fantastic world or a historical period. Each round, each scholar contributes an article on a particular topic, citing other articles in the burgeoning encyclopedia. Some of the cited articles won't exist at the time they're cited. As the game progresses, the other scholars will fill in the phantom citations you've made, and you will fill in theirs -- after all, it is an academic sin to cite yourself. Each new article, however, cannot contradict what has already been written: though your peers are self-important, narrow-minded dunderheads, they are honest scholars. No matter how strained their interpretations are, their facts are as accurate as historical research can make them.
|
In Lexicon, each player takes on the role of a scholar. You are cranky, opinionated, prejudiced, and eccentric. You are also collaborating with a number of your peers (the other players) on the construction of an encyclopedia describing some bounded space, such as a fantastic world or a historical period. Each turn, you will write an article on a particular topic, which will cite other, related articles within the burgeoning encyclopedia. This process is complicated by three factors. First, some of the articles you cite will not actually exist at the time you cite them. Second, it is an academic sin to cite yourself. Third, your article may not contradict anything that has already been written. Though your peers are self-important, narrow-minded dunderheads, they are honest scholars. No matter how strained their interpretations are, their facts are as accurate as historical research can make them.
|
||||||
|
|
||||||
## Lexicon and Lexipython
|
## Basic Rules: What Everyone Should Know
|
||||||
|
|
||||||
To play Lexicon, all you really need is something for the scholars to write their articles on and a place to collect them. However, if people with doctorates are known for anything, it's their terrible handwriting and disorganization. Lexipython therefore provides tools to solve these most ancient of problems. To play Lexicon with Lexipython, appoint a player or a third party as the game master (GM). The GM will need to download Lexipython, set up the game, and handle posting the pages somewhere all the players can access them.
|
1. Each Lexicon has a _topic statement_ that sets the tone for the game. It provides a starting point for shaping the developing world of the Lexicon. As it is a starting point, don't feel contrained to write only about the topics mentioned directly in it.
|
||||||
|
|
||||||
To aid the GM in running the Lexicon game, Lexipython **does** provide:
|
1. Articles are sorted under an _index_, a grouping of letters. An article is in an index if its first letter is in that group of letters. "The", "A", and "An" aren't counted in indexing. _Example: One of the indices is JKL. An article titled 'The Jabberwock' would index under JKL, not T's index._
|
||||||
* Specialized markdown parsing into formatted lexicon entries
|
|
||||||
* Page generation and interlinking
|
|
||||||
* Handy help pages for rules, session information, statistics, and more.
|
|
||||||
|
|
||||||
Lexipython **does not** provide:
|
1. Until the game is over, some of the articles will have been cited, but not yet written. These are called _phantom_ articles. A phantom article has a title, which is defined by the first citation to it, but no content.
|
||||||
* Web hosting for the Lexicon pages
|
|
||||||
* Checks for factual consistency between submitted articles
|
|
||||||
|
|
||||||
## Playing Lexicon
|
1. Generally, an index has a number of "slots" equal to the number of players. When an article is first written or cited, it takes up one slot in its corresponding index.
|
||||||
|
|
||||||
1. At the beginning of the game, you will be provided with a _topic statement_ that sets the tone for the game. Use it for inspiration and a stepping-stone into shaping the world of the Lexicon.
|
1. Each turn, you will be assigned to write in an index.
|
||||||
|
|
||||||
1. Each round, you will be assigned an _index_, a grouping of letters. Your entry must alphabetize under that index.
|
1. Your articles should be written from the perspective of your character. Your character should be a scholar collaborating with the other scholars on the production of the Lexicon. You should play the same character for the duration of the game.
|
||||||
|
|
||||||
1. Each index has a number of open slots equal to the number of players, which are taken up by article titles when an article is written in that index or a citation is made to an unwritten article, or _phantom_. If there are no open slots in your index, you must write the article for a phantom in that index.
|
1. If the index has open slots, you may come up with a new article title and write an article under that title. If all unwritten slots in your index are filled by phantom articles, you must choose one of them and write it.
|
||||||
|
|
||||||
1. "The" and "A" aren't counted in indexing.
|
1. There are no hard and fast rules about style, but it is recommended that players imitate an encyclopedic style to stay true to the game's conceit.
|
||||||
|
|
||||||
1. Once you've picked an article title, write your article on that subject.
|
1. There are no hard and fast rules about length, but it is recommended that the Editor enforce a maximum word limit. In general, aiming for 200-300 words is ideal.
|
||||||
|
|
||||||
1. There are no hard and fast rules about style. Try to sound like an encyclopedia entry or the overview section at the top of a wiki article.
|
1. You must respect and not contradict the factual content of all written articles. You may introduce new facts that put things in a new light, provide alternative interpretations, or flesh out unexplained details in unexpected ways; but you must not _contradict_ what has been previously established as fact. Use the "yes, and" rule from improv acting: accept what your fellow scholars have written and add to it in new ways, rather than trying to undo their work. This rule includes facts that have been established in written articles about the topics of phantom articles.
|
||||||
|
|
||||||
1. Aim for around 200-300 words.
|
1. Each article will cite other articles in the Lexicon.
|
||||||
|
|
||||||
1. You must respect and not contradict any factual content of any posted articles. You may introduce new facts that place things in a new light, provide alternative interpretations, or flesh out unexplained details in unexpected ways; but you must not _contradict_ what has been previously established as fact. Use the "Yes, And" rule from improv: accept what your fellow scholars write and add to it, rather than trying to work around them.
|
1. You may not cite an entry that you have written. When you write an article, you may not cite it in later articles.
|
||||||
|
|
||||||
1. Your article must cite other articles in the Lexicon. Sometimes these citations will be to phantoms, articles that have not been written yet.
|
1. As a corollary, you may not write phantom articles that you have cited. If you cite an article and then write it later, your former article now cites you, which is forbidden per the above.
|
||||||
|
|
||||||
1. On the first turn, your article must cite _exactly two_ phantom articles.
|
1. On the first turn, there are no written articles. Your first article must cite _exactly two_ phantom articles.
|
||||||
|
|
||||||
1. On subsequent turns, your article must cite _exactly two_ phantom articles, either already-cited phantoms or new ones. Your article must also cite _at least one_ written article.
|
1. On subsequent turns, your article must cite _exactly two_ phantoms, but you can cite phantoms that already exist. Your article must also cite _at least one_ written article. You can cite more than one.
|
||||||
|
|
||||||
1. On the penultimate turn, you must cite _exactly one_ phantom article and _at least two_ written articles.
|
1. On the penultimate turn, you must cite _exactly one_ phantom article and _at least two_ written articles.
|
||||||
|
|
||||||
1. On the final turn, you must cite _at least three_ written articles.
|
1. On the final turn, you must cite _at least three_ written articles.
|
||||||
|
|
||||||
1. You may not cite an entry you wrote. You may cite phantoms you have cited before.
|
1. As the game goes on, it may come to pass that a player must write an article in an index, but that index is full, and that player has already cited all the phantoms in it. When this happens, the player instead writes their article as **Ersatz Scrivener**, radical skeptic. Ersatz does not believe in the existence of whatever he is writing about, no matter how obvious it seems to others or how central it is in the developing history of the world. For Ersatz, all references, testimony, etc. with regard to its existence are tragic delusion at best or malicious lies at worst. Unlike the other scholars, Ersatz does not treat the research of his peers as fact, because he does not believe he has peers. Players writing articles as Ersatz are encouraged to lambast the amateur work of his misguided "collaborators".
|
||||||
|
|
||||||
1. Once you cite a phantom, you cannot choose to write it if you write an article for that index later.
|
## Procedural Rules: Running the Game
|
||||||
|
|
||||||
**Ersatz Scrivener.** In the course of the game, it may come to pass that a scholar is assigned an index in which no slots are available, because this scholar has already cited all the phantoms in previous articles. When this happens, the player instead writes their article as Ersatz Scrivener, radical skeptic. Ersatz does not believe in the existence of whatever he is writing about, no matter how obvious it seems to others or how central it is in the developing history of the world. All references, testimony, etc. with regard to its existence are tragic delusion at best or malicious lies at worst. Unlike the other scholars, Ersatz does not treat the research of his peers as fact, because he does not believe he has peers. Players writing articles as Ersatz are encouraged to name and shame the work of the misguided amateurs collaborating with him.
|
### The Editor
|
||||||
|
|
||||||
## Running Lexicon
|
The player running the game is the Editor. The Editor should handle the following:
|
||||||
|
* Reading the [usage readme](LEXIPYTHON.md) and configuring Lexipython for the Lexicon game.
|
||||||
|
* Arranging for making the Lexicon available to all players. (GitHub Pages is an easy solution, and you're already here.)
|
||||||
|
* Setting the tone for the game, including choosing a topic statement, a logo image, and any other constraints on the flavor of the game.
|
||||||
|
* Determining other game parameters, such as how many indices there are and how long players will have to write each turn.
|
||||||
|
* Ensuring that submitted articles do not contradict what has already been established as fact.
|
||||||
|
* Ensuring that players submit their articles on time.
|
||||||
|
|
||||||
**Topic statement.** The GM should pick an appropriate topic statement for the Lexicon. This topic should be vague, but give the players something to start with, such as "You are all revisionist scholars from the Paleotechnic Era arguing about how the Void Ghost Rebellion led to the overthrow of the cyber-gnostic theocracy and the establishment of the Third Republic" or "In the wake of the Quartile Reformation, you are scholars investigating the influence of Remigrationism on the Disquietists". What happened to the first two Republics or what Remigrationism is are unknown at the beginning; they are named to evoke a mood and inspire creativity.
|
### Game Parameters
|
||||||
|
|
||||||
**Indices and turns.** Each turn, the GM will assign each player to write in a certain index. An index is a subset of however entries are sorted. By default, index by letter or by a group of letters. The GM should decide how many turns the game will last and divide up the entry space into that many indices. Each player will take one turn in each index. The suggested index count is 8, dividing the alphabet into the indices ABC, DEF, GHI, JKL, MNO, PQRS, TUV, WXYZ.
|
* **Topic statement.** The topic statement should be vague, but give the players some hooks to begin writing. Examples: "You are all revisionist scholars from the Paleotechnic Era arguing about how the Void Ghost Rebellion led to the overthrow of the cyber-gnostic theocracy and the establishment of the Third Republic"; "In the wake of the Quartile Reformation, you are scholars investigating the influence of Remigrationism on the Disquietists". What happened to the first two Republics or what Remigrationism is are left open for the players to determine.
|
||||||
|
|
||||||
The GM should also decide how long players will have to write their article for each turn. At one turn per day, it will take about a week to finish an 8-turn game. Avoid letting too long go between turns, or players may forget what was going on in the Lexicon.
|
* **Indices and turns.** In general, the Editor will decide on a number of turns and divide the alphabet into that many indices. Each player then takes one turn in each index. A game of 6 or 8 turns is suggested. _Example: An 8-turn game over the indices ABC/DEF/GHI/JKL/MNO/PQRS/TUV/QXYZ._ The Editor should determine how much time the players can devote to playing Lexicon and set a time limit on turns accordingly.
|
||||||
|
|
||||||
Unless the players have a method of coordinating who is writing what article, it may be useful to always assign players to different indices. The easiest way to do this is to initially distribute players randomly across all indices and have them move through the index list in order, wrapping around to the top from the bottom.
|
* **Index assignments.** Each turn, the Editor should assign each player to an index. Unless players have a method of coordinating who is writing what article, it is suggested that the Editor always assign players to write in different indices. The easiest way to do this is to distribute players randomly across the indices for the first turn, then move them through the indices in order, wrapping around to the top from the bottom.
|
||||||
|
|
||||||
**Varying the rules.** The GM is free to alter the game procedures in service of the goal of the game. By default, articles are indexed alphabetically into 8 indices. A longer game might split the alphabet into 13 pairs. An alternative indexing might index by date over a historical period or some other quality. By default, players move through the index list in order. An alternative might assign the order of indices randomly. In a variation called _Follow the Phantoms_, players can choose what index they write in, but they must always write a phantom article after the first turn. In some circumstances, two players may both have to write an Ersatz article but could write an article in the other's index. The GM might swap the index assignments of such players.
|
### Stylistic Considerations
|
||||||
|
|
||||||
**Additional responsibilities.** Lexipython will generate browsable static HTML files. The GM is responsible for making these generated pages available to the players by hosting or otherwise making the pages available after each round's articles have been turned in and generated. The GM is also responsible for reviewing each round's articles and ensuring that the submitted articles do not contradict each other or the previously written articles.
|
How the game develops is entirely up to the players, and your group may have a different "meta" concerning how they normally play it out. Here are some miscellanea to keep in mind:
|
||||||
|
|
||||||
## Using Lexipython
|
* Some encyclopedias in real life will put an important part of the title in front, so as to alphabetize by a word other than the technically first. This common with the names of persons, resulting in titles like "Lastname, Firstname Q." Players should either stick to this, or avoid it, since it reads oddly to have some titles alphabetized by a last name and others by whatever adjective is in front.
|
||||||
|
|
||||||
To run a game of Lexicon with Lexipython, clone this repository out to a new folder:
|
* While it can be fun to write a long article that ties many disparate parts of the Lexicon world together, article length creep can make the amount of content in the Lexicon difficult to keep up with. Additionally, as articles grow longer, they tend to have more citations, which results in more articles being relevant to a particular topic, which in turn makes writing on that topic harder to research to avoid contradictions.
|
||||||
```
|
|
||||||
$ git clone https://github.com/Jaculabilis/Lexipython.git [name]
|
|
||||||
```
|
|
||||||
Steps for setup:
|
|
||||||
1. Add a sidebar image to the `out/` directory.
|
|
||||||
2. Edit `lexicon.cfg` and fill in the Lexicon game information.
|
|
||||||
3. Build the lexicon.
|
|
||||||
4. When the first turn's articles come in, delete the example page.
|
|
||||||
|
|
||||||
For build commands, run `$ python lexipython.py` to see the available commands.
|
* Even if articles don't get too long, having too many articles on one subject can lead to the same problem of writing on the topic becoming too hard to do consistently. Avoid having multiple articles about the same thing, and avoid having too many articles about different facets of one particular element of the world.
|
||||||
|
|
||||||
|
* Encyclopedias are written about things in the past. Players may, of course, want to mention how something in the past still affects the world in the present day. However, if players begin to write about purely contemporary things or events, the Lexicon shifts from an _encyclopedic_ work to a _narrative_ one. If that's what you want out of the game, go ahead and do so, but writing about an ongoing narrative insead of settled history introduce the additional complication of keeping abreast of the current state of the plot. It is more difficult for players to avoid contradiction when the facts are changing as they write.
|
||||||
|
|
||||||
|
* Articles whose titles do not begin with a character in any index pattern are sorted to the "&c" index. This usually includes numbers and symbols. If the Editor wants to make purposive use of this, they can assign players to it as an index.
|
||||||
|
|
||||||
|
### Rule Variants
|
||||||
|
|
||||||
|
The Editor is always free to alter the game procedures when it would make for a better game. The following are some known rule variations:
|
||||||
|
|
||||||
|
* **Follow the Phantoms:** Players make two phantom citations on the first turn. On subsequent turns, rather than choosing from phantoms and open slots in an assigned index, players must write an existing phantom. Until all slots are full, players must make one of their phantom citations to a new phantom article and one to an existing phantom.
|
||||||
|
|
||||||
|
* Occasionally, if more players make a citation to an index than there are open slots, the index will be over capacity. If the Editor is assigning players to indices in order, the Editor may need to shift players' index assignments around. This may also be useful for decreasing the number of Ersatz articles, if a player can't write in their assigned index but could write in another.
|
||||||
|
|
||||||
## Acknowledgements
|
## Acknowledgements
|
||||||
|
|
||||||
The first version of Lexicon was posted by Neel Krishnaswami on The 20' by 20' Room in 2003. It was inspired by, and named in honor of, Milorad Pavic's _Dictionary of the Khazars: A Lexicon Novel_. The rules were later given some minor clarifications and expansion by Alexander Cherry and posted on Twisted Confessions in 2010. The rules described here were adapted mostly from the 2010 version and revised by Tim Van Baak in 2017.
|
The first version of Lexicon was posted by Neel Krishnaswami on The 20' by 20' Room in 2003. It was inspired by, and named in honor of, Milorad Pavic's _Dictionary of the Khazars: A Lexicon Novel_. The rules were later given some minor clarifications and expansion by Alexander Cherry and posted on Twisted Confessions in 2010. The rules described here were adapted mostly from the 2010 version and revised by Tim Van Baak in 2017-2018.
|
||||||
|
|
||||||
Special thanks to the scholars of Lexicon Alpha and Lexicon Proximum for their contributions and feedback.
|
Special thanks to the scholars of Lexicon Alpha and Lexicon Proximum for their contributions and feedback.
|
||||||
|
|
|
@ -76,6 +76,9 @@ class LexiconArticle:
|
||||||
# Escape angle brackets
|
# Escape angle brackets
|
||||||
para = re.sub("<", "<", para)
|
para = re.sub("<", "<", para)
|
||||||
para = re.sub(">", ">", para)
|
para = re.sub(">", ">", para)
|
||||||
|
# Escape curly braces
|
||||||
|
para = re.sub("{", "{", para)
|
||||||
|
para = re.sub("}", "}", para)
|
||||||
# Replace bold and italic marks with tags
|
# Replace bold and italic marks with tags
|
||||||
para = re.sub(r"//([^/]+)//", r"<i>\1</i>", para)
|
para = re.sub(r"//([^/]+)//", r"<i>\1</i>", para)
|
||||||
para = re.sub(r"\*\*([^*]+)\*\*", r"<b>\1</b>", para)
|
para = re.sub(r"\*\*([^*]+)\*\*", r"<b>\1</b>", para)
|
||||||
|
@ -140,7 +143,8 @@ class LexiconArticle:
|
||||||
target = cite_tuple[1]
|
target = cite_tuple[1]
|
||||||
# Create article objects for phantom citations
|
# Create article objects for phantom citations
|
||||||
if target not in article_by_title:
|
if target not in article_by_title:
|
||||||
article_by_title[target] = LexiconArticle(None, sys.maxsize, target, "<p><i>This entry hasn't been written yet.</i></p>", {})
|
article_by_title[target] = LexiconArticle(None, sys.maxsize, target,
|
||||||
|
"<p><i>This entry hasn't been written yet.</i></p>", {})
|
||||||
# Interlink citations
|
# Interlink citations
|
||||||
if article_by_title[target].player is None:
|
if article_by_title[target].player is None:
|
||||||
article.pcites.add(target)
|
article.pcites.add(target)
|
||||||
|
|
165
src/build.py
165
src/build.py
|
@ -46,12 +46,13 @@ def build_contents_page(articles, config):
|
||||||
content += "</ul>\n</div>\n"
|
content += "</ul>\n</div>\n"
|
||||||
# Write the articles in turn order
|
# Write the articles in turn order
|
||||||
content += "<div id=\"turn-order\" style=\"display:none\">\n<ul>\n"
|
content += "<div id=\"turn-order\" style=\"display:none\">\n<ul>\n"
|
||||||
latest_turn = max([article.turn for article in articles if article.player is not None])
|
turn_numbers = [article.turn for article in articles if article.player is not None]
|
||||||
|
first_turn, last_turn = min(turn_numbers), max(turn_numbers)
|
||||||
turn_order = sorted(
|
turn_order = sorted(
|
||||||
articles,
|
articles,
|
||||||
key=lambda a: (a.turn, utils.titlesort(a.title)))
|
key=lambda a: (a.turn, utils.titlesort(a.title)))
|
||||||
check_off = list(turn_order)
|
check_off = list(turn_order)
|
||||||
for turn_num in range(1, latest_turn + 1):
|
for turn_num in range(first_turn, last_turn + 1):
|
||||||
content += "<h3>Turn {0}</h3>\n".format(turn_num)
|
content += "<h3>Turn {0}</h3>\n".format(turn_num)
|
||||||
for article in turn_order:
|
for article in turn_order:
|
||||||
if article.turn == turn_num:
|
if article.turn == turn_num:
|
||||||
|
@ -128,6 +129,24 @@ def build_session_page(config):
|
||||||
content=config["SESSION_PAGE"],
|
content=config["SESSION_PAGE"],
|
||||||
citeblock="")
|
citeblock="")
|
||||||
|
|
||||||
|
def reverse_statistics_dict(stats, reverse=True):
|
||||||
|
"""
|
||||||
|
Transforms a dictionary mapping titles to a value into a list of values
|
||||||
|
and lists of titles. The list is sorted by the value, and the titles are
|
||||||
|
sorted alphabetically.
|
||||||
|
"""
|
||||||
|
rev = {}
|
||||||
|
for key, value in stats.items():
|
||||||
|
if value not in rev:
|
||||||
|
rev[value] = []
|
||||||
|
rev[value].append(key)
|
||||||
|
for key, value in rev.items():
|
||||||
|
rev[key] = sorted(value, key=lambda t: utils.titlesort(t))
|
||||||
|
return sorted(rev.items(), key=lambda x:x[0], reverse=reverse)
|
||||||
|
|
||||||
|
def itemize(stats_list):
|
||||||
|
return map(lambda x: "{0} – {1}".format(x[0], "; ".join(x[1])), stats_list)
|
||||||
|
|
||||||
def build_statistics_page(articles, config):
|
def build_statistics_page(articles, config):
|
||||||
"""
|
"""
|
||||||
Builds the full HTML of the statistics page.
|
Builds the full HTML of the statistics page.
|
||||||
|
@ -136,61 +155,58 @@ def build_statistics_page(articles, config):
|
||||||
cite_map = {
|
cite_map = {
|
||||||
article.title : [
|
article.title : [
|
||||||
cite_tuple[1]
|
cite_tuple[1]
|
||||||
for cite_tuple in article.citations.values()]
|
for cite_tuple
|
||||||
|
in article.citations.values()
|
||||||
|
]
|
||||||
for article in articles}
|
for article in articles}
|
||||||
|
|
||||||
# Pages by pagerank
|
# Top pages by pagerank
|
||||||
content += "<div class=\"moveable\">\n"
|
# Compute pagerank for each article
|
||||||
content += "<p><u>Top 10 pages by page rank:</u><br>\n"
|
|
||||||
G = networkx.Graph()
|
G = networkx.Graph()
|
||||||
for citer, citeds in cite_map.items():
|
for citer, citeds in cite_map.items():
|
||||||
for cited in citeds:
|
for cited in citeds:
|
||||||
G.add_edge(citer, cited)
|
G.add_edge(citer, cited)
|
||||||
ranks = networkx.pagerank(G)
|
rank_by_article = networkx.pagerank(G)
|
||||||
sranks = sorted(ranks.items(), key=lambda x: x[1], reverse=True)
|
# Get the top ten articles by pagerank
|
||||||
ranking = list(enumerate(map(lambda x: x[0], sranks)))
|
top_pageranks = reverse_statistics_dict(rank_by_article)[:10]
|
||||||
content += "<br>\n".join(map(lambda x: "{0} – {1}".format(x[0]+1, x[1]), ranking[:10]))
|
# Replace the pageranks with ordinals
|
||||||
content += "</p>\n"
|
top_ranked = enumerate(map(lambda x: x[1], top_pageranks), start=1)
|
||||||
content += "</div>\n"
|
# Format the ranks into strings
|
||||||
|
top_ranked_items = itemize(top_ranked)
|
||||||
|
# Write the statistics to the page
|
||||||
|
content += "<div class=\"moveable\">\n"
|
||||||
|
content += "<p><u>Top 10 pages by page rank:</u><br>\n"
|
||||||
|
content += "<br>\n".join(top_ranked_items)
|
||||||
|
content += "</p>\n</div>\n"
|
||||||
|
|
||||||
# Top number of citations made
|
# Top number of citations made
|
||||||
|
citations_made = { title : len(cites) for title, cites in cite_map.items() }
|
||||||
|
top_citations = reverse_statistics_dict(citations_made)[:3]
|
||||||
|
top_citations_items = itemize(top_citations)
|
||||||
content += "<div class=\"moveable\">\n"
|
content += "<div class=\"moveable\">\n"
|
||||||
content += "<p><u>Most citations made from:</u><br>\n"
|
content += "<p><u>Most citations made from:</u><br>\n"
|
||||||
citation_tally = [(kv[0], len(kv[1])) for kv in cite_map.items()]
|
content += "<br>\n".join(top_citations_items)
|
||||||
citation_count = defaultdict(list)
|
content += "</p>\n</div>\n"
|
||||||
for title, count in citation_tally: citation_count[count].append(title)
|
|
||||||
content += "<br>\n".join(map(
|
|
||||||
lambda kv: "{0} – {1}".format(
|
|
||||||
kv[0],
|
|
||||||
"; ".join(sorted(
|
|
||||||
kv[1],
|
|
||||||
key=lambda t: utils.titlesort(t)))),
|
|
||||||
sorted(citation_count.items(), reverse=True)[:3]))
|
|
||||||
content += "</p>\n"
|
|
||||||
content += "</div>\n"
|
|
||||||
|
|
||||||
# Top number of times cited
|
# Top number of times cited
|
||||||
content += "<div class=\"moveable\">\n"
|
# Build a map of what cites each article
|
||||||
content += "<p><u>Most citations made to:</u><br>\n"
|
all_cited = set([title for citeds in cite_map.values() for title in citeds])
|
||||||
all_cited = set([title for cites in cite_map.values() for title in cites])
|
|
||||||
cited_by_map = {
|
cited_by_map = {
|
||||||
cited: [
|
cited: [
|
||||||
citer
|
citer
|
||||||
for citer in cite_map.keys()
|
for citer in cite_map.keys()
|
||||||
if cited in cite_map[citer]]
|
if cited in cite_map[citer]]
|
||||||
for cited in all_cited }
|
for cited in all_cited }
|
||||||
cited_tally = [(kv[0], len(kv[1])) for kv in cited_by_map.items()]
|
# Compute the number of citations to each article
|
||||||
cited_count = defaultdict(list)
|
citations_to = { title : len(cites) for title, cites in cited_by_map.items() }
|
||||||
for title, count in cited_tally: cited_count[count].append(title)
|
top_cited = reverse_statistics_dict(citations_to)[:3]
|
||||||
content += "<br>\n".join(map(
|
top_cited_items = itemize(top_cited)
|
||||||
lambda kv: "{0} – {1}".format(kv[0], "; ".join(sorted(kv[1]))),
|
content += "<div class=\"moveable\">\n"
|
||||||
sorted(cited_count.items(), reverse=True)[:3]))
|
content += "<p><u>Most citations made to:</u><br>\n"
|
||||||
content += "</p>\n"
|
content += "<br>\n".join(top_cited_items)
|
||||||
content += "</div>\n"
|
content += "</p>\n</div>\n"
|
||||||
|
|
||||||
# Top article length, roughly by words
|
# Top article length, roughly by words
|
||||||
content += "<div class=\"moveable\">\n"
|
|
||||||
content += "<p><u>Longest article:</u><br>\n"
|
|
||||||
article_length = {}
|
article_length = {}
|
||||||
for article in articles:
|
for article in articles:
|
||||||
format_map = {
|
format_map = {
|
||||||
|
@ -198,57 +214,66 @@ def build_statistics_page(articles, config):
|
||||||
for format_id, cite_tuple in article.citations.items()
|
for format_id, cite_tuple in article.citations.items()
|
||||||
}
|
}
|
||||||
plain_content = article.content.format(**format_map)
|
plain_content = article.content.format(**format_map)
|
||||||
words = len(plain_content.split())
|
wordcount = len(plain_content.split())
|
||||||
article_length[article.title] = words
|
article_length[article.title] = wordcount
|
||||||
content += "<br>\n".join(map(
|
top_length = reverse_statistics_dict(article_length)[:3]
|
||||||
lambda kv: "{0} – {1}".format(kv[1], kv[0]),
|
top_length_items = itemize(top_length)
|
||||||
sorted(article_length.items(), reverse=True, key=lambda t: t[1])[:3]))
|
content += "<div class=\"moveable\">\n"
|
||||||
content += "</p>\n"
|
content += "<p><u>Longest article:</u><br>\n"
|
||||||
content += "</div>\n"
|
content += "<br>\n".join(top_length_items)
|
||||||
|
content += "</p>\n</div>\n"
|
||||||
|
|
||||||
|
# Total word count
|
||||||
|
content += "<div class=\"moveable\">\n"
|
||||||
|
content += "<p><u>Total word count:</u><br>\n"
|
||||||
|
content += str(sum(article_length.values())) + "</p>"
|
||||||
|
content += "</p>\n</div>\n"
|
||||||
|
|
||||||
# Player pageranks
|
# Player pageranks
|
||||||
content += "<div class=\"moveable\">\n"
|
|
||||||
content += "<p><u>Player total page rank:</u><br>\n"
|
|
||||||
players = sorted(set([article.player for article in articles if article.player is not None]))
|
players = sorted(set([article.player for article in articles if article.player is not None]))
|
||||||
articles_by = {
|
articles_by_player = {
|
||||||
player : [
|
player : [
|
||||||
a
|
a
|
||||||
for a in articles
|
for a in articles
|
||||||
if a.player == player]
|
if a.player == player]
|
||||||
for player in players}
|
for player in players}
|
||||||
player_rank = {
|
pagerank_by_player = {
|
||||||
player : sum(map(lambda a: ranks[a.title] if a.title in ranks else 0, articles))
|
player : round(
|
||||||
for player, articles in articles_by.items()}
|
sum(map(
|
||||||
content += "<br>\n".join(map(
|
lambda a: rank_by_article[a.title] if a.title in rank_by_article else 0,
|
||||||
lambda kv: "{0} – {1}".format(kv[0], round(kv[1], 3)),
|
articles)),
|
||||||
sorted(player_rank.items(), key=lambda t:t[1], reverse=True)))
|
3)
|
||||||
content += "</p>\n"
|
for player, articles
|
||||||
content += "</div>\n"
|
in articles_by_player.items()}
|
||||||
|
player_rank = reverse_statistics_dict(pagerank_by_player)
|
||||||
|
player_rank_items = itemize(player_rank)
|
||||||
|
content += "<div class=\"moveable\">\n"
|
||||||
|
content += "<p><u>Player total page rank:</u><br>\n"
|
||||||
|
content += "<br>\n".join(player_rank_items)
|
||||||
|
content += "</p>\n</div>\n"
|
||||||
|
|
||||||
# Player citations made
|
# Player citations made
|
||||||
content += "<div class=\"moveable\">\n"
|
|
||||||
content += "<p><u>Citations made by player</u><br>\n"
|
|
||||||
player_cite_count = {
|
player_cite_count = {
|
||||||
player : sum(map(lambda a:len(a.wcites | a.pcites), articles))
|
player : sum(map(lambda a:len(a.wcites | a.pcites), articles))
|
||||||
for player, articles in articles_by.items()}
|
for player, articles in articles_by_player.items()}
|
||||||
content += "<br>\n".join(map(
|
player_cites_made_ranks = reverse_statistics_dict(player_cite_count)
|
||||||
lambda kv: "{0} – {1}".format(kv[0], kv[1]),
|
player_cites_made_items = itemize(player_cites_made_ranks)
|
||||||
sorted(player_cite_count.items(), key=lambda t:t[1], reverse=True)))
|
content += "<div class=\"moveable\">\n"
|
||||||
content += "</p>\n"
|
content += "<p><u>Citations made by player</u><br>\n"
|
||||||
content += "</div>\n"
|
content += "<br>\n".join(player_cites_made_items)
|
||||||
|
content += "</p>\n</div>\n"
|
||||||
|
|
||||||
# Player cited count
|
# Player cited count
|
||||||
content += "<div class=\"moveable\">\n"
|
|
||||||
content += "<p><u>Citations made to player</u><br>\n"
|
|
||||||
cited_times = {player : 0 for player in players}
|
cited_times = {player : 0 for player in players}
|
||||||
for article in articles:
|
for article in articles:
|
||||||
if article.player is not None:
|
if article.player is not None:
|
||||||
cited_times[article.player] += len(article.citedby)
|
cited_times[article.player] += len(article.citedby)
|
||||||
content += "<br>\n".join(map(
|
cited_times_ranked = reverse_statistics_dict(cited_times)
|
||||||
lambda kv: "{0} – {1}".format(kv[0], kv[1]),
|
cited_times_items = itemize(cited_times_ranked)
|
||||||
sorted(cited_times.items(), key=lambda t:t[1], reverse=True)))
|
content += "<div class=\"moveable\">\n"
|
||||||
content += "</p>\n"
|
content += "<p><u>Citations made to player</u><br>\n"
|
||||||
content += "</div>\n"
|
content += "<br>\n".join(cited_times_items)
|
||||||
|
content += "</p>\n</div>\n"
|
||||||
|
|
||||||
# Fill in the entry skeleton
|
# Fill in the entry skeleton
|
||||||
entry_skeleton = utils.load_resource("entry-page.html")
|
entry_skeleton = utils.load_resource("entry-page.html")
|
||||||
|
|
|
@ -19,8 +19,7 @@ def titleescape(s):
|
||||||
s = re.sub(r"\s+", '_', s) # Replace whitespace with _
|
s = re.sub(r"\s+", '_', s) # Replace whitespace with _
|
||||||
s = parse.quote(s) # Encode all other characters
|
s = parse.quote(s) # Encode all other characters
|
||||||
s = re.sub(r"%", "", s) # Strip encoding %s
|
s = re.sub(r"%", "", s) # Strip encoding %s
|
||||||
if len(s) > 64: # If the result is unreasonably long,
|
s = s[:64] # Limit to 64 characters
|
||||||
s = hex(abs(hash(s)))[2:] # Replace it with a hex hash
|
|
||||||
return s
|
return s
|
||||||
|
|
||||||
def titlesort(s):
|
def titlesort(s):
|
||||||
|
@ -51,7 +50,7 @@ def load_config(name):
|
||||||
line = f.readline()
|
line = f.readline()
|
||||||
while line:
|
while line:
|
||||||
# Skim lines until a value definition begins
|
# Skim lines until a value definition begins
|
||||||
conf_match = re.match(">>>([^>]+)>>>\s+", line)
|
conf_match = re.match(r">>>([^>]+)>>>\s+", line)
|
||||||
if not conf_match:
|
if not conf_match:
|
||||||
line = f.readline()
|
line = f.readline()
|
||||||
continue
|
continue
|
||||||
|
@ -59,11 +58,11 @@ def load_config(name):
|
||||||
conf = conf_match.group(1)
|
conf = conf_match.group(1)
|
||||||
conf_value = ""
|
conf_value = ""
|
||||||
line = f.readline()
|
line = f.readline()
|
||||||
conf_match = re.match("<<<{0}<<<\s+".format(conf), line)
|
conf_match = re.match(r"<<<{0}<<<\s+".format(conf), line)
|
||||||
while line and not conf_match:
|
while line and not conf_match:
|
||||||
conf_value += line
|
conf_value += line
|
||||||
line = f.readline()
|
line = f.readline()
|
||||||
conf_match = re.match("<<<{0}<<<\s+".format(conf), line)
|
conf_match = re.match(r"<<<{0}<<<\s+".format(conf), line)
|
||||||
if not line:
|
if not line:
|
||||||
# TODO Not this
|
# TODO Not this
|
||||||
raise SystemExit("Reached EOF while reading config value {}".format(conf))
|
raise SystemExit("Reached EOF while reading config value {}".format(conf))
|
||||||
|
|
Loading…
Reference in New Issue