init
This commit is contained in:
commit
5c2d5626d0
8
.gitignore
vendored
Normal file
8
.gitignore
vendored
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
.vscode/
|
||||||
|
tags
|
||||||
|
tags.*
|
||||||
|
venv/
|
||||||
|
__pycache__/
|
||||||
|
issue*
|
||||||
|
dial_a_zine_issue*
|
||||||
|
newsession_issue2
|
3
.gitmodules
vendored
Normal file
3
.gitmodules
vendored
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
[submodule "issue1"]
|
||||||
|
path = issue1
|
||||||
|
url = git@github.com:caraesten/dial_a_zine_issue1.git
|
24
1x 40 right.txt
Normal file
24
1x 40 right.txt
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
o--------------------------------------o
|
||||||
|
| |
|
||||||
|
| |
|
||||||
|
| |
|
||||||
|
| |
|
||||||
|
| |
|
||||||
|
| |
|
||||||
|
| |
|
||||||
|
| |
|
||||||
|
| |
|
||||||
|
this page intentionally left blank | |
|
||||||
|
| |
|
||||||
|
| |
|
||||||
|
| |
|
||||||
|
| |
|
||||||
|
| |
|
||||||
|
| |
|
||||||
|
| |
|
||||||
|
| |
|
||||||
|
| |
|
||||||
|
| |
|
||||||
|
| |
|
||||||
|
| |
|
||||||
|
o--------------------------------------o
|
24
1x 40.txt
Normal file
24
1x 40.txt
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
o--------------------------------------o
|
||||||
|
| |
|
||||||
|
| |
|
||||||
|
| |
|
||||||
|
| |
|
||||||
|
| |
|
||||||
|
| |
|
||||||
|
| |
|
||||||
|
| |
|
||||||
|
| |
|
||||||
|
| |
|
||||||
|
| |
|
||||||
|
| |
|
||||||
|
| |
|
||||||
|
| |
|
||||||
|
| |
|
||||||
|
| |
|
||||||
|
| |
|
||||||
|
| |
|
||||||
|
| |
|
||||||
|
| |
|
||||||
|
| |
|
||||||
|
| |
|
||||||
|
o--------------------------------------o
|
24
1x 80.txt
Normal file
24
1x 80.txt
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
o-----------------------------------------------------------------------------o
|
||||||
|
| |
|
||||||
|
| |
|
||||||
|
| |
|
||||||
|
| |
|
||||||
|
| |
|
||||||
|
| |
|
||||||
|
| |
|
||||||
|
| |
|
||||||
|
| |
|
||||||
|
| |
|
||||||
|
| |
|
||||||
|
| |
|
||||||
|
| |
|
||||||
|
| |
|
||||||
|
| |
|
||||||
|
| |
|
||||||
|
| |
|
||||||
|
| |
|
||||||
|
| |
|
||||||
|
| |
|
||||||
|
| |
|
||||||
|
| |
|
||||||
|
o-----------------------------------------------------------------------------o
|
24
2x 40.txt
Normal file
24
2x 40.txt
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
o--------------------------------------o--------------------------------------o
|
||||||
|
| | |
|
||||||
|
| | |
|
||||||
|
| | |
|
||||||
|
| | |
|
||||||
|
| | |
|
||||||
|
| | |
|
||||||
|
| | |
|
||||||
|
| | |
|
||||||
|
| | |
|
||||||
|
| | |
|
||||||
|
| | |
|
||||||
|
| | |
|
||||||
|
| | |
|
||||||
|
| | |
|
||||||
|
| | |
|
||||||
|
| | |
|
||||||
|
| | |
|
||||||
|
| | |
|
||||||
|
| | |
|
||||||
|
| | |
|
||||||
|
| | |
|
||||||
|
| | |
|
||||||
|
o--------------------------------------o--------------------------------------o
|
17
Dockerfile
Normal file
17
Dockerfile
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
# syntax=docker/dockerfile:1
|
||||||
|
|
||||||
|
ARG ISSUE_DIR=issue1
|
||||||
|
|
||||||
|
FROM python:3.8-slim-buster
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
COPY requirements.txt requirements.txt
|
||||||
|
RUN pip3 install -r requirements.txt
|
||||||
|
|
||||||
|
COPY dialazine dialazine
|
||||||
|
COPY $ISSUE_DIR $ISSUE_DIR
|
||||||
|
|
||||||
|
EXPOSE 23/tcp
|
||||||
|
|
||||||
|
CMD [ "python3", "dialazine/server.py" ]
|
21
LICENSE
Normal file
21
LICENSE
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2021 Cara Esten Hurtle
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
19
README.md
Normal file
19
README.md
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
## How to run
|
||||||
|
|
||||||
|
To run locally, run server.py as a python file e.g. from the root directory
|
||||||
|
|
||||||
|
`python3 dialazine/server.py`
|
||||||
|
|
||||||
|
To access locally, run
|
||||||
|
|
||||||
|
`telnet localhost 23`
|
||||||
|
|
||||||
|
(or equivalent with another telnet client)
|
||||||
|
|
||||||
|
in server.py, change CONTENT_FOLDER to "example_issue" to see example
|
||||||
|
|
||||||
|
## Zine Structure
|
||||||
|
|
||||||
|
In the index.json, the "hello" message is the filepath within the issue folder for what is first displayed,
|
||||||
|
|
||||||
|
Then the contents is a list with each article, taking the "title" and "author" for the index, and the "directory" is the folder within the issue folder that contains the article pages - the pages must be called `1.txt`, `2.txt` etc. and will automatically display in that order
|
18
default_templates/intro.html
Normal file
18
default_templates/intro.html
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<link rel="stylesheet" href="main.css">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="main">
|
||||||
|
<div class="viewport">
|
||||||
|
{{#intro_lines}}
|
||||||
|
{{{.}}} <br>
|
||||||
|
{{/intro_lines}}
|
||||||
|
</div>
|
||||||
|
<div class="navigation">
|
||||||
|
<a href="{{ index_url }}">(index)</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
44
default_templates/main_sample.css
Normal file
44
default_templates/main_sample.css
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
body {
|
||||||
|
font-family: monospace;
|
||||||
|
background-color: black;
|
||||||
|
color: #39ff14;
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
color: #ddd;
|
||||||
|
}
|
||||||
|
|
||||||
|
h2 {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.main {
|
||||||
|
width: 82ch; /* fudge it a bit */
|
||||||
|
margin-left: auto;
|
||||||
|
margin-right: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.viewport {
|
||||||
|
height: 30em;
|
||||||
|
margin-top: 2em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stories-list li {
|
||||||
|
margin: 0.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.navigation {
|
||||||
|
margin-top: 2em;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.navigation a {
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.about {
|
||||||
|
color: #1c830a;
|
||||||
|
text-align: center;
|
||||||
|
font-size: 10pt;
|
||||||
|
margin-top: 1em;
|
||||||
|
}
|
24
default_templates/story_index.html
Normal file
24
default_templates/story_index.html
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<link rel="stylesheet" href="main.css">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="main">
|
||||||
|
<div class="viewport">
|
||||||
|
<h2>{{header}}</h2>
|
||||||
|
<ul class="stories-list">
|
||||||
|
{{#stories}}
|
||||||
|
<li><a href='{{story_link}}'>{{title_text}}</a></li>
|
||||||
|
{{/stories}}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<div class="navigation">
|
||||||
|
<a href="index.html">(intro)</a>
|
||||||
|
</div>
|
||||||
|
<div class="about">
|
||||||
|
Sample zine, generated by <a href="https://github.com/caraesten/dial_a_zine">dial-a-zine</a> telnet cms!
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
33
default_templates/story_page.html
Normal file
33
default_templates/story_page.html
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<link rel="stylesheet" href="../main.css">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="main">
|
||||||
|
<div class="viewport">
|
||||||
|
{{#story_lines}}
|
||||||
|
{{{.}}} <br>
|
||||||
|
{{/story_lines}}
|
||||||
|
</div>
|
||||||
|
<div class="navigation">
|
||||||
|
{{#back_link}}
|
||||||
|
<a href="{{.}}">back</a>
|
||||||
|
{{/back_link}}
|
||||||
|
{{^back_link}}
|
||||||
|
back
|
||||||
|
{{/back_link}}
|
||||||
|
<a href="{{ index_url }}">(index)</a>
|
||||||
|
{{#next_link}}
|
||||||
|
<a href="{{.}}">next</a>
|
||||||
|
{{/next_link}}
|
||||||
|
{{^next_link}}
|
||||||
|
next
|
||||||
|
{{/next_link}}
|
||||||
|
</div>
|
||||||
|
<div class="about">
|
||||||
|
{{story_title}} by {{story_author}}, page {{page_number}}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
24
dialazine/generate_html.py
Executable file
24
dialazine/generate_html.py
Executable file
@ -0,0 +1,24 @@
|
|||||||
|
#!/usr/bin/python3
|
||||||
|
import os
|
||||||
|
import argparse
|
||||||
|
from pathlib import Path
|
||||||
|
from lib.html_generator import HtmlGenerator
|
||||||
|
|
||||||
|
TEMPLATES = "default_templates"
|
||||||
|
CONTENT_FOLDER = "example_issue"
|
||||||
|
|
||||||
|
parser = argparse.ArgumentParser(description="Use this tool to export html documents for your zine")
|
||||||
|
parser.add_argument("outputdir", type=Path, help="output directory for generated HTML")
|
||||||
|
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
if not args.outputdir:
|
||||||
|
raise ValueError("Required param outputdir missing, run program with -h")
|
||||||
|
|
||||||
|
html_directory = args.outputdir.as_posix()
|
||||||
|
|
||||||
|
root_dir_path = Path(__file__).parent.parent.absolute()
|
||||||
|
index_directory = "%s/%s" % (root_dir_path.as_posix(), f"{CONTENT_FOLDER}/index.json")
|
||||||
|
templates_directory = "%s/%s" % (root_dir_path.as_posix(), TEMPLATES)
|
||||||
|
generator = HtmlGenerator(index_directory, html_directory, templates_directory)
|
||||||
|
generator.write_zine_html()
|
12
dialazine/lib/common_tools.py
Normal file
12
dialazine/lib/common_tools.py
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
|
||||||
|
def index_header(include_linebreaks=True):
|
||||||
|
linebreak_char = ''
|
||||||
|
if include_linebreaks:
|
||||||
|
linebreak_char = '\n'
|
||||||
|
return linebreak_char + " [ INDEX ] " + linebreak_char
|
||||||
|
|
||||||
|
def index_item_string(option, title, author, include_linebreaks=True):
|
||||||
|
linebreak_char = ''
|
||||||
|
if include_linebreaks:
|
||||||
|
linebreak_char = '\n'
|
||||||
|
return linebreak_char + ("%s > %s < ...by %s" % (option, title, author)) + linebreak_char
|
92
dialazine/lib/contents_reader.py
Normal file
92
dialazine/lib/contents_reader.py
Normal file
@ -0,0 +1,92 @@
|
|||||||
|
import json, os, string
|
||||||
|
from lib.common_tools import index_item_string
|
||||||
|
from lib.text_screen_reader import TextScreenReader
|
||||||
|
from lib.common_tools import index_header
|
||||||
|
|
||||||
|
class ContentsReader:
|
||||||
|
def __init__(self, contents_file_path):
|
||||||
|
self.issue_directory = os.path.dirname(contents_file_path)
|
||||||
|
self.text_reader = TextScreenReader(self.issue_directory)
|
||||||
|
contents_file = open(contents_file_path, 'r')
|
||||||
|
self.contents_json = json.load(contents_file)
|
||||||
|
self.verify_contents()
|
||||||
|
|
||||||
|
def verify_contents(self):
|
||||||
|
if not self.contents_json['hello']:
|
||||||
|
raise Exception("Contents JSON requires a hello message")
|
||||||
|
|
||||||
|
def hello_file_path(self):
|
||||||
|
return self.contents_json['hello']
|
||||||
|
|
||||||
|
def read_hello_file(self, wrap_returns=True):
|
||||||
|
lines = self.text_reader.read_file_name(self.hello_file_path())
|
||||||
|
if wrap_returns:
|
||||||
|
return self._wrap_carriage_returns(lines)
|
||||||
|
else:
|
||||||
|
return lines
|
||||||
|
|
||||||
|
def read_index_lines(self):
|
||||||
|
index_lines = [index_header()]
|
||||||
|
option_number = 1
|
||||||
|
for index_item in self.read_story_object():
|
||||||
|
index_lines.append(index_item_string(self._index_to_option(option_number), index_item['title'], index_item['author']))
|
||||||
|
option_number += 1
|
||||||
|
index_lines.append("\n(or X to quit!)\n")
|
||||||
|
return self._wrap_carriage_returns(index_lines)
|
||||||
|
|
||||||
|
def read_story_object(self, include_contents=False):
|
||||||
|
index_list = []
|
||||||
|
story_number = 1
|
||||||
|
for index_item in self.contents_json['contents']:
|
||||||
|
story_item = {
|
||||||
|
'title': index_item['title'],
|
||||||
|
'author': index_item['author'],
|
||||||
|
'directory': index_item['directory'],
|
||||||
|
}
|
||||||
|
if include_contents:
|
||||||
|
page_number = 1
|
||||||
|
story_lines = self.read_story(story_number, page_number, wrap_returns=False)
|
||||||
|
pages = []
|
||||||
|
while len(story_lines) > 0:
|
||||||
|
pages.append(story_lines)
|
||||||
|
page_number += 1
|
||||||
|
story_lines = self.read_story(story_number, page_number, wrap_returns=False)
|
||||||
|
story_item['contents'] = pages
|
||||||
|
story_number += 1
|
||||||
|
index_list.append(story_item)
|
||||||
|
return index_list
|
||||||
|
|
||||||
|
def read_story(self, story_number, page = 1, wrap_returns=True):
|
||||||
|
if story_number > len(self.contents_json['contents']):
|
||||||
|
return []
|
||||||
|
story_obj = self.contents_json['contents'][story_number - 1]
|
||||||
|
file_path = "%s/%s.txt" % (story_obj['directory'], page)
|
||||||
|
|
||||||
|
if self.text_reader.does_file_exist(file_path):
|
||||||
|
page_lines = self.text_reader.read_file_name(file_path)
|
||||||
|
if wrap_returns:
|
||||||
|
return self._wrap_carriage_returns(page_lines)
|
||||||
|
else:
|
||||||
|
return page_lines
|
||||||
|
else:
|
||||||
|
return []
|
||||||
|
|
||||||
|
def _wrap_carriage_returns(self, lines_list):
|
||||||
|
return [x + '\r' for x in lines_list]
|
||||||
|
|
||||||
|
def map_input_to_numerical_index(self, input_string):
|
||||||
|
try:
|
||||||
|
return int(input_string)
|
||||||
|
except ValueError:
|
||||||
|
pass
|
||||||
|
try:
|
||||||
|
return 10 + string.ascii_uppercase[0:10].index(input_string)
|
||||||
|
except ValueError:
|
||||||
|
return -1
|
||||||
|
|
||||||
|
def _index_to_option(self, input_index):
|
||||||
|
if (input_index < 10):
|
||||||
|
return input_index
|
||||||
|
else:
|
||||||
|
return string.ascii_uppercase[input_index - 10]
|
||||||
|
|
93
dialazine/lib/html_generator.py
Normal file
93
dialazine/lib/html_generator.py
Normal file
@ -0,0 +1,93 @@
|
|||||||
|
import chevron
|
||||||
|
from pathlib import Path
|
||||||
|
from lib.common_tools import index_item_string, index_header
|
||||||
|
from lib.contents_reader import ContentsReader
|
||||||
|
|
||||||
|
class HtmlGenerator:
|
||||||
|
def __init__(self, index_file_path, html_output_path, templates_path):
|
||||||
|
self.contents_reader = ContentsReader(index_file_path)
|
||||||
|
self.html_output_path = html_output_path
|
||||||
|
self.templates_path = templates_path
|
||||||
|
# TODO: this could be customizeable
|
||||||
|
self.index_url = "story_index.html"
|
||||||
|
|
||||||
|
def write_zine_html(self):
|
||||||
|
intro_text = self.contents_reader.read_hello_file(wrap_returns=False)
|
||||||
|
zine_dict = self.contents_reader.read_story_object(include_contents=True)
|
||||||
|
|
||||||
|
intro_template_path = f'{self.templates_path}/intro.html'
|
||||||
|
index_template_path = f'{self.templates_path}/story_index.html'
|
||||||
|
story_template_path = f'{self.templates_path}/story_page.html'
|
||||||
|
|
||||||
|
Path(self.html_output_path).mkdir(parents=True, exist_ok=True)
|
||||||
|
|
||||||
|
self._write_intro_html(intro_template_path, intro_text)
|
||||||
|
self._write_index_html(index_template_path, zine_dict)
|
||||||
|
self._write_stories_html(story_template_path, zine_dict)
|
||||||
|
|
||||||
|
def _write_intro_html(self, intro_template_path, intro_text):
|
||||||
|
intro_html = ''
|
||||||
|
html_text = [self._htmlize_whitespace(x) for x in intro_text]
|
||||||
|
with open(intro_template_path, 'r') as f:
|
||||||
|
intro_html = chevron.render(f, {'intro_lines': html_text, 'index_url': self.index_url})
|
||||||
|
file_output_path = self.html_output_path + "/index.html"
|
||||||
|
print("Writing intro page to: %s" % file_output_path)
|
||||||
|
with open(file_output_path, 'w') as f:
|
||||||
|
f.write(intro_html)
|
||||||
|
|
||||||
|
def _write_index_html(self, index_template_path, zine_dict):
|
||||||
|
index_html = ''
|
||||||
|
with open(index_template_path, 'r') as f:
|
||||||
|
index_items = []
|
||||||
|
for index in range(len(zine_dict)):
|
||||||
|
item = zine_dict[index]
|
||||||
|
first_page_link = "%s/1.html" % item['directory']
|
||||||
|
item_dict = {
|
||||||
|
'title_text': index_item_string(index, item['title'], item['author'], include_linebreaks=False),
|
||||||
|
'story_link': first_page_link
|
||||||
|
}
|
||||||
|
index_items.append(item_dict)
|
||||||
|
index_html = chevron.render(f, {
|
||||||
|
'header': index_header(include_linebreaks=False),
|
||||||
|
'stories': index_items,
|
||||||
|
'index_url': self.index_url
|
||||||
|
})
|
||||||
|
file_output_path = self.html_output_path + "/" + self.index_url
|
||||||
|
print("Writing index page to: %s" % file_output_path)
|
||||||
|
with open(file_output_path, 'w') as f:
|
||||||
|
f.write(index_html)
|
||||||
|
|
||||||
|
def _write_stories_html(self, story_page_template_path, zine_dict):
|
||||||
|
story_template = ''
|
||||||
|
with open(story_page_template_path, 'r') as f:
|
||||||
|
story_template = f.read()
|
||||||
|
|
||||||
|
for item in zine_dict:
|
||||||
|
output_directory = item['directory']
|
||||||
|
story_contents = item['contents']
|
||||||
|
for index, page in enumerate(story_contents):
|
||||||
|
# pages are 1-indexed, just to make them a bit more human readable
|
||||||
|
current_page = index + 1
|
||||||
|
previous_page = f'{str(current_page - 1)}.html' if index > 0 else ''
|
||||||
|
next_page = f'{str(current_page + 1)}.html' if index < len(story_contents) -1 else ''
|
||||||
|
page_html = chevron.render(story_template, {
|
||||||
|
'back_link': previous_page,
|
||||||
|
'next_link': next_page,
|
||||||
|
'story_lines': [self._htmlize_whitespace(x.replace("\n", "")) for x in page],
|
||||||
|
'index_url': '../story_index.html',
|
||||||
|
'story_title': item['title'],
|
||||||
|
'story_author': item['author'],
|
||||||
|
'page_number': current_page,
|
||||||
|
})
|
||||||
|
file_output_dir = self.html_output_path + "/" + output_directory + "/"
|
||||||
|
file_output_path = file_output_dir + str(current_page) + ".html"
|
||||||
|
Path(file_output_dir).mkdir(parents=True, exist_ok=True)
|
||||||
|
print("Writing story page to: %s" % file_output_path)
|
||||||
|
with open(file_output_path, 'w') as f:
|
||||||
|
f.write(page_html)
|
||||||
|
|
||||||
|
def _htmlize_whitespace(self, string):
|
||||||
|
# linebreaks are already determined via newlines in source text files.
|
||||||
|
# we can (and should) just make all spaces non-breaking.
|
||||||
|
# TODO: evaluate this with screen readers
|
||||||
|
return string.replace(' ', ' ')
|
13
dialazine/lib/text_screen_reader.py
Normal file
13
dialazine/lib/text_screen_reader.py
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
import os
|
||||||
|
|
||||||
|
class TextScreenReader:
|
||||||
|
def __init__(self, root_directory):
|
||||||
|
self.root_directory = root_directory
|
||||||
|
def read_file_name(self, path):
|
||||||
|
full_path = "%s/%s" % (self.root_directory, path)
|
||||||
|
with open(full_path, 'r') as f:
|
||||||
|
full_file = f.readlines()
|
||||||
|
f.close()
|
||||||
|
return full_file
|
||||||
|
def does_file_exist(self, file_path):
|
||||||
|
return os.path.exists("%s/%s" % (self.root_directory, file_path))
|
54
dialazine/lib/zine_functions.py
Normal file
54
dialazine/lib/zine_functions.py
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
from lib.contents_reader import ContentsReader
|
||||||
|
import asyncio
|
||||||
|
|
||||||
|
CLEAR_SCREEN = "\u001b[2J"
|
||||||
|
NEW_LINE = "\r\n"
|
||||||
|
|
||||||
|
class ZineFunctions:
|
||||||
|
def __init__(self, reader, writer, index_file_path):
|
||||||
|
self.reader = reader
|
||||||
|
self.writer = writer
|
||||||
|
self.contents_reader = ContentsReader(index_file_path)
|
||||||
|
|
||||||
|
async def run_index(self):
|
||||||
|
for welcome_line in self.contents_reader.read_hello_file():
|
||||||
|
self.writer.write(welcome_line)
|
||||||
|
await self.writer.drain()
|
||||||
|
# Read one byte (any key)
|
||||||
|
await self.reader.read(1)
|
||||||
|
running = True
|
||||||
|
while (running):
|
||||||
|
for index_line in self.contents_reader.read_index_lines():
|
||||||
|
self.writer.write(index_line)
|
||||||
|
item_choice = await self.reader.read(1)
|
||||||
|
item_choice_int = -1
|
||||||
|
if item_choice.upper() == 'X':
|
||||||
|
running = False
|
||||||
|
continue
|
||||||
|
item_choice_int = self.contents_reader.map_input_to_numerical_index(item_choice)
|
||||||
|
if item_choice_int == -1:
|
||||||
|
self.writer.write(f"{NEW_LINE}{NEW_LINE}Pick a story, or X to quit.{NEW_LINE}")
|
||||||
|
continue
|
||||||
|
self.writer.write(f"{NEW_LINE}{NEW_LINE}...you picked: %s" % (item_choice))
|
||||||
|
self.writer.write(f"{NEW_LINE}{NEW_LINE}...press RETURN to start reading, and to continue after each page")
|
||||||
|
await self.reader.read(1)
|
||||||
|
self.writer.write(NEW_LINE + CLEAR_SCREEN)
|
||||||
|
await asyncio.sleep(1)
|
||||||
|
await self.run_story(item_choice_int)
|
||||||
|
self.disconnect()
|
||||||
|
|
||||||
|
async def run_story(self, story_number):
|
||||||
|
page_number = 1
|
||||||
|
|
||||||
|
story_lines = self.contents_reader.read_story(story_number, page_number)
|
||||||
|
while len(story_lines) > 0:
|
||||||
|
self.writer.write(CLEAR_SCREEN)
|
||||||
|
for story_line in story_lines:
|
||||||
|
self.writer.write(story_line)
|
||||||
|
await self.writer.drain()
|
||||||
|
char_read = await self.reader.readline()
|
||||||
|
page_number += 1
|
||||||
|
story_lines = self.contents_reader.read_story(story_number, page_number)
|
||||||
|
|
||||||
|
def disconnect(self):
|
||||||
|
self.writer.close()
|
19
dialazine/server.py
Executable file
19
dialazine/server.py
Executable file
@ -0,0 +1,19 @@
|
|||||||
|
#!/usr/bin/python3
|
||||||
|
import asyncio, telnetlib3
|
||||||
|
import os
|
||||||
|
import pathlib
|
||||||
|
from lib.zine_functions import ZineFunctions
|
||||||
|
|
||||||
|
LOCALHOST_PORT = 23
|
||||||
|
CONTENT_FOLDER = "tyrel"
|
||||||
|
|
||||||
|
async def shell(reader, writer):
|
||||||
|
root_dir_path = pathlib.Path(__file__).parent.parent.absolute()
|
||||||
|
|
||||||
|
zine = ZineFunctions(reader, writer, "%s/%s" % (root_dir_path.as_posix(), f"{CONTENT_FOLDER}/index.json"))
|
||||||
|
await zine.run_index()
|
||||||
|
|
||||||
|
loop = asyncio.get_event_loop()
|
||||||
|
srv = telnetlib3.create_server(port=LOCALHOST_PORT, shell=shell, timeout=3600)
|
||||||
|
server = loop.run_until_complete(srv)
|
||||||
|
loop.run_until_complete(server.wait_closed())
|
53
dialazine/tools/proof_ascii.html
Normal file
53
dialazine/tools/proof_ascii.html
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>ASCII Scanner</title>
|
||||||
|
<script type="text/javascript" src="proof_ascii.js"></script>
|
||||||
|
<style type="text/css">
|
||||||
|
body {
|
||||||
|
background-color: #444;
|
||||||
|
color: #ddd;
|
||||||
|
}
|
||||||
|
#main-body {
|
||||||
|
width: 60%;
|
||||||
|
margin-left: auto;
|
||||||
|
margin-right: auto;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
textarea {
|
||||||
|
background-color: #666;
|
||||||
|
}
|
||||||
|
#text-input {
|
||||||
|
width: 90%;
|
||||||
|
margin-left: auto;
|
||||||
|
margin-right: auto;
|
||||||
|
min-height: 20em;
|
||||||
|
color: #ddd;
|
||||||
|
}
|
||||||
|
#matches-output {
|
||||||
|
margin-top: 1em;
|
||||||
|
background-color: #666;
|
||||||
|
width: 90%;
|
||||||
|
margin-left: auto;
|
||||||
|
margin-right: auto;
|
||||||
|
}
|
||||||
|
#text-output {
|
||||||
|
margin-top: 1em;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
#text-output .invalid {
|
||||||
|
background-color: #d66;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="main-body">
|
||||||
|
<h1>ASCII Scanner</h1>
|
||||||
|
<h3>Paste Text Below</h3>
|
||||||
|
<textarea id="text-input"></textarea>
|
||||||
|
<div id="matches-output"></div>
|
||||||
|
<div id="text-output"></div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
34
dialazine/tools/proof_ascii.js
Normal file
34
dialazine/tools/proof_ascii.js
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
class AsciiScanner {
|
||||||
|
static ASCII_REGEX = /([\u{00e1}-\u{FFFF}]+)/gu;
|
||||||
|
constructor(inputElement, matchesOutput, textOutput) {
|
||||||
|
this.inputElement = inputElement;
|
||||||
|
this.matchesOutput = matchesOutput;
|
||||||
|
this.textOutput = textOutput;
|
||||||
|
}
|
||||||
|
|
||||||
|
start() {
|
||||||
|
this.inputElement.addEventListener('input', (event) => {
|
||||||
|
const text = event.target.value;
|
||||||
|
this.onTextChange(text);
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
onTextChange(newText) {
|
||||||
|
const output = newText.replace(AsciiScanner.ASCII_REGEX, (match) => {
|
||||||
|
return `<span class='invalid'>${match}</span>`;
|
||||||
|
});
|
||||||
|
this.textOutput.innerHTML = output;
|
||||||
|
|
||||||
|
const errors = ((newText || '').match(AsciiScanner.ASCII_REGEX) || []).length;
|
||||||
|
this.matchesOutput.innerHTML = `Found: ${errors} non-ASCII blocks`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
window.onload = function() {
|
||||||
|
const scanner = new AsciiScanner(
|
||||||
|
document.getElementById('text-input'),
|
||||||
|
document.getElementById('matches-output'),
|
||||||
|
document.getElementById('text-output'));
|
||||||
|
|
||||||
|
scanner.start();
|
||||||
|
}
|
2
requirements.txt
Normal file
2
requirements.txt
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
telnetlib3==1.0.4
|
||||||
|
chevron==0.14.0
|
10
tyrel/index.json
Normal file
10
tyrel/index.json
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
{
|
||||||
|
"hello": "intro.txt",
|
||||||
|
"contents": [
|
||||||
|
{
|
||||||
|
"title": "Issue 1 - Intro",
|
||||||
|
"author": "Tyrel Souza",
|
||||||
|
"directory": "iss001"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
23
tyrel/intro.txt
Normal file
23
tyrel/intro.txt
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Welcome to Tyrel's Zine.
|
||||||
|
Will keep updated at a very infrequent rate.
|
||||||
|
|
||||||
|
|
||||||
|
- Tyrel Souza
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
24
tyrel/iss001/1.txt
Normal file
24
tyrel/iss001/1.txt
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
o--------------------------------------o
|
||||||
|
| |
|
||||||
|
| |
|
||||||
|
| |
|
||||||
|
| ___ ___ ___ |
|
||||||
|
| /\ \ /\ \ /\ \ |
|
||||||
|
| \:\ \ /::\ \ /::\ \ |
|
||||||
|
| /::\__\ /::\:\__\ /\:\:\__\ |
|
||||||
|
| /:/\/__/ \/\::/ / \:\:\/__/ |
|
||||||
|
| \/__/ /:/ / \::/ / |
|
||||||
|
| \/__/ \/__/ |
|
||||||
|
this page intentionally left blank | |
|
||||||
|
| tyrel |
|
||||||
|
| anthony |
|
||||||
|
| souza |
|
||||||
|
| |
|
||||||
|
| |
|
||||||
|
| |
|
||||||
|
| |
|
||||||
|
| |
|
||||||
|
| |
|
||||||
|
| |
|
||||||
|
| 1 |
|
||||||
|
2023-07-29 o--------------------------------------o
|
24
tyrel/iss001/2.txt
Normal file
24
tyrel/iss001/2.txt
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
o--------------------------------------o--------------------------------------o
|
||||||
|
| \ |
|
||||||
|
| Welcome to my zine / Wow so apparently I have a telnet |
|
||||||
|
| ------------------ \ zine now. |
|
||||||
|
| / |
|
||||||
|
| Everything in all its ASCII \ Not sure what to put here yet! |
|
||||||
|
| glory. / |
|
||||||
|
| \ You can find me --\ |
|
||||||
|
| / | |
|
||||||
|
| \ | |
|
||||||
|
| / | |
|
||||||
|
| \ https://tyrel.dev/links </ |
|
||||||
|
| / @tyrel:tyrel.dev |
|
||||||
|
| \ email@tyrel.dev |
|
||||||
|
| / @tyrel@tyrel.social |
|
||||||
|
| \ |
|
||||||
|
| / |
|
||||||
|
| \ |
|
||||||
|
| / |
|
||||||
|
| \ |
|
||||||
|
| / |
|
||||||
|
| \ |
|
||||||
|
| 2 / 3 |
|
||||||
|
o--------------------------------------o--------------------------------------o
|
Loading…
Reference in New Issue
Block a user