Author Archive

CTAN-Pakete per REST-API hochladen

Vermutlich ist es nicht so bekannt, dass man LaTeX-Pakete auch per REST-API auf CTAN hochladen kann. Das ist insbesondere dann praktisch, wenn man öfter Pakete aktualisieren muss, wie ich es beispielsweise mit der DTK Bibliografie mehrmals im Jahr mache.

Manfred Lotz vom CTAN-Team hat dazu ein Python-Skript geschrieben (https://gitlab.com/Lotz/pkgcheck/blob/master/ctan_upload.py), das diese API befüttert.

Ich habe sein Skript noch ein wenig angepasst (ich erstelle auch die ZIP-Datei damit und kopiere die richtigen Dateien an ihren Platz), mein Skript findet ihr im Github unter https://github.com/dante-ev/dtk-bibliography/blob/master/pack_and_upload_to_ctan.py. Für den Upload selbst benötigt man noch eine TOML-Datei, in der die Upload-Informationen als Key-Value-Paare stehen. Ein Beispiel für eine solche TOML-Datei findet ihr bei Manfred unter https://gitlab.com/Lotz/pkgcheck/-/blob/master/pkgcheck.toml.

Uwe

Uwe Ziegenhagen likes LaTeX and Python, sometimes even combined. Do you like my content and would like to thank me for it? Consider making a small donation to my local fablab, the Dingfabrik Köln. Details on how to donate can be found here Spenden für die Dingfabrik.

More Posts - Website

Fortnite teilweise blockieren – Erweiterung um Status

This entry is part 2 of 2 in the series Pi-Hole

Im ersten Artikel zu diesem Thema hatte ich schon beschrieben, wie man mit Pi-Hole DNS Lookups so unterbinden kann, dass man Fortnite oder andere Zeitfresser temporär blockieren kann. In diesem Teil erweitern wir das Skript um eine Statusabfrage. Dazu fügen wir einfach eine Route hinzu, die die URL mit dem Parameter /status abfragt. Dann wird der Inhalt der epicstatus.txt abgefragt, die von den Routen /on und /off mit dem Zeitstempel versehen wurde.

import os
from datetime import datetime
from flask import Flask
 
app = Flask(__name__)
 
@app.route('/')
def index():
    return('<h1>Use /off and /on to enable/disable blocking, /status to get epic.log</h1>')
 
@app.route('/<param>')
def setter(param):
    if param=='status':
        with open('/home/pi/epicstatus.txt') as f:
            content = f.read()
            return '<h2>' + content + '</h2>'
    if param=='off':
        os.system("/usr/local/bin/pihole regex '.*\.epicgames.com' > /home/pi/epic.log")
        now = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
        with open('/home/pi/epicstatus.txt','w') as ausgabe:
            ausgabe.write(now + ' off')
        return '<h1>Turning off Fortnite</h1>'
    elif param=='on':
        os.system("/usr/local/bin/pihole regex -d '.*\.epicgames.com' > /home/pi/epic.log")
        now = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
        with open('/home/pi/epicstatus.txt','w') as ausgabe:
            ausgabe.write(now+ ' on')
        return '<h1>Turning on Fortnite</h1>'

Uwe

Uwe Ziegenhagen likes LaTeX and Python, sometimes even combined. Do you like my content and would like to thank me for it? Consider making a small donation to my local fablab, the Dingfabrik Köln. Details on how to donate can be found here Spenden für die Dingfabrik.

More Posts - Website

Liste meiner genutzten VSC-Plugins

Neben UltraEdit, Notepad++, Emacs, etc. nutze ich auch Visual Studio Code. Hier die Liste der wichtigsten von mir genutzten Plugins:

  • German Language Pack for Visual Studio Code für deutsche Menüs
  • Render Line Endings, um zwischen LF und CRLF umzuschalten
  • Python für die Python-Unterstützung
  • LaTeX Workshop
  • Graphviz (dot) language support for Visual Studio Code für gv
  • Graphviz Preview für ebendas
  • Encryptor for VS Code, Text mit AES 256 zu verschlüsseln
  • Bracket Pair Colorizer um zueinandergehörige Klammern zu markieren

Uwe

Uwe Ziegenhagen likes LaTeX and Python, sometimes even combined. Do you like my content and would like to thank me for it? Consider making a small donation to my local fablab, the Dingfabrik Köln. Details on how to donate can be found here Spenden für die Dingfabrik.

More Posts - Website

Dateien kopieren, zippen und löschen mit Python

Hier ein kurzes Beispiel, wie man mit Python-Modulen Dateien kopieren, zippen und löschen kann.

import zipfile
from shutil import copyfile
from os import unlink
 
# copy file
copyfile('dtk-authoryear.bbx'  , './dtk-bibliography/dtk-authoryear.bbx')
copyfile('dtk-authoryear.dbx'  , './dtk-bibliography/dtk-authoryear.dbx')
copyfile('dtk-bibliography.pdf', './dtk-bibliography/dtk-bibliography.pdf')
copyfile('dtk-bibliography.tex', './dtk-bibliography/dtk-bibliography.tex')
 
# create the zip file
with zipfile.ZipFile('dtk-bibliography.zip', 'w', zipfile.ZIP_DEFLATED) as z:
    z.write('./dtk-bibliography/README.md')
    z.write('./dtk-bibliography/dtk-authoryear.bbx')
    z.write('./dtk-bibliography/dtk-authoryear.dbx')
    z.write('./dtk-bibliography/dtk-bibliography.pdf')
    z.write('./dtk-bibliography/dtk-bibliography.tex')
 
# delete copied files
unlink('./dtk-bibliography/dtk-authoryear.bbx')
unlink('./dtk-bibliography/dtk-authoryear.dbx')
unlink('./dtk-bibliography/dtk-bibliography.pdf')
unlink('./dtk-bibliography/dtk-bibliography.tex')

Uwe

Uwe Ziegenhagen likes LaTeX and Python, sometimes even combined. Do you like my content and would like to thank me for it? Consider making a small donation to my local fablab, the Dingfabrik Köln. Details on how to donate can be found here Spenden für die Dingfabrik.

More Posts - Website

Mailman Spammer mit Python blocken

Für Dante e.V. betreue ich einige E-Mail-Listen auf mailman-Basis, die seit einigen Tagen von Spammern geflutet werden. Jeden Tag sind dutzende bis hunderte Aufnahme-Requests in der Liste, die ich manuell wegwerfen müsste. Nachdem ich dies einmal händisch getan hatte, musste eine automatische Lösung gefunden werden.

Die Lösung bestand darin, einen Treiber für Firefox („geckodriver“) zu installieren, der das Fernsteuern des Browsers erlaubt. Dann kann mittels selenium Modul die Steuerung aus Python heraus erfolgen. Unten der wesentliche Quellcode als Basis für eigene Arbeiten, den Teil zum Erkennen von legitimen Anfragen habe ich weggelassen.

# -*- coding: utf-8 -*-
"""
https://www.edureka.co/community/47679/is-it-possible-to-run-headless-browser-using-python-selenium
"""
 
from selenium.webdriver import Firefox
from selenium.webdriver.firefox.options import Options
 
opts = Options()
#opts.set_headless() # Ich will sehen, wie selenium arbeitet
#assert opts.headless  # Operating in headless mode
browser = Firefox(executable_path=r"C:\Users\Uwe\Downloads\geckodriver-v0.27.0-win64\geckodriver.exe", options=opts)
browser.implicitly_wait(3)
 
# einloggen
browser.get('<url des mailman admin panels')
search_form = browser.find_element_by_name('<passwortfeld_ID>')
search_form.send_keys('<adminpasswort>')
search_form.submit()
 
#wegwerfen Button pro Zeile
fields = browser.find_elements_by_xpath("//input[@value='3']")
#email Adresse des Spammers
emails = browser.find_elements_by_xpath('//td[contains(text(),"@")]')
 
if len(fields) == len(emails):
    zipped_list = list(zip(emails, fields))
 
    for i in zipped_list:
        email, field = i
        field.click()

Uwe

Uwe Ziegenhagen likes LaTeX and Python, sometimes even combined. Do you like my content and would like to thank me for it? Consider making a small donation to my local fablab, the Dingfabrik Köln. Details on how to donate can be found here Spenden für die Dingfabrik.

More Posts - Website

Linux-Software installieren mit Ansible

Ich habe deutlich mehr als nur ein oder zwei Linux-Rechner, die ich alle gern auf dem aktuellen Software-Stand halte. Da manuelle apt Kommandos auf Dauer lästig werden, habe ich mir jetzt Ansible-Skripte geschrieben, die sehr bald nach dem Aufsetzen eines Linux-Rechners die restliche Arbeit übernehmen. „Sehr bald“ heißt in dem Zusammenhang, nach der grundlegenden Installation und dem Installieren von Ansible selbst mittels apt install ansible. Ansible ist zwar eigentlich dafür gemacht, dutzende, hunderte oder gar tausende Server zu administrieren, aber für die Arbeiten am localhost bietet es auch Vorteile.

Ich nutze dabei zwei Skripte, sogenannte „Playbooks“, eines für die Kombination aus apt update/upgrade und ein Skript für die grundlegende Paketinstallation.

Hier das erste (Quelle: https://www.cyberciti.biz/faq/ansible-apt-update-all-packages-on-ubuntu-debian-linux/):


- hosts: all
become: true
become_user: root
tasks:
- name: Update apt repo and cache on all Debian/Ubuntu boxes
apt: update_cache=yes force_apt_get=yes cache_valid_time=3600

- name: Upgrade all packages on servers
apt: upgrade=dist force_apt_get=yes

Und hier das zweite Playbook für die Installation der grundlegenden Software:


- hosts: all
become: true
become_user: root
tasks:
- name: Install git
apt:
name: git
state: present
update_cache: yes

- name: Install gparted
apt:
name: gparted
state: present
update_cache: yes

- name: Install subversion
apt:
name: subversion
state: present
update_cache: yes

- name: Install emacs
apt:
name: emacs
state: present
update_cache: yes

- name: Install geany
apt:
name: geany
state: present
update_cache: yes

- name: Install vlc
apt:
name: vlc
state: present
update_cache: yes

- name: Install ssh
apt:
name: ssh
state: present
update_cache: yes

- name: Install VSC via snap
snap:
name: code
classic: yes

Ausgeführt werden die Skripte dann mittels


ansible-playbook --connection=local --inventory 127.0.0.1, playbookname.yml

Ansible kann noch unendlich viel mehr, dazu vielleicht in weiteren Beiträgen.

Uwe

Uwe Ziegenhagen likes LaTeX and Python, sometimes even combined. Do you like my content and would like to thank me for it? Consider making a small donation to my local fablab, the Dingfabrik Köln. Details on how to donate can be found here Spenden für die Dingfabrik.

More Posts - Website

Fehlerhafte SQLite Updates abfangen

Bei einem kleinen Projekt, an dem ich arbeite, fiel mir auf, dass fehlerhafte — d.h. syntaktisch richtige, aber inhaltlich falsche — SQLite Updates im Nirvana verschwinden. Danke Google und Stackexchange war die Lösung wie folgt: fehlerhafte SQL-Updates erzeugen keine Exception, hier muss man nach dem Update den rowcount prüfen, um die Anzahl der aktualisierten Zeilen zu checken. Das folgende Programm illustriert das ganze:

import sqlite3
 
conn = sqlite3.connect(':memory:')
conn.execute('create table if not exists mytable(id INTEGER PRIMARY KEY \
             AUTOINCREMENT, status TEXT, prio TEXT)')
 
today = '2020-08-06'
cursor = conn.cursor()
task = "Foobar"
 
cursor.execute('insert into mytable(status, prio) values (?, ?)',
               [task, today])
conn.commit()
 
my_id = 1 # For my_id = 1 it works, for any other number rowcount is 0
prio = 'A'
cursor = conn.cursor()
cursor.execute('UPDATE mytable SET prio = ? WHERE id = ?', [prio, my_id])
conn.commit()
print(cursor.rowcount)
 
try:
    cursor = conn.cursor()
    cursor.execute('select * from mytable')
    results = cursor.fetchall()
    for entry in results:
        print(entry)
except Exception as err:
    print('Listing failed: %s\nError: %s' % str(err))
 
finally:
    cursor.close()

Uwe

Uwe Ziegenhagen likes LaTeX and Python, sometimes even combined. Do you like my content and would like to thank me for it? Consider making a small donation to my local fablab, the Dingfabrik Köln. Details on how to donate can be found here Spenden für die Dingfabrik.

More Posts - Website

Lose-Blatt-Sammlungen mit LaTeX

Für ein spezielles Projekt habe ich eine Möglichkeit gesucht, eine Lose-Blatt-Sammlung mit LaTeX zu gestalten. Lose-Blatt-Sammlung heißt in dem Zusammenhang:

1) Für jede (logische) Seite existiert ein separates Unterdokument.

2) Im Dateinamen jedes Unterdokuments ist die Seitennummer enthalten.

3) Einzelne Seiten können dadurch problemlos ausgetauscht werden, ohne dass sich die Seitenzahlen der anderen Seiten ändern.

4) Jedes Unterdokument besteht grundsätzlich aus nur einer physischen Textseite, kann jedoch auch länger sein. Daher hat jedes Unterdokument eine Seitennummerierung der Form xx-yy. xx steht dabei für die logische Seitennummer im Hauptdokument, die aus dem Dateinamen gezogen wird, yy für die fortlaufende (physische) Seitennummer im Unterdokument selbst. Die fortlaufende Seitennummer für das Gesamtdokument werte ich nicht aus.

5) Alle Dokumente stehen natürlich unter Versionverwaltung, hier nutze ich Subversion.

Wie habe ich es umgesetzt?

Das Hauptdokument sieht wie folgt aus. Neben dem svn-multi Paket, das die Subversion-Daten bereitstellt, nutze ich varsfromjobname, um die einzelnen Komponenten der Dateinamen (xx-yy.tex) auswerten zu können. Die einzelnen Teile von Kopf- und Fußzeile werden durch scrlayer-scrpage modifiziert.

%!TEX TS-program = Arara
% arara: pdflatex: {shell: yes}
% arara: pdflatex: {shell: yes}
 
\documentclass[12pt,ngerman,headsepline,footsepline]{scrartcl}
\usepackage[utf8]{inputenc}
\usepackage[T1]{fontenc}
\usepackage{babel}
\usepackage{csquotes}
\usepackage{xcolor}
\usepackage{svn-multi}
\usepackage{currfile}
\usepackage{varsfromjobname}
 
\usepackage{blindtext}
\addtokomafont{pagenumber}{\textit}
\addtokomafont{sectionentrypagenumber}{\color{white}}
\usepackage[left=3cm,right=1.5cm,top=2cm,bottom=3cm]{geometry}
 
\usepackage[headsepline=0.25pt,footsepline=0.25pt]{scrlayer-scrpage}
\pagestyle{scrheadings}
 
\svnidlong{$HeadURL: svn://192.168.0.80/Dokumente/Uwe/Main.tex $}
{$LastChangedDate: 2020-07-16 10:42:03 +0200 (Do, 16 Jul 2020) $}
{$LastChangedRevision: 62 $}
{$LastChangedBy: uwe $}
 
\title{Main Document}
\author{Max Mustermann}
 
\begin{document}
 
\tableofcontents
 
\include{sub-01}
\include{sub-02}
 
\end{document}

Die Subdokumente sehen jeweils wie folgt aus. Über die *foot und *head Befehle werden die Seitenköpfe und -füße mit den Komponenten aus dem Dateinamen befüllt, \addsec fügt die Dokumente zum Inhaltsverzeichnis hinzu.

%!TeX root=main.tex
 
\svnidlong
{$HeadURL: svn://192.168.0.42/Dokumente/Uwe/Sub-01.tex $}
{$LastChangedDate: 2020-07-16 10:10:19 +0200 (Do, 16 Jul 2020) $}
{$LastChangedRevision: 60 $}
{$LastChangedBy: uwe $}
 
\setcounter{page}{1}
 
\ohead{V\svnfilerev\ vom \svnfileday.\svnfilemonth.\svnfileyear}
\ofoot[\gettwofromcurrfilename-\pagemark]{\gettwofromcurrfilename-\pagemark}
\ifoot[\currfilename]{\currfilename} % inner foot
\ihead[]{} % inner head
\cfoot[]{} % center foot
\chead[]{} % center head
 
 
\addsec{\gettwofromcurrfilename~\getonefromcurrfilename}
 
 
\blindtext[6]

Beispieldokument: Main.pdf

Uwe

Uwe Ziegenhagen likes LaTeX and Python, sometimes even combined. Do you like my content and would like to thank me for it? Consider making a small donation to my local fablab, the Dingfabrik Köln. Details on how to donate can be found here Spenden für die Dingfabrik.

More Posts - Website

Appendixnotes für Beamer-Präsentationen

Remark: An English version of this article has been published on latex.net.

Stellen wir uns vor, dass wir in einer Präsentation eine Abschlussarbeit verteidigen müssen. Es kann sein, dass weitergehende Fragen kommen, die dann tiefergehend beantwortet werden müssen.

Wir könnten das entsprechende Material direkt in die Präsentation setzen, müssen dann beim Vortrag aber eventuell Folien überspringen, was auch kein guter Präsentationsstil ist. Die zusätzlichen Inhalte einfach nur ans Ende setzen ist auch schlecht, im schlimmsten Fall muss man dann während der Präsentation hin- und herspringen.

Eine elegante Lösung gibt es mit dem beamerappendixnote Paket. Es nutzt zwei Befehle, \appxnote und \printappxnotes, um Material für den Anhang zu integrieren.

Der erste Befehl erstellt den Link zum Anhang und den anzuzeigenden Link-Text, der zweite Befehl setzt alle Appendix-Notizen.

\documentclass[12pt,ngerman]{beamer}
\usepackage[T1]{fontenc}
\usepackage{babel}
\usepackage{beamerappendixnote}
 
\begin{document}
 
\begin{frame}
\frametitle{Some Beamer Slide}
 
\begin{itemize}
	\item Some stuff
	\item that requires
	\item more background 
\end{itemize}
 
\appxnote{Proof}{We can easily prove this.}
 
\end{frame}
 
\printappxnotes
 
\end{document}

Uwe

Uwe Ziegenhagen likes LaTeX and Python, sometimes even combined. Do you like my content and would like to thank me for it? Consider making a small donation to my local fablab, the Dingfabrik Köln. Details on how to donate can be found here Spenden für die Dingfabrik.

More Posts - Website

Fortnite zeitweise blockieren

This entry is part 1 of 2 in the series Pi-Hole

Ich wurde von Freunden gebeten, eine Lösung für das temporäre Blockieren von Fortnite zu geben, um ihren Kindern a) die Möglichkeit zum Spielen zu geben, aber b) auch eine zeitliche Vorgabe wirksam umsetzen zu können.

Hier möchte ich kurz beschreiben, wie ich das umgesetzt habe. Hinweis: Leider eignet sich das Skript (noch) nicht dazu, eine Spielbeschränkung hart durchzusetzen. Das Betriebssystem cached DNS-Lookups, so dass auch nach dem Ende der Spielzeit, wenn die URL wieder auf der Blacklist steht, keine Spielunterbrechung stattfindet.

Schritt 1: Ein Pi-Hole auf einem Raspberry Pi 4 wird als DNS Server im Netzwerk eingerichtet. Der Raspi 4 war bereits vorhanden, alternativ hätte es auch ein älteres Modell getan.

Zur Installation von Pi-Hole siehe beispielsweise https://www.youtube.com/watch?v=ubzd2H1wZxE oder https://www.heise.de/tipps-tricks/Pi-Hole-auf-dem-Raspberry-Pi-einrichten-so-geht-s-4358553.html.

Schritt 2: Eine Web-Anwendung mit Flask bauen. Flask ist ein Python-Framework für Web-Applikationen, mit dem man recht schnell zu guten Ergebnissen kommt. Wir legen die folgende Datei server.py an:

import os
from flask import Flask
 
app = Flask(__name__)
 
@app.route('/')
def index():
    return('<h1>Use /on and /off to enable/disable blocking</h1>')
 
@app.route('/<status>')
def setter(status):
    if status=='off':
        os.system("/usr/local/bin/pihole regex '.*\.epicgames.com' > /home/pi/epic.log")
        return '<h1>Turning off Fortnite</h1>'
    elif status=='on':
        os.system("/usr/local/bin/pihole regex -d '.*\.epicgames.com' >/home/pi/epic.log")
        return '<h1>Turning on Fortnite</h1>'

Je nachdem, ob diese server.py mit dem Parameter /on oder /off aufgerufen wird, wird epicgames geblockt oder nicht. Den entsprechenden Aufruf von pihole habe ich unter https://www.reddit.com/r/pihole/comments/a51wjr/blocking_fortnite_monday_to_friday/ gefunden.

Schritt 3: Setzen der Umgebungsvariablen für die Flask-App mittels export FLASK_APP=server.py. Optional kann man noch export FLASK_DEBUG=1 für das Debugging nutzen.

Schritt 4: Starten der Server.py mittels nohup flask run --host=192.168.0.91 & Dieser Aufruf sorgt dafür, dass auch nach dem Beenden der Shell das Programm weiterläuft. Als host muss man natürlich die IP-Adresse des Raspi nutzen.

Unter 192.168.0.91/on bzw. 192.168.0.91/off kann man jetzt die Blockierung an- bzw. ausschalten, diese URLs lassen sich auch gut auf dem Mobiltelefon wie eine App ablegen.

Verbesserungspotential besteht noch:

  • Nach dem Neustart des Raspi muss auch das Skript neu gestartet werden, das sollte man automatisieren. (Hinweis: Ich habe versucht, https://blog.miguelgrinberg.com/post/running-a-flask-application-as-a-service-with-systemd zu folgen, klappt aber noch nicht.)
  • Es kann sein, dass Pi-Hole auch ein Caching vornimmt und das Blockieren nicht sofort greift.
  • Flask läuft hier in einem DEV-Modus, für „Produktionsumgebungen“ empfiehlt sich ein WSGI-Server
  • Ein Aktivieren für einen bestimmten Zeitraum mit folgender automatischer Blockierung ist noch nicht implementiert, mit atd und cron sollte das aber möglich sein.

Uwe

Uwe Ziegenhagen likes LaTeX and Python, sometimes even combined. Do you like my content and would like to thank me for it? Consider making a small donation to my local fablab, the Dingfabrik Köln. Details on how to donate can be found here Spenden für die Dingfabrik.

More Posts - Website