Basic rebuild done
This commit is contained in:
parent
a77c282081
commit
6b955e945a
12
README.md
12
README.md
|
@ -1,20 +1,14 @@
|
|||
# AfpyLogs
|
||||
|
||||
Web view of IRC logs from #python-fr channel on Freenode.
|
||||
Web view of IRC logs from #afpy channel on Freenode.
|
||||
|
||||
|
||||
## Installing
|
||||
|
||||
pip install -e .
|
||||
|
||||
|
||||
## Testing
|
||||
|
||||
pip install -e .[test]
|
||||
pytest
|
||||
pip install -r requirements.txt
|
||||
|
||||
|
||||
## Running
|
||||
|
||||
gunicorn --workers 2 --bind unix:afpylogs.sock -m 007 server
|
||||
gunicorn --workers 2 --bind 0.0.0.0:8000 app
|
||||
|
||||
|
|
82
app.py
Normal file
82
app.py
Normal file
|
@ -0,0 +1,82 @@
|
|||
# encoding: utf-8
|
||||
|
||||
import os
|
||||
import re
|
||||
|
||||
from flask import Flask, g, redirect, render_template, url_for
|
||||
|
||||
|
||||
application = Flask(__name__, template_folder=".")
|
||||
application.config.from_object("config")
|
||||
application.jinja_env.trim_blocks = application.config["JINJA_ENV"]["TRIM_BLOCKS"]
|
||||
application.jinja_env.lstrip_blocks = application.config["JINJA_ENV"]["LSTRIP_BLOCKS"]
|
||||
|
||||
LOG_PATTERN = re.compile(application.config["LOG_PATTERN"])
|
||||
LINK_PATTERN = re.compile(application.config["LINK_PATTERN"])
|
||||
BOLD_PATTERN = re.compile(application.config["BOLD_PATTERN"])
|
||||
|
||||
|
||||
def get_archives():
|
||||
archives = []
|
||||
dates = {"years": [], "months": {}, "days": {}}
|
||||
for filename in sorted(os.listdir(application.config["LOG_PATH"])):
|
||||
date = filename[:-4].split("-")[1:]
|
||||
archives.append(date)
|
||||
if date[0] not in dates["years"]:
|
||||
dates["years"].append(date[0])
|
||||
dates["months"][date[0]] = []
|
||||
if date[1] not in dates["months"][date[0]]:
|
||||
dates["months"][date[0]].append(date[1])
|
||||
dates["days"]["%s%s" % tuple(date[:2])] = []
|
||||
if date[2] not in dates["days"]["%s%s" % tuple(date[:2])]:
|
||||
dates["days"]["%s%s" % tuple(date[:2])].append(date[2])
|
||||
return archives, dates
|
||||
|
||||
|
||||
@application.route("/")
|
||||
@application.route("/archives/<year>")
|
||||
@application.route("/archives/<year>/<month>")
|
||||
@application.route("/archives/<year>/<month>/<day>")
|
||||
def archives(year=None, month=None, day=None):
|
||||
# Récupération des fichiers disponibles
|
||||
archives, g.dates = get_archives()
|
||||
# Récupération de la date souhaitée
|
||||
if year is None or month is None or day is None:
|
||||
# Si date mal ou non fournie, on prend la dernière existante
|
||||
year = archives[-1][0]
|
||||
month = archives[-1][1]
|
||||
day = archives[-1][2]
|
||||
# Et on redirige proprement
|
||||
return redirect(url_for("archives", year=year, month=month, day=day))
|
||||
# On vérifie que le tuple existe
|
||||
if [year, month, day] not in archives:
|
||||
# TODO: Renvoyer une 404 ou mettre un message d'erreur ?
|
||||
return "404"
|
||||
# Ok, on charge et on affiche le contenu du fichier
|
||||
filename = "log-%s-%s-%s.txt" % (year, month, day)
|
||||
filepath = os.path.join(application.config["LOG_PATH"], filename)
|
||||
with open(filepath) as f:
|
||||
lines = f.read().splitlines()
|
||||
g.lines = []
|
||||
g.year, g.month, g.day = year, month, day
|
||||
for line in lines:
|
||||
result = LOG_PATTERN.match(line)
|
||||
if result is not None:
|
||||
message = result.group("message")
|
||||
for link in LINK_PATTERN.findall(message):
|
||||
message = message.replace(
|
||||
link, application.config["LINK_HTML"].format(link=link)
|
||||
)
|
||||
for text in BOLD_PATTERN.findall(message):
|
||||
message = message.replace(
|
||||
text, application.config["BOLD_HTML"].format(text=text)
|
||||
)
|
||||
g.lines.append(
|
||||
{
|
||||
"time": result.group("time"),
|
||||
"nick": result.group("nick"),
|
||||
"message": message,
|
||||
}
|
||||
)
|
||||
|
||||
return render_template("template.html")
|
15
config.py
15
config.py
|
@ -7,6 +7,17 @@ JINJA_ENV = {
|
|||
"LSTRIP_BLOCKS": True,
|
||||
}
|
||||
|
||||
PATH = "../logs.afpy.org"
|
||||
CHANNEL = "#afpy"
|
||||
LOG_PATH = "../logs.afpy.org"
|
||||
|
||||
# IRSSI log pattern
|
||||
DATE_FORMAT = "(\d+-\d+-\d+ )?(?P<time>\d\d:\d\d)"
|
||||
LOG_PATTERN = r"^%s\s+[<*]\s*(?P<nick>[^> ]+)[> ]\s+(?P<message>.*)$" % DATE_FORMAT
|
||||
|
||||
# Patterns
|
||||
LINK_PATTERN = r"https?://\S+"
|
||||
BOLD_PATTERN = r"\*[^\*\s]+\*"
|
||||
|
||||
# html
|
||||
LINK_HTML = '<a href="{link}">{link}</a>'
|
||||
BOLD_HTML = "<b>{text}</b>"
|
||||
|
||||
|
|
17
server.py
17
server.py
|
@ -1,17 +0,0 @@
|
|||
# encoding: utf-8
|
||||
|
||||
from flask import Flask
|
||||
|
||||
app = Flask(__name__)
|
||||
app.config.from_object("config")
|
||||
app.jinja_env.trim_blocks = app.config["JINJA_ENV"]["TRIM_BLOCKS"]
|
||||
app.jinja_env.lstrip_blocks = app.config["JINJA_ENV"]["LSTRIP_BLOCKS"]
|
||||
|
||||
|
||||
@app.route("/")
|
||||
def hello_world():
|
||||
return "Hello, World!"
|
||||
|
||||
|
||||
# WSGI needs an "application" variable
|
||||
application = app
|
43
setup.cfg
43
setup.cfg
|
@ -1,43 +0,0 @@
|
|||
[metadata]
|
||||
name = AfpyLogs
|
||||
url = https://github.com/AFPy/AfpyLogs
|
||||
version = file: afpylogs/VERSION
|
||||
description = Web view of IRC logs from #python-fr channel on Freenode
|
||||
long_description = file: README.md
|
||||
long_description_content_type = text/markdown
|
||||
license = GNU AFFERO GENERAL PUBLIC LICENSE version 3 or later
|
||||
license_file = LICENSE
|
||||
author = Mindiell
|
||||
classifiers =
|
||||
Development Status :: 3 - Alpha
|
||||
Environment :: Web Environment
|
||||
License :: OSI Approved :: GNU Affero General Public License v3 or later (AGPLv3+)
|
||||
Operating System :: OS Independent
|
||||
Programming Language :: Python :: 3
|
||||
Programming Language :: Python :: 3.8
|
||||
project_urls =
|
||||
Documentation = https://github.com/AFPy/AfpyLogs/blob/master/README.md
|
||||
|
||||
[options]
|
||||
packages = find:
|
||||
setup_requires = pytest_runner
|
||||
python_requires = >= 3.8
|
||||
install_requires =
|
||||
flask
|
||||
gunicorn
|
||||
|
||||
[options.extras_require]
|
||||
test =
|
||||
pytest-black
|
||||
pytest-isort
|
||||
pytest
|
||||
|
||||
[bdist_wheel]
|
||||
python-tag = py3
|
||||
|
||||
[tool:pytest]
|
||||
addopts = --isort --black
|
||||
|
||||
[isort]
|
||||
default_section = THIRDPARTY
|
||||
multi_line_output = 3
|
9
setup.py
9
setup.py
|
@ -1,9 +0,0 @@
|
|||
# encoding: utf-8
|
||||
"""
|
||||
Setup for AfpyLogs
|
||||
"""
|
||||
|
||||
from setuptools import setup
|
||||
|
||||
setup()
|
||||
|
74
template.html
Normal file
74
template.html
Normal file
|
@ -0,0 +1,74 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Logs du chan #afpy pour le </title>
|
||||
<style type="text/css">
|
||||
body {background-color: #000; color: #fff; font-family: "Verdana"}
|
||||
.time {color: #ffd738; font-size: .9em; font-weight: bold}
|
||||
.bracket {color: #ccc; font-size: .8em}
|
||||
.nick {color: #dAa642}
|
||||
.message {color: #eaeaea}
|
||||
.action {color: #a4c}
|
||||
.click {cursor: pointer}
|
||||
.off {display: none}
|
||||
.on {display: block}
|
||||
#content a {color: #ffd738; font-size: .9em; font-weight: bold; text-decoration: none}
|
||||
#content a:hover {text-decoration: underline}
|
||||
.calendar {color: #ffd738}
|
||||
.day {font-weight: bold}
|
||||
#calendar {padding-bottom: .6em; margin-bottom: .6em; border-bottom: 1px dashed #ffd738}
|
||||
#calendar a {color: #ffd738; text-decoration: none}
|
||||
#calendar a:hover {text-decoration: underline}
|
||||
</style>
|
||||
<script type="text/javascript">
|
||||
function hide(id) {
|
||||
var els = document.getElementsByClassName("on");
|
||||
[].forEach.call(els, function (el){
|
||||
console.log(el.id);
|
||||
console.log(id);
|
||||
console.log(id.substr(0, el.id.length));
|
||||
console.log(id.substr(0, el.id.length) != el.id);
|
||||
if (el.id != id.substr(0, el.id.length)) {
|
||||
el.className = "off";
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function display(id) {
|
||||
hide(id.toString());
|
||||
var el = document.getElementById(id);
|
||||
if (el!=null) {
|
||||
el.className = "on";
|
||||
}
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<div id="calendar">
|
||||
<nav>
|
||||
{% for year in g.dates.years %}
|
||||
<span onclick="display('{{ year }}')" class="calendar click {%if year==g.year%}day{%endif%}">{{ year }}</span>
|
||||
{% endfor %}
|
||||
</nav>
|
||||
{% for year in g.dates.years %}
|
||||
<nav id="{{year}}" class="{%if year==g.year%}on{%else%}off{%endif%}">
|
||||
{% for month in g.dates.months[year] %}
|
||||
<span onclick="display('{{ year }}{{ month }}')" class="calendar click {%if year==g.year and month==g.month%}day{%endif%}">{{ month }}</span>
|
||||
{% endfor %}
|
||||
{% for month in g.dates.months[year] %}
|
||||
<nav id="{{year}}{{month}}" class="{%if year==g.year and month==g.month%}on{%else%}off{%endif%}">
|
||||
{% for day in g.dates.days[year+month] %}
|
||||
<a href="{{url_for('archives', year=year, month=month, day=day)}}" class="calendar {%if year==g.year and month==g.month and day==g.day%}day{%endif%}">{{ day }}</a>
|
||||
{% endfor %}
|
||||
</nav>
|
||||
{% endfor %}
|
||||
</nav>
|
||||
{% endfor %}
|
||||
</div>
|
||||
<div id="content">
|
||||
{% for line in g.lines %}
|
||||
<span class="time">{{ line.time }}</span> <span class="bracket"><</span><span class="nick">{{ line.nick }}</span><span class="bracket">></span> <span class="message">{{ line.message |safe }}</span><br />
|
||||
{% endfor %}
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
Loading…
Reference in New Issue
Block a user