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