diff --git a/docs/LICENSE b/docs/LICENSE new file mode 100644 index 0000000000..29f8f5aca2 --- /dev/null +++ b/docs/LICENSE @@ -0,0 +1,29 @@ +BSD 3-Clause License + +Copyright (c) 2019, evennia +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/docs/Makefile b/docs/Makefile new file mode 100644 index 0000000000..219e56c1c7 --- /dev/null +++ b/docs/Makefile @@ -0,0 +1,120 @@ +# Makefile to control Evennia documentation building. +# Most common commands are `make help`, `make quick` and `make local`. + +# You can set these variables from the command line, and also +# from the environment for the first two. +SPHINXOPTS ?= +SPHINXBUILD ?= sphinx-build +SPHINXMULTIVERSION ?= sphinx-multiversion +SPHINXAPIDOC ?= sphinx-apidoc +SPHINXAPIDOCOPTS = --tocfile evennia-api --module-first --force +SPHINXAPIDOCENV = members,undoc-members,show-inheritance +SPHINXAPIDOCEXCLUDE = */migrations/* +SOURCEDIR = source +BUILDDIR = build +AUTODOCDIR = $(SOURCEDIR)/api + +EVDIR ?= $(realpath ../evennia) +EVGAMEDIR ?= $(realpath ../../gamedir) + +cblue = $(shell echo "\033[1m\033[34m") +cnorm = $(shell echo "\033[0m") + +QUICKFILES=$(SOURCEDIR)/*.md + + +# Put it first so that "make" without argument is like "make help". +help: + @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) + @echo "Evennia-specific: " + @echo " $(cblue)install$(cnorm) to get build requirements" + @echo " $(cblue)clean$(cnorm) to remove remnants of a previous build" + @echo " $(cblue)local$(cnorm) to build local html docs of the current branch (no multiversion)." + @echo " $(cblue)mv-local$(cnorm) to build multiversion html docs, without deploying (req. local git commit)" + @echo " $(cblue)release$(cnorm) to build and deploy multiversion docs online (req. commit and github push access)" + + +.PHONY: help Makefile + +# Catch-all target: route all unknown targets to Sphinx using the new +# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). +%: Makefile + @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) + +# Evennia - custom commands + +# helper targets + +_check-env: + @EVDIR=$(EVDIR) EVGAMEDIR=$(EVGAMEDIR) bash -e checkenv.sh + +_multiversion-check-env: + @EVDIR=$(EVDIR) EVGAMEDIR=$(EVGAMEDIR) bash -e checkenv.sh multiversion + +_clean_api_index: + rm source/api/* + +_autodoc-index: + @EVDIR=$(EVDIR) EVGAMEDIR=$(EVGAMEDIR) SPHINX_APIDOC_OPTIONS=$(SPHINXAPIDOCENV) $(SPHINXAPIDOC) $(SPHINXAPIDOCOPTS) -o $(SOURCEDIR)/api/ $(EVDIR) $(SPHINXAPIDOCEXCLUDE) + +_multiversion-autodoc-index: + @EVDIR=$(EVDIR) EVGAMEDIR=$(EVGAMEDIR) SPHINX_APIDOC_OPTIONS=$(SPHINXAPIDOCENV) $(SPHINXAPIDOC) $(SPHINXAPIDOCOPTS) -o $(SOURCEDIR)/api/ $(EVDIR) $(SPHINXAPIDOCEXCLUDE) + git diff-index --quiet HEAD || git commit -a -m "Updated API autodoc index." + +_build: + @EVDIR=$(EVDIR) EVGAMEDIR=$(EVGAMEDIR) $(SPHINXBUILD) $(SPHINXOPTS) "$(SOURCEDIR)" "$(BUILDDIR)/html" + +_quick-build: + @NOAUTODOC=1 EVDIR=$(EVDIR) EVGAMEDIR=$(EVGAMEDIR) $(SPHINXBUILD) $(SPHINXOPTS) "$(SOURCEDIR)" "$(BUILDDIR)/html" $(QUICKFILES) + +_multiversion-build: + @EVDIR=$(EVDIR) EVGAMEDIR=$(EVGAMEDIR) $(SPHINXMULTIVERSION) $(SPHINXOPTS) "$(SOURCEDIR)" "$(BUILDDIR)/html" $(SPHINXOPTS) + +_multiversion-deploy: + @bash -e deploy.sh + +# main targets + +install: + @pip install -r requirements.txt + +clean: + @rm -Rf $(BUILDDIR) + @git clean -f -d docs/ + @echo "Cleaned old build dir and leftover files." + +# TODO remove once done with migration +copy: + @cd pylib && python copy_from_wiki.py && cd .. + make quick + +quick: + make _check-env + make _quick-build $(FILES) + @echo "" + @echo "Documentation built (no autodocs). \nTo see result, open evennia/docs/build/html/index.html in a browser." + +local: + make _check-env + make clean + make _autodoc-index + make _build + @echo "" + @echo "Documentation built. \nTo see result, open evennia/docs/build/html/index.html in a browser." + +mv-local: + make _multiversion-check-env + make clean + make _multiversion-autodoc-index + make _multiversion-build + @echo "Documentation built. \nTo see result, open evennia/docs/build/html/versions//index.html in a browser." + +deploy: + make _multiversion-deploy + +# build and prepare the docs for release +release: + make mv-local + # make _mv-deploy + @echo "" + @echo "Deployment complete." diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 0000000000..4e9676cfaf --- /dev/null +++ b/docs/README.md @@ -0,0 +1,247 @@ +# evennia-docs +Documentation for the Evennia MUD creation system. + +> WARNING: This system is still WIP and many things are bound to change! +> Contributing is still primarily to be done in the wiki. + +The live documentation is (will in the future be) available at `https://evennia.github.io/evennia/`. + +# Editing the docs + +The documentation source files are `*.md` (Markdown) files found in `evennia/docs/source/`. +Markdown files are simple text files that can be edited with a normal text editor. They use +the [Markdown][commonmark] syntax. + +Don't edit the files in `source/api/`. These are auto-generated and your changes +will be lost. + +See also later in this doc for [Help with editing syntax](#Help-with-editing-syntax). + +## Contributing + +Contributing to the docs is is like [contributing to the rest of Evennia][contributing]: +Check out the branch of Evennia you want to edit the documentation for. Create your +own work-branch, make your changes and make a PR for it! + +# Building the docs + +The sources in `evennia/docs/source/` are built into a pretty documentation using +the [Sphinx][sphinx] static generator system. To do so locally you need to either +use a system with `make` (Linux/Unix/Mac/Windows-WSL) or run sphinx-commands manually +(read the `Makefile` to see which commands are run by `make`). + +You don't necessarily _have_ to build the docs locally to contribute. But +building them allows you to check for yourself that syntax is correct and that +your change comes out looking as you expected. + +## Building only the main documentation + +If you only want to build the main documentation pages (not the API autodocs), +you don't need to install Evennia itself, only the documentation resources. +All is done in your terminal/console. + +- (Optional, but recommended): Activate a virtualenv with Python 3.7. +- `cd` to into the `evennia/docs` folder (where this README is). +- Install the documentation-build requirements: + + ``` + make install + or + pip install -r requirements.txt + ``` + +- Next, build the html-based documentation. + + ``` + make quick + ``` + +- The html-based documentation will appear in the new + folder `evennia/docs/build/html/`. Note any errors from files you have edited. +- Use a web browser to open `evennia/docs/build/html/index.html` and view the docs. + Note that you will get errors if clicking a link to the auto-docs, because you didn't build them! + +## Building the main documentation and API docs + +The full documentation includes both the doc pages and the API documentation +generated from the Evennia source. For this you must install Evennia and +initialize a new game with a default database (you don't need to have it +running) + +- Follow the normal [Evennia Getting-Started instructions][getting-started] + to install Evennia. Use a virtualenv. +- Make sure you `cd` to the folder _containing_ your `evennia/` repo (so two levels up from `docs/`). +- Create a new game folder called `gamedir` at the same level as your `evennia` +repo with + + ``` + evennia --init gamedir + ``` + +- Then `cd` into it and create a new, empty database. You don't need to start the game + or do any further changes. + + ``` + evennia migrate + ``` + +- This is how the structure should look at this point: + + ``` + (top) + | + ----- evennia/ (the top-level folder, containing docs/) + | + ----- gamedir/ + ``` + +- Make sure you are still in your virtualenv, then go to `evennia/docs/` and + install the doc-building requirements: + + ``` + make install + or + pip install -r requirements.txt + ``` + +- Finally, build the full documentation, including the auto-docs: + + ``` + make local + ``` + +- The rendered files will appear in a new folder `evennia/docs/build/html`. + Note any errors from files you have edited. +- Point your web browser to `evennia/docs/build/html/index.html` to view the full docs. + +### Building with another gamedir + +If you for some reason want to use another location of your `gamedir/`, or want it +named something else (maybe you already use the name 'gamedir' for your development ...), +you can do so by setting the `EVGAMEDIR` environment variable to the absolute path +of your alternative game dir. For example: + + ``` + EVGAMEDIR=/my/path/to/mygamedir make local + ``` + +## Building for release + +The full Evennia documentation also tracks documentation from older Evennia +versions. This is done by pulling documentation from Evennia's old release +branches and building them all so readers can choose which one to view. Only +specific official Evennia branches will be built, so you can't use this to +build your own testing branch. + +- All local changes must have been committed to git first, since the versioned +docs are built by looking at the git tree. + +- To build for local checking, run (`mv` stands for "multi-version"): + + ``` + make mv-local + ``` + +- The different versions will be found under `evennia/docs/build/versions/`. +- If you have git-push access to the Evennia `gh-pages` branch on `github`, you +can now deploy. + + ``` + make deploy + ``` + +- If you know what you are doing you can also do build + deploy in one step: + + ``` + make release + ``` + +- After deployment finishes, the updated live documentation will be +available at `https://evennia.github.io/evennia/`. + +# Help with editing syntax + +> This needs expanding in the future. + +## Referring to a heading in the same file + +You can self-reference by pointing to a header/label elsewhere in the +same document by using `#` and replacing any spaces in the name with `-`. + +``` +This is a [link to the heading](#My-Heading-Name). + +# My Heading Name + +``` + +## Referring to titles in another file + +> WIP: Most of these special structures need more work and checking. + +If file1 looks like this: + +``` +# Header title + +``` + +You can refer to it from another file as + +``` +Read more about it [here](path.to.file1.md:Header title) + + +``` +> This is not actually working at this time (WIP) + +To refer to code in the Evennia repository, you can use a relative reference from the docs/ folder: + +``` +You can find this code [here](../evennia/objects/objects.py). + +``` +This will be automatically translated to the matching github link so the reader can click and jump to that code directly. +> This is not currently working. (WIP) + + +## Making toc-tree indices + +To make a Table-of-Contents listing (what Sphinx refers to as a "Toc Tree"), one +must make new heading named either `Contents` or `Index`, followed by a bullet-list of +links: + +``` +# Index + +- [Title1](doc1) +- [Title2](doc2) + +``` + +This will create a toc-tree structure behind the scenes. + + + +We may expand on this later. For now, check out existing docs and refer to the +[Markdown][commonmark] (CommonMark) specification. + +# Technical + +Evennia leverages [Sphinx][sphinx] with the [recommonmark][recommonmark] extension, which allows us to write our +docs in light-weight Markdown (more specifically [CommonMark][commonmark], like on github) rather than ReST. +The recommonmark extension however also allows us to use ReST selectively in the places were it is more +expressive than the simpler (but much easier) Markdown. + +For [autodoc-generation][sphinx-autodoc] generation, we use the sphinx-[napoleon][sphinx-napoleon] extension +to understand our friendly Google-style docstrings used in classes and functions etc. + + +[sphinx]: https://www.sphinx-doc.org/en/master/ +[recommonmark]: https://recommonmark.readthedocs.io/en/latest/index.html +[commonmark]: https://spec.commonmark.org/current/ +[sphinx-autodoc]: http://www.sphinx-doc.org/en/master/usage/extensions/autodoc.html#module-sphinx.ext.autodoc +[sphinx-napoleon]: http://www.sphinx-doc.org/en/master/usage/extensions/napoleon.html +[getting-started]: https://github.com/evennia/evennia/wiki/Getting-Started +[contributing]: https://github.com/evennia/evennia/wiki/Contributing + diff --git a/docs/checkenv.sh b/docs/checkenv.sh new file mode 100644 index 0000000000..1683ca14eb --- /dev/null +++ b/docs/checkenv.sh @@ -0,0 +1,30 @@ + +# check environment + +# common checks + +if [ ! -d "$EVDIR" ]; then + + echo "The evennia dir is not found at $EVDIR."; + exit 1 +fi + +if [ ! -d "$EVGAMEDIR" ]; then + + echo "The gamedir is not found at $EVGAMEDIR"; + exit 1 +fi + +if [ $# -ne 0 ] + + # a multi-version build + + then + + if [ -n "$(git status --untracked-files=no --porcelain)" ]; then + echo "There are uncommitted changes. Make sure to commit everything in your current branch before doing a multiversion build." + exit 1 + fi + +fi + diff --git a/docs/deploy.sh b/docs/deploy.sh new file mode 100644 index 0000000000..05712c8111 --- /dev/null +++ b/docs/deploy.sh @@ -0,0 +1,26 @@ +# +# deploy to github +# +# This copies the recently built files from build/html into the github-gh branch. Note that +# it's important that build/ must not be committed to git! +# + +if [ -n "$(git status --untracked-files=no --porcelain)" ]; then + echo "There are uncommitted changes. Make sure to commit everything in your current branch first." + exit 1 +fi + +git checkout gh-pages + +rm -Rf versions +mv build/html/versions . +git add versions + +git commit -a -m "Updated HTML docs" +git push origin gh-pages + +# get back to previous branch + + git checkout - + +echo "Deployed to https://evennia.github.io/evennia-docs." diff --git a/docs/make.bat b/docs/make.bat new file mode 100644 index 0000000000..6247f7e231 --- /dev/null +++ b/docs/make.bat @@ -0,0 +1,35 @@ +@ECHO OFF + +pushd %~dp0 + +REM Command file for Sphinx documentation + +if "%SPHINXBUILD%" == "" ( + set SPHINXBUILD=sphinx-build +) +set SOURCEDIR=source +set BUILDDIR=build + +if "%1" == "" goto help + +%SPHINXBUILD% >NUL 2>NUL +if errorlevel 9009 ( + echo. + echo.The 'sphinx-build' command was not found. Make sure you have Sphinx + echo.installed, then set the SPHINXBUILD environment variable to point + echo.to the full path of the 'sphinx-build' executable. Alternatively you + echo.may add the Sphinx directory to PATH. + echo. + echo.If you don't have Sphinx installed, grab it from + echo.http://sphinx-doc.org/ + exit /b 1 +) + +%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% +goto end + +:help +%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% + +:end +popd diff --git a/docs/pylib/build_search_index.py b/docs/pylib/build_search_index.py new file mode 100644 index 0000000000..4c9497cf08 --- /dev/null +++ b/docs/pylib/build_search_index.py @@ -0,0 +1,93 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +""" +Builds a lunr static search index for optimized search + +""" +import os +import json +import glob +from argparse import ArgumentParser +from os.path import sep, abspath, dirname, join as joinpath +from lunr import lunr + +_DOCS_PATH = dirname(dirname(abspath(__file__))) + +_DEFAULT_BUILD_DIR = joinpath(_DOCS_PATH, "build", "html") +_DEFAULT_URL_BASE = f"file://{_DEFAULT_BUILD_DIR}" +_INDEX_PATH = joinpath("_static", "js", "lunr", "search_index.json") + +DEFAULT_SOURCE_DIR = joinpath(_DOCS_PATH, "source") +DEFAULT_OUTFILE = joinpath(DEFAULT_SOURCE_DIR, _INDEX_PATH) + +URL_BASE = os.environ.get("SEARCH_URL_BASE", _DEFAULT_URL_BASE) + + +def create_search_index(sourcedir, outfile): + """ + Create the index. + + Args: + sourcedir (str): Path to the source directory. This will be searched + for both .md and .rst files. + outfile (str): Path to the index file to create. + + """ + markdown_files = glob.glob(f"{sourcedir}{sep}*.md") + markdown_files.extend(glob.glob(f"{sourcedir}{sep}*{sep}*.md")) + rest_files = glob.glob(f"{sourcedir}{sep}*.rst") + rest_files.extend(glob.glob(f"{sourcedir}{sep}*{sep}*.rst")) + filepaths = markdown_files + rest_files + + outlist = [] + + print(f"Building Search index from {len(filepaths)} files ... ", end="") + + for filepath in filepaths: + with open(filepath, 'r') as fil: + filename = filepath.rsplit(sep, 1)[1].split(".", 1)[0] + url = f"{URL_BASE}{sep}{filename}.html".strip() + title = filename.replace("-", " ").strip() + body = fil.read() + + data = { + "url": url, + "title": title, + "text": body, + } + outlist.append(data) + + idx = lunr( + ref="url", + documents=outlist, + fields=[ + { + "field_name": "title", + "boost": 10 + }, + { + "field_name": "text", + "boost": 1 + } + ], + ) + + with open(outfile, "w") as fil: + fil.write(json.dumps(idx.serialize())) + + print(f"wrote to source{sep}{_INDEX_PATH}.") + + +if __name__ == "__main__": + + parser = ArgumentParser(description="Build a static search index.") + + parser.add_argument("-i", dest="sourcedir", default=DEFAULT_SOURCE_DIR, + help="Absolute path to the documentation source dir") + parser.add_argument("-o", dest="outfile", default=DEFAULT_OUTFILE, + help="Absolute path to the index file to output.") + + args = parser.parse_args() + + create_search_index(args.sourcedir, args.outfile) diff --git a/docs/pylib/copy_from_wiki.py b/docs/pylib/copy_from_wiki.py new file mode 100644 index 0000000000..34d9b94b0b --- /dev/null +++ b/docs/pylib/copy_from_wiki.py @@ -0,0 +1,254 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +""" +Copy data from old Evennia github Wiki to static files. + +Prepare files for mkdoc. This assumes evennia.wiki is cloned +to a folder at the same level as the evennia repo. + +Just run this to update everything. + +We also need to build the toc-tree and should do so automatically for now. + +""" + +import glob +import re +import datetime + +_RE_MD_LINK = re.compile(r"\[(?P[\w -\[\]]+?)\]\((?P.+?)\)", re.I + re.S + re.U) +_RE_REF_LINK = re.compile(r"\[[\w -\[\]]+?\]\(.+?\)", re.I + re.S + re.U) + +_RE_CLEAN = re.compile(r"\|-+?|-+\|", re.I + re.S + re.U) + +_IGNORE_FILES = ( + "_Sidebar.md", + "Wiki-Index.md" +) + +_INDEX_PREFIX = f""" + + +# VERSION WARNING + +> This is the experimental static v0.9 documentation of Evennia, _automatically_ generated from the +> [evennia wiki](https://github.com/evennia/evennia/wiki/) at {datetime.datetime.now()}. +> There are known conversion issues which will _not_ be addressed in this version - refer to +> the original wiki if you have trouble. +> +> Manual conversion and cleanup will instead happen during development of the upcoming v1.0 +> version of this static documentation. + +""" + +_WIKI_DIR = "../../../evennia.wiki/" +_INFILES = [path for path in sorted(glob.glob(_WIKI_DIR + "/*.md")) + if path.rsplit('/', 1)[-1] not in _IGNORE_FILES] +_FILENAMES = [path.rsplit("/", 1)[-1] for path in _INFILES] +_FILENAMES = [path.split(".", 1)[0] for path in _FILENAMES] +_FILENAMESLOW = [path.lower() for path in _FILENAMES] +_OUTDIR = "../source/" +_OLD_WIKI_URL = "https://github.com/evennia/evennia/wiki/" +_OLD_WIKI_URL_LEN = len(_OLD_WIKI_URL) +_CODE_PREFIX = "github:" +_API_PREFIX = "api:" + +_CUSTOM_LINK_REMAP = { + "CmdSets": "Command-Sets", + "CmdSet": "Command-Sets", + "Cmdsets": "Command-Sets", + "CommandSet": "Command-Sets", + "batch-code-processor": "Batch-Code-Processor", + "Batch-code-processor": "Batch-Code-Processor", + "batch-command-processor": "Batch-Command-Processor", + "Batch-command-processor": "Batch-Command-Processor", + "evennia-API": "Evennia-API", + "Channels": "Communications#Channels", + "Comms": "Communications", + "typeclass": "Typeclasses", + "Home": "index", + "Help-system": "Help-System", + "Using-Mux-as-a-Standard": "Using-MUX-as-a-Standard", + "Building-quickstart": "Building-Quickstart", + "Adding-Object-Typeclass-tutorial": "Adding-Object-Typeclass-Tutorial", + "EvTable": _API_PREFIX + "evennia.utils#module-evennia.utils.evtable", +} +# complete reference remaps +_REF_REMAP = { + "[![Getting Started][icon_new]](Getting-Started)": "![Getting Started][icon_new]", + "[![Admin Docs][icon_admin]](Administrative-Docs)": "![Admin Docs][icon_admin]", + "[![Builder Docs][icon_builder]](Builder-Docs)": "![Builder Docs][icon_builder]", + "[![Developer-Central][icon_devel]](Developer-Central)": "![Developer-Central][icon_devel]", + "[![tutorial][icon_tutorial]](Tutorials)": "![Tutorials][icon_tutorial]", + "[![API][icon_api]](evennia)": "![API][icon_api]", +} + + +# absolute links (mainly github links) that should not be converted. This +# should be given without any #anchor. +_ABSOLUTE_LINK_SKIP = ( + # "https://github.com/evennia/evennia/wiki/feature-request", +) + +# specific references tokens that should be ignored. Should be given +# without any #anchor. +_REF_SKIP = ( + "[5](Win)", "[6](Win)", "[7](Win)", "[10](Win)", "[11](Mac)", "[13](Win)", + "[14](IOS)", "[15](IOS)", "[16](Andr)", "[17](Andr)", "[18](Unix)", + "[21](Chrome)", + # these should be checked + "[EvTable](EvTable)", + "[styled](OptionStyles)", + "[Inputfunc](Inputfunc)", + "[online documentation wiki](index)", + "[online documentation](index)", + "[Accounts](Account)", + "[Session](Session)", + "[Inputfuncs](Inputfunc)", +) + + +_CURRENT_TITLE = "" + + +def _sub_remap(match): + """Total remaps""" + ref = match.group(0) + if ref in _REF_REMAP: + new_ref = _REF_REMAP[ref] + print(f" Replacing reference {ref} -> {new_ref}") + return new_ref + return ref + + +def _sub_link(match): + + mdict = match.groupdict() + txt, url_orig = mdict['txt'], mdict['url'] + url = url_orig + # if not txt: + # # the 'comment' is not supported by Mkdocs + # return "" + print(f" [{txt}]({url})") + + + url = _CUSTOM_LINK_REMAP.get(url, url) + + url, *anchor = url.rsplit("#", 1) + + if url in _ABSOLUTE_LINK_SKIP: + url += (("#" + anchor[0]) if anchor else "") + return f"[{txt}]({url})" + + if url.startswith("evennia"): + print(f" Convert evennia url {url} -> {_CODE_PREFIX + url}") + url = _API_PREFIX + url + + if url.startswith(_OLD_WIKI_URL): + # old wiki is an url on the form https:///wiki/TextTags#header + # we don't refer to the old wiki but use internal mapping. + url_conv = url[_OLD_WIKI_URL_LEN:] + url_conv = re.sub(r"%20", "-", url_conv) + if url_conv.endswith("/_edit"): + # this is actually a bug in the wiki format + url_conv = url_conv[:-6] + if url_conv.startswith("evennia"): + # this is an api link + url_conv = _CODE_PREFIX + url_conv + + print(f" Converting wiki-url: {url} -> {url_conv}") + url = url_conv + + if not url and anchor: + # this happens on same-file #labels in wiki + url = _CURRENT_TITLE + + if (url not in _FILENAMES and + not url.startswith("http") and not url.startswith(_CODE_PREFIX)): + + url_cap = url.capitalize() + url_plur = url[:-3] + 's' + ".md" + url_cap_plur = url_plur.capitalize() + + link = f"[{txt}]({url})" + if link in _REF_SKIP: + url = link + elif url_cap in _FILENAMES: + print(f" Replacing (capitalized): {url.capitalize()}") + url = url_cap + elif url_plur in _FILENAMES: + print(f" Replacing (pluralized): {url + 's'}") + url = url_plur + elif url_cap_plur in _FILENAMES: + print(f" Replacing (capitalized, pluralized): {url.capitalize() + 's'}") + url = url_cap_plur + elif url.lower() in _FILENAMESLOW: + ind = _FILENAMESLOW.index(url.lower()) + alt = _FILENAMES[ind] + print(f" Replacing {url} with different cap: {alt}") + url = alt + + # print(f"\nlink {link} (orig: [{txt}]({url_orig})) found no file match") + # inp = input("Enter alternate url (return to keep old): ") + # if inp.strip(): + # url = inp.strip() + + if anchor: + url += "#" + anchor[0] + + return f"[{txt}]({url})" + +def create_toctree(files): + + with open("../source/toc.md", "w") as fil: + fil.write("# Toc\n") + + for path in files: + filename = path.rsplit("/", 1)[-1] + ref = filename.rsplit(".", 1)[0] + linkname = ref.replace("-", " ") + + if ref == "Home": + ref = "index" + + fil.write(f"\n* [{linkname}]({ref}.md)") + +def convert_links(files, outdir): + global _CURRENT_TITLE + + for inpath in files: + + is_index = False + outfile = inpath.rsplit('/', 1)[-1] + if outfile == "Home.md": + outfile = "index.md" + is_index = True + outfile = _OUTDIR + outfile + + title = inpath.rsplit("/", 1)[-1].split(".", 1)[0].replace("-", " ") + + print(f"Converting links in {inpath} -> {outfile} ...") + with open(inpath) as fil: + text = fil.read() + + if is_index: + text = _INDEX_PREFIX + text + + _CURRENT_TITLE = title.replace(" ", "-") + text = _RE_CLEAN.sub("", text) + text = _RE_REF_LINK.sub(_sub_remap, text) + text = _RE_MD_LINK.sub(_sub_link, text) + text = text.split('\n')[1:] if text.split('\n')[0].strip().startswith('[]') else text.split('\n') + text = "\n".join(text) + + if not is_index: + text = f"# {title}\n\n{text}" + + with open(outfile, 'w') as fil: + fil.write(text) + +if __name__ == "__main__": + + create_toctree(_INFILES) + convert_links(_INFILES, _OUTDIR) diff --git a/docs/requirements.txt b/docs/requirements.txt new file mode 100644 index 0000000000..1d25af7679 --- /dev/null +++ b/docs/requirements.txt @@ -0,0 +1,11 @@ +# requirements for building the docs + +sphinx==2.4.4 +sphinx-multiversion==0.1.1 +lunr==0.5.8 + +# recommonmark custom branch with evennia-specific fixes +git+https://github.com/evennia/recommonmark.git@evennia-mods#egg=recommonmark + +# sphinxcontrib-lunrsearch custom branch with evennia-specific fixes +git+https://github.com/evennia/sphinxcontrib-lunrsearch.git@evennia-mods#egg=sphinxcontrib-lunrsearch diff --git a/docs/search/README.md b/docs/search/README.md new file mode 100644 index 0000000000..1d713230aa --- /dev/null +++ b/docs/search/README.md @@ -0,0 +1,11 @@ +# Search plugin + +This is a search plugin that modifies and combines code from two projects: + +- Sphinxcontrib-lunrsearch (https://github.com/rmcgibbo/sphinxcontrib-lunrsearch) - the lunr-index + generation component was reworked to read more than just the api and addded supoort for + using the `lunr` python library to pre-generate the search index. +- Mkdocs lunr search (https://www.mkdocs.org/) - the javascript components with + web-worker support, using the index generated by the above module. + +Parsing of the documentation to create the index is custom. diff --git a/docs/search/__init__.py b/docs/search/__init__.py new file mode 100644 index 0000000000..0236933e93 --- /dev/null +++ b/docs/search/__init__.py @@ -0,0 +1,188 @@ +""" +Custom Evennia search plugin. This combines code from sphinxplugin-lunr and the +Mkdocs search implementation. + +""" +from os.path import dirname, join, exists +from os import makedirs, getcwd +import json +import sphinx.search +from six import iteritems +from sphinx.util.osutil import copyfile +from sphinx.jinja2glue import SphinxFileSystemLoader + + +# Sphinx setup + +lunr = None +try: + import lunr +except ImportError: + pass + + +def _make_iter(inp): + """make sure input is an iterable""" + if not hasattr(inp, "__iter__"): + return (inp, ) + return inp + + +class IndexBuilder(sphinx.search.IndexBuilder): + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.filetexts = {} + + def _get_filetext(self, filename): + """Helper to get file content from file in main source dir""" + text = self.filetexts.get(filename) + if not text: + with open(join(dirname(dirname(__file__)), "source", filename), 'r') as fil: + text = self.filetexts[filename] = fil.read() + return text + + def freeze(self): + """Create a usable data structure for serializing.""" + data = super(IndexBuilder, self).freeze() + base_file_names = data['docnames'] + + lunrdocuments = [] + for prefix, items in iteritems(data['objects']): + # This parses API objects + for name, (index, typeindex, _, shortanchor) in iteritems(items): + objtype = data['objtypes'][typeindex] + + if objtype.startswith("py:"): + # Python API entitites + last_prefix = prefix.split('.')[-1] + if objtype == "py:method": + displayname = last_prefix + "." + name + else: + displayname = prefix + "." + name + + else: + last_prefix = prefix.split('.')[-1] + displayname = name + + anchor = f"#{shortanchor}" if shortanchor else '' + lunrdocuments.append({ + 'location': base_file_names[index] + anchor, + 'title': displayname, + 'text': prefix + }) + + titles = data['titles'] + filenames = data['filenames'] + + for titleterm, indices in data['titleterms'].items(): + for index in _make_iter(indices): + + title = titles[index] + text = self._get_filetext(filenames[index]) + anchor = "#" + title.replace(" ", "-") + + lunrdocuments.append({ + 'location': "../../" + base_file_names[index] + ".html" + anchor, + 'title': titles[index], + 'text': text + }) + + # this is just too big for regular use + # for term, indices in data['terms'].items(): + # # In-file terms + # for index in _make_iter(indices): + # ref = next(c) + # lunrdocuments[ref] = { + # 'ref': str(ref), + # 'filename': base_file_names[index], + # 'objtype': "", + # 'prefix': term, + # 'last_prefix': '', + # 'name': titles[index], + # 'displayname': titles[index], + # 'shortanchor': '' + # } + + if not lunr: + print("\npython package `lunr==0.5.8` required in order " + "to pre-build search index.") + return data + + print("\nPre-building search index using python-lunr ...") + # pre-compile the data store into a lunr index + fields = ["location", "title", "text"] + lunr_index = lunr.lunr(ref='location', fields=fields, + documents=lunrdocuments) + lunr_index = lunr_index.serialize() + + # required by js file + page_store = { + "config": {"lang": ['en'], + "separator": r'\s\-]+', + "min_search_length": 3, + "prebuild_index": "python"}, + "docs": lunrdocuments, + "index": lunr_index + } + + lunr_index_json = json.dumps(page_store, sort_keys=True, + separators=(',', ':')) + try: + fname = join( + dirname(__file__), "js", "search", "search_index.json") + with open(fname, 'w') as fil: + fil.write(lunr_index_json) + except Exception as err: + print("Failed saving lunr index to", fname, err) + + return data + + +def builder_inited(app): + """ + Adding a new loader to the template system puts our searchbox.html + template in front of the others, it overrides whatever searchbox.html + the current theme is using. + it's still up to the theme to actually _use_ a file called searchbox.html + somewhere in its layout. but the base theme and pretty much everything + else that inherits from it uses this filename. + """ + app.builder.templates.loaders.insert( + 0, SphinxFileSystemLoader(dirname(__file__))) + + +def copy_static_files(app, _): + """ + Because we're using the extension system instead of the theme system, it's + our responsibility to copy over static files outselves. files = + [join('js', 'searchbox.js'), join('css', 'searchbox.css')] + """ + files = [join('js', 'search', 'main.js'), + join('js', 'search', 'worker.js'), + join('js', 'search', 'lunr.js')] + + if lunr: + files.append(join('js', 'search', "search_index.json")) + + for f in files: + src = join(dirname(__file__), f) + dest = join(app.outdir, '_static', f) + if not exists(dirname(dest)): + makedirs(dirname(dest)) + copyfile(src, dest) + + +def setup(app): + # adds +``` + +And you should put it at the bottom of the page. Just before the closing body would be good, but for the time being, the base page doesn't provide a footer block, so we'll put it in the content block. Note that it's not the best place, but it will work. In the end, your `web/chargen/templates/chargen/create.html` file should look like this: + +```html +{% extends "base.html" %} +{% block content %} +

Character Creation

+{% if user.is_authenticated %} +
+ {% csrf_token %} + {{ form }} + +
+{% else %} +

You aren't logged in.

+{% endif %} + +{% endblock %} +``` + +Reload and open [http://localhost:4001/chargen/create](http://localhost:4001/chargen/create/) and you should see your beautiful CAPCHA just before the "submit" button. Try not to check the checkbox to see what happens. And do the same while checking the checkbox! diff --git a/docs/source/Web-Character-View-Tutorial.md b/docs/source/Web-Character-View-Tutorial.md new file mode 100644 index 0000000000..4967c27093 --- /dev/null +++ b/docs/source/Web-Character-View-Tutorial.md @@ -0,0 +1,165 @@ +# Web Character View Tutorial + + +**Before doing this tutorial you will probably want to read the intro in [Basic Web tutorial](Web-Tutorial).** + +In this tutorial we will create a web page that displays the stats of a game character. For this, and all other pages we want to make specific to our game, we'll need to create our own Django "app" + +We'll call our app `character`, since it will be dealing with character information. From your game dir, run + + evennia startapp character + +This will create a directory named `character` in the root of your game dir. It contains all basic files that a Django app needs. To keep `mygame` well ordered, move it to your `mygame/web/` directory instead: + + mv character web/ + +Note that we will not edit all files in this new directory, many of the generated files are outside the scope of this tutorial. + +In order for Django to find our new web app, we'll need to add it to the `INSTALLED_APPS` setting. Evennia's default installed apps are already set, so in `server/conf/settings.py`, we'll just extend them: + +```python +INSTALLED_APPS += ('web.character',) +``` + +> Note: That end comma is important. It makes sure that Python interprets the addition as a tuple instead of a string. + +The first thing we need to do is to create a *view* and an *URL pattern* to point to it. A view is a function that generates the web page that a visitor wants to see, while the URL pattern lets Django know what URL should trigger the view. The pattern may also provide some information of its own as we shall see. + +Here is our `character/urls.py` file (**Note**: you may have to create this file if a blank one wasn't generated for you): + +```python +# URL patterns for the character app + +from django.conf.urls import url +from web.character.views import sheet + +urlpatterns = [ + url(r'^sheet/(?P\d+)/$', sheet, name="sheet") +] +``` + +This file contains all of the URL patterns for the application. The `url` function in the `urlpatterns` list are given three arguments. The first argument is a pattern-string used to identify which URLs are valid. Patterns are specified as *regular expressions*. Regular expressions are used to match strings and are written in a special, very compact, syntax. A detailed description of regular expressions is beyond this tutorial but you can learn more about them [here](https://docs.python.org/2/howto/regex.html). For now, just accept that this regular expression requires that the visitor's URL looks something like this: + +```` +sheet/123/ +```` + +That is, `sheet/` followed by a number, rather than some other possible URL pattern. We will interpret this number as object ID. Thanks to how the regular expression is formulated, the pattern recognizer stores the number in a variable called `object_id`. This will be passed to the view (see below). We add the imported view function (`sheet`) in the second argument. We also add the `name` keyword to identify the URL pattern itself. You should always name your URL patterns, this makes them easy to refer to in html templates using the `{% url %}` tag (but we won't get more into that in this tutorial). + +> Security Note: Normally, users do not have the ability to see object IDs within the game (it's restricted to superusers only). Exposing the game's object IDs to the public like this enables griefers to perform what is known as an [account enumeration attack](http://www.sans.edu/research/security-laboratory/article/attacks-browsing) in the efforts of hijacking your superuser account. Consider this: in every Evennia installation, there are two objects that we can *always* expect to exist and have the same object IDs-- Limbo (#2) and the superuser you create in the beginning (#1). Thus, the griefer can get 50% of the information they need to hijack the admin account (the admin's username) just by navigating to `sheet/1`! + +Next we create `views.py`, the view file that `urls.py` refers to. + +```python +# Views for our character app + +from django.http import Http404 +from django.shortcuts import render +from django.conf import settings + +from evennia.utils.search import object_search +from evennia.utils.utils import inherits_from + +def sheet(request, object_id): + object_id = '#' + object_id + try: + character = object_search(object_id)[0] + except IndexError: + raise Http404("I couldn't find a character with that ID.") + if not inherits_from(character, settings.BASE_CHARACTER_TYPECLASS): + raise Http404("I couldn't find a character with that ID. " + "Found something else instead.") + return render(request, 'character/sheet.html', {'character': character}) +``` + +As explained earlier, the URL pattern parser in `urls.py` parses the URL and passes `object_id` to our view function `sheet`. We do a database search for the object using this number. We also make sure such an object exists and that it is actually a Character. The view function is also handed a `request` object. This gives us information about the request, such as if a logged-in user viewed it - we won't use that information here but it is good to keep in mind. + +On the last line, we call the `render` function. Apart from the `request` object, the `render` function takes a path to an html template and a dictionary with extra data you want to pass into said template. As extra data we pass the Character object we just found. In the template it will be available as the variable "character". + +The html template is created as `templates/character/sheet.html` under your `character` app folder. You may have to manually create both `template` and its subfolder `character`. Here's the template to create: + +````html +{% extends "base.html" %} +{% block content %} + +

{{ character.name }}

+ +

{{ character.db.desc }}

+ +

Stats

+ + + + + + + + + + + + + + + + + + + + + +
StatValue
Strength{{ character.db.str }}
Intelligence{{ character.db.int }}
Speed{{ character.db.spd }}
+ +

Skills

+
    + {% for skill in character.db.skills %} +
  • {{ skill }}
  • + {% empty %} +
  • This character has no skills yet.
  • + {% endfor %} +
+ + {% if character.db.approved %} +

This character has been approved!

+ {% else %} +

This character has not yet been approved!

+ {% endif %} +{% endblock %} +```` + +In Django templates, `{% ... %}` denotes special in-template "functions" that Django understands. The `{{ ... }}` blocks work as "slots". They are replaced with whatever value the code inside the block returns. + +The first line, `{% extends "base.html" %}`, tells Django that this template extends the base template that Evennia is using. The base template is provided by the theme. Evennia comes with the open-source third-party theme `prosimii`. You can find it and its `base.html` in `evennia/web/templates/prosimii`. Like other templates, these can be overwritten. + +The next line is `{% block content %}`. The `base.html` file has `block`s, which are placeholders that templates can extend. The main block, and the one we use, is named `content`. + +We can access the `character` variable anywhere in the template because we passed it in the `render` call at the end of `view.py`. That means we also have access to the Character's `db` attributes, much like you would in normal Python code. You don't have the ability to call functions with arguments in the template-- in fact, if you need to do any complicated logic, you should do it in `view.py` and pass the results as more variables to the template. But you still have a great deal of flexibility in how you display the data. + +We can do a little bit of logic here as well. We use the `{% for %} ... {% endfor %}` and `{% if %} ... {% else %} ... {% endif %}` structures to change how the template renders depending on how many skills the user has, or if the user is approved (assuming your game has an approval system). + +The last file we need to edit is the master URLs file. This is needed in order to smoothly integrate the URLs from your new `character` app with the URLs from Evennia's existing pages. Find the file `web/urls.py` and update its `patterns` list as follows: + +```python +# web/urls.py + +custom_patterns = [ + url(r'^character/', include('web.character.urls')) + ] +``` + +Now reload the server with `evennia reload` and visit the page in your browser. If you haven't changed your defaults, you should be able to find the sheet for character `#1` at `http://localhost:4001/character/sheet/1/` + +Try updating the stats in-game and refresh the page in your browser. The results should show immediately. + +As an optional final step, you can also change your character typeclass to have a method called 'get_absolute_url'. +```python +# typeclasses/characters.py + + # inside Character + def get_absolute_url(self): + from django.urls import reverse + return reverse('character:sheet', kwargs={'object_id':self.id}) +``` +Doing so will give you a 'view on site' button in the top right of the Django Admin Objects changepage that links to your new character sheet, and allow you to get the link to a character's page by using {{ object.get_absolute_url }} in any template where you have a given object. + +*Now that you've made a basic page and app with Django, you may want to read the full Django tutorial to get a better idea of what it can do. [You can find Django's tutorial here](https://docs.djangoproject.com/en/1.8/intro/tutorial01/).* \ No newline at end of file diff --git a/docs/source/Web-Features.md b/docs/source/Web-Features.md new file mode 100644 index 0000000000..bd26d6e611 --- /dev/null +++ b/docs/source/Web-Features.md @@ -0,0 +1,78 @@ +# Web Features + + +Evennia is its own webserver and hosts a default website and browser webclient. + +## Web site + +The Evennia website is a Django application that ties in with the MUD database. Since the website shares this database you could, for example, tell website visitors how many accounts are logged into the game at the moment, how long the server has been up and any other database information you may want. During development you can access the website by pointing your browser to `http://localhost:4001`. + +> You may also want to set `DEBUG = True` in your settings file for debugging the website. You will then see proper tracebacks in the browser rather than just error codes. Note however that this will *leak memory a lot* (it stores everything all the time) and is *not to be used in production*. It's recommended to only use `DEBUG` for active web development and to turn it off otherwise. + +A Django (and thus Evennia) website basically consists of three parts, a [view](https://docs.djangoproject.com/en/1.9/topics/http/views/) an associated [template](https://docs.djangoproject.com/en/1.9/topics/templates/) and an `urls.py` file. Think of the view as the Python back-end and the template as the HTML files you are served, optionally filled with data from the back-end. The urls file is a sort of mapping that tells Django that if a specific URL is given in the browser, a particular view should be triggered. You are wise to review the Django documentation for details on how to use these components. + +Evennia's default website is located in [evennia/web/website](https://github.com/evennia/evennia/tree/master/evennia/web/website). In this folder you'll find the simple default view as well as subfolders `templates` and `static`. Static files are things like images, CSS files and Javascript. + +### Customizing the Website + +You customize your website from your game directory. In the folder `web` you'll find folders `static`, `templates`, `static_overrides` and `templates_overrides`. The first two of those are populated automatically by Django and used to serve the website. You should not edit anything in them - the change will be lost. To customize the website you'll need to copy the file you want to change from the `web/website/template/` or `web/website/static/ path to the corresponding place under one of `_overrides` directories. + +Example: To override or modify `evennia/web/website/template/website/index.html` you need to add/modify `mygame/web/template_overrides/website/index.html`. + +The detailed description on how to customize the website is best described in tutorial form. See the [Web Tutorial](Web-Tutorial) for more information. + +### Overloading Django views + +The Python backend for every HTML page is called a [Django view](https://docs.djangoproject.com/en/1.9/topics/http/views/). A view can do all sorts of functions, but the main one is to update variables data that the page can display, like how your out-of-the-box website will display statistics about number of users and database objects. + +To re-point a given page to a `view.py` of your own, you need to modify `mygame/web/urls.py`. An [URL pattern](https://docs.djangoproject.com/en/1.9/topics/http/urls/) is a [regular expression](https://en.wikipedia.org/wiki/Regular_expression) that you need to enter in the address field of your web browser to get to the page in question. If you put your own URL pattern *before* the default ones, your own view will be used instead. The file `urls.py` even marks where you should put your change. + +Here's an example: + +```python +# mygame/web/urls.py + +from django.conf.urls import url, include +# default patterns +from evennia.web.urls import urlpatterns + +# our own view to use as a replacement +from web.myviews import myview + +# custom patterns to add +patterns = [ + # overload the main page view + url(r'^', myview, name='mycustomview'), +] + +urlpatterns = patterns + urlpatterns + +``` + +Django will always look for a list named `urlpatterns` which consists of the results of `url()` calls. It will use the *first* match it finds in this list. Above, we add a new URL redirect from the root of the website. It will now our own function `myview` from a new module `mygame/web/myviews.py`. + +> If our game is found on `http://mygame.com`, the regular expression `"^"` means we just entered `mygame.com` in the address bar. If we had wanted to add a view for `http://mygame.com/awesome`, the regular expression would have been `^/awesome`. + +Look at [evennia/web/website/views.py](https://github.com/evennia/evennia/blob/master/evennia/web/website/views.py#L82) to see the inputs and outputs you must have to define a view. Easiest may be to copy the default file to `mygame/web` to have something to modify and expand on. + +Restart the server and reload the page in the browser - the website will now use your custom view. If there are errors, consider turning on `settings.DEBUG` to see the full tracebacks - in debug mode you will also log all requests in `mygame/server/logs/http_requests.log`. + +## Web client + + +Evennia comes with a MUD client accessible from a normal web browser. During +development you can try it at `http://localhost:4001/webclient`. +[See the Webclient page](Webclient) for more details. + + +## The Django 'Admin' Page + +Django comes with a built-in [admin website](https://docs.djangoproject.com/en/1.10/ref/contrib/admin/). This is accessible by clicking the 'admin' button from your game website. The admin site allows you to see, edit and create objects in your database from a graphical interface. + +The behavior of default Evennia models are controlled by files `admin.py` in the Evennia package. New database models you choose to add yourself (such as in the Web Character View Tutorial) can/will also have `admin.py` files. New models are registered to the admin website by a call of `admin.site.register(model class, admin class)` inside an admin.py file. It is an error to attempt to register a model that has already been registered. + +To overload Evennia's admin files you don't need to modify Evennia itself. To customize you can call `admin.site.unregister(model class)`, then follow that with `admin.site.register` in one of your own admin.py files in a new app that you add. + +## More reading + +Evennia relies on Django for its web features. For details on expanding your web experience, the [Django documentation](https://docs.djangoproject.com/en) or the [Django Book](http://www.djangobook.com/en/2.0/index.html) are the main resources to look into. In Django lingo, the Evennia is a django "project" that consists of Django "applications". For the sake of web implementation, the relevant django "applications" in default Evennia are `web/website` or `web/webclient`. diff --git a/docs/source/Web-Tutorial.md b/docs/source/Web-Tutorial.md new file mode 100644 index 0000000000..71f63035b7 --- /dev/null +++ b/docs/source/Web-Tutorial.md @@ -0,0 +1,64 @@ +# Web Tutorial + + +Evennia uses the [Django](https://www.djangoproject.com/) web framework as the basis of both its database configuration and the website it provides. While a full understanding of Django requires reading the Django documentation, we have provided this tutorial to get you running with the basics and how they pertain to Evennia. This text details getting everything set up. The [Web-based Character view Tutorial](Web-Character-View-Tutorial) gives a more explicit example of making a custom web page connected to your game, and you may want to read that after finishing this guide. + +## A Basic Overview + +Django is a web framework. It gives you a set of development tools for building a website quickly and easily. + +Django projects are split up into *apps* and these apps all contribute to one project. For instance, you might have an app for conducting polls, or an app for showing news posts or, like us, one for creating a web client. + +Each of these applications has a `urls.py` file, which specifies what [URL](http://en.wikipedia.org/wiki/Uniform_resource_locator)s are used by the app, a `views.py` file for the code that the URLs activate, a `templates` directory for displaying the results of that code in [HTML](http://en.wikipedia.org/wiki/Html) for the user, and a `static` folder that holds assets like [CSS](http://en.wikipedia.org/wiki/CSS), [Javascript](http://en.wikipedia.org/wiki/Javascript), and Image files (You may note your mygame/web folder does not have a `static` or `template` folder. This is intended and explained further below). Django applications may also have a `models.py` file for storing information in the database. We will not change any models here, take a look at the [New Models](New-Models) page (as well as the [Django docs](https://docs.djangoproject.com/en/1.7/topics/db/models/) on models) if you are interested. + +There is also a root `urls.py` that determines the URL structure for the entire project. A starter `urls.py` is included in the default game template, and automatically imports all of Evennia's default URLs for you. This is located in `web/urls.py`. + +## Changing the logo on the front page + +Evennia's default logo is a fun little googly-eyed snake wrapped around a gear globe. As cute as it is, it probably doesn't represent your game. So one of the first things you may wish to do is replace it with a logo of your own. + +Django web apps all have _static assets_: CSS files, Javascript files, and Image files. In order to make sure the final project has all the static files it needs, the system collects the files from every app's `static` folder and places it in the `STATIC_ROOT` defined in `settings.py`. By default, the Evennia `STATIC_ROOT` is in `web/static`. + +Because Django pulls files from all of those separate places and puts them in one folder, it's possible for one file to overwrite another. We will use this to plug in our own files without having to change anything in the Evennia itself. + +By default, Evennia is configured to pull files you put in the `web/static_overrides` *after* all other static files. That means that files in `static_overrides` folder will overwrite any previously loaded files *having the same path under its static folder*. This last part is important to repeat: To overload the static resource from a standard `static` folder you need to replicate the path of folders and file names from that `static` folder in exactly the same way inside `static_overrides`. + +Let's see how this works for our logo. The default web application is in the Evennia library itself, in `evennia/web/`. We can see that there is a `static` folder here. If we browse down, we'll eventually find the full path to the Evennia logo file: `evennia/web/static/evennia_general/images/evennia_logo.png`. + +Inside our `static_overrides` we must replicate the part of the path inside the `static` folder, in other words, we must replicate `evennia_general/images/evennia_logo.png`. + +So, to change the logo, we need to create the folder path `evennia_general/images/` in `static_overrides`. We then rename our own logo file to `evennia_logo.png` and copy it there. The final path for this file would thus be: `web/static_overrides/evennia_general/images/evennia_logo.png` in your local game folder. + +To get this file pulled in, just change to your own game directory and reload the server: + +``` +evennia reload +``` + +This will reload the configuration and bring in the new static file(s). If you didn't want to reload the server you could instead use + +``` +evennia collectstatic +``` + +to only update the static files without any other changes. + +> **Note**: Evennia will collect static files automatically during startup. So if `evennia collectstatic` reports finding 0 files to collect, make sure you didn't start the engine at some point - if so the collector has already done its work! To make sure, connect to the website and check so the logo has actually changed to your own version. + +> **Note**: Sometimes the static asset collector can get confused. If no matter what you do, your overridden files aren't getting copied over the defaults, try removing the target file (or everything) in the `web/static` directory, and re-running `collectstatic` to gather everything from scratch. + +## Changing the Front Page's Text + +The default front page for Evennia contains information about the Evennia project. You'll probably want to replace this information with information about your own project. Changing the page template is done in a similar way to changing static resources. + +Like static files, Django looks through a series of template folders to find the file it wants. The difference is that Django does not copy all of the template files into one place, it just searches through the template folders until it finds a template that matches what it's looking for. This means that when you edit a template, the changes are instant. You don't have to reload the server or run any extra commands to see these changes - reloading the web page in your browser is enough. + +To replace the index page's text, we'll need to find the template for it. We'll go into more detail about how to determine which template is used for rendering a page in the [Web-based Character view Tutorial](Web-Character-View-Tutorial). For now, you should know that the template we want to change is stored in `evennia/web/website/templates/website/index.html`. + +To replace this template file, you will put your changed template inside the `web/template_overrides/website` directory in your game folder. In the same way as with static resources you must replicate the path inside the default `template` directory exactly. So we must copy our replacement template named `index.html` there (or create the `website` directory in web/template_overrides` if it does not exist, first). The final path to the file should thus be: `web/template_overrides/website/index.html` within your game directory. + +Note that it is usually easier to just copy the original template over and edit it in place. The original file already has all the markup and tags, ready for editing. + +## Further reading + +For further hints on working with the web presence, you could now continue to the [Web-based Character view Tutorial](Web-Character-View-Tutorial) where you learn to make a web page that displays in-game character stats. You can also look at [Django's own tutorial](https://docs.djangoproject.com/en/1.7/intro/tutorial01/) to get more insight in how Django works and what possibilities exist. diff --git a/docs/source/Webclient-brainstorm.md b/docs/source/Webclient-brainstorm.md new file mode 100644 index 0000000000..7a70b1f865 --- /dev/null +++ b/docs/source/Webclient-brainstorm.md @@ -0,0 +1,251 @@ +# Webclient brainstorm + +# Ideas for a future webclient gui + +*This is a brainstorming whitepage. Add your own comments in a named section below.* + +## From Chat on 2019/09/02 +``` + Griatch (IRC)APP @friarzen: Could one (with goldenlayout) programmatically provide pane positions and sizes? +I recall it was not trivial for the old split.js solution. + friarzen take a look at the goldenlayout_default_config.js +It is kinda cryptic but that is the layout json. + Griatch (IRC)APP @friarzen: Hm, so dynamically replacing the goldenlayout_config in the global scope at the right + thing would do it? + friarzen yep + friarzen the biggest pain in the butt is that goldenlayout_config needs to be set before the goldenlayout init() +is called, which isn't trivial with the current structure, but it isn't impossible. + Griatch (IRC)APP One could in principle re-run init() at a later date though, right? + friarzen Hmm...not sure I've ever tried it... seems doable off the top of my head... +right now, that whole file exists to be overridden on page load as a separate +``` +Remember, plugins are load-order dependent, so make sure the new ` +``` +Remember, plugins are load-order dependent, so make sure the new `