initialise project
This commit is contained in:
+246
@@ -0,0 +1,246 @@
|
||||
# Byte-compiled / optimized / DLL files
|
||||
__pycache__/
|
||||
*.py[codz]
|
||||
*$py.class
|
||||
|
||||
# C extensions
|
||||
*.so
|
||||
|
||||
# Distribution / packaging
|
||||
.Python
|
||||
build/
|
||||
develop-eggs/
|
||||
dist/
|
||||
downloads/
|
||||
eggs/
|
||||
.eggs/
|
||||
lib/
|
||||
lib64/
|
||||
parts/
|
||||
sdist/
|
||||
var/
|
||||
wheels/
|
||||
share/python-wheels/
|
||||
*.egg-info/
|
||||
.installed.cfg
|
||||
*.egg
|
||||
MANIFEST
|
||||
|
||||
# PyInstaller
|
||||
# Usually these files are written by a python script from a template
|
||||
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
||||
*.manifest
|
||||
*.spec
|
||||
|
||||
# Installer logs
|
||||
pip-log.txt
|
||||
pip-delete-this-directory.txt
|
||||
|
||||
# Unit test / coverage reports
|
||||
htmlcov/
|
||||
.tox/
|
||||
.nox/
|
||||
.coverage
|
||||
.coverage.*
|
||||
.cache
|
||||
nosetests.xml
|
||||
coverage.xml
|
||||
*.cover
|
||||
*.py.cover
|
||||
*.lcov
|
||||
.hypothesis/
|
||||
.pytest_cache/
|
||||
cover/
|
||||
|
||||
# Translations
|
||||
*.mo
|
||||
*.pot
|
||||
|
||||
# Django stuff:
|
||||
*.log
|
||||
local_settings.py
|
||||
db.sqlite3
|
||||
db.sqlite3-journal
|
||||
|
||||
# Flask stuff:
|
||||
instance/
|
||||
.webassets-cache
|
||||
|
||||
# Scrapy stuff:
|
||||
.scrapy
|
||||
|
||||
# Sphinx documentation
|
||||
docs/_build/
|
||||
|
||||
# PyBuilder
|
||||
.pybuilder/
|
||||
target/
|
||||
|
||||
# Jupyter Notebook
|
||||
.ipynb_checkpoints
|
||||
|
||||
# IPython
|
||||
profile_default/
|
||||
ipython_config.py
|
||||
|
||||
# pyenv
|
||||
# For a library or package, you might want to ignore these files since the code is
|
||||
# intended to run in multiple environments; otherwise, check them in:
|
||||
# .python-version
|
||||
|
||||
# pipenv
|
||||
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
|
||||
# However, in case of collaboration, if having platform-specific dependencies or dependencies
|
||||
# having no cross-platform support, pipenv may install dependencies that don't work, or not
|
||||
# install all needed dependencies.
|
||||
# Pipfile.lock
|
||||
|
||||
# UV
|
||||
# Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control.
|
||||
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
||||
# commonly ignored for libraries.
|
||||
# uv.lock
|
||||
|
||||
# poetry
|
||||
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
|
||||
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
||||
# commonly ignored for libraries.
|
||||
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
|
||||
# poetry.lock
|
||||
# poetry.toml
|
||||
|
||||
# pdm
|
||||
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
|
||||
# pdm recommends including project-wide configuration in pdm.toml, but excluding .pdm-python.
|
||||
# https://pdm-project.org/en/latest/usage/project/#working-with-version-control
|
||||
# pdm.lock
|
||||
# pdm.toml
|
||||
.pdm-python
|
||||
.pdm-build/
|
||||
|
||||
# pixi
|
||||
# Similar to Pipfile.lock, it is generally recommended to include pixi.lock in version control.
|
||||
# pixi.lock
|
||||
# Pixi creates a virtual environment in the .pixi directory, just like venv module creates one
|
||||
# in the .venv directory. It is recommended not to include this directory in version control.
|
||||
.pixi/*
|
||||
!.pixi/config.toml
|
||||
|
||||
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
|
||||
__pypackages__/
|
||||
|
||||
# Celery stuff
|
||||
celerybeat-schedule*
|
||||
celerybeat.pid
|
||||
|
||||
# Redis
|
||||
*.rdb
|
||||
*.aof
|
||||
*.pid
|
||||
|
||||
# RabbitMQ
|
||||
mnesia/
|
||||
rabbitmq/
|
||||
rabbitmq-data/
|
||||
|
||||
# ActiveMQ
|
||||
activemq-data/
|
||||
|
||||
# SageMath parsed files
|
||||
*.sage.py
|
||||
|
||||
# Environments
|
||||
.env
|
||||
.envrc
|
||||
.venv
|
||||
env/
|
||||
venv/
|
||||
ENV/
|
||||
env.bak/
|
||||
venv.bak/
|
||||
|
||||
# Spyder project settings
|
||||
.spyderproject
|
||||
.spyproject
|
||||
|
||||
# Rope project settings
|
||||
.ropeproject
|
||||
|
||||
# mkdocs documentation
|
||||
/site
|
||||
|
||||
# mypy
|
||||
.mypy_cache/
|
||||
.dmypy.json
|
||||
dmypy.json
|
||||
|
||||
# Pyre type checker
|
||||
.pyre/
|
||||
|
||||
# pytype static type analyzer
|
||||
.pytype/
|
||||
|
||||
# Cython debug symbols
|
||||
cython_debug/
|
||||
|
||||
# PyCharm
|
||||
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
|
||||
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
|
||||
# and can be added to the global gitignore or merged into this file. For a more nuclear
|
||||
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
||||
# .idea/
|
||||
|
||||
# Abstra
|
||||
# Abstra is an AI-powered process automation framework.
|
||||
# Ignore directories containing user credentials, local state, and settings.
|
||||
# Learn more at https://abstra.io/docs
|
||||
.abstra/
|
||||
|
||||
# Visual Studio Code
|
||||
# Visual Studio Code specific template is maintained in a separate VisualStudioCode.gitignore
|
||||
# that can be found at https://github.com/github/gitignore/blob/main/Global/VisualStudioCode.gitignore
|
||||
# and can be added to the global gitignore or merged into this file. However, if you prefer,
|
||||
# you could uncomment the following to ignore the entire vscode folder
|
||||
# .vscode/
|
||||
# Temporary file for partial code execution
|
||||
tempCodeRunnerFile.py
|
||||
|
||||
# Ruff stuff:
|
||||
.ruff_cache/
|
||||
|
||||
# PyPI configuration file
|
||||
.pypirc
|
||||
|
||||
# Marimo
|
||||
marimo/_static/
|
||||
marimo/_lsp/
|
||||
__marimo__/
|
||||
|
||||
# Streamlit
|
||||
.streamlit/secrets.toml
|
||||
|
||||
|
||||
# Generated by Cargo
|
||||
# will have compiled files and executables
|
||||
debug
|
||||
target
|
||||
|
||||
# These are backup files generated by rustfmt
|
||||
**/*.rs.bk
|
||||
|
||||
# MSVC Windows builds of rustc generate these, which store debugging information
|
||||
*.pdb
|
||||
|
||||
# Generated by cargo mutants
|
||||
# Contains mutation testing data
|
||||
**/mutants.out*/
|
||||
|
||||
# rustc will dump stack traces when hitting an internal compiler error to PWD
|
||||
rustc-ice-*.txt
|
||||
|
||||
# RustRover
|
||||
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
|
||||
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
|
||||
# and can be added to the global gitignore or merged into this file. For a more nuclear
|
||||
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
||||
#.idea/
|
||||
+702
@@ -0,0 +1,702 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import random
|
||||
import time
|
||||
import json
|
||||
import os
|
||||
import subprocess
|
||||
from datetime import datetime
|
||||
|
||||
|
||||
# ============================================================
|
||||
# EINSTELLUNGEN
|
||||
# ============================================================
|
||||
|
||||
# Durchschnittlicher Abstand zwischen zwei Funfacts
|
||||
AVERAGE_HOURS = 2
|
||||
|
||||
# Niemals kürzer als diese Zeit warten
|
||||
MIN_WAIT_MINUTES = 5
|
||||
|
||||
# Wie viele bereits gezeigte Facts gespeichert werden
|
||||
HISTORY_SIZE = 200
|
||||
|
||||
# Datei für die History
|
||||
HISTORY_FILE = os.path.expanduser(
|
||||
"~/.tech_funfacts_history.json"
|
||||
)
|
||||
|
||||
|
||||
# ============================================================
|
||||
# FUNFACTS
|
||||
# ============================================================
|
||||
|
||||
FUNFACTS = [
|
||||
|
||||
# ---------------- INTERNET ----------------
|
||||
|
||||
"Es gibt tatsächlich einen offiziellen Standard für Netzwerkpakete per Brieftaube: RFC 1149 aus dem Jahr 1990.",
|
||||
|
||||
"Die erste registrierte Domain der Welt war symbolics.com. Sie wurde am 15. März 1985 registriert.",
|
||||
|
||||
"Die erste E-Mail wurde 1971 von Ray Tomlinson verschickt.",
|
||||
|
||||
"Ray Tomlinson verwendete das @-Zeichen, um Benutzer und Computer voneinander zu trennen.",
|
||||
|
||||
"Das Internet und das World Wide Web sind nicht dasselbe. Das Web ist nur ein Dienst, der das Internet benutzt.",
|
||||
|
||||
"Das World Wide Web wurde 1989 von Tim Berners-Lee vorgeschlagen.",
|
||||
|
||||
"Die erste Website der Welt war info.cern.ch.",
|
||||
|
||||
"Die erste Webcam wurde benutzt, um den Füllstand einer Kaffeemaschine zu überwachen.",
|
||||
|
||||
"Das ARPANET war einer der wichtigsten Vorläufer des heutigen Internets.",
|
||||
|
||||
"Der erste ARPANET-Versuch zwischen UCLA und Stanford sollte das Wort LOGIN übertragen. Nach den ersten zwei Buchstaben stürzte das System ab.",
|
||||
|
||||
"IPv4 besitzt etwa 4,3 Milliarden mögliche Adressen.",
|
||||
|
||||
"IPv6 besitzt ungefähr 340 Sextillionen mögliche Adressen.",
|
||||
|
||||
"Eine IPv6-Adresse kann 128 Bit lang sein.",
|
||||
|
||||
"DNS übersetzt normalerweise Domainnamen wie example.com in IP-Adressen.",
|
||||
|
||||
"DNS steht für Domain Name System.",
|
||||
|
||||
"HTTP steht für Hypertext Transfer Protocol.",
|
||||
|
||||
"HTTPS verwendet normalerweise TLS, um die Verbindung zwischen Browser und Server zu verschlüsseln.",
|
||||
|
||||
"Ein Ping verwendet normalerweise ICMP und nicht TCP.",
|
||||
|
||||
"Der Begriff Spam für unerwünschte Nachrichten wurde durch Monty-Python-Sketches inspiriert.",
|
||||
|
||||
"Der erste große Internet-Wurm hieß Morris Worm und verbreitete sich 1988.",
|
||||
|
||||
|
||||
# ---------------- COMPUTER-GESCHICHTE ----------------
|
||||
|
||||
"Der erste bekannte Computer-Bug war tatsächlich eine Motte. Sie wurde 1947 aus einem Relais eines Harvard-Mark-II-Computers entfernt.",
|
||||
|
||||
"Die erste Festplatte von IBM wurde 1956 vorgestellt.",
|
||||
|
||||
"Die IBM 350 Disk Storage Unit von 1956 konnte ungefähr 5 Megabyte speichern.",
|
||||
|
||||
"Die IBM 350 von 1956 wog ungefähr eine Tonne.",
|
||||
|
||||
"Ein modernes Smartphone besitzt mehr Rechenleistung als viele Computer aus der Apollo-Ära.",
|
||||
|
||||
"ENIAC wurde 1945 fertiggestellt und war einer der ersten großen elektronischen Universalcomputer.",
|
||||
|
||||
"ENIAC verwendete ungefähr 18.000 Elektronenröhren.",
|
||||
|
||||
"Die ersten Computer waren teilweise so groß, dass sie ganze Räume füllten.",
|
||||
|
||||
"Der Begriff Computer bezeichnete ursprünglich tatsächlich Menschen, die Berechnungen durchführten.",
|
||||
|
||||
"Konrad Zuse entwickelte mit der Z3 einen der ersten funktionsfähigen, programmierbaren Computer.",
|
||||
|
||||
"Die Z3 von Konrad Zuse wurde 1941 fertiggestellt.",
|
||||
|
||||
"Die Z3 verwendete Relais statt moderner Transistoren.",
|
||||
|
||||
"Der erste kommerziell erfolgreiche Mikroprozessor war der Intel 4004.",
|
||||
|
||||
"Der Intel 4004 erschien 1971.",
|
||||
|
||||
"Der Intel 4004 war ein 4-Bit-Prozessor.",
|
||||
|
||||
"Der Intel 4004 hatte nur etwa 2.300 Transistoren.",
|
||||
|
||||
"Moderne CPUs besitzen dagegen Milliarden von Transistoren.",
|
||||
|
||||
"Der Begriff CPU steht für Central Processing Unit.",
|
||||
|
||||
"RAM steht für Random Access Memory.",
|
||||
|
||||
"ROM steht für Read-Only Memory.",
|
||||
|
||||
|
||||
# ---------------- HARDWARE ----------------
|
||||
|
||||
"Eine moderne CPU kann Milliarden Transistoren enthalten.",
|
||||
|
||||
"Ein Transistor kann vereinfacht als elektronischer Schalter betrachtet werden.",
|
||||
|
||||
"Moderne Prozessoren bestehen aus Milliarden winziger Schalter.",
|
||||
|
||||
"CPUs werden häufig in Nanometern gefertigt. Diese Zahl beschreibt heute nicht einfach die tatsächliche Größe eines einzelnen Transistors.",
|
||||
|
||||
"Eine CPU mit 3 GHz hat einen Takt von ungefähr drei Milliarden Zyklen pro Sekunde.",
|
||||
|
||||
"GHz bedeutet Gigahertz.",
|
||||
|
||||
"1 Gigahertz entspricht einer Milliarde Hertz.",
|
||||
|
||||
"RAM verliert seinen Inhalt normalerweise, wenn die Stromversorgung abgeschaltet wird.",
|
||||
|
||||
"SSDs besitzen keine beweglichen mechanischen Schreib-Leseköpfe.",
|
||||
|
||||
"HDDs speichern Daten magnetisch auf rotierenden Scheiben.",
|
||||
|
||||
"Eine klassische Festplatte kann mehrere rotierende Magnetscheiben enthalten.",
|
||||
|
||||
"SSDs verwenden Flash-Speicher.",
|
||||
|
||||
"Flash-Speicher kann Daten auch ohne permanente Stromversorgung behalten.",
|
||||
|
||||
"USB steht für Universal Serial Bus.",
|
||||
|
||||
"USB wurde unter anderem entwickelt, um die große Anzahl verschiedener PC-Anschlüsse zu reduzieren.",
|
||||
|
||||
"USB-C beschreibt hauptsächlich einen Stecker und nicht automatisch die Geschwindigkeit oder Leistung eines Geräts.",
|
||||
|
||||
"Ein USB-C-Anschluss kann je nach Gerät USB 2, USB 3, USB4, Thunderbolt oder andere Funktionen unterstützen.",
|
||||
|
||||
"PCI Express wird häufig verwendet, um Grafikkarten und andere schnelle Erweiterungskarten anzubinden.",
|
||||
|
||||
"NVMe wurde speziell für schnelle Speichergeräte über PCI Express entwickelt.",
|
||||
|
||||
"Eine NVMe-SSD kommuniziert nicht einfach über SATA, sondern verwendet normalerweise PCI Express.",
|
||||
|
||||
|
||||
# ---------------- LINUX ----------------
|
||||
|
||||
"Linux wurde 1991 von Linus Torvalds begonnen.",
|
||||
|
||||
"Linux hieß ursprünglich nicht als fertiges Betriebssystem Linux. Linus Torvalds entwickelte zunächst den Kernel.",
|
||||
|
||||
"Android verwendet den Linux-Kernel.",
|
||||
|
||||
"Linux läuft auf Smartphones, Servern, Routern, Supercomputern, Fernsehern und vielen eingebetteten Geräten.",
|
||||
|
||||
"Ein großer Teil der weltweit leistungsstärksten Supercomputer verwendet Linux.",
|
||||
|
||||
"Der Linux-Kernel ist Open Source.",
|
||||
|
||||
"GNU und Linux sind technisch gesehen unterschiedliche Projekte.",
|
||||
|
||||
"Viele Linux-Distributionen verwenden GNU-Programme zusammen mit dem Linux-Kernel.",
|
||||
|
||||
"Arch Linux verwendet standardmäßig den Paketmanager pacman.",
|
||||
|
||||
"Der Linux-Befehl chmod kann die Zugriffsrechte einer Datei verändern.",
|
||||
|
||||
"Der Befehl ps kann laufende Prozesse anzeigen.",
|
||||
|
||||
"Der Befehl top kann laufende Prozesse und Systemauslastung anzeigen.",
|
||||
|
||||
"htop ist eine interaktive Alternative zu top.",
|
||||
|
||||
"Unter Linux wird nahezu alles als Datei oder über dateiähnliche Schnittstellen dargestellt.",
|
||||
|
||||
"Die Verzeichnisstruktur von Linux beginnt bei /.",
|
||||
|
||||
"Das Home-Verzeichnis eines Benutzers liegt normalerweise unter /home.",
|
||||
|
||||
"Der Linux-Kernel kann während des Betriebs Informationen über /proc bereitstellen.",
|
||||
|
||||
"Linux besitzt ein virtuelles Dateisystem namens sysfs.",
|
||||
|
||||
"Viele Hardwareinformationen können unter Linux über virtuelle Dateisysteme ausgelesen werden.",
|
||||
|
||||
"Ein Linux-System kann vollständig ohne grafische Oberfläche betrieben werden.",
|
||||
|
||||
|
||||
# ---------------- NETZWERK ----------------
|
||||
|
||||
"Ein Ethernet-Kabel kann Daten übertragen, obwohl die Daten im Kabel als elektrische Signale dargestellt werden.",
|
||||
|
||||
"WLAN steht für Wireless Local Area Network.",
|
||||
|
||||
"LAN steht für Local Area Network.",
|
||||
|
||||
"WAN steht für Wide Area Network.",
|
||||
|
||||
"MAC-Adressen sind normalerweise 48 Bit lang.",
|
||||
|
||||
"Eine IPv4-Adresse besteht aus 32 Bit.",
|
||||
|
||||
"Eine IPv6-Adresse besteht aus 128 Bit.",
|
||||
|
||||
"Ein /24-IPv4-Netz enthält theoretisch 256 Adressen.",
|
||||
|
||||
"Bei einem normalen /24-Netz sind klassischerweise zwei Adressen für Netzwerk und Broadcast reserviert.",
|
||||
|
||||
"127.0.0.1 bezeichnet normalerweise den eigenen Computer.",
|
||||
|
||||
"Die Adresse 0.0.0.0 kann je nach Kontext eine besondere Bedeutung haben, beispielsweise als nicht spezifizierte Adresse.",
|
||||
|
||||
"192.168.0.0/16 gehört zu den privaten IPv4-Adressbereichen.",
|
||||
|
||||
"10.0.0.0/8 gehört zu den privaten IPv4-Adressbereichen.",
|
||||
|
||||
"172.16.0.0/12 gehört zu den privaten IPv4-Adressbereichen.",
|
||||
|
||||
"Router verbinden unterschiedliche Netzwerke miteinander.",
|
||||
|
||||
"Switches verbinden Geräte innerhalb eines lokalen Netzwerks.",
|
||||
|
||||
"Ein Switch arbeitet hauptsächlich auf Layer 2 des OSI-Modells.",
|
||||
|
||||
"Router arbeiten hauptsächlich auf Layer 3 des OSI-Modells.",
|
||||
|
||||
"TCP ist verbindungsorientiert.",
|
||||
|
||||
"UDP ist verbindungslos und besitzt weniger Overhead als TCP.",
|
||||
|
||||
|
||||
# ---------------- PROGRAMMIERUNG ----------------
|
||||
|
||||
"Python wurde nach der Comedy-Gruppe Monty Python benannt und nicht nach einer Schlange.",
|
||||
|
||||
"Python wurde erstmals 1991 veröffentlicht.",
|
||||
|
||||
"C wurde in den frühen 1970er-Jahren bei Bell Labs entwickelt.",
|
||||
|
||||
"C++ entstand als Erweiterung der Programmiersprache C.",
|
||||
|
||||
"JavaScript und Java sind trotz ihrer Namen unterschiedliche Programmiersprachen.",
|
||||
|
||||
"HTML ist keine klassische Programmiersprache, sondern eine Auszeichnungssprache.",
|
||||
|
||||
"CSS wird hauptsächlich verwendet, um das Aussehen von Webseiten zu beschreiben.",
|
||||
|
||||
"SQL wird verwendet, um mit relationalen Datenbanken zu arbeiten.",
|
||||
|
||||
"Git wurde ursprünglich von Linus Torvalds entwickelt.",
|
||||
|
||||
"Git wurde ursprünglich für die Entwicklung des Linux-Kernels entwickelt.",
|
||||
|
||||
"Ein Compiler übersetzt Quellcode normalerweise in eine andere Form, beispielsweise Maschinencode oder Bytecode.",
|
||||
|
||||
"Ein Interpreter führt Code vereinfacht gesagt direkt bzw. über eine Laufzeitumgebung aus.",
|
||||
|
||||
"Ein Algorithmus ist eine eindeutig beschriebene Vorgehensweise zur Lösung eines Problems.",
|
||||
|
||||
"Ein Bit kann grundsätzlich zwei Zustände darstellen: 0 oder 1.",
|
||||
|
||||
"Ein Byte besteht normalerweise aus 8 Bit.",
|
||||
|
||||
"1 Kilobyte kann je nach Definition 1.000 oder 1.024 Bytes bedeuten.",
|
||||
|
||||
"1 Kibibyte entspricht exakt 1.024 Bytes.",
|
||||
|
||||
"1 Megabyte entspricht im SI-System 1.000.000 Bytes.",
|
||||
|
||||
"1 MiB entspricht exakt 1.048.576 Bytes.",
|
||||
|
||||
"Binärzahlen verwenden nur die Ziffern 0 und 1.",
|
||||
|
||||
|
||||
# ---------------- ZEIT / DATEN ----------------
|
||||
|
||||
"Unix-Zeit zählt normalerweise Sekunden seit dem 1. Januar 1970.",
|
||||
|
||||
"Der sogenannte Year-2038-Bug betrifft Systeme, die Unix-Zeit in einem signierten 32-Bit-Wert speichern.",
|
||||
|
||||
"Am 19. Januar 2038 erreicht ein klassischer signierter 32-Bit-Unix-Zeitstempel sein Maximum.",
|
||||
|
||||
"Ein 64-Bit-Unix-Zeitstempel kann für sehr lange Zeiträume verwendet werden.",
|
||||
|
||||
"Computer speichern Zeit häufig nicht so, wie Menschen sie auf einer Uhr sehen.",
|
||||
|
||||
"Zeitzonen machen Zeitberechnungen in Computersystemen deutlich komplizierter.",
|
||||
|
||||
"UTC steht für Coordinated Universal Time.",
|
||||
|
||||
"NTP kann verwendet werden, um Computeruhren über Netzwerke zu synchronisieren.",
|
||||
|
||||
"GPS-Satelliten benötigen extrem genaue Zeitmessung.",
|
||||
|
||||
"Schaltsekunden können bei Computersystemen zu unerwarteten Problemen führen.",
|
||||
|
||||
|
||||
# ---------------- SICHERHEIT ----------------
|
||||
|
||||
"Ein Passwort muss nicht nur lang, sondern auch möglichst einzigartig sein.",
|
||||
|
||||
"Passwort-Hashing ist etwas anderes als Verschlüsselung.",
|
||||
|
||||
"Ein guter Passwort-Hash soll nicht einfach wieder in das ursprüngliche Passwort zurückgerechnet werden können.",
|
||||
|
||||
"Salts werden beim Passwort-Hashing verwendet, damit identische Passwörter nicht denselben Hash erzeugen.",
|
||||
|
||||
"HTTPS bedeutet nicht automatisch, dass eine Website vertrauenswürdig ist. Es schützt hauptsächlich die Verbindung.",
|
||||
|
||||
"Ein Vorhängeschloss im Browser bedeutet nicht automatisch, dass eine Website seriös ist.",
|
||||
|
||||
"Zwei-Faktor-Authentifizierung kann einen zusätzlichen Schutz bieten, selbst wenn ein Passwort bekannt wird.",
|
||||
|
||||
"SSH steht für Secure Shell.",
|
||||
|
||||
"SSH wird häufig verwendet, um Linux-Server remote zu verwalten.",
|
||||
|
||||
"Ein SSH-Schlüssel kann zur Authentifizierung verwendet werden, ohne jedes Mal ein Passwort einzugeben.",
|
||||
|
||||
|
||||
# ---------------- DATEIEN / SPEICHER ----------------
|
||||
|
||||
"Eine Datei mit der Endung .jpg muss nicht zwingend ein Bild sein. Die Endung ist nur eine Namenskonvention.",
|
||||
|
||||
"ZIP-Dateien können mehrere Dateien und Verzeichnisse in einem Archiv zusammenfassen.",
|
||||
|
||||
"Kompression kann Daten kleiner machen, ohne sie zu verändern, oder Informationen entfernen, um noch kleinere Dateien zu erzeugen.",
|
||||
|
||||
"PNG verwendet verlustfreie Kompression.",
|
||||
|
||||
"JPEG verwendet normalerweise verlustbehaftete Kompression.",
|
||||
|
||||
"FLAC ist ein verlustfreies Audioformat.",
|
||||
|
||||
"MP3 verwendet verlustbehaftete Audiokompression.",
|
||||
|
||||
"Eine Datei kann gelöscht werden, obwohl ihre Daten auf einem Datenträger noch nicht sofort überschrieben wurden.",
|
||||
|
||||
"Dateisysteme speichern zusätzliche Informationen wie Dateigröße, Besitzer und Änderungszeit.",
|
||||
|
||||
"ext4 ist ein weit verbreitetes Linux-Dateisystem.",
|
||||
|
||||
|
||||
# ---------------- WEIRD / FUN ----------------
|
||||
|
||||
"Es gibt einen offiziellen Internetstandard für die Übertragung von IP-Paketen mit Brieftauben.",
|
||||
|
||||
"Ein Computer kann theoretisch eine komplette Festplatte mit Nullen überschreiben, obwohl diese Nullen keine sichtbaren Informationen enthalten.",
|
||||
|
||||
"Der Name CAPTCHA steht für Completely Automated Public Turing test to tell Computers and Humans Apart.",
|
||||
|
||||
"CAPTCHAs wurden entwickelt, um Menschen und automatisierte Programme voneinander zu unterscheiden.",
|
||||
|
||||
"QR-Codes können auch dann noch funktionieren, wenn ein Teil des Codes beschädigt ist.",
|
||||
|
||||
"QR-Codes wurden ursprünglich für die Automobilindustrie entwickelt.",
|
||||
|
||||
"Der erste Computermonitor war noch weit von heutigen 4K-Displays entfernt.",
|
||||
|
||||
"Frühe Computerspiele konnten mit wenigen Kilobyte auskommen.",
|
||||
|
||||
"Das Spiel DOOM von 1993 war klein genug, dass seine Größe nach heutigen Maßstäben geradezu lächerlich wirkt.",
|
||||
|
||||
"Tetris wurde ursprünglich 1984 von Alexei Paschitnow entwickelt.",
|
||||
|
||||
"Der Game-Boy-Prozessor lief nur mit wenigen Megahertz.",
|
||||
|
||||
"Moderne Grafikkarten besitzen tausende Recheneinheiten.",
|
||||
|
||||
"Eine GPU kann viele Berechnungen parallel durchführen und ist deshalb besonders gut für bestimmte mathematische Aufgaben geeignet.",
|
||||
|
||||
"Minecraft-Welten können extrem große Koordinatenbereiche besitzen.",
|
||||
|
||||
"Ein Pixel ist ein einzelnes Bildelement eines digitalen Bildes.",
|
||||
|
||||
"4K bedeutet bei Video nicht immer exakt dieselbe Auflösung. Im TV-Bereich ist 3840×2160 üblich.",
|
||||
|
||||
"Ein 1080p-Bild besitzt ungefähr zwei Millionen Pixel.",
|
||||
|
||||
"Ein 4K-UHD-Bild besitzt ungefähr acht Millionen Pixel.",
|
||||
|
||||
"Ein 8K-UHD-Bild besitzt ungefähr 33 Millionen Pixel.",
|
||||
|
||||
|
||||
# ---------------- WEITERE TECH-FACTS ----------------
|
||||
|
||||
"Bluetooth und WLAN verwenden beide Funktechnik, sind aber für unterschiedliche Einsatzzwecke optimiert.",
|
||||
|
||||
"Bluetooth wurde ursprünglich nach Harald Blauzahn, einem dänischen König, benannt.",
|
||||
|
||||
"Das Bluetooth-Logo kombiniert Runen, die für Harald Blauzahn stehen.",
|
||||
|
||||
"ASCII verwendet ursprünglich 7 Bit pro Zeichen.",
|
||||
|
||||
"ASCII kann deshalb 128 verschiedene Werte darstellen.",
|
||||
|
||||
"Unicode wurde entwickelt, um Zeichen aus sehr vielen Schriftsystemen darstellen zu können.",
|
||||
|
||||
"UTF-8 kann ASCII-Zeichen direkt darstellen.",
|
||||
|
||||
"UTF-8 ist heute eine der wichtigsten Zeichenkodierungen im Internet.",
|
||||
|
||||
"Ein Emoji ist technisch gesehen nicht einfach nur ein einzelnes Byte.",
|
||||
|
||||
"Die Zahl 255 ist in Binärdarstellung 11111111.",
|
||||
|
||||
"Die Zahl 256 ist 2 hoch 8.",
|
||||
|
||||
"Ein 32-Bit-Wert kann 2 hoch 32 verschiedene Bitmuster darstellen.",
|
||||
|
||||
"Ein 64-Bit-Wert kann 2 hoch 64 verschiedene Bitmuster darstellen.",
|
||||
|
||||
"2 hoch 64 ist 18.446.744.073.709.551.616.",
|
||||
|
||||
"Ein einzelnes Bit ist die kleinste klassische Informationseinheit in einem Binärsystem.",
|
||||
|
||||
"Computer verwenden Binärlogik, weil elektronische Schaltungen besonders gut zwischen zwei Zuständen unterscheiden können.",
|
||||
|
||||
"Ein CPU-Cache ist deutlich schneller als der normale Arbeitsspeicher, aber auch wesentlich kleiner.",
|
||||
|
||||
"L1-Cache ist normalerweise kleiner und schneller als L2-Cache.",
|
||||
|
||||
"L3-Cache ist normalerweise größer und langsamer als L1-Cache.",
|
||||
|
||||
"Moderne CPUs können mehrere Cache-Ebenen besitzen.",
|
||||
|
||||
|
||||
# ---------------- DYNAMIC FACTS ----------------
|
||||
|
||||
"Seit dem Start des Unix-Zeitstempels am 1. Januar 1970 sind bereits {unix_days} Tage vergangen.",
|
||||
|
||||
"Seit der ersten E-Mail von Ray Tomlinson sind ungefähr {email_days} Tage vergangen.",
|
||||
|
||||
"Die erste E-Mail wurde 1971 verschickt. Das ist inzwischen mehr als ein halbes Jahrhundert her.",
|
||||
|
||||
"Der Unix-Zeitstempel startete am 1. Januar 1970. Heute sind bereits {unix_seconds} Sekunden seit diesem Zeitpunkt vergangen.",
|
||||
|
||||
"Bis zum Year-2038-Problem sind es noch ungefähr {days_until_2038} Tage.",
|
||||
|
||||
"Der erste Webserver wurde am CERN betrieben. Das World Wide Web ist inzwischen Jahrzehnte alt.",
|
||||
|
||||
]
|
||||
|
||||
|
||||
# ============================================================
|
||||
# DYNAMISCHE FAKTEN
|
||||
# ============================================================
|
||||
|
||||
def replace_dynamic(text):
|
||||
|
||||
now = datetime.now()
|
||||
|
||||
# Unix-Tage
|
||||
unix_start = datetime(1970, 1, 1)
|
||||
unix_seconds = int((now - unix_start).total_seconds())
|
||||
unix_days = (now - unix_start).days
|
||||
|
||||
# Erste E-Mail
|
||||
# Ray Tomlinsons erste Netzwerknachricht wird meist auf 1971 datiert.
|
||||
email_date = datetime(1971, 10, 29)
|
||||
email_days = (now - email_date).days
|
||||
|
||||
# Year 2038
|
||||
y2038 = datetime(2038, 1, 19, 3, 14, 7)
|
||||
days_until_2038 = (y2038 - now).days
|
||||
|
||||
replacements = {
|
||||
"{unix_seconds}": f"{unix_seconds:,}".replace(",", "."),
|
||||
"{unix_days}": f"{unix_days:,}".replace(",", "."),
|
||||
"{email_days}": f"{email_days:,}".replace(",", "."),
|
||||
"{days_until_2038}": f"{days_until_2038:,}".replace(",", "."),
|
||||
}
|
||||
|
||||
for placeholder, value in replacements.items():
|
||||
text = text.replace(placeholder, value)
|
||||
|
||||
return text
|
||||
|
||||
|
||||
# ============================================================
|
||||
# HISTORY LADEN
|
||||
# ============================================================
|
||||
|
||||
def load_history():
|
||||
|
||||
if not os.path.exists(HISTORY_FILE):
|
||||
return []
|
||||
|
||||
try:
|
||||
with open(HISTORY_FILE, "r", encoding="utf-8") as file:
|
||||
data = json.load(file)
|
||||
|
||||
if isinstance(data, list):
|
||||
return data
|
||||
|
||||
except (json.JSONDecodeError, OSError):
|
||||
pass
|
||||
|
||||
return []
|
||||
|
||||
|
||||
# ============================================================
|
||||
# HISTORY SPEICHERN
|
||||
# ============================================================
|
||||
|
||||
def save_history(history):
|
||||
|
||||
try:
|
||||
with open(HISTORY_FILE, "w", encoding="utf-8") as file:
|
||||
json.dump(
|
||||
history,
|
||||
file,
|
||||
ensure_ascii=False,
|
||||
indent=2
|
||||
)
|
||||
|
||||
except OSError as error:
|
||||
print(f"Fehler beim Speichern der History: {error}")
|
||||
|
||||
|
||||
# ============================================================
|
||||
# NOTIFICATION
|
||||
# ============================================================
|
||||
|
||||
def notify(text):
|
||||
|
||||
try:
|
||||
|
||||
subprocess.run(
|
||||
[
|
||||
"notify-send",
|
||||
"IT Funfact",
|
||||
text
|
||||
],
|
||||
check=False
|
||||
)
|
||||
|
||||
except FileNotFoundError:
|
||||
|
||||
print(
|
||||
"FEHLER: notify-send wurde nicht gefunden."
|
||||
)
|
||||
|
||||
except Exception as error:
|
||||
|
||||
print(
|
||||
f"Fehler beim Anzeigen der Notification: {error}"
|
||||
)
|
||||
|
||||
|
||||
# ============================================================
|
||||
# ZUFÄLLIGE WARTEZEIT
|
||||
# ============================================================
|
||||
|
||||
def random_wait():
|
||||
|
||||
# Exponentialverteilung:
|
||||
#
|
||||
# Durchschnitt = 6 Stunden
|
||||
#
|
||||
# Dadurch entstehen absichtlich sehr unterschiedliche
|
||||
# Abstände.
|
||||
#
|
||||
# Beispiel:
|
||||
# 20 Minuten
|
||||
# 2 Stunden
|
||||
# 7 Stunden
|
||||
# 13 Stunden
|
||||
# usw.
|
||||
#
|
||||
# Extrem lange/kürzere Abstände sind möglich.
|
||||
|
||||
average_seconds = AVERAGE_HOURS * 60 * 60
|
||||
|
||||
seconds = random.expovariate(
|
||||
1 / average_seconds
|
||||
)
|
||||
|
||||
minimum_seconds = MIN_WAIT_MINUTES * 60
|
||||
|
||||
return max(
|
||||
seconds,
|
||||
minimum_seconds
|
||||
)
|
||||
|
||||
|
||||
# ============================================================
|
||||
# EINEN ZUFÄLLIGEN FACT AUSWÄHLEN
|
||||
# ============================================================
|
||||
|
||||
def choose_fact(history):
|
||||
|
||||
# Bereits angezeigte Facts vermeiden
|
||||
available = [
|
||||
fact
|
||||
for fact in FUNFACTS
|
||||
if fact not in history
|
||||
]
|
||||
|
||||
# Wenn alle Facts irgendwann angezeigt wurden:
|
||||
# History zurücksetzen.
|
||||
if not available:
|
||||
|
||||
print(
|
||||
"Alle Funfacts wurden bereits angezeigt."
|
||||
)
|
||||
|
||||
history.clear()
|
||||
|
||||
available = FUNFACTS
|
||||
|
||||
return random.choice(available)
|
||||
|
||||
|
||||
# ============================================================
|
||||
# HAUPTPROGRAMM
|
||||
# ============================================================
|
||||
|
||||
def main():
|
||||
|
||||
print()
|
||||
print("=" * 60)
|
||||
print(" IT FUNFACTS")
|
||||
print("=" * 60)
|
||||
print()
|
||||
print(f"Funfacts insgesamt: {len(FUNFACTS)}")
|
||||
print(f"Durchschnittliches Intervall: {AVERAGE_HOURS} Stunden")
|
||||
print(f"History: {HISTORY_FILE}")
|
||||
print()
|
||||
print("Programm läuft...")
|
||||
print("Mit Ctrl+C beenden.")
|
||||
print()
|
||||
|
||||
history = load_history()
|
||||
|
||||
try:
|
||||
|
||||
while True:
|
||||
|
||||
wait_time = random_wait()
|
||||
|
||||
print(
|
||||
f"Nächster Funfact in "
|
||||
f"{wait_time / 3600:.2f} Stunden."
|
||||
)
|
||||
|
||||
time.sleep(wait_time)
|
||||
|
||||
fact = choose_fact(history)
|
||||
|
||||
fact = replace_dynamic(fact)
|
||||
|
||||
print()
|
||||
print("FUNFACT:")
|
||||
print(fact)
|
||||
print()
|
||||
|
||||
notify(fact)
|
||||
|
||||
# Den fertigen Text speichern.
|
||||
# Dadurch wird auch verhindert,
|
||||
# dass derselbe dynamische Fact direkt
|
||||
# wieder erscheint.
|
||||
history.append(fact)
|
||||
|
||||
# History begrenzen
|
||||
history = history[-HISTORY_SIZE:]
|
||||
|
||||
save_history(history)
|
||||
|
||||
except KeyboardInterrupt:
|
||||
|
||||
print()
|
||||
print("Funfacts beendet.")
|
||||
print()
|
||||
|
||||
|
||||
# ============================================================
|
||||
# START
|
||||
# ============================================================
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,2 @@
|
||||
[mzsh: command not found: dig
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
#!/usr/bin/env python3
|
||||
print("test")
|
||||
print("main.py wurde gestartet")
|
||||
@@ -0,0 +1,46 @@
|
||||
MYRUMMY CLI
|
||||
|
||||
Start:
|
||||
python main.py
|
||||
|
||||
Beim Start werden zwei Einstellungen abgefragt:
|
||||
- 13-1-Umlauf: Folgen dürfen über 13 hinweg weiterlaufen (z.B. 12,13,1)
|
||||
- Erstmeldung 30: Für die erste Meldung werden 30 Punkte gebraucht
|
||||
|
||||
Nach jedem beendeten Zug erscheint ein Übergabe-Bildschirm, damit der
|
||||
nächste Spieler die Karten des Vorgängers nicht sieht.
|
||||
|
||||
Farben sind absichtlich nur ANSI-Farben + Zahlen:
|
||||
Rot / Gelb / Grün / Blau
|
||||
Joker = J
|
||||
|
||||
Der Tisch darf während eines Zuges beliebig umgebaut werden. Erst `finish`
|
||||
prüft alle Gruppen. Dadurch muss der Editor keine gültigen Zwischenzustände
|
||||
erzwingen.
|
||||
|
||||
Befehle:
|
||||
m H G Handkarte H in Tischgruppe G
|
||||
m H new Handkarte H in neue Gruppe
|
||||
t G C Tischkarte C aus Gruppe G zurück in Hand
|
||||
n neue leere Gruppe
|
||||
a G1 G2 Gruppen verbinden
|
||||
s G POS Gruppe nach POS teilen
|
||||
d Karte ziehen
|
||||
u letzte Änderung rückgängig
|
||||
sort Hand sortieren
|
||||
finish Zug prüfen/beenden
|
||||
q beenden
|
||||
|
||||
Regeln der Implementierung:
|
||||
- 2 komplette Kartensätze
|
||||
- 4 Farben, Werte 1-13
|
||||
- 4 Joker
|
||||
- Satz: gleiche Zahl, unterschiedliche Farben, 3-4 Karten
|
||||
- Folge: gleiche Farbe, aufsteigend, 3+ Karten
|
||||
- Joker dürfen in Folgen Lücken füllen
|
||||
- Erstmeldung: mindestens 30 Punkte
|
||||
- Wer keine Handkarten mehr hat, gewinnt
|
||||
|
||||
Hinweis: Das ist eine bewusst einfache CLI-Version. Die Regeln für Sonderfälle
|
||||
wie Joker-Rücknahme, 1-13-Runden, Punktwertung nach Spielende usw. können später
|
||||
noch ergänzt werden.
|
||||
@@ -0,0 +1,75 @@
|
||||
from dataclasses import dataclass
|
||||
|
||||
RESET='\033[0m'
|
||||
COLORS={'r':'\033[31m','y':'\033[33m','g':'\033[32m','b':'\033[34m'}
|
||||
COLOR_ORDER={'r':0,'y':1,'g':2,'b':3}
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class Card:
|
||||
color: str|None
|
||||
value: int|None
|
||||
joker: bool=False
|
||||
uid: int=0
|
||||
|
||||
def text(self):
|
||||
if self.joker:
|
||||
return f'{COLORS["r"]}J{RESET}'
|
||||
return f'{COLORS[self.color]}{self.value}{RESET}'
|
||||
|
||||
def plain(self):
|
||||
return 'J' if self.joker else f'{self.color}{self.value}'
|
||||
|
||||
|
||||
def deck():
|
||||
cards=[]
|
||||
uid=0
|
||||
for _ in range(2):
|
||||
for c in COLORS:
|
||||
for n in range(1,14):
|
||||
cards.append(Card(c,n,False,uid)); uid+=1
|
||||
for _ in range(2):
|
||||
cards.append(Card(None,None,True,uid)); uid+=1
|
||||
return cards
|
||||
|
||||
|
||||
def points(card):
|
||||
return 30 if card.joker else card.value
|
||||
|
||||
|
||||
def valid_group(cards):
|
||||
if not 3 <= len(cards) <= 4: return False
|
||||
normal=[c for c in cards if not c.joker]
|
||||
jok=len(cards)-len(normal)
|
||||
if not normal: return False
|
||||
return len({c.value for c in normal})==1 and len({c.color for c in normal})==len(normal) and len(normal)+jok>=3
|
||||
|
||||
|
||||
def valid_run(cards, wrap=False):
|
||||
if not 3 <= len(cards) <= 13: return False
|
||||
normal=sorted((c for c in cards if not c.joker), key=lambda x:x.value)
|
||||
jok=len(cards)-len(normal)
|
||||
if not normal or len({c.color for c in normal}) != 1: return False
|
||||
if len({c.value for c in normal}) != len(normal): return False
|
||||
if not wrap:
|
||||
gaps=sum(b.value-a.value-1 for a,b in zip(normal,normal[1:]))
|
||||
return gaps <= jok and normal[-1].value-normal[0].value+1+jok <= 13
|
||||
# 13-1-Umlauf: Werte liegen auf einem Kreis 1..13, die Folge ist ein
|
||||
# zusammenhängender Bogen. Alle Lücken außer der größten müssen durch
|
||||
# Joker gefüllt werden.
|
||||
gaps=[b.value-a.value-1 for a,b in zip(normal,normal[1:])]
|
||||
gaps.append(normal[0].value+13-normal[-1].value-1)
|
||||
return 13-len(normal)-jok <= max(gaps)
|
||||
|
||||
|
||||
def valid(cards, wrap=False):
|
||||
return valid_group(cards) or valid_run(cards, wrap)
|
||||
|
||||
|
||||
def kind(cards, wrap=False):
|
||||
if valid_run(cards, wrap): return 'Folge'
|
||||
if valid_group(cards): return 'Satz'
|
||||
return 'UNGÜLTIG'
|
||||
|
||||
|
||||
def sort_hand(cards):
|
||||
return sorted(cards,key=lambda c:(c.joker, COLOR_ORDER.get(c.color,99), c.value or 99))
|
||||
+159
@@ -0,0 +1,159 @@
|
||||
import copy, random
|
||||
from cards import *
|
||||
|
||||
def yes_no(q, default=None):
|
||||
while True:
|
||||
a=input(q+' (j/n): ').strip().lower()
|
||||
if a in ('j','ja','y','yes'): return True
|
||||
if a in ('n','nein','no'): return False
|
||||
if not a and default is not None: return default
|
||||
print(' Bitte j oder n eingeben.')
|
||||
|
||||
class Player:
|
||||
def __init__(self,name):
|
||||
self.name=name
|
||||
self.hand=[]
|
||||
self.opened=False
|
||||
|
||||
class Game:
|
||||
def __init__(self):
|
||||
print('EINSTELLUNGEN')
|
||||
print(' ~~~~~~~~~~~~')
|
||||
self.wrap=yes_no(' 13-1-Umlauf erlauben (z.B. 12,13,1)')
|
||||
self.min30=yes_no(' Braucht die Erstmeldung 30 Punkte')
|
||||
self.players=[Player('Spieler 1'),Player('Spieler 2')]
|
||||
self.turn=0
|
||||
self.table=[]
|
||||
self.deck=deck(); random.shuffle(self.deck)
|
||||
self.history=[]
|
||||
self.drew=False
|
||||
self.handover=False
|
||||
for p in self.players:
|
||||
p.hand=sort_hand([self.deck.pop() for _ in range(14)])
|
||||
|
||||
@property
|
||||
def p(self): return self.players[self.turn]
|
||||
|
||||
def save(self):
|
||||
self.history.append(copy.deepcopy((self.players,self.table,self.deck,self.drew)))
|
||||
|
||||
def undo(self):
|
||||
if not self.history:
|
||||
print('Nichts zum Rückgängigmachen.'); return
|
||||
self.players,self.table,self.deck,self.drew=self.history.pop()
|
||||
|
||||
def clear(self): print('\033[2J\033[H',end='')
|
||||
|
||||
def show(self):
|
||||
self.clear()
|
||||
print('╔════════════════════════════════════════════════════╗')
|
||||
print('║ MYRUMMY ║')
|
||||
print('╚════════════════════════════════════════════════════╝')
|
||||
print(f' Am Zug: {self.p.name} Karten im Stapel: {len(self.deck)}')
|
||||
print(f' Regeln: 13-1-Umlauf: {"ja" if self.wrap else "nein"} Erstmeldung 30: {"ja" if self.min30 else "nein"}')
|
||||
print(f' Erstmeldung: {"ja" if self.p.opened else "nein"}')
|
||||
print('\nTISCH')
|
||||
if not self.table: print(' (leer)')
|
||||
for i,g in enumerate(self.table,1):
|
||||
print(f' {i:>2}: ' + ' '.join(f'{j}:{c.text()}' for j,c in enumerate(g,1)) + f' [{kind(g, self.wrap)}]')
|
||||
print('\nHAND')
|
||||
lines=[]; key=None
|
||||
for i,c in enumerate(self.p.hand,1):
|
||||
k='J' if c.joker else c.color
|
||||
if k!=key: lines.append([]); key=k
|
||||
lines[-1].append(f'{i}:{c.text()}')
|
||||
for l in lines: print(' '+ ' '.join(l))
|
||||
print('\nBEFEHLE')
|
||||
print(' m H G Handkarte H -> Gruppe G')
|
||||
print(' m H new Handkarte H -> neue Gruppe')
|
||||
print(' t G C Tischkarte C aus Gruppe G -> Hand')
|
||||
print(' n leere neue Gruppe')
|
||||
print(' a G1 G2 Gruppen verbinden')
|
||||
print(' s G POS Gruppe nach POS teilen')
|
||||
print(' d ziehen')
|
||||
print(' u undo')
|
||||
print(' sort Hand sortieren')
|
||||
print(' finish Zug prüfen/beenden')
|
||||
print(' q beenden')
|
||||
|
||||
def idx(self,x,maxi):
|
||||
n=int(x)
|
||||
if not 1<=n<=maxi: raise ValueError('Nummer außerhalb des gültigen Bereichs.')
|
||||
return n-1
|
||||
|
||||
def move_hand(self,h,g):
|
||||
hi=self.idx(h,len(self.p.hand)); self.save()
|
||||
card=self.p.hand.pop(hi)
|
||||
if g.lower()=='new': self.table.append([card])
|
||||
else: self.table[self.idx(g,len(self.table))].append(card)
|
||||
|
||||
def move_table(self,g,c):
|
||||
gi=self.idx(g,len(self.table)); ci=self.idx(c,len(self.table[gi])); self.save()
|
||||
self.p.hand.append(self.table[gi].pop(ci))
|
||||
if not self.table[gi]: self.table.pop(gi)
|
||||
self.p.hand=sort_hand(self.p.hand)
|
||||
|
||||
def merge(self,a,b):
|
||||
ai=self.idx(a,len(self.table)); bi=self.idx(b,len(self.table))
|
||||
if ai==bi: raise ValueError('Gleiche Gruppe.')
|
||||
self.save(); self.table[ai]+=self.table[bi]; self.table.pop(bi)
|
||||
|
||||
def split(self,g,pos):
|
||||
gi=self.idx(g,len(self.table)); pos=int(pos)
|
||||
if not 1<=pos<len(self.table[gi]): raise ValueError('Teilposition ungültig.')
|
||||
self.save(); x=self.table[gi]; self.table[gi]=x[:pos]; self.table.insert(gi+1,x[pos:])
|
||||
|
||||
def draw(self):
|
||||
if self.drew: raise ValueError('Du hast bereits gezogen.')
|
||||
if not self.deck: raise ValueError('Stapel leer.')
|
||||
self.save(); self.p.hand.append(self.deck.pop()); self.p.hand=sort_hand(self.p.hand); self.drew=True
|
||||
|
||||
def finish(self):
|
||||
if any(not g or not valid(g, self.wrap) for g in self.table):
|
||||
print('Zug ungültig: Es gibt eine ungültige Tischgruppe.')
|
||||
return False
|
||||
if not self.p.opened:
|
||||
if self.min30:
|
||||
total=sum(sum(points(c) for c in g) for g in self.table)
|
||||
if total<30:
|
||||
print(f'Erstmeldung braucht mindestens 30 Punkte. Aktuell: {total}')
|
||||
return False
|
||||
self.p.opened=True
|
||||
if not self.p.hand:
|
||||
print(f'\n{self.p.name} gewinnt!')
|
||||
return True
|
||||
self.turn=1-self.turn; self.drew=False; self.history=[]
|
||||
self.handover=True
|
||||
return False
|
||||
|
||||
def command(self,line):
|
||||
a=line.split()
|
||||
if not a:return False
|
||||
try:
|
||||
cmd=a[0].lower()
|
||||
if cmd=='m' and len(a)==3:self.move_hand(a[1],a[2])
|
||||
elif cmd=='t' and len(a)==3:self.move_table(a[1],a[2])
|
||||
elif cmd=='n':self.save();self.table.append([])
|
||||
elif cmd=='a' and len(a)==3:self.merge(a[1],a[2])
|
||||
elif cmd=='s' and len(a)==3:self.split(a[1],a[2])
|
||||
elif cmd=='d':self.draw()
|
||||
elif cmd=='u':self.undo()
|
||||
elif cmd=='sort':self.p.hand=sort_hand(self.p.hand)
|
||||
elif cmd=='finish':return self.finish()
|
||||
elif cmd in ('q','quit','exit'):raise SystemExit
|
||||
elif cmd=='show':pass
|
||||
else: raise ValueError('Unbekannter Befehl.')
|
||||
except (ValueError,IndexError) as e: print('Fehler:',e)
|
||||
input('\nENTER...')
|
||||
return False
|
||||
|
||||
def run(self):
|
||||
while True:
|
||||
self.show()
|
||||
if self.command(input('\n> ')): return
|
||||
if self.handover:
|
||||
self.handover=False
|
||||
self.clear()
|
||||
print(f'\n Zug beendet. Bitte gib den Computer an {self.p.name} weiter,')
|
||||
print(' damit der andere Spieler deine Karten nicht sehen kann.')
|
||||
input(' ENTER drücken, wenn du bereit bist...')
|
||||
@@ -0,0 +1,4 @@
|
||||
from game import Game
|
||||
|
||||
if __name__ == '__main__':
|
||||
Game().run()
|
||||
@@ -0,0 +1,646 @@
|
||||
#!/usr/bin/env python3
|
||||
"""SJN File Manager - a TUI file manager.
|
||||
|
||||
Keys:
|
||||
Up/Down or k/j Navigate
|
||||
Enter Open (folder -> cd, file -> menu)
|
||||
/ Search (live-filter)
|
||||
Backspace or h Go to parent directory
|
||||
. Toggle hidden files
|
||||
q Quit
|
||||
"""
|
||||
|
||||
import curses
|
||||
import os
|
||||
import shlex
|
||||
import subprocess
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
PARENT = Path("..")
|
||||
|
||||
|
||||
# ---------- helpers ---------------------------------------------------------
|
||||
|
||||
|
||||
FILE_CATEGORIES = {
|
||||
"Bilder": {
|
||||
".jpg", ".jpeg", ".png", ".gif", ".webp", ".bmp", ".svg"
|
||||
},
|
||||
"Videos": {
|
||||
".mp4", ".mkv", ".avi", ".mov", ".webm"
|
||||
},
|
||||
"Musik": {
|
||||
".mp3", ".wav", ".flac", ".ogg", ".m4a"
|
||||
},
|
||||
"Dokumente": {
|
||||
".pdf", ".doc", ".docx", ".odt", ".txt", ".rtf"
|
||||
},
|
||||
"Tabellen": {
|
||||
".xls", ".xlsx", ".ods", ".csv"
|
||||
},
|
||||
"Präsentationen": {
|
||||
".ppt", ".pptx", ".odp"
|
||||
},
|
||||
"Code": {
|
||||
".py", ".js", ".ts", ".java", ".c", ".cpp", ".h",
|
||||
".rs", ".go", ".php", ".sh"
|
||||
},
|
||||
"Archive": {
|
||||
".zip", ".tar", ".gz", ".bz2", ".xz", ".7z", ".rar"
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
|
||||
def get_category(filename):
|
||||
extension = Path(filename).suffix.lower()
|
||||
|
||||
for category, extensions in FILE_CATEGORIES.items():
|
||||
if extension in extensions:
|
||||
return category
|
||||
|
||||
return "Sonstige"
|
||||
|
||||
|
||||
|
||||
def human_size(n: int) -> str:
|
||||
"""Return a human-readable file size."""
|
||||
units = ["B", "K", "M", "G", "T"]
|
||||
size = float(n)
|
||||
for unit in units:
|
||||
if size < 1024 or unit == units[-1]:
|
||||
if unit == "B":
|
||||
return f"{int(size)}{unit}"
|
||||
return f"{size:.1f}{unit}"
|
||||
size /= 1024
|
||||
return f"{int(size)}T"
|
||||
|
||||
|
||||
def list_entries(cwd: Path, show_hidden: bool):
|
||||
"""Return [.., dir1, dir2, ..., file1, file2, ...]."""
|
||||
try:
|
||||
all_entries = list(cwd.iterdir())
|
||||
except PermissionError:
|
||||
return [PARENT]
|
||||
|
||||
if not show_hidden:
|
||||
all_entries = [e for e in all_entries if not e.name.startswith(".")]
|
||||
|
||||
dirs = sorted(
|
||||
(e for e in all_entries if e.is_dir(follow_symlinks=False)),
|
||||
key=lambda e: e.name.lower(),
|
||||
)
|
||||
files = sorted(
|
||||
(e for e in all_entries if e.is_file(follow_symlinks=False)),
|
||||
key=lambda e: e.name.lower(),
|
||||
)
|
||||
return [PARENT] + dirs + files
|
||||
|
||||
|
||||
def is_parent(entry: Path) -> bool:
|
||||
return entry == PARENT
|
||||
|
||||
|
||||
def format_entry(name: str, is_dir: bool, full_path: Path, width: int) -> str:
|
||||
"""Format an entry for display, padded to width."""
|
||||
if is_dir:
|
||||
label = f"[D] {name}/"
|
||||
else:
|
||||
try:
|
||||
label = f"[F] {name} ({human_size(full_path.stat().st_size)})"
|
||||
except OSError:
|
||||
label = f"[F] {name}"
|
||||
return label[:width].ljust(width)
|
||||
|
||||
|
||||
# ---------- input ------------------------------------------------------------
|
||||
|
||||
def prompt_line(stdscr, prompt: str, initial: str = "") -> str | None:
|
||||
"""Show a prompt at the bottom and read a line. Returns None if cancelled (ESC).
|
||||
|
||||
Modes are restored in `finally` so a curses.error or any other exception
|
||||
cannot leave the terminal in a half-configured state.
|
||||
"""
|
||||
max_y, max_x = stdscr.getmaxyx()
|
||||
row = max_y - 2
|
||||
|
||||
curses.curs_set(1)
|
||||
curses.echo()
|
||||
# Keep keypad ON so arrow keys arrive as KEY_* constants instead of an
|
||||
# ESC prefix that would otherwise be mis-read as "user pressed cancel".
|
||||
try:
|
||||
stdscr.addstr(row, 0, prompt)
|
||||
stdscr.clrtoeol()
|
||||
stdscr.refresh()
|
||||
|
||||
buf = initial
|
||||
while True:
|
||||
ch = stdscr.getch()
|
||||
if ch == 27: # bare ESC (no following escape-sequence bytes)
|
||||
return None
|
||||
if ch in (10, 13, curses.KEY_ENTER):
|
||||
break
|
||||
if ch in (curses.KEY_BACKSPACE, 127, 8):
|
||||
if buf:
|
||||
buf = buf[:-1]
|
||||
elif 0 <= ch < 256:
|
||||
# Plain ASCII printable; safe to append.
|
||||
buf += chr(ch)
|
||||
# Arrow keys, function keys, etc. are silently ignored.
|
||||
|
||||
stdscr.addstr(row, 0, prompt + (buf or ""))
|
||||
stdscr.clrtoeol()
|
||||
stdscr.refresh()
|
||||
|
||||
return buf
|
||||
finally:
|
||||
curses.noecho()
|
||||
curses.curs_set(0)
|
||||
try:
|
||||
stdscr.touchwin()
|
||||
stdscr.refresh()
|
||||
except curses.error:
|
||||
pass
|
||||
|
||||
|
||||
# ---------- rendering -------------------------------------------------------
|
||||
|
||||
def init_colors() -> dict[str, int]:
|
||||
"""Initialize color pairs and return a mapping of name -> pair id."""
|
||||
pairs = {
|
||||
"dir": 1,
|
||||
"file": 2,
|
||||
"selected": 3,
|
||||
"selected_dir": 4,
|
||||
"status_ok": 5,
|
||||
"status_err": 6,
|
||||
"header": 7,
|
||||
}
|
||||
if not curses.has_colors():
|
||||
return {}
|
||||
curses.start_color()
|
||||
curses.use_default_colors()
|
||||
try:
|
||||
curses.init_pair(1, curses.COLOR_CYAN, -1)
|
||||
curses.init_pair(2, curses.COLOR_WHITE, -1)
|
||||
curses.init_pair(3, curses.COLOR_BLACK, curses.COLOR_WHITE)
|
||||
curses.init_pair(4, curses.COLOR_CYAN, curses.COLOR_WHITE)
|
||||
curses.init_pair(5, curses.COLOR_GREEN, -1)
|
||||
curses.init_pair(6, curses.COLOR_RED, -1)
|
||||
curses.init_pair(7, curses.COLOR_YELLOW, -1)
|
||||
except curses.error:
|
||||
pass
|
||||
return pairs
|
||||
|
||||
|
||||
def draw(stdscr, entries, filtered, selected, cwd, status, status_kind, show_hidden, query, colors):
|
||||
stdscr.erase()
|
||||
max_y, max_x = stdscr.getmaxyx()
|
||||
|
||||
visible = filtered if filtered is not None else entries
|
||||
|
||||
# Header
|
||||
hidden_flag = " (hidden shown)" if show_hidden else ""
|
||||
header = f" {cwd}{hidden_flag} "
|
||||
if colors:
|
||||
stdscr.attron(curses.color_pair(colors["header"]) | curses.A_BOLD)
|
||||
try:
|
||||
stdscr.addstr(0, 0, header[: max_x - 1])
|
||||
except curses.error:
|
||||
pass
|
||||
if colors:
|
||||
stdscr.attroff(curses.color_pair(colors["header"]) | curses.A_BOLD)
|
||||
|
||||
# List area
|
||||
list_top = 1
|
||||
list_bottom = max_y - 2 # leave one row for status, one for footer
|
||||
rows = max(0, list_bottom - list_top)
|
||||
|
||||
# Clamp selection to a valid index in `visible`.
|
||||
if visible:
|
||||
selected = max(0, min(selected, len(visible) - 1))
|
||||
else:
|
||||
selected = 0
|
||||
|
||||
if rows <= 0:
|
||||
# Terminal too small to render anything meaningful.
|
||||
stdscr.refresh()
|
||||
return selected
|
||||
|
||||
# Scroll window
|
||||
start = 0
|
||||
if selected >= rows:
|
||||
start = selected - rows + 1
|
||||
|
||||
end = min(start + rows, len(visible))
|
||||
for entry_i in range(start, end):
|
||||
entry = visible[entry_i]
|
||||
if is_parent(entry):
|
||||
name = ".."
|
||||
is_dir = True
|
||||
full = cwd.parent
|
||||
else:
|
||||
name = entry.name
|
||||
is_dir = entry.is_dir(follow_symlinks=False)
|
||||
full = entry
|
||||
|
||||
label = format_entry(name, is_dir, full, max_x - 1)
|
||||
|
||||
attr = curses.A_NORMAL
|
||||
if colors:
|
||||
if entry_i == selected:
|
||||
attr = curses.color_pair(
|
||||
colors["selected_dir"] if is_dir else colors["selected"]
|
||||
) | curses.A_BOLD
|
||||
else:
|
||||
attr = curses.color_pair(
|
||||
colors["dir"] if is_dir else colors["file"]
|
||||
)
|
||||
else:
|
||||
if entry_i == selected:
|
||||
attr = curses.A_REVERSE
|
||||
elif is_dir:
|
||||
attr = curses.A_BOLD
|
||||
|
||||
line_y = list_top + (entry_i - start)
|
||||
if line_y < list_bottom:
|
||||
try:
|
||||
stdscr.addnstr(line_y, 0, label, max_x - 1, attr)
|
||||
except curses.error:
|
||||
pass
|
||||
|
||||
# Status line (one row above footer)
|
||||
status_y = max_y - 2
|
||||
status_text = ""
|
||||
status_attr = curses.A_DIM
|
||||
if status:
|
||||
status_text = status
|
||||
elif query is not None:
|
||||
status_text = f"/ {query}"
|
||||
if status_text and colors and status_kind in colors:
|
||||
status_attr = curses.color_pair(colors[status_kind]) | curses.A_BOLD
|
||||
try:
|
||||
if status_text:
|
||||
stdscr.addnstr(status_y, 0, status_text, max_x - 1, status_attr)
|
||||
except curses.error:
|
||||
pass
|
||||
|
||||
# Footer (key hints)
|
||||
footer = " Up/Dn move | Enter open | / search | .. parent | . hidden | q quit "
|
||||
try:
|
||||
stdscr.addnstr(max_y - 1, 0, footer, max_x - 1, curses.A_DIM)
|
||||
except curses.error:
|
||||
pass
|
||||
|
||||
stdscr.refresh()
|
||||
return selected
|
||||
|
||||
|
||||
# ---------- file type / open-with configuration -----------------------------
|
||||
|
||||
OPEN_COMMANDS = {
|
||||
"Bilder": [
|
||||
("feh", ["feh", "{file}"]),
|
||||
("imv", ["imv", "{file}"]),
|
||||
],
|
||||
|
||||
"Videos": [
|
||||
("mpv", ["mpv", "{file}"]),
|
||||
("vlc", ["vlc", "{file}"]),
|
||||
("show", ["show", "{file}"]),
|
||||
("newshow", ["newshow", "{file}"]),
|
||||
],
|
||||
|
||||
"Musik": [
|
||||
("mpv", ["mpv", "{file}"]),
|
||||
("ffplay", ["ffplay", "{file}"]),
|
||||
],
|
||||
|
||||
"Dokumente": [
|
||||
("LibreOffice", ["libreoffice", "{file}"]),
|
||||
("firefox", ["firefox", "{file}"]),
|
||||
],
|
||||
|
||||
"PDF": [
|
||||
("zathura", ["zathura", "{file}"]),
|
||||
("firefox", ["firefox", "{file}"]),
|
||||
],
|
||||
|
||||
"Code": [
|
||||
("nano", ["nano", "{file}"]),
|
||||
("helix", ["helix", "{file}"]),
|
||||
],
|
||||
|
||||
"Archive": [
|
||||
("File Roller", ["file-roller", "{file}"]),
|
||||
],
|
||||
|
||||
"Sonstige": [
|
||||
("nano", ["nano", "{file}"]),
|
||||
],
|
||||
}
|
||||
|
||||
|
||||
def file_menu(stdscr, filepath: Path, colors) -> list[str] | None:
|
||||
"""Show the open-with menu based on the file category.
|
||||
Returns argv list, or None if cancelled.
|
||||
"""
|
||||
|
||||
category = get_category(filepath)
|
||||
|
||||
# Get commands for this category.
|
||||
# If the category does not exist, fall back to "Sonstige".
|
||||
commands = OPEN_COMMANDS.get(
|
||||
category,
|
||||
OPEN_COMMANDS.get("Sonstige", [])
|
||||
)
|
||||
|
||||
stdscr.erase()
|
||||
max_y, max_x = stdscr.getmaxyx()
|
||||
|
||||
lines = [
|
||||
f"Open: {filepath.name}",
|
||||
f"Type: {category}",
|
||||
"",
|
||||
]
|
||||
|
||||
# Add category-specific programs.
|
||||
for number, (name, _argv) in enumerate(commands, start=1):
|
||||
lines.append(f" {number}) {name}")
|
||||
|
||||
# Custom command is always available.
|
||||
custom_number = len(commands) + 1
|
||||
|
||||
lines.extend([
|
||||
f" {custom_number}) custom command",
|
||||
"",
|
||||
f"Press 1-{custom_number} - ESC to cancel",
|
||||
])
|
||||
|
||||
for i, line in enumerate(lines):
|
||||
if i >= max_y - 1:
|
||||
break
|
||||
|
||||
try:
|
||||
if colors:
|
||||
stdscr.addnstr(
|
||||
i,
|
||||
2,
|
||||
line,
|
||||
max_x - 3,
|
||||
curses.color_pair(colors["header"]) | curses.A_BOLD,
|
||||
)
|
||||
else:
|
||||
stdscr.addnstr(
|
||||
i,
|
||||
2,
|
||||
line,
|
||||
max_x - 3,
|
||||
curses.A_BOLD,
|
||||
)
|
||||
except curses.error:
|
||||
pass
|
||||
|
||||
stdscr.refresh()
|
||||
|
||||
while True:
|
||||
ch = stdscr.getch()
|
||||
|
||||
if ch == 27: # ESC
|
||||
return None
|
||||
|
||||
if ord("1") <= ch <= ord("9"):
|
||||
number = ch - ord("0")
|
||||
|
||||
# Predefined command
|
||||
if 1 <= number <= len(commands):
|
||||
_name, argv = commands[number - 1]
|
||||
|
||||
return [
|
||||
arg.replace("{file}", str(filepath))
|
||||
for arg in argv
|
||||
]
|
||||
|
||||
# Custom command
|
||||
if number == custom_number:
|
||||
cmd = prompt_line(
|
||||
stdscr,
|
||||
"Command (use {{file}} for path): "
|
||||
)
|
||||
|
||||
if cmd is None or not cmd.strip():
|
||||
return None
|
||||
|
||||
if "{file}" in cmd:
|
||||
resolved = cmd.replace(
|
||||
"{file}",
|
||||
str(filepath)
|
||||
)
|
||||
else:
|
||||
resolved = cmd + " " + str(filepath)
|
||||
|
||||
try:
|
||||
return shlex.split(resolved)
|
||||
except ValueError:
|
||||
return resolved.split()
|
||||
|
||||
|
||||
|
||||
def run_external(stdscr, argv: list[str]) -> str:
|
||||
"""Suspend curses, run argv, then restore. Returns a status string.
|
||||
|
||||
Defensive: we snapshot cwd before, run, then forcibly chdir back to the
|
||||
snapshot afterwards. Even though `subprocess.run` with a list argv does
|
||||
not invoke a shell and therefore cannot change the parent's cwd, if a
|
||||
future change ever switches to `shell=True` this guarantees we stay put.
|
||||
"""
|
||||
saved_cwd = os.getcwd()
|
||||
curses.def_prog_mode()
|
||||
curses.endwin()
|
||||
status = ""
|
||||
try:
|
||||
try:
|
||||
result = subprocess.run(argv)
|
||||
if result.returncode == 0:
|
||||
status = f"Ran: {' '.join(argv)}"
|
||||
else:
|
||||
status = f"Exit {result.returncode}: {' '.join(argv)}"
|
||||
except FileNotFoundError:
|
||||
status = f"Command not found: {argv[0]}"
|
||||
except KeyboardInterrupt:
|
||||
status = "Interrupted"
|
||||
except Exception as e: # noqa: BLE001
|
||||
status = f"Error: {e}"
|
||||
finally:
|
||||
# Always restore cwd before resuming curses.
|
||||
try:
|
||||
os.chdir(saved_cwd)
|
||||
except OSError:
|
||||
pass
|
||||
curses.reset_prog_mode()
|
||||
try:
|
||||
stdscr.redrawwin()
|
||||
stdscr.refresh()
|
||||
except curses.error:
|
||||
pass
|
||||
return status
|
||||
|
||||
|
||||
# ---------- main loop -------------------------------------------------------
|
||||
|
||||
def main(stdscr):
|
||||
colors = init_colors()
|
||||
curses.curs_set(0)
|
||||
stdscr.keypad(True)
|
||||
|
||||
cwd = Path.cwd()
|
||||
show_hidden = False
|
||||
|
||||
selected = 0
|
||||
status = ""
|
||||
status_kind = "status_ok"
|
||||
query = None
|
||||
filtered = None # when not None, used for display (search active)
|
||||
|
||||
while True:
|
||||
try:
|
||||
entries = list_entries(cwd, show_hidden)
|
||||
|
||||
if filtered is not None:
|
||||
# Re-apply filter against the fresh entries
|
||||
if query is None:
|
||||
filtered = None
|
||||
else:
|
||||
qlow = query.lower()
|
||||
candidates = [e for e in entries if qlow in e.name.lower()]
|
||||
if not candidates:
|
||||
filtered = []
|
||||
else:
|
||||
if candidates[0] != PARENT and PARENT in entries:
|
||||
candidates = [PARENT] + [
|
||||
e for e in candidates if e != PARENT
|
||||
]
|
||||
filtered = candidates
|
||||
|
||||
selected = draw(
|
||||
stdscr,
|
||||
entries,
|
||||
filtered,
|
||||
selected,
|
||||
cwd,
|
||||
status,
|
||||
status_kind,
|
||||
show_hidden,
|
||||
query,
|
||||
colors,
|
||||
)
|
||||
|
||||
status = ""
|
||||
status_kind = "status_ok"
|
||||
|
||||
ch = stdscr.getch()
|
||||
|
||||
if ch in (ord("q"),):
|
||||
break
|
||||
|
||||
if ch in (curses.KEY_UP, ord("k")):
|
||||
vis = filtered if filtered is not None else entries
|
||||
if vis:
|
||||
selected = (selected - 1) % len(vis)
|
||||
|
||||
elif ch in (curses.KEY_DOWN, ord("j")):
|
||||
vis = filtered if filtered is not None else entries
|
||||
if vis:
|
||||
selected = (selected + 1) % len(vis)
|
||||
|
||||
elif ch in (curses.KEY_BACKSPACE, 127, 8, ord("h")):
|
||||
# Parent directory
|
||||
if cwd != cwd.parent:
|
||||
cwd = cwd.parent
|
||||
os.chdir(cwd)
|
||||
selected = 0
|
||||
filtered = None
|
||||
query = None
|
||||
else:
|
||||
status = "Already at filesystem root"
|
||||
status_kind = "status_err"
|
||||
|
||||
elif ch in (curses.KEY_ENTER, 10, 13):
|
||||
vis = filtered if filtered is not None else entries
|
||||
if not vis:
|
||||
continue
|
||||
entry = vis[selected]
|
||||
if is_parent(entry):
|
||||
if cwd != cwd.parent:
|
||||
cwd = cwd.parent
|
||||
os.chdir(cwd)
|
||||
selected = 0
|
||||
filtered = None
|
||||
query = None
|
||||
elif entry.is_dir(follow_symlinks=False):
|
||||
try:
|
||||
os.chdir(entry)
|
||||
cwd = Path.cwd()
|
||||
selected = 0
|
||||
filtered = None
|
||||
query = None
|
||||
except PermissionError:
|
||||
status = f"Permission denied: {entry.name}"
|
||||
status_kind = "status_err"
|
||||
except OSError as e:
|
||||
status = f"Cannot enter {entry.name}: {e}"
|
||||
status_kind = "status_err"
|
||||
else:
|
||||
if entry.is_file(follow_symlinks=False):
|
||||
argv = file_menu(stdscr, entry, colors)
|
||||
if argv is not None:
|
||||
status = run_external(stdscr, argv)
|
||||
status_kind = "status_ok" if status.startswith("Ran:") else "status_err"
|
||||
cwd = Path.cwd()
|
||||
selected = 0
|
||||
|
||||
elif ch == ord("/"):
|
||||
q = prompt_line(stdscr, "/ ")
|
||||
if q is None:
|
||||
# cancel search
|
||||
filtered = None
|
||||
query = None
|
||||
selected = 0
|
||||
else:
|
||||
query = q
|
||||
if q == "":
|
||||
filtered = None
|
||||
else:
|
||||
qlow = q.lower()
|
||||
candidates = [e for e in entries if qlow in e.name.lower()]
|
||||
if PARENT in entries:
|
||||
candidates = [PARENT] + [
|
||||
e for e in candidates if e != PARENT
|
||||
]
|
||||
filtered = candidates if candidates else []
|
||||
|
||||
elif ch == ord("."):
|
||||
show_hidden = not show_hidden
|
||||
selected = 0
|
||||
# Re-apply search if active
|
||||
if query:
|
||||
qlow = query.lower()
|
||||
candidates = [
|
||||
e for e in list_entries(cwd, show_hidden) if qlow in e.name.lower()
|
||||
]
|
||||
filtered = candidates if candidates else []
|
||||
|
||||
except KeyboardInterrupt:
|
||||
break
|
||||
|
||||
|
||||
def entrypoint():
|
||||
try:
|
||||
curses.wrapper(main)
|
||||
except KeyboardInterrupt:
|
||||
pass
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
entrypoint()
|
||||
@@ -0,0 +1,22 @@
|
||||
import time
|
||||
from datetime import datetime, timezone
|
||||
|
||||
ziel = datetime(2038, 1, 19, 3, 14, 7, tzinfo=timezone.utc)
|
||||
|
||||
while True:
|
||||
jetzt = datetime.now(timezone.utc)
|
||||
rest = ziel - jetzt
|
||||
|
||||
tage = rest.days
|
||||
stunden = rest.seconds // 3600
|
||||
minuten = (rest.seconds % 3600) // 60
|
||||
sekunden = rest.seconds % 60
|
||||
|
||||
print(
|
||||
f"\rY2038 Countdown: {tage} Tage "
|
||||
f"{stunden:02d}:{minuten:02d}:{sekunden:02d}",
|
||||
end="",
|
||||
flush=True
|
||||
)
|
||||
|
||||
time.sleep(1)
|
||||
@@ -0,0 +1,22 @@
|
||||
import time
|
||||
from datetime import datetime, timezone
|
||||
|
||||
ziel = datetime(2038, 1, 19, 3, 14, 7, tzinfo=timezone.utc)
|
||||
|
||||
while True:
|
||||
jetzt = datetime.now(timezone.utc)
|
||||
rest = ziel - jetzt
|
||||
|
||||
tage = rest.days
|
||||
stunden = rest.seconds // 3600
|
||||
minuten = (rest.seconds % 3600) // 60
|
||||
sekunden = rest.seconds % 60
|
||||
|
||||
print(
|
||||
f"\rY2038 Countdown: {tage} Tage "
|
||||
f"{stunden:02d}:{minuten:02d}:{sekunden:02d}",
|
||||
end="",
|
||||
flush=True
|
||||
)
|
||||
|
||||
time.sleep(1)
|
||||
@@ -0,0 +1,45 @@
|
||||
#!/bin/bash
|
||||
|
||||
INTERNET_TEST="https://google.com"
|
||||
HOST="https://serverjonas.de"
|
||||
SOUND="/home/jonas/Downloads/eb79fa9a-5d7a-42da-9f00-8d6160f96a62.mp3"
|
||||
|
||||
PLAYER_PID=""
|
||||
|
||||
while true; do
|
||||
# Erst prüfen, ob überhaupt Internet vorhanden ist
|
||||
if ! curl --silent --fail --max-time 3 "$INTERNET_TEST" > /dev/null; then
|
||||
echo "$(date): Kein Internet auf diesem Rechner."
|
||||
|
||||
# Falls Alarm läuft, stoppen
|
||||
if [[ -n "$PLAYER_PID" ]] && kill -0 "$PLAYER_PID" 2>/dev/null; then
|
||||
kill "$PLAYER_PID"
|
||||
PLAYER_PID=""
|
||||
echo "Alarm gestoppt."
|
||||
fi
|
||||
|
||||
sleep 10
|
||||
continue
|
||||
fi
|
||||
|
||||
# Internet vorhanden -> Server prüfen
|
||||
if curl --silent --fail --max-time 3 "$HOST" > /dev/null; then
|
||||
echo "$(date): $HOST erreichbar."
|
||||
|
||||
if [[ -n "$PLAYER_PID" ]] && kill -0 "$PLAYER_PID" 2>/dev/null; then
|
||||
kill "$PLAYER_PID"
|
||||
PLAYER_PID=""
|
||||
echo "Alarm gestoppt."
|
||||
fi
|
||||
else
|
||||
echo "$(date): $HOST NICHT erreichbar!"
|
||||
|
||||
if [[ -z "$PLAYER_PID" ]] || ! kill -0 "$PLAYER_PID" 2>/dev/null; then
|
||||
ffplay -nodisp -loop 0 "$SOUND" >/dev/null 2>&1 &
|
||||
PLAYER_PID=$!
|
||||
echo "Alarm gestartet."
|
||||
fi
|
||||
fi
|
||||
|
||||
sleep 10
|
||||
done
|
||||
Executable
+132
@@ -0,0 +1,132 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
|
||||
#importe
|
||||
from shutil import get_terminal_size
|
||||
import os
|
||||
import sys
|
||||
import subprocess
|
||||
import platform
|
||||
|
||||
#helper funktions
|
||||
def line(content=None):
|
||||
print("-"*int(get_terminal_size().columns))
|
||||
if content is not None:
|
||||
print(content)
|
||||
print("-"*int(get_terminal_size().columns))
|
||||
|
||||
def box(content):
|
||||
content_lines = content.split("\n")
|
||||
width = max(len(x) for x in content_lines)
|
||||
|
||||
print("+" + "-" * width + "+")
|
||||
|
||||
for text in content_lines:
|
||||
print("|" + text.ljust(width) + "|")
|
||||
|
||||
print("+" + "-"* width + "+")
|
||||
|
||||
path = os.path.join(os.path.dirname(__file__), "prg")
|
||||
|
||||
|
||||
programs = {}
|
||||
|
||||
for root, dirs, files in os.walk(path):
|
||||
matching = []
|
||||
|
||||
for f in files:
|
||||
if f.startswith("SJNet_") and f.endswith(".py"):
|
||||
matching.append(f)
|
||||
|
||||
if matching:
|
||||
folder = os.path.relpath(root, path)
|
||||
|
||||
if folder == ".":
|
||||
folder = ""
|
||||
|
||||
programs[folder] = matching
|
||||
|
||||
def detect():
|
||||
try:
|
||||
distro = platform.freedesktop_os_release()
|
||||
except:
|
||||
return
|
||||
|
||||
name = distro.get("NAME", "").lower()
|
||||
|
||||
if "arch" in name:
|
||||
box("Arch Linux detected.\nRespect. You chose the hard mode.")
|
||||
elif "gentoo" in name:
|
||||
box("Gentoo detected.\nThe compiler has awakened.")
|
||||
elif "debian" in name:
|
||||
box("Debian detected.\nStable mode activated.")
|
||||
elif "ubuntu" in name:
|
||||
box("Ubuntu detected.\nThe beginner gateway has opened.")
|
||||
elif os.geteuid() == 0:
|
||||
box("Running as root.\nPlease don't delete the universe.")
|
||||
|
||||
#main
|
||||
|
||||
|
||||
def menu():
|
||||
detect()
|
||||
|
||||
if not programs:
|
||||
box("Keine SJNet_*.py Programme gefunden.")
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
box("Available programs:")
|
||||
|
||||
counter = 1
|
||||
program_map = {}
|
||||
|
||||
for folder, files in programs.items():
|
||||
|
||||
if folder:
|
||||
print(folder)
|
||||
|
||||
for file in files:
|
||||
print(f" {counter}: {file}")
|
||||
|
||||
program_map[counter] = os.path.join(
|
||||
path,
|
||||
folder,
|
||||
file
|
||||
)
|
||||
|
||||
counter += 1
|
||||
|
||||
else:
|
||||
for file in files:
|
||||
print(f"{counter}: {file}")
|
||||
|
||||
program_map[counter] = os.path.join(
|
||||
path,
|
||||
file
|
||||
)
|
||||
|
||||
counter += 1
|
||||
|
||||
try:
|
||||
choose = int(input("enter number of the programm you want: > "))
|
||||
chosen = program_map[choose]
|
||||
|
||||
except ValueError:
|
||||
print("Es muss eine Nummer sein.")
|
||||
sys.exit(1)
|
||||
|
||||
except KeyError:
|
||||
print("Es muss eine der angebotenen Nummern sein.")
|
||||
sys.exit(1)
|
||||
|
||||
print("\n")
|
||||
try:
|
||||
subprocess.run([sys.executable, chosen])
|
||||
except KeyboardInterrupt:
|
||||
print("Interrupt signal recived...")
|
||||
sys.exit(0)
|
||||
|
||||
|
||||
|
||||
menu()
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
import ollama
|
||||
|
||||
response = ollama.chat(
|
||||
model="qwen3:0.6b",
|
||||
messages=[
|
||||
{
|
||||
"role": "user",
|
||||
"content": "Hallo, wer bist du?"
|
||||
}
|
||||
]
|
||||
)
|
||||
|
||||
print(response["message"]["content"])
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
from faster_whisper import WhisperModel
|
||||
|
||||
model = WhisperModel(
|
||||
"small",
|
||||
device="cpu",
|
||||
compute_type="int8"
|
||||
)
|
||||
|
||||
segments, info = model.transcribe(
|
||||
"test.wav",
|
||||
language="de"
|
||||
)
|
||||
|
||||
print("Sprache:", info.language)
|
||||
|
||||
for segment in segments:
|
||||
print(segment.text)
|
||||
Generated
+646
@@ -0,0 +1,646 @@
|
||||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 4
|
||||
|
||||
[[package]]
|
||||
name = "android_system_properties"
|
||||
version = "0.1.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ae221649c9976a6f6c56ae1facf410f3ddb33cc661c4b7b61020a912d4237fbc"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anstream"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "824a212faf96e9acacdbd09febd34438f8f711fb84e09a8916013cd7815ca28d"
|
||||
dependencies = [
|
||||
"anstyle",
|
||||
"anstyle-parse",
|
||||
"anstyle-query",
|
||||
"anstyle-wincon",
|
||||
"colorchoice",
|
||||
"is_terminal_polyfill",
|
||||
"utf8parse",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anstyle"
|
||||
version = "1.0.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000"
|
||||
|
||||
[[package]]
|
||||
name = "anstyle-parse"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "52ce7f38b242319f7cabaa6813055467063ecdc9d355bbb4ce0c68908cd8130e"
|
||||
dependencies = [
|
||||
"utf8parse",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anstyle-query"
|
||||
version = "1.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc"
|
||||
dependencies = [
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anstyle-wincon"
|
||||
version = "3.0.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d"
|
||||
dependencies = [
|
||||
"anstyle",
|
||||
"once_cell_polyfill",
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "autocfg"
|
||||
version = "1.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53"
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "2.13.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b588b76d00fde79687d7646a9b5bdf3cc0f655e0bbd080335a95d7e96f3587da"
|
||||
|
||||
[[package]]
|
||||
name = "bumpalo"
|
||||
version = "3.20.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649"
|
||||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.4.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "509591b7bcd67f4ef775afad7662703b4935daaa6ec0e5605cfb1090b32a2b6d"
|
||||
dependencies = [
|
||||
"find-msvc-tools",
|
||||
"shlex",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
version = "1.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
|
||||
|
||||
[[package]]
|
||||
name = "chrono"
|
||||
version = "0.4.45"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1aa79e62e7697b8e29b513a68abacf485adcd1fe8284a4316c5ae868e6633327"
|
||||
dependencies = [
|
||||
"iana-time-zone",
|
||||
"js-sys",
|
||||
"num-traits",
|
||||
"wasm-bindgen",
|
||||
"windows-link",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap"
|
||||
version = "4.6.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "473c7e07f409a8d772161724aa8db6a765a2532a70f9667eeb7b49d3d02fbdca"
|
||||
dependencies = [
|
||||
"clap_builder",
|
||||
"clap_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap_builder"
|
||||
version = "4.6.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7b48fea5a88e9ae728a2dcbedbfc0e730f7d60da42e1cb049a83c9fb8b789889"
|
||||
dependencies = [
|
||||
"anstream",
|
||||
"anstyle",
|
||||
"clap_lex",
|
||||
"strsim",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap_derive"
|
||||
version = "4.6.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d012d2b9d65aca7f18f4d9878a045bc17899bba951561ba5ec3c2ba1eed9a061"
|
||||
dependencies = [
|
||||
"heck",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 3.0.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap_lex"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9"
|
||||
|
||||
[[package]]
|
||||
name = "colorchoice"
|
||||
version = "1.0.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570"
|
||||
|
||||
[[package]]
|
||||
name = "core-foundation-sys"
|
||||
version = "0.8.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b"
|
||||
|
||||
[[package]]
|
||||
name = "dirs"
|
||||
version = "6.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c3e8aa94d75141228480295a7d0e7feb620b1a5ad9f12bc40be62411e38cce4e"
|
||||
dependencies = [
|
||||
"dirs-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dirs-sys"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e01a3366d27ee9890022452ee61b2b63a67e6f13f58900b651ff5665f0bb1fab"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"option-ext",
|
||||
"redox_users",
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fallible-iterator"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2acce4a10f12dc2fb14a218589d4f1f62ef011b2d0cc4b3cb1bba8e94da14649"
|
||||
|
||||
[[package]]
|
||||
name = "fallible-streaming-iterator"
|
||||
version = "0.1.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7360491ce676a36bf9bb3c56c1aa791658183a54d2744120f27285738d90465a"
|
||||
|
||||
[[package]]
|
||||
name = "find-msvc-tools"
|
||||
version = "0.1.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d45db016d36b838f563236e9193d0ee6ce38f3f68b6c94e914b4929c96bbb890"
|
||||
|
||||
[[package]]
|
||||
name = "foldhash"
|
||||
version = "0.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2"
|
||||
|
||||
[[package]]
|
||||
name = "futures-core"
|
||||
version = "0.3.34"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "92d699e522242e69e3003b94ecc1f960f3a5e015aa7c5d7486e65ad01dd94f5e"
|
||||
|
||||
[[package]]
|
||||
name = "futures-task"
|
||||
version = "0.3.34"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cd417de3d1d015fc3bfd2b1ea46dfc7bab72ef86f1cc7cc9c78e728b34a6d1fd"
|
||||
|
||||
[[package]]
|
||||
name = "futures-util"
|
||||
version = "0.3.34"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0d50a92467f8ba5dd6e3ee5d4bd04d73ab2e4e1c44474a0674821dfce14b79bc"
|
||||
dependencies = [
|
||||
"futures-core",
|
||||
"futures-task",
|
||||
"pin-project-lite",
|
||||
"slab",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "getrandom"
|
||||
version = "0.2.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"wasi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.15.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1"
|
||||
dependencies = [
|
||||
"foldhash",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hashlink"
|
||||
version = "0.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7382cf6263419f2d8df38c55d7da83da5c18aef87fc7a7fc1fb1e344edfe14c1"
|
||||
dependencies = [
|
||||
"hashbrown",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "heck"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
|
||||
|
||||
[[package]]
|
||||
name = "iana-time-zone"
|
||||
version = "0.1.65"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e31bc9ad994ba00e440a8aa5c9ef0ec67d5cb5e5cb0cc7f8b744a35b389cc470"
|
||||
dependencies = [
|
||||
"android_system_properties",
|
||||
"core-foundation-sys",
|
||||
"iana-time-zone-haiku",
|
||||
"js-sys",
|
||||
"log",
|
||||
"wasm-bindgen",
|
||||
"windows-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "iana-time-zone-haiku"
|
||||
version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f"
|
||||
dependencies = [
|
||||
"cc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "is_terminal_polyfill"
|
||||
version = "1.70.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695"
|
||||
|
||||
[[package]]
|
||||
name = "js-sys"
|
||||
version = "0.3.104"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0e0c1080212aad755ea003d18543e8768dd432c48819efd73a7bf1e39b7a5a3a"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"futures-util",
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.189"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3eaf3ede3fee6db1a4c2ee091bf8a8b4dccdc6d17f656fb07896ee72867612f2"
|
||||
|
||||
[[package]]
|
||||
name = "libredox"
|
||||
version = "0.1.20"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "28d0a00925a9f930d679b6789b721e3a7f9ed110f41b86d2497caa780c3a070a"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libsqlite3-sys"
|
||||
version = "0.35.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "133c182a6a2c87864fe97778797e46c7e999672690dc9fa3ee8e241aa4a9c13f"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"pkg-config",
|
||||
"vcpkg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "log"
|
||||
version = "0.4.33"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0ceec5bc11778974d1bcb055b18002eba7f4b3518b6a0081b3af5f21666da9ad"
|
||||
|
||||
[[package]]
|
||||
name = "num-traits"
|
||||
version = "0.2.19"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "once_cell"
|
||||
version = "1.21.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50"
|
||||
|
||||
[[package]]
|
||||
name = "once_cell_polyfill"
|
||||
version = "1.70.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe"
|
||||
|
||||
[[package]]
|
||||
name = "option-ext"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d"
|
||||
|
||||
[[package]]
|
||||
name = "pin-project-lite"
|
||||
version = "0.2.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd"
|
||||
|
||||
[[package]]
|
||||
name = "pkg-config"
|
||||
version = "0.3.34"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f6b464fbc74e149a392436b17d523f769e057cb6877f6a5c4618bc6f11800548"
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.107"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.47"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "redox_users"
|
||||
version = "0.5.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a4e608c6638b9c18977b00b475ac1f28d14e84b27d8d42f70e0bf1e3dec127ac"
|
||||
dependencies = [
|
||||
"getrandom",
|
||||
"libredox",
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rusqlite"
|
||||
version = "0.37.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "165ca6e57b20e1351573e3729b958bc62f0e48025386970b6e4d29e7a7e71f3f"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"fallible-iterator",
|
||||
"fallible-streaming-iterator",
|
||||
"hashlink",
|
||||
"libsqlite3-sys",
|
||||
"smallvec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustversion"
|
||||
version = "1.0.23"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cf54715a573b99ac80df0bc206da022bcd442c974952c7b9720069370852e21f"
|
||||
|
||||
[[package]]
|
||||
name = "shell-words"
|
||||
version = "1.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dc6fe69c597f9c37bfeeeeeb33da3530379845f10be461a66d16d03eca2ded77"
|
||||
|
||||
[[package]]
|
||||
name = "shlex"
|
||||
version = "2.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba"
|
||||
|
||||
[[package]]
|
||||
name = "slab"
|
||||
version = "0.4.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5"
|
||||
|
||||
[[package]]
|
||||
name = "smallvec"
|
||||
version = "1.15.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8ed6a63f02c8539c91a8685a86f4099661ba3da017932f6ebbea6de3f0fa7c90"
|
||||
|
||||
[[package]]
|
||||
name = "strsim"
|
||||
version = "0.11.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.119"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "872831b642d1a07999a962a351ed35b955ea2cfc8f3862091e2a240a84f17297"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "3.0.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "53e9bae58849f64dfa4f5d5ae372c8341f7305f82a3868709269343628b659a3"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror"
|
||||
version = "2.0.20"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ec86235f5fcc2a73650310756d2ac5b138a5780bbbdfae3eeccec992c435ba4f"
|
||||
dependencies = [
|
||||
"thiserror-impl",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror-impl"
|
||||
version = "2.0.20"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bc04cd3e1236dd4a98afca4569f2deb3f120e5422a4023be2cb683f8486292af"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 3.0.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "todo"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"clap",
|
||||
"dirs",
|
||||
"rusqlite",
|
||||
"shell-words",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.24"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"
|
||||
|
||||
[[package]]
|
||||
name = "utf8parse"
|
||||
version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
|
||||
|
||||
[[package]]
|
||||
name = "vcpkg"
|
||||
version = "0.2.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426"
|
||||
|
||||
[[package]]
|
||||
name = "wasi"
|
||||
version = "0.11.1+wasi-snapshot-preview1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b"
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen"
|
||||
version = "0.2.127"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1b70935747edd64d89de3efa29d73789b806c15798f8e7dca4d8ac356b50ce70"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"once_cell",
|
||||
"rustversion",
|
||||
"wasm-bindgen-macro",
|
||||
"wasm-bindgen-shared",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-macro"
|
||||
version = "0.2.127"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "77775f8f3f7217702089053b94958f8f54061a3f663417df76e19cbdcca29bc1"
|
||||
dependencies = [
|
||||
"quote",
|
||||
"wasm-bindgen-macro-support",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-macro-support"
|
||||
version = "0.2.127"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e11d33f857dc2fb11b8bc75aee111aa9cbeb12cd9f25efd3d4c2a3dd4e235284"
|
||||
dependencies = [
|
||||
"bumpalo",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.119",
|
||||
"wasm-bindgen-shared",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-shared"
|
||||
version = "0.2.127"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7ef64dbcc55df09c7e5a46182d181c2cfa3e925f3da937ea764728b4bbb9dcbf"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-core"
|
||||
version = "0.62.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb"
|
||||
dependencies = [
|
||||
"windows-implement",
|
||||
"windows-interface",
|
||||
"windows-link",
|
||||
"windows-result",
|
||||
"windows-strings",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-implement"
|
||||
version = "0.60.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.119",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-interface"
|
||||
version = "0.59.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.119",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-link"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
|
||||
|
||||
[[package]]
|
||||
name = "windows-result"
|
||||
version = "0.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5"
|
||||
dependencies = [
|
||||
"windows-link",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-strings"
|
||||
version = "0.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091"
|
||||
dependencies = [
|
||||
"windows-link",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.61.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc"
|
||||
dependencies = [
|
||||
"windows-link",
|
||||
]
|
||||
@@ -0,0 +1,11 @@
|
||||
[package]
|
||||
name = "todo"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
chrono = { version = "0.4", features = ["clock"] }
|
||||
clap = { version = "4", features = ["derive"] }
|
||||
dirs = "6"
|
||||
rusqlite = { version = "0.37", features = ["bundled"] }
|
||||
shell-words = "1"
|
||||
@@ -0,0 +1,307 @@
|
||||
use chrono::Local;
|
||||
use clap::{Parser, Subcommand};
|
||||
use rusqlite::{params, Connection};
|
||||
use std::io::{self, Write};
|
||||
use std::path::PathBuf;
|
||||
|
||||
#[derive(Parser)]
|
||||
#[command(
|
||||
name = "todo",
|
||||
version,
|
||||
about = "A simple SQLite-based todo manager"
|
||||
)]
|
||||
struct Cli {
|
||||
#[command(subcommand)]
|
||||
command: Option<Command>,
|
||||
}
|
||||
|
||||
#[derive(Subcommand)]
|
||||
enum Command {
|
||||
/// Add a new todo
|
||||
Add {
|
||||
/// Text of the todo
|
||||
text: String,
|
||||
},
|
||||
|
||||
/// List all todos
|
||||
List,
|
||||
|
||||
/// Delete a todo by number or exact text
|
||||
Del {
|
||||
/// Todo number or exact todo text
|
||||
target: String,
|
||||
},
|
||||
}
|
||||
|
||||
fn database_path() -> Result<PathBuf, Box<dyn std::error::Error>> {
|
||||
let home = dirs::home_dir()
|
||||
.ok_or("Could not determine home directory")?;
|
||||
|
||||
let directory = home.join(".sjn").join("todo");
|
||||
|
||||
std::fs::create_dir_all(&directory)?;
|
||||
|
||||
Ok(directory.join("todo.db"))
|
||||
}
|
||||
|
||||
fn open_database() -> Result<Connection, Box<dyn std::error::Error>> {
|
||||
let path = database_path()?;
|
||||
let connection = Connection::open(path)?;
|
||||
|
||||
connection.execute(
|
||||
"
|
||||
CREATE TABLE IF NOT EXISTS todos (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
text TEXT NOT NULL,
|
||||
added_at TEXT NOT NULL
|
||||
)
|
||||
",
|
||||
[],
|
||||
)?;
|
||||
|
||||
Ok(connection)
|
||||
}
|
||||
|
||||
fn add_todo(
|
||||
connection: &Connection,
|
||||
text: &str,
|
||||
) -> Result<(), Box<dyn std::error::Error>> {
|
||||
let text = text.trim();
|
||||
|
||||
if text.is_empty() {
|
||||
println!("Error: todo text cannot be empty.");
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let timestamp = Local::now().to_rfc3339();
|
||||
|
||||
connection.execute(
|
||||
"INSERT INTO todos (text, added_at) VALUES (?1, ?2)",
|
||||
params![text, timestamp],
|
||||
)?;
|
||||
|
||||
let id = connection.last_insert_rowid();
|
||||
|
||||
println!("Todo #{} added: {}", id, text);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn list_todos(
|
||||
connection: &Connection,
|
||||
) -> Result<(), Box<dyn std::error::Error>> {
|
||||
let mut statement = connection.prepare(
|
||||
"
|
||||
SELECT id, text, added_at
|
||||
FROM todos
|
||||
ORDER BY id
|
||||
",
|
||||
)?;
|
||||
|
||||
let todos = statement.query_map([], |row| {
|
||||
Ok((
|
||||
row.get::<_, i64>(0)?,
|
||||
row.get::<_, String>(1)?,
|
||||
row.get::<_, String>(2)?,
|
||||
))
|
||||
})?;
|
||||
|
||||
let mut found = false;
|
||||
|
||||
for todo in todos {
|
||||
let (id, text, added_at) = todo?;
|
||||
|
||||
found = true;
|
||||
|
||||
println!("#{} {} [{}]", id, text, added_at);
|
||||
}
|
||||
|
||||
if !found {
|
||||
println!("No todos.");
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn delete_todo(
|
||||
connection: &Connection,
|
||||
target: &str,
|
||||
) -> Result<(), Box<dyn std::error::Error>> {
|
||||
let target = target.trim();
|
||||
|
||||
if target.is_empty() {
|
||||
println!("Error: no todo specified.");
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
// Try interpreting the target as a todo number first.
|
||||
if let Ok(id) = target.parse::<i64>() {
|
||||
let affected = connection.execute(
|
||||
"DELETE FROM todos WHERE id = ?1",
|
||||
params![id],
|
||||
)?;
|
||||
|
||||
if affected == 0 {
|
||||
println!("Error: todo #{} does not exist.", id);
|
||||
} else {
|
||||
println!("Todo #{} deleted.", id);
|
||||
}
|
||||
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
// Otherwise delete by exact text.
|
||||
let affected = connection.execute(
|
||||
"DELETE FROM todos WHERE text = ?1",
|
||||
params![target],
|
||||
)?;
|
||||
|
||||
if affected == 0 {
|
||||
println!("Error: no todo with that text exists.");
|
||||
} else {
|
||||
println!("Todo deleted: {}", target);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn execute_command(
|
||||
connection: &Connection,
|
||||
input: &str,
|
||||
) -> Result<bool, Box<dyn std::error::Error>> {
|
||||
let input = input.trim();
|
||||
|
||||
if input.is_empty() {
|
||||
return Ok(true);
|
||||
}
|
||||
|
||||
if input.eq_ignore_ascii_case("exit")
|
||||
|| input.eq_ignore_ascii_case("quit")
|
||||
{
|
||||
return Ok(false);
|
||||
}
|
||||
|
||||
if input.eq_ignore_ascii_case("help") {
|
||||
println!();
|
||||
println!("Commands:");
|
||||
println!(" add <text> Add a todo");
|
||||
println!(" list List all todos");
|
||||
println!(" del <number> Delete todo by number");
|
||||
println!(" del <text> Delete todo by exact text");
|
||||
println!(" help Show this help");
|
||||
println!(" exit / quit Exit the shell");
|
||||
println!();
|
||||
|
||||
return Ok(true);
|
||||
}
|
||||
|
||||
let args = match shell_words::split(input) {
|
||||
Ok(args) => args,
|
||||
Err(error) => {
|
||||
println!("Error parsing command: {}", error);
|
||||
return Ok(true);
|
||||
}
|
||||
};
|
||||
|
||||
if args.is_empty() {
|
||||
return Ok(true);
|
||||
}
|
||||
|
||||
let command = args[0].to_lowercase();
|
||||
|
||||
match command.as_str() {
|
||||
"add" => {
|
||||
if args.len() < 2 {
|
||||
println!("Usage: add <text>");
|
||||
} else {
|
||||
let text = args[1..].join(" ");
|
||||
add_todo(connection, &text)?;
|
||||
}
|
||||
}
|
||||
|
||||
"list" => {
|
||||
if args.len() > 1 {
|
||||
println!("Usage: list");
|
||||
} else {
|
||||
list_todos(connection)?;
|
||||
}
|
||||
}
|
||||
|
||||
"del" | "delete" | "remove" => {
|
||||
if args.len() < 2 {
|
||||
println!("Usage: del <number> or del <text>");
|
||||
} else {
|
||||
let target = args[1..].join(" ");
|
||||
delete_todo(connection, &target)?;
|
||||
}
|
||||
}
|
||||
|
||||
_ => {
|
||||
println!("Unknown command: {}", args[0]);
|
||||
println!("Type \"help\" for help.");
|
||||
}
|
||||
}
|
||||
|
||||
Ok(true)
|
||||
}
|
||||
|
||||
fn shell(
|
||||
connection: &Connection,
|
||||
) -> Result<(), Box<dyn std::error::Error>> {
|
||||
println!("Todo Shell");
|
||||
println!("Type \"help\" for help.");
|
||||
println!();
|
||||
|
||||
loop {
|
||||
print!("todo> ");
|
||||
io::stdout().flush()?;
|
||||
|
||||
let mut input = String::new();
|
||||
|
||||
match io::stdin().read_line(&mut input) {
|
||||
Ok(0) => {
|
||||
println!();
|
||||
break;
|
||||
}
|
||||
|
||||
Ok(_) => {
|
||||
if !execute_command(connection, &input)? {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Err(error) => {
|
||||
eprintln!("Error reading input: {}", error);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let connection = open_database()?;
|
||||
|
||||
let cli = Cli::parse();
|
||||
|
||||
match cli.command {
|
||||
Some(Command::Add { text }) => {
|
||||
add_todo(&connection, &text)?;
|
||||
}
|
||||
|
||||
Some(Command::List) => {
|
||||
list_todos(&connection)?;
|
||||
}
|
||||
|
||||
Some(Command::Del { target }) => {
|
||||
delete_todo(&connection, &target)?;
|
||||
}
|
||||
|
||||
None => {
|
||||
shell(&connection)?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
import sqlite3
|
||||
import uuid
|
||||
import math
|
||||
import time
|
||||
|
||||
DB = "uuid_test.db"
|
||||
|
||||
# SQLite-Datenbank öffnen/erstellen
|
||||
conn = sqlite3.connect(DB)
|
||||
cursor = conn.cursor()
|
||||
|
||||
cursor.execute("""
|
||||
CREATE TABLE IF NOT EXISTS uuids (
|
||||
uuid TEXT PRIMARY KEY
|
||||
)
|
||||
""")
|
||||
conn.commit()
|
||||
|
||||
n = 0
|
||||
start = time.time()
|
||||
|
||||
# UUIDv4 hat 122 zufällige Bits
|
||||
N = 2**122
|
||||
|
||||
print("UUIDv4-Kollisionstest gestartet!")
|
||||
print("Strg+C zum Beenden.\n")
|
||||
|
||||
try:
|
||||
while True:
|
||||
new_uuid = str(uuid.uuid4())
|
||||
n += 1
|
||||
|
||||
# Einfügen versuchen
|
||||
try:
|
||||
cursor.execute(
|
||||
"INSERT INTO uuids (uuid) VALUES (?)",
|
||||
(new_uuid,)
|
||||
)
|
||||
conn.commit()
|
||||
|
||||
duplicate = False
|
||||
|
||||
except sqlite3.IntegrityError:
|
||||
# UUID existiert bereits
|
||||
duplicate = True
|
||||
|
||||
# Kollisionswahrscheinlichkeit
|
||||
probability = 1 - math.exp(
|
||||
-(n * (n - 1)) / (2 * N)
|
||||
)
|
||||
|
||||
print(
|
||||
f"\rUUIDs: {n:,} | "
|
||||
f"Letzte: {new_uuid} | "
|
||||
f"Doppelt: {'JA' if duplicate else 'NEIN'} | "
|
||||
f"Kolliw.: {probability:.12e}",
|
||||
end="",
|
||||
flush=True
|
||||
)
|
||||
|
||||
if duplicate:
|
||||
print("\n\n!!! KOLLISION GEFUNDEN !!!")
|
||||
print(f"UUID: {new_uuid}")
|
||||
print(f"Nach {n:,} Versuchen")
|
||||
break
|
||||
|
||||
except KeyboardInterrupt:
|
||||
print("\n\nTest beendet.")
|
||||
|
||||
finally:
|
||||
conn.close()
|
||||
Reference in New Issue
Block a user