aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--README.md (renamed from README)57
-rw-r--r--rss_generator.py74
-rw-r--r--server.py6
-rw-r--r--siteconfig.py64
-rw-r--r--static/main.css2
-rw-r--r--static/rss.svg3
-rw-r--r--templates/base.html2
-rw-r--r--templates/feed.xml27
-rw-r--r--views.py23
9 files changed, 216 insertions, 42 deletions
diff --git a/README b/README.md
index 895d9b0..38cf6d6 100644
--- a/README
+++ b/README.md
@@ -18,7 +18,7 @@ useful for someone learning to use Flask, or someone who also likes the
look of websites from the 90s.
-### Why not just neocities?
+### Why not just neocities or wordpress?
Neocities is awesome! You should definitely host a site there.
@@ -27,7 +27,11 @@ https://neocities.org/
It's easy to get a simple static site going there and it's totally
free, but it lacks server side scripting as far as I know.
-## Quick start
+I did used to have wordpress blog, but I got bored of maintaining it. I
+felt like making something from scratch might make me more invested in
+making an interesting site.
+
+## Quickstart
It's recommended to run each server in it's own virtual environment. This
program uses python 3.7, so change `python` to either `python3` or `python3.7`
@@ -92,18 +96,18 @@ boilerplate that I provide in `base.html`:
```
-## Customization (or things you'll want to change right now)
+## Configuration
-To make customization easier, this program comes with a configuration file with
-variables to tweak the display of your site call `siteconfig.py`.
-For example by default this program makes the navbar out of the directories in
+This program comes with a configuration file with variables to tweak
+the display of your site called `siteconfig.py`. For example, by
+default this program makes the navbar out of the directories in
the `templates/site` directory, but you might want include other directories,
or even external sites. Examples of how to change these options are provided
in the comments on that file. Customization is also provided through the use of
specific files.
-### Navbar customization
+### Navbar
Be default, the top navbar is populated by indexing and sorting the top-level
`templates/site/` directory. You can override this to include any directories
@@ -111,7 +115,7 @@ you want in any order, so long as they exist, but it's advised to still keep
them all in the `site` directory to avoid confusion.
-### Index File Configuration
+### Indexing
This program uses a single master index file which is used when navigating to
any directory--instead of having to put in an 'index.html' in each folder, or
@@ -147,7 +151,7 @@ this program will not index any file or directory with a "." prepended--and a
user will receive a 404 error if they attempt to do so.
-### Mimetype Configuration
+### Mimetypes
Default mime types are primarily sourced from
[this page](https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types/Common_types)
@@ -163,7 +167,7 @@ Otherwise, the default mime type is `application/octet-stream`, which (for most
browsers) triggers the browser to download the file
-### License Configuration
+### License
The default license type is the same as this program's: CC0. The HTML is from
Creative Commons, with some modifications I like to add. You can of course
@@ -171,6 +175,23 @@ replace the HTML with your own license (or none), by editing
`templates/site/license.html`
+### RSS and Caching
+
+This app comes packaged with an auto-RSS generator that makes a feed
+based on the files in the site directory. You can omit files by adding
+them to the omit file from the perspective of the site dir.
+
+For example:
+`thoughts/rants/dontread.txt`
+or for files run under 'site'
+`home.html`
+
+You can also make use of the Flask-Caching module to optimize your
+site's perfomance. There are many options available and to be honest, I don't know much about them, but a simple cache of 5 minutes is provided by default. You'll want to read the
+ [Flask-Caching
+docs](https://flask-caching.readthedocs.io/en/latest/index.html)
+ before experimenting.
+
### Other Tips
There are a few special directories linked that are needed to
@@ -183,8 +204,9 @@ You can disable it by deleting the code under `send_file_from_site` or
## Deploying a server
-You should NOT run this server as in the quick start, but instead deploy it in
-an appropriate container.
+If you do actually use this for anything on the open Internet,
+you should NOT run this server as in the quick start, but instead
+use something like UWSGI as the Flask project recommends.
Refer to https://flask.palletsprojects.com/en/2.0.x/deploying/
for options, but an easy option I use is uwsgi since it's well
@@ -197,8 +219,10 @@ $ sudo chown www-data -R /var/path/to/your-flask
$ uwsgi -s /var/path/to/your-flask.sock --manage-script-name --mount /=server:app --virtualenv ./env
```
-Then point your main http daemon (niginx, apache, httpd) to the socket you
-made. There are examples for a nginx configuration in the uWSGI and Flask docs:
+Then point your main http daemon (nginx, apache, httpd) to the socket
+you made.
+
+There are examples for a nginx configuration in the uWSGI and Flask docs:
- https://uwsgi-docs.readthedocs.io/en/latest/Nginx.html
- https://flask.palletsprojects.com/en/2.0.x/deploying/uwsgi/
@@ -206,7 +230,8 @@ made. There are examples for a nginx configuration in the uWSGI and Flask docs:
I recommend making an ini file so you can run `uwsgi --ini yourini.ini`
without all the extra arguments. A simple ini file, which I stole from
-[here](https://github.com/mking/flask-uwsgi), looks like this:
+[here](https://github.com/mking/flask-uwsgi), looks like this (your
+socket path will be different depending on your environment):
```code
[uwsgi]
@@ -220,4 +245,4 @@ processes = 4
logger = file:/var/www/logs/uwsgi.log
```
-Be sure to follow that link above for a full explanation.
+Be sure to refer to all of those links above for a full explanation.
diff --git a/rss_generator.py b/rss_generator.py
new file mode 100644
index 0000000..4397a45
--- /dev/null
+++ b/rss_generator.py
@@ -0,0 +1,74 @@
+import os
+from time import strftime, strptime, ctime
+from siteconfig import siteconfig
+
+
+class RSS_Item:
+ PARAGRAPHS = siteconfig.rss_channel_config['DESCRIPTION_LENGTH']
+
+ class NotAFile(Exception):
+ def __init__(self, path: str):
+ self.path = path
+ self.message = f"{path} not a file"
+ super().__init__(self.message)
+
+ def __init__(self, path: str):
+ if not os.path.isfile(path):
+ raise self.NotAFile(path)
+
+ self.FULL_PATH = path
+ self.TITLE = path.rsplit('.', 1)[0].split('/')[-1]
+ self.DESCRIPTION = self.parse_file()
+ self.LAST_UPDATE = self.file_last_modified()
+ self.URI = self.get_uri()
+ self.LINK = siteconfig.rss_channel_config['LINK'] + self.URI
+
+ def __str__(self):
+ return "<RSS_Item at {} - {}, {}>".format(
+ self.FULL_PATH, self.TITLE, self.short_timestamp()
+ )
+
+ def short_timestamp(self):
+ return strftime("%Y-%m-%d %H:%M %z", strptime(self.LAST_UPDATE))
+
+ def parse_file(self):
+ """
+ parse_file - reads the file at FULL_PATH and saves the content
+ from when the first <p> tag is hit up to and including the
+ closing </p> tag. Expects an HTML style file
+ """
+ with open(self.FULL_PATH) as f:
+ in_body = False
+ paragraphs = 0
+ description = ""
+ for line in f.readlines():
+ if paragraphs >= self.PARAGRAPHS:
+ break
+ line = line.strip()
+ if line.startswith("<p>"):
+ in_body = True
+ if in_body:
+ description += line
+ if line.endswith("</p>"):
+ in_body = False
+ paragraphs += 1
+
+ return ''.join(description)
+
+ def file_last_modified(self):
+ return ctime(os.stat(self.FULL_PATH).st_ctime)
+
+ def get_uri(self):
+ return '/'.join(self.FULL_PATH.split('/')[2:])
+
+
+def get_rss_channel():
+ items = []
+ for root, dirs, files in os.walk(siteconfig.BASE_DIR):
+ for f in files:
+ path = os.path.join(root, f)
+ if (
+ path.endswith(".html") or f.endswith(".html!")
+ ) and path not in siteconfig.RSS_OMIT:
+ items.append(RSS_Item(path))
+ return items
diff --git a/server.py b/server.py
index 545f4b6..c5522b9 100644
--- a/server.py
+++ b/server.py
@@ -6,6 +6,7 @@ from flask import Flask
from siteconfig import siteconfig
from flask_compress import Compress
from flask_caching import Cache
+from rss_generator import RSS_Item, get_rss_channel
app = Flask(__name__)
compress = Compress()
@@ -41,11 +42,12 @@ def setup():
app.config.update(
{'COMPRESS_MIMETYPES': siteconfig.COMPRESS_MIMETYPES}
)
+ app.config.update({'RSS_CHANNEL': get_rss_channel()})
# Setup needs to come first to be compatible with wsgi
setup()
+compress.init_app(app)
+cache.init_app(app)
if __name__ == "__main__":
- compress.init_app(app)
- cache.init_app(app)
app.run()
diff --git a/siteconfig.py b/siteconfig.py
index 412dfce..f42875b 100644
--- a/siteconfig.py
+++ b/siteconfig.py
@@ -7,20 +7,30 @@ class siteconfig:
# REQUIRED SETTINGS#
DOMAIN = "example.net" # Your site here!
- HOME_TITLE = "WELCOME" # Goes right under
- # your site
+ HOME_TITLE = "WELCOME"
LINKS_FILE = ".links" # ".lnx" if you like
DESC_FILE = ".description" # ".desc"
DEFAULT_MIMETYPE = "application/octet-stream"
# ^This usually prompts a browser to download a file if the mime
# type is unknown. A good alternative might be "text/plain"
+ # This setting is required, don't change it unless you're running
+ # things in different directories
+ BASE_DIR = "./templates/site/"
# Add your desired mimetypes to the csv file
MIMETYPES = {}
with open('mimetypes.csv') as f:
for line in f.readlines():
ext, mime = line.strip().split(',')
MIMETYPES.update({ext: mime})
+ # This reads your omit file.
+ # Entries should be the full path from the site directory.
+ # For example "dontread.txt" in this project is entered as
+ # 'thoughts/rants/donread.txt'
+ RSS_OMIT = []
+ with open('omit') as f:
+ for line in f.readlines():
+ RSS_OMIT.append(BASE_DIR + line.strip())
# OPTIONAL SETTINGS #
@@ -33,35 +43,51 @@ class siteconfig:
# Most of the time, you don't need to set this!
SECRET_KEY = None # Something random.
- # Option for Flask Compress
+ # Options for Flask Compress
# see here https://pypi.org/project/Flask-Compress/
COMPRESS_MIMETYPES = list(MIMETYPES.values())
- # Option for Flask Caching
+ # Options for Flask Caching
# https://flask-caching.readthedocs.io/en/latest/#configuring-flask-caching
cache_config = {
- 'CACHE_TYPE' : "SimpleCache",
- 'CACHE_DEFAULT_TIMEOUT' : 300,
+ 'CACHE_TYPE': "SimpleCache",
+ 'CACHE_DEFAULT_TIMEOUT': 300,
# You should only fill ONE of the sections below
# uswgi
- 'CACHE_UWSGI_NAME' : None,
+ 'CACHE_UWSGI_NAME': None,
##
# memcache
- 'CACHE_MEMCACHED_SERVERS' : None,
- 'CACHE_MEMCACHED_USERNAME' : None,
- 'CACHE_MEMCACHED_PASSWORD' : None,
+ 'CACHE_MEMCACHED_SERVERS': None,
+ 'CACHE_MEMCACHED_USERNAME': None,
+ 'CACHE_MEMCACHED_PASSWORD': None,
##
# redis
- 'CACHE_REDIS_HOST' : None,
- 'CACHE_REDIS_PORT' : None,
- 'CACHE_REDIS_PASSWORD' : None,
- 'CACHE_REDIS_DB' : None,
- 'CACHE_REDIS_URL' : None,
- 'CACHE_REDIS_SENTINELS' : None,
- 'CACHE_REDIS_SENTINEL_MASTER' : None,
- 'CACHE_REDIS_CLUSTER' : None,
+ 'CACHE_REDIS_HOST': None,
+ 'CACHE_REDIS_PORT': None,
+ 'CACHE_REDIS_PASSWORD': None,
+ 'CACHE_REDIS_DB': None,
+ 'CACHE_REDIS_URL': None,
+ 'CACHE_REDIS_SENTINELS': None,
+ 'CACHE_REDIS_SENTINEL_MASTER': None,
+ 'CACHE_REDIS_CLUSTER': None,
##
# filesystem
- 'CACHE_DIR' : None,
+ 'CACHE_DIR': None,
# add more options as needed from the URL above
}
+
+ # RSS Settings
+ rss_channel_config = {
+ 'TITLE': "RSS Feed for example.net",
+ 'LINK': "http://127.0.0.1:5000/",
+ 'DESCRIPTION': "My example feed",
+ 'LANGUAGE': "en-us",
+ 'PUBDATE': "",
+ 'LASTBUILDDATE': "",
+ 'DOCS': "https://git.mjfer.net/ezcms.git/",
+ 'GENERATOR': "EZCMS",
+ 'AUTHOR': "editor@example.net",
+ 'WEBMASTER': "webmaster@example.net",
+ # Max amount of paragraphs to print in each description
+ 'DESCRIPTION_LENGTH': 3,
+ }
diff --git a/static/main.css b/static/main.css
index f8837d0..9b8c6db 100644
--- a/static/main.css
+++ b/static/main.css
@@ -14,7 +14,7 @@ body {
body {
font-size: 200%
}
- .loicense {
+ .license {
font-size: 75%;
}
}
diff --git a/static/rss.svg b/static/rss.svg
new file mode 100644
index 0000000..39bef06
--- /dev/null
+++ b/static/rss.svg
@@ -0,0 +1,3 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-rss-fill" viewBox="0 0 16 16">
+ <path d="M2 0a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V2a2 2 0 0 0-2-2H2zm1.5 2.5c5.523 0 10 4.477 10 10a1 1 0 1 1-2 0 8 8 0 0 0-8-8 1 1 0 0 1 0-2zm0 4a6 6 0 0 1 6 6 1 1 0 1 1-2 0 4 4 0 0 0-4-4 1 1 0 0 1 0-2zm.5 7a1.5 1.5 0 1 1 0-3 1.5 1.5 0 0 1 0 3z"/>
+</svg> \ No newline at end of file
diff --git a/templates/base.html b/templates/base.html
index 6476091..5e71e6b 100644
--- a/templates/base.html
+++ b/templates/base.html
@@ -15,6 +15,8 @@
{% for dir in navbar %}
<b> <a href="/site/{{ dir }}">{{ dir }}/</a> |</b>
{% endfor %}
+ <!-- Add icons here -->
+ <a href="/feed.xml"><img src="/static/rss.svg"></a>
</div>
<h2>{{ title }}</h2>
<div class="content">
diff --git a/templates/feed.xml b/templates/feed.xml
new file mode 100644
index 0000000..a252134
--- /dev/null
+++ b/templates/feed.xml
@@ -0,0 +1,27 @@
+
+<rss version="2.0">
+ <channel>
+ <title>{{ config.TITLE }}</title>
+ <link>{{ config.LINK }}</link>
+ <description>{{ config.DESCRIPTION }}</description>
+ <!-- Optional -->
+ <language>{{ config.LANGUAGE }}</language>
+ <pubDate> {{ config.PUBDATE }}</pubDate>
+ <lastBuildDate> {{ config.LASTBUILDDATE }}</lastBuildDate>
+ <docs>{{ config.DOCS }}</docs>
+ <generator>{{ config.GENERATOR }}</generator>
+ <managingEditor>{{ config.AUTHOR }}</managingEditor>
+ <webMaster>{{ config.WEBMASTER }}</webMaster>
+ {% for item in items %}
+ <item>
+ <title>{{ item.TITLE }}</title>
+ <link>{{ item.LINK }}</link>
+ <guid>{{ item.LINK }}</guid>
+ <description>{{ item.DESCRIPTION }}</description>
+ <pubDate>{{ item.LAST_UPDATE }}</pubDate>
+ <category>{{ item.SITE_DIR }}</category>
+ <author>{{ config.AUTHOR }}</author>
+ </item>
+ {% endfor %}
+ </channel>
+</rss>
diff --git a/views.py b/views.py
index dd37902..7a6f273 100644
--- a/views.py
+++ b/views.py
@@ -4,7 +4,7 @@ browsing to certain pages
"""
import os
from flask import request, send_from_directory, abort
-from flask import render_template, render_template_string
+from flask import render_template, render_template_string, make_response
from siteconfig import siteconfig
from server import app, cache
from view_functions import default_context, index_dir, is_hidden_path
@@ -19,6 +19,8 @@ CONTENT_BLOCK = (
@app.route("/")
@app.route("/site")
+@app.route("/site/home.html")
+@app.route("/site/index.html")
@cache.cached()
def home():
"""
@@ -48,7 +50,7 @@ def render_file(path):
"""
if is_hidden_path(path):
abort(404)
- abs_path = "./templates/site/" + path
+ abs_path = siteconfig.BASE_DIR + path
context = default_context()
context.update(
{
@@ -68,7 +70,7 @@ def render_file(path):
else:
# not an html file, so don't render it
return send_from_directory(
- 'templates/site/',
+ siteconfig.BASE_DIR,
path,
mimetype=siteconfig.MIMETYPES.get(
f".{ path.split('.')[-1] }",
@@ -101,7 +103,7 @@ def send_file_from_site(path):
as with `render_file`, send the raw file to the user
"""
return send_from_directory(
- 'templates/site/',
+ siteconfig.BASE_DIR,
path,
mimetype=siteconfig.MIMETYPES.get(
f".{ path.split('.')[-1] }", siteconfig.DEFAULT_MIMETYPE
@@ -122,3 +124,16 @@ def send_file_from_static(path):
f".{ path.split('.')[-1] }", siteconfig.DEFAULT_MIMETYPE
),
)
+
+
+@app.route("/feed.xml")
+@cache.cached()
+def render_rss_feed():
+ context = {
+ 'config': siteconfig.rss_channel_config,
+ 'items': app.config['RSS_CHANNEL'],
+ }
+ feed = render_template("feed.xml", **context)
+ response = make_response(feed)
+ response.headers['Content-Type'] = siteconfig.MIMETYPES.get(".xml")
+ return response