Skip to content
This repository was archived by the owner on May 11, 2022. It is now read-only.

Commit 25b212e

Browse files
authored
Added README Automation (#30)
* Abstracted the Wiki into the generator * Documented the new generator class * Separator files out into responsibilities * Reworked output page method * Refactoring some of the repo code * Doing a bit of refactoring * Clean and polished the repo file * Added documentation to wiki code * Working through documentation * Slowly adding documentation * Added readme path to the language collection * Added constructor documentation * Adding yet more documentation * Updated test file link code * Beginning to toy with readme generation * Back to documenting code * Documented and reworked wiki code * Refactoring code for convenience * Working through README code * Made repo module read-only * Moved source directory code to repo * Created a README generator prototype * Successfully tested previous functionality * Generated my first READMEs 👍 * Generated all of the readmes! * Fixed up some bugs * Implemented URL generation for projects * Added URL generator * Added doc links * Added issue query to missing articles * Tested Issue Linking * Updated README format * Experimenting with updating repo directly * Fixed an issue with program lists * Not sure how this got here! * Refactored the generator code * Added new command to setup.py * Updated version number * Working through the documentation * Cleaned up README * Deleted Travis CI
1 parent e2b3063 commit 25b212e

File tree

12 files changed

+611
-287
lines changed

12 files changed

+611
-287
lines changed

.gitignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -107,4 +107,5 @@ venv.bak/
107107
.idea/
108108

109109
# Wiki
110-
wiki/
110+
/generate_docs/wiki/
111+
/generate_docs/readme/

.travis.yml

Lines changed: 0 additions & 17 deletions
This file was deleted.

README.md

Lines changed: 28 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,38 @@
1-
# The Sample Programs Wiki Generator
1+
# The Sample Programs Docs Generator
22

3-
Currently, the Sample Programs Wiki Generator repo houses the `generate-wiki.py` script which
4-
we use in the Sample Programs repository to generate our wiki documentation. The script
5-
is automated by a Travis CI build in the Sample Programs repo.
3+
Previously known as the Sample Programs Wiki Generator, the Sample Programs Docs Generator repo houses
4+
the `generator.py` script which we use in the Sample Programs repository to generate our documentation. The script is
5+
automated by a GitHub Actions build in the Sample Programs repo.
66

77
If you would like to propose a change, feel free to leverage the issues tab or make a pull request.
88

99
## How It Works
1010

11-
The `generate_wiki.py` script works by analyzing the information in the Sample Programs
12-
repository and storing that information in objects. These objects are then used to
13-
generate various wiki pages in Markdown.
11+
The `generator.py` script works by analyzing the information in the Sample Programs repository and storing that
12+
information in objects. These objects are then used to generate various documentation pages in Markdown.
1413

1514
## What is Automated
1615

17-
Currently, the script generates 27 pages: 1 alphabet catalog and 26 alphabet pages.
16+
Currently, the script does two things:
1817

19-
The alphabet catalog contains a table with links to each alphabet page as well as meta data like the number
20-
of scripts and languages per letter. In addition, the alphabet catalog contains the total number of scripts
21-
and languages for the entire repo.
18+
- It maintains our entire [Sample Programs wiki](https://github.com/TheRenegadeCoder/sample-programs/wiki)
19+
- It maintains all of our READMEs in the Sample Programs repo
2220

23-
Each alphabet page contains a table which lists each language for that particular letter as well as meta data
24-
like the number of scripts per language and links to articles and issues.
21+
In terms of wiki automation, it generates 27 pages: 1 alphabet catalog and 26 alphabet pages.
22+
23+
The alphabet catalog contains a table with links to each alphabet page as well as metadata like the number of scripts
24+
and languages per letter. In addition, the alphabet catalog contains the total number of scripts and languages for the
25+
entire repo.
26+
27+
Each alphabet page contains a table which lists each language for that particular letter as well as metadata like the
28+
number of scripts per language as well as linking to articles and issues.
2529

2630
## How to Run
2731

28-
At this time, the wiki generator is hardcoded for the Sample Programs repo. In order to run it,
29-
you can install it as a package using `pip`:
32+
At this time, the wiki generator is hardcoded for the Sample Programs repo. In order to run it, you can install it as a
33+
package using `pip`:
3034

31-
`pip install generate_wiki`
35+
`pip install generate_docs`
3236

3337
After that, you'll need a copy of the Sample Programs repo:
3438

@@ -38,12 +42,16 @@ Finally, you can build the wiki using the following command:
3842

3943
`wikig /path/to/sample-programs/repo/archive`
4044

45+
Likewise, you can build the READMEs using the following command:
46+
47+
`wikir /path/to/sample-programs/repo/archive`
48+
4149
Alternatively, you can clone this repo to run the `generate_wiki.py` script directly:
4250

43-
`python generate-wiki.py /path/to/sample-programs/repo/archive`
51+
`python generator.py /path/to/sample-programs/repo/archive`
4452

45-
Both solutions are designed to handle repo exploration from the `/archive/` directory. If successful, you should
46-
begin to see print statements for the various links under test for The Renegade Coder. When finished, you'll
47-
have a `/wiki/` directory next to your script which contains the wiki.
53+
Both solutions are designed to handle repo exploration from the `/archive/` directory. If successful, you should begin
54+
to see print statements for the various links under test for The Renegade Coder. When finished, you'll have a `/wiki/`
55+
directory next to your script which contains the wiki. Likewise, all the READMEs should be updated.
4856

4957
At this point, you can push the wiki directly to the Sample Programs wiki.

generate_docs/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
name = "generate_docs"

generate_docs/generator.py

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
import sys
2+
from typing import Optional
3+
4+
from generate_docs.readme import ReadMeCatalog
5+
from generate_docs.repo import Repo
6+
from generate_docs.wiki import Wiki
7+
8+
9+
class Generator:
10+
"""
11+
The top-level class used to generate wiki and README objects.
12+
"""
13+
def __init__(self, source_dir):
14+
self.source_dir = source_dir
15+
self.repo: Repo = Repo(self.source_dir)
16+
17+
def generate_wiki(self) -> None:
18+
"""
19+
Builds and outputs the wiki.
20+
:return: None
21+
"""
22+
wiki = Wiki(self.repo)
23+
for page in wiki.pages:
24+
page.output_page("wiki")
25+
26+
def generate_readmes(self) -> None:
27+
"""
28+
Builds and outputs the READMEs.
29+
:return:
30+
"""
31+
readme_catalog = ReadMeCatalog(self.repo)
32+
for language, page in readme_catalog.pages.items():
33+
page.output_page(f"{self.source_dir}/{language[0]}/{language}")
34+
35+
36+
def _create_generator() -> Optional[Generator]:
37+
"""
38+
Creates the generator object from
39+
:return:
40+
"""
41+
if len(sys.argv) > 1:
42+
generator = Generator(sys.argv[1])
43+
return generator
44+
else:
45+
print("Please supply an input path")
46+
exit(1)
47+
48+
49+
def main_wiki():
50+
"""
51+
Builds the wiki.
52+
:return: None
53+
"""
54+
_create_generator().generate_wiki()
55+
56+
57+
def main_readmes():
58+
"""
59+
Builds the READMEs.
60+
:return: None
61+
"""
62+
_create_generator().generate_readmes()
63+
64+
65+
if __name__ == '__main__':
66+
main_wiki()

generate_docs/markdown.py

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
import os
2+
import pathlib
3+
from urllib.error import HTTPError
4+
from urllib import request
5+
6+
from generate_docs.repo import LanguageCollection, SampleProgram
7+
8+
9+
def create_md_link(text: str, url: str) -> str:
10+
"""
11+
Generates a markdown link in the form [text](url).
12+
:param text: the link text
13+
:param url: the url to link
14+
:return: a markdown link
15+
"""
16+
separator = ""
17+
return separator.join(["[", text, "]", "(", url, ")"])
18+
19+
20+
def build_language_link(language: LanguageCollection) -> str:
21+
"""
22+
A handy abstraction for the create_md_link() method which creates a link to a sample programs language page.
23+
(e.g., https://sample-programs.therenegadecoder.com/languages/c/)
24+
:param language: the language to link
25+
:return: a markdown link to the language page if it exists; an empty string otherwise
26+
"""
27+
if not verify_link(language.sample_program_url):
28+
markdown_url = ""
29+
else:
30+
markdown_url = create_md_link("Here", language.sample_program_url)
31+
return markdown_url
32+
33+
34+
def build_doc_link(sample_program: SampleProgram, text: str) -> str:
35+
"""
36+
A handy abstraction for the create_md_link() method which creates a link to a sample programs docs page.
37+
(e.g., https://sample-programs.therenegadecoder.com/projects/even-odd/c/)
38+
:param sample_program: the sample program
39+
:param text: the label for the link
40+
:return: a markdown link to the docs page if it exists; an empty string otherwise
41+
"""
42+
if not verify_link(sample_program.sample_program_doc_url):
43+
markdown_url = f":warning: {create_md_link(text, sample_program.sample_program_issue_url)}"
44+
else:
45+
markdown_url = f":white_check_mark: {create_md_link(text, sample_program.sample_program_doc_url)}"
46+
return markdown_url
47+
48+
49+
def verify_link(url: str) -> bool:
50+
"""
51+
Verifies that a URL is a valid URL.
52+
:param url: a website URL
53+
:return: True if the URL is valid; False otherwise
54+
"""
55+
req = request.Request(url)
56+
req.get_method = lambda: 'HEAD'
57+
print(f"Trying: {url}")
58+
try:
59+
request.urlopen(req)
60+
print(f"\tVALID")
61+
return True
62+
except HTTPError:
63+
print(f"\tINVALID")
64+
return False
65+
66+
67+
class MarkdownPage:
68+
def __init__(self, name: str):
69+
self.name: str = name
70+
self.ext = ".md"
71+
self.wiki_url_base: str = "/jrg94/sample-programs/wiki/"
72+
self.content = list()
73+
74+
def __str__(self):
75+
return f"{self.name}\n{self._build_page()}"
76+
77+
def _build_page(self):
78+
return "\n".join(self.content)
79+
80+
def add_content(self, *lines: str):
81+
self.content.extend(lines)
82+
83+
def add_table_header(self, *args):
84+
column_separator = " | "
85+
header = column_separator.join(args)
86+
divider = column_separator.join(["-----"] * len(args))
87+
self.content.append(header)
88+
self.content.append(divider)
89+
90+
def add_table_row(self, *args):
91+
column_separator = " | "
92+
row = column_separator.join(args)
93+
self.content.append(row)
94+
95+
def add_section_break(self):
96+
self.content.append("")
97+
98+
def output_page(self, dump_dir):
99+
pathlib.Path(dump_dir).mkdir(parents=True, exist_ok=True)
100+
output_file = open(os.path.join(dump_dir, self._get_file_name()), "w+")
101+
output_file.write(self._build_page())
102+
output_file.close()
103+
104+
def _get_file_name(self):
105+
separator = "-"
106+
file_name = f"{separator.join(self.name.split())}{self.ext}"
107+
return file_name

generate_docs/readme.py

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
from generate_docs.markdown import MarkdownPage, build_doc_link, verify_link
2+
from generate_docs.repo import Repo, LanguageCollection
3+
4+
5+
def _get_intro_text(language: LanguageCollection) -> str:
6+
introduction = f"Welcome to Sample Programs in {language.name.capitalize()}!"
7+
docs = f"""To find documentation related to the {language.name.capitalize()}
8+
code in this repo, look [here]({language.sample_program_url}).
9+
"""
10+
interlude_valid = "Otherwise, below you'll find a list of code snippets in this collection."
11+
interlude_invalid = "Below, you'll find a list of code snippets in this collection."
12+
emojis = """
13+
Code snippets preceded by :warning: link to a GitHub
14+
issue query featuring a possible article request issue. If an article request issue
15+
doesn't exist, we encourage you to create one. Meanwhile, code snippets preceded
16+
by :white_check_mark: link to an existing article which provides further documentation.
17+
"""
18+
if not verify_link(language.sample_program_url):
19+
return " ".join([introduction, interlude_invalid, emojis])
20+
else:
21+
return " ".join([introduction, docs, interlude_valid, emojis])
22+
23+
24+
def _generate_program_list(language: LanguageCollection) -> list:
25+
"""
26+
A helper function which generates a list of programs for the README.
27+
:param language: a language collection
28+
:return: a list of sample programs list items
29+
"""
30+
list_items = list()
31+
for program in language.sample_programs:
32+
readable_name = program.normalized_name.replace("-", " ").title()
33+
doc_link = build_doc_link(program, f"{readable_name} in {program.language.capitalize()}")
34+
list_items.append(f"- {doc_link}")
35+
return list_items
36+
37+
38+
class ReadMeCatalog:
39+
"""
40+
An representation of the collection of READMEs in the Sample Programs repo.
41+
"""
42+
43+
def __init__(self, repo: Repo):
44+
"""
45+
Constructs an instance of a ReadMeCatalog.
46+
:param repo: a repository instance
47+
"""
48+
self.repo: Repo = repo
49+
self.pages: dict[str, MarkdownPage] = dict()
50+
self._build_readmes()
51+
52+
def _build_readme(self, language: LanguageCollection) -> None:
53+
"""
54+
Creates a README page from a language collection.
55+
:param language: a programming language collection (e.g., Python)
56+
:return: None
57+
"""
58+
page = MarkdownPage("README")
59+
page.add_content(f"# Sample Programs in {language.name.capitalize()}")
60+
page.add_section_break()
61+
page.add_content(_get_intro_text(language))
62+
page.add_section_break()
63+
page.add_content(*_generate_program_list(language))
64+
page.add_section_break()
65+
self.pages[language.name] = page
66+
67+
def _build_readmes(self) -> None:
68+
"""
69+
Generates all READMEs for the repo.
70+
:return: None
71+
"""
72+
for language in self.repo.languages:
73+
self._build_readme(language)

0 commit comments

Comments
 (0)