Get home page and login working #14
|
@ -1,99 +0,0 @@
|
||||||
# Standard library imports
|
|
||||||
import argparse
|
|
||||||
import logging
|
|
||||||
import os
|
|
||||||
import sys
|
|
||||||
|
|
||||||
# Module imports
|
|
||||||
from amanuensis.cli import describe_commands, get_commands
|
|
||||||
from amanuensis.config import (
|
|
||||||
RootConfigDirectoryContext,
|
|
||||||
ENV_CONFIG_DIR,
|
|
||||||
ENV_LOG_FILE)
|
|
||||||
from amanuensis.errors import AmanuensisError
|
|
||||||
from amanuensis.log import init_logging
|
|
||||||
from amanuensis.models import ModelFactory
|
|
||||||
|
|
||||||
|
|
||||||
def process_doc(docstring):
|
|
||||||
return '\n'.join([
|
|
||||||
line.strip()
|
|
||||||
for line in (docstring or "").strip().splitlines()
|
|
||||||
])
|
|
||||||
|
|
||||||
|
|
||||||
def get_parser(valid_commands):
|
|
||||||
# Set up the top-level parser.
|
|
||||||
parser = argparse.ArgumentParser(
|
|
||||||
description=describe_commands(),
|
|
||||||
formatter_class=argparse.RawDescriptionHelpFormatter)
|
|
||||||
# The config directory.
|
|
||||||
parser.add_argument("--config-dir",
|
|
||||||
dest="config_dir",
|
|
||||||
default=os.environ.get(ENV_CONFIG_DIR, "./config"),
|
|
||||||
help="The config directory for Amanuensis")
|
|
||||||
# Logging settings.
|
|
||||||
parser.add_argument("--verbose", "-v",
|
|
||||||
action="store_true",
|
|
||||||
dest="verbose",
|
|
||||||
help="Enable verbose console logging")
|
|
||||||
parser.add_argument("--log-file",
|
|
||||||
dest="log_file",
|
|
||||||
default=os.environ.get(ENV_LOG_FILE),
|
|
||||||
help="Enable verbose file logging")
|
|
||||||
parser.set_defaults(func=lambda args: parser.print_help())
|
|
||||||
subp = parser.add_subparsers(
|
|
||||||
metavar="COMMAND",
|
|
||||||
dest="command",
|
|
||||||
help="The command to execute")
|
|
||||||
|
|
||||||
# Set up command subparsers.
|
|
||||||
# command_ functions perform setup or execution depending on
|
|
||||||
# whether their argument is an ArgumentParser.
|
|
||||||
for name, func in valid_commands.items():
|
|
||||||
# Create the subparser, set the docstring as the description.
|
|
||||||
cmd = subp.add_parser(name,
|
|
||||||
description=process_doc(func.__doc__),
|
|
||||||
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
||||||
aliases=func.__dict__.get("aliases", []))
|
|
||||||
# Delegate subparser setup to the command.
|
|
||||||
func(cmd)
|
|
||||||
# Store function for later execution.
|
|
||||||
cmd.set_defaults(func=func)
|
|
||||||
|
|
||||||
return parser
|
|
||||||
|
|
||||||
|
|
||||||
def main(argv):
|
|
||||||
# Enumerate valid commands from the CLI module.
|
|
||||||
commands = get_commands()
|
|
||||||
|
|
||||||
# Parse args
|
|
||||||
args = get_parser(commands).parse_args(argv)
|
|
||||||
|
|
||||||
# First things first, initialize logging
|
|
||||||
init_logging(args.verbose, args.log_file)
|
|
||||||
logger = logging.getLogger('amanuensis')
|
|
||||||
|
|
||||||
# The init command initializes a config directory at --config-dir.
|
|
||||||
# All other commands assume that the config dir already exists.
|
|
||||||
if args.command and args.command != "init":
|
|
||||||
args.root = RootConfigDirectoryContext(args.config_dir)
|
|
||||||
args.model_factory = ModelFactory(args.root)
|
|
||||||
|
|
||||||
# If verbose logging, dump args namespace
|
|
||||||
if args.verbose:
|
|
||||||
logger.debug('amanuensis')
|
|
||||||
for key, val in vars(args).items():
|
|
||||||
logger.debug(f' {key}: {val}')
|
|
||||||
|
|
||||||
# Execute command.
|
|
||||||
try:
|
|
||||||
args.func(args)
|
|
||||||
except AmanuensisError as e:
|
|
||||||
logger.error('Unexpected internal {}: {}'.format(
|
|
||||||
type(e).__name__, str(e)))
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
sys.exit(main(sys.argv[1:]))
|
|
|
@ -1,104 +0,0 @@
|
||||||
"""
|
|
||||||
Submodule of functions for creating and managing lexicons within the
|
|
||||||
general Amanuensis context.
|
|
||||||
"""
|
|
||||||
import json
|
|
||||||
import logging
|
|
||||||
import os
|
|
||||||
import re
|
|
||||||
import time
|
|
||||||
from typing import Iterable
|
|
||||||
import uuid
|
|
||||||
|
|
||||||
from amanuensis.config import RootConfigDirectoryContext, AttrOrderedDict
|
|
||||||
from amanuensis.errors import ArgumentError
|
|
||||||
from amanuensis.models import ModelFactory, UserModel, LexiconModel
|
|
||||||
from amanuensis.resources import get_stream
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
|
|
||||||
def valid_name(name: str) -> bool:
|
|
||||||
"""
|
|
||||||
Validates that a lexicon name consists only of alpahnumerics, dashes,
|
|
||||||
underscores, and spaces
|
|
||||||
"""
|
|
||||||
return re.match(r'^[A-Za-z0-9-_ ]+$', name) is not None
|
|
||||||
|
|
||||||
|
|
||||||
def create_lexicon(
|
|
||||||
root: RootConfigDirectoryContext,
|
|
||||||
name: str,
|
|
||||||
editor: UserModel) -> LexiconModel:
|
|
||||||
"""
|
|
||||||
Creates a lexicon with the given name and sets the given user as its editor
|
|
||||||
"""
|
|
||||||
# Verify arguments
|
|
||||||
if not name:
|
|
||||||
raise ArgumentError(f'Empty lexicon name: "{name}"')
|
|
||||||
if not valid_name(name):
|
|
||||||
raise ArgumentError(f'Invalid lexicon name: "{name}"')
|
|
||||||
with root.lexicon.read_index() as extant_lexicons:
|
|
||||||
if name in extant_lexicons.keys():
|
|
||||||
raise ArgumentError(f'Lexicon name already taken: "{name}"')
|
|
||||||
if editor is None:
|
|
||||||
raise ArgumentError('Editor must not be None')
|
|
||||||
|
|
||||||
# Create the lexicon directory and initialize it with a blank lexicon
|
|
||||||
lid: str = uuid.uuid4().hex
|
|
||||||
lex_dir = os.path.join(root.lexicon.path, lid)
|
|
||||||
os.mkdir(lex_dir)
|
|
||||||
with get_stream("lexicon.json") as s:
|
|
||||||
path: str = os.path.join(lex_dir, 'config.json')
|
|
||||||
with open(path, 'wb') as f:
|
|
||||||
f.write(s.read())
|
|
||||||
|
|
||||||
# Create subdirectories
|
|
||||||
os.mkdir(os.path.join(lex_dir, 'draft'))
|
|
||||||
os.mkdir(os.path.join(lex_dir, 'src'))
|
|
||||||
os.mkdir(os.path.join(lex_dir, 'article'))
|
|
||||||
|
|
||||||
# Update the index with the new lexicon
|
|
||||||
with root.lexicon.edit_index() as index:
|
|
||||||
index[name] = lid
|
|
||||||
|
|
||||||
# Fill out the new lexicon
|
|
||||||
with root.lexicon[lid].edit_config() as cfg:
|
|
||||||
cfg.lid = lid
|
|
||||||
cfg.name = name
|
|
||||||
cfg.editor = editor.uid
|
|
||||||
cfg.time.created = int(time.time())
|
|
||||||
|
|
||||||
with root.lexicon[lid].edit('info', create=True):
|
|
||||||
pass # Create an empry config file
|
|
||||||
|
|
||||||
# Load the lexicon and add the editor and default character
|
|
||||||
model_factory: ModelFactory = ModelFactory(root)
|
|
||||||
lexicon = model_factory.lexicon(lid)
|
|
||||||
with lexicon.ctx.edit_config() as cfg:
|
|
||||||
cfg.join.joined.append(editor.uid)
|
|
||||||
with get_stream('character.json') as template:
|
|
||||||
character = json.load(template, object_pairs_hook=AttrOrderedDict)
|
|
||||||
character.cid = 'default'
|
|
||||||
character.name = 'Ersatz Scrivener'
|
|
||||||
character.player = None
|
|
||||||
cfg.character.new(character.cid, character)
|
|
||||||
|
|
||||||
# Log the creation
|
|
||||||
message = f'Created {lexicon.title}, ed. {editor.cfg.displayname} ({lid})'
|
|
||||||
lexicon.log(message)
|
|
||||||
logger.info(message)
|
|
||||||
|
|
||||||
return lexicon
|
|
||||||
|
|
||||||
|
|
||||||
def load_all_lexicons(
|
|
||||||
root: RootConfigDirectoryContext) -> Iterable[LexiconModel]:
|
|
||||||
"""
|
|
||||||
Iterably loads every lexicon in the config store
|
|
||||||
"""
|
|
||||||
model_factory: ModelFactory = ModelFactory(root)
|
|
||||||
with root.lexicon.read_index() as index:
|
|
||||||
for lid in index.values():
|
|
||||||
lexicon: LexiconModel = model_factory.lexicon(lid)
|
|
||||||
yield lexicon
|
|
|
@ -2,5 +2,5 @@ import pkg_resources
|
||||||
|
|
||||||
|
|
||||||
def get_stream(*path):
|
def get_stream(*path):
|
||||||
rs_path = "/".join(path)
|
rs_path = "/".join(path)
|
||||||
return pkg_resources.resource_stream(__name__, rs_path)
|
return pkg_resources.resource_stream(__name__, rs_path)
|
||||||
|
|
|
@ -1,13 +0,0 @@
|
||||||
{
|
|
||||||
"version": "0",
|
|
||||||
"aid": null,
|
|
||||||
"lexicon": null,
|
|
||||||
"character": null,
|
|
||||||
"title": null,
|
|
||||||
"turn": null,
|
|
||||||
"status": {
|
|
||||||
"ready": false,
|
|
||||||
"approved": false
|
|
||||||
},
|
|
||||||
"contents": null
|
|
||||||
}
|
|
|
@ -1,7 +0,0 @@
|
||||||
{
|
|
||||||
"version": "0",
|
|
||||||
"cid": null,
|
|
||||||
"name": null,
|
|
||||||
"player": null,
|
|
||||||
"signature": null
|
|
||||||
}
|
|
|
@ -1,15 +0,0 @@
|
||||||
{
|
|
||||||
"version": "0",
|
|
||||||
"secret_key": null,
|
|
||||||
"address": "127.0.0.1",
|
|
||||||
"port": "5000",
|
|
||||||
"lexicon_data": "./lexicon",
|
|
||||||
"static_root": "../resources",
|
|
||||||
"email": {
|
|
||||||
"server": null,
|
|
||||||
"port": null,
|
|
||||||
"tls": null,
|
|
||||||
"username": null,
|
|
||||||
"password": null
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,107 +0,0 @@
|
||||||
{
|
|
||||||
"version": "0",
|
|
||||||
"lid": null,
|
|
||||||
"name": null,
|
|
||||||
"title": null,
|
|
||||||
"editor": null,
|
|
||||||
"prompt": null,
|
|
||||||
"time": {
|
|
||||||
"created": null,
|
|
||||||
"completed": null
|
|
||||||
},
|
|
||||||
"turn": {
|
|
||||||
"current": null,
|
|
||||||
"max": 8,
|
|
||||||
"assignment": {
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"join": {
|
|
||||||
"public": false,
|
|
||||||
"open": false,
|
|
||||||
"password": null,
|
|
||||||
"max_players": 4,
|
|
||||||
"chars_per_player": 1,
|
|
||||||
"joined": []
|
|
||||||
},
|
|
||||||
"publish": {
|
|
||||||
"notify": {
|
|
||||||
"editor_on_ready": true,
|
|
||||||
"player_on_reject": true,
|
|
||||||
"player_on_accept": false
|
|
||||||
},
|
|
||||||
"deadlines": null,
|
|
||||||
"asap": false,
|
|
||||||
"quorum": null,
|
|
||||||
"block_on_ready": true
|
|
||||||
},
|
|
||||||
"article": {
|
|
||||||
"index": {
|
|
||||||
"list": [
|
|
||||||
{
|
|
||||||
"type": "char",
|
|
||||||
"pri": 0,
|
|
||||||
"pattern": "ABC"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "char",
|
|
||||||
"pri": 0,
|
|
||||||
"pattern": "DEF"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "char",
|
|
||||||
"pri": 0,
|
|
||||||
"pattern": "GHI"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "char",
|
|
||||||
"pri": 0,
|
|
||||||
"pattern": "JKL"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "char",
|
|
||||||
"pri": 0,
|
|
||||||
"pattern": "MNO"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "char",
|
|
||||||
"pri": 0,
|
|
||||||
"pattern": "PQRS"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "char",
|
|
||||||
"pri": 0,
|
|
||||||
"pattern": "TUV"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "char",
|
|
||||||
"pri": 0,
|
|
||||||
"pattern": "WXYZ"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"capacity": null
|
|
||||||
},
|
|
||||||
"citation": {
|
|
||||||
"allow_self": false,
|
|
||||||
"min_extant": null,
|
|
||||||
"max_extant": null,
|
|
||||||
"min_phantom": null,
|
|
||||||
"max_phantom": null,
|
|
||||||
"min_total": null,
|
|
||||||
"max_total": null,
|
|
||||||
"min_chars": null,
|
|
||||||
"max_chars": null
|
|
||||||
},
|
|
||||||
"word_limit": {
|
|
||||||
"soft": null,
|
|
||||||
"hard": null
|
|
||||||
},
|
|
||||||
"addendum": {
|
|
||||||
"allowed": false,
|
|
||||||
"max": null
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"character": {
|
|
||||||
},
|
|
||||||
"log": [
|
|
||||||
]
|
|
||||||
}
|
|
|
@ -1,13 +0,0 @@
|
||||||
{
|
|
||||||
"version": "0",
|
|
||||||
"uid": null,
|
|
||||||
"username": null,
|
|
||||||
"displayname": null,
|
|
||||||
"email": null,
|
|
||||||
"password": null,
|
|
||||||
"created": null,
|
|
||||||
"last_login": null,
|
|
||||||
"last_activity": null,
|
|
||||||
"new_password_required": true,
|
|
||||||
"is_admin": false
|
|
||||||
}
|
|
|
@ -10,7 +10,7 @@ bp = Blueprint("home", __name__, url_prefix="/home", template_folder=".")
|
||||||
|
|
||||||
@bp.get("/")
|
@bp.get("/")
|
||||||
def home():
|
def home():
|
||||||
return render_template('home.root.jinja')
|
return render_template("home.root.jinja")
|
||||||
|
|
||||||
|
|
||||||
@bp.get("/admin/")
|
@bp.get("/admin/")
|
||||||
|
|
|
@ -2,16 +2,16 @@ from flask_wtf import FlaskForm
|
||||||
from wtforms import StringField, SubmitField, TextAreaField
|
from wtforms import StringField, SubmitField, TextAreaField
|
||||||
from wtforms.validators import DataRequired
|
from wtforms.validators import DataRequired
|
||||||
|
|
||||||
from amanuensis.server.forms import User, Lexicon
|
# from amanuensis.server.forms import User, Lexicon
|
||||||
|
|
||||||
|
|
||||||
class LexiconCreateForm(FlaskForm):
|
# class LexiconCreateForm(FlaskForm):
|
||||||
"""/admin/create/"""
|
# """/admin/create/"""
|
||||||
lexiconName = StringField(
|
# lexiconName = StringField(
|
||||||
'Lexicon name',
|
# 'Lexicon name',
|
||||||
validators=[DataRequired(), Lexicon(should_exist=False)])
|
# validators=[DataRequired(), Lexicon(should_exist=False)])
|
||||||
editorName = StringField(
|
# editorName = StringField(
|
||||||
'Username of editor',
|
# 'Username of editor',
|
||||||
validators=[DataRequired(), User(should_exist=True)])
|
# validators=[DataRequired(), User(should_exist=True)])
|
||||||
promptText = TextAreaField('Prompt')
|
# promptText = TextAreaField('Prompt')
|
||||||
submit = SubmitField('Create')
|
# submit = SubmitField('Create')
|
||||||
|
|
2
mypy.ini
2
mypy.ini
|
@ -1,4 +1,4 @@
|
||||||
[mypy]
|
[mypy]
|
||||||
ignore_missing_imports = true
|
ignore_missing_imports = true
|
||||||
exclude = "|amanuensis/lexicon/.*|amanuensis/models/.*|amanuensis/resources/.*|amanuensis/server/.*|amanuensis/user/.*|amanuensis/__main__.py|"
|
exclude = "|amanuensis/lexicon/.*|amanuensis/server/.*|amanuensis/server/lexicon/.*|amanuensis/server/session/.*|"
|
||||||
; mypy stable doesn't support pyproject.toml yet
|
; mypy stable doesn't support pyproject.toml yet
|
|
@ -22,11 +22,11 @@ amanuensis-cli = "amanuensis.cli:main"
|
||||||
amanuensis-server = "amanuensis.server:run"
|
amanuensis-server = "amanuensis.server:run"
|
||||||
|
|
||||||
[tool.black]
|
[tool.black]
|
||||||
extend-exclude = "^/amanuensis/lexicon/.*|^/amanuensis/models/.*|^/amanuensis/resources/.*|^/amanuensis/server/.*|^/amanuensis/user/.*|^/amanuensis/__main__.py"
|
extend-exclude = "^/amanuensis/lexicon/.*|^/amanuensis/server/[^/]*py|^/amanuensis/server/lexicon/.*|^/amanuensis/server/session/.*|"
|
||||||
|
|
||||||
[tool.mypy]
|
[tool.mypy]
|
||||||
ignore_missing_imports = true
|
ignore_missing_imports = true
|
||||||
exclude = "amanuensis/cli/.*|amanuensis/config/.*|amanuensis/lexicon/.*|amanuensis/log/.*|amanuensis/models/.*|amanuensis/resources/.*|amanuensis/server/.*|amanuensis/user/.*|amanuensis/__main__.py"
|
exclude = "|amanuensis/lexicon/.*|amanuensis/server/.*|amanuensis/server/lexicon/.*|amanuensis/server/session/.*|"
|
||||||
|
|
||||||
[tool.pytest.ini_options]
|
[tool.pytest.ini_options]
|
||||||
addopts = "--show-capture=log"
|
addopts = "--show-capture=log"
|
||||||
|
|
Loading…
Reference in New Issue