Initial commit
This commit is contained in:
commit
e612b471a5
11 changed files with 862 additions and 0 deletions
264
preamble.tex
Normal file
264
preamble.tex
Normal file
|
@ -0,0 +1,264 @@
|
||||||
|
% Some basic packages
|
||||||
|
\usepackage[utf8]{inputenc}
|
||||||
|
\usepackage[T1]{fontenc}
|
||||||
|
\usepackage{textcomp}
|
||||||
|
\usepackage[dutch]{babel}
|
||||||
|
\usepackage{url}
|
||||||
|
\usepackage{graphicx}
|
||||||
|
\usepackage{float}
|
||||||
|
\usepackage{booktabs}
|
||||||
|
\usepackage{enumitem}
|
||||||
|
|
||||||
|
% Don't indent paragraphs, leave some space between them
|
||||||
|
\usepackage{parskip}
|
||||||
|
|
||||||
|
% Hide page number when page is empty
|
||||||
|
\usepackage{emptypage}
|
||||||
|
\usepackage{subcaption}
|
||||||
|
\usepackage{multicol}
|
||||||
|
\usepackage{xcolor}
|
||||||
|
|
||||||
|
% Other font I sometimes use.
|
||||||
|
% \usepackage{cmbright}
|
||||||
|
|
||||||
|
% Math stuff
|
||||||
|
\usepackage{amsmath, amsfonts, mathtools, amsthm, amssymb}
|
||||||
|
% Fancy script capitals
|
||||||
|
\usepackage{mathrsfs}
|
||||||
|
\usepackage{cancel}
|
||||||
|
% Bold math
|
||||||
|
\usepackage{bm}
|
||||||
|
% Some shortcuts
|
||||||
|
\newcommand\N{\ensuremath{\mathbb{N}}}
|
||||||
|
\newcommand\R{\ensuremath{\mathbb{R}}}
|
||||||
|
\newcommand\Z{\ensuremath{\mathbb{Z}}}
|
||||||
|
\renewcommand\O{\ensuremath{\emptyset}}
|
||||||
|
\newcommand\Q{\ensuremath{\mathbb{Q}}}
|
||||||
|
\newcommand\C{\ensuremath{\mathbb{C}}}
|
||||||
|
|
||||||
|
% Easily typeset systems of equations (French package)
|
||||||
|
\usepackage{systeme}
|
||||||
|
|
||||||
|
% Put x \to \infty below \lim
|
||||||
|
\let\svlim\lim\def\lim{\svlim\limits}
|
||||||
|
|
||||||
|
%Make implies and impliedby shorter
|
||||||
|
\let\implies\Rightarrow
|
||||||
|
\let\impliedby\Leftarrow
|
||||||
|
\let\iff\Leftrightarrow
|
||||||
|
\let\epsilon\varepsilon
|
||||||
|
|
||||||
|
% Add \contra symbol to denote contradiction
|
||||||
|
\usepackage{stmaryrd} % for \lightning
|
||||||
|
\newcommand\contra{\scalebox{1.5}{$\lightning$}}
|
||||||
|
|
||||||
|
% \let\phi\varphi
|
||||||
|
|
||||||
|
% Command for short corrections
|
||||||
|
% Usage: 1+1=\correct{3}{2}
|
||||||
|
|
||||||
|
\definecolor{correct}{HTML}{009900}
|
||||||
|
\newcommand\correct[2]{\ensuremath{\:}{\color{red}{#1}}\ensuremath{\to }{\color{correct}{#2}}\ensuremath{\:}}
|
||||||
|
\newcommand\green[1]{{\color{correct}{#1}}}
|
||||||
|
|
||||||
|
% horizontal rule
|
||||||
|
\newcommand\hr{
|
||||||
|
\noindent\rule[0.5ex]{\linewidth}{0.5pt}
|
||||||
|
}
|
||||||
|
|
||||||
|
% hide parts
|
||||||
|
\newcommand\hide[1]{}
|
||||||
|
|
||||||
|
% si unitx
|
||||||
|
\usepackage{siunitx}
|
||||||
|
\sisetup{locale = FR}
|
||||||
|
|
||||||
|
% Environments
|
||||||
|
\makeatother
|
||||||
|
% For box around Definition, Theorem, \ldots
|
||||||
|
\usepackage{mdframed}
|
||||||
|
\mdfsetup{skipabove=1em,skipbelow=0em}
|
||||||
|
\theoremstyle{definition}
|
||||||
|
\newmdtheoremenv[nobreak=true]{definitie}{Definitie}
|
||||||
|
\newmdtheoremenv[nobreak=true]{eigenschap}{Eigenschap}
|
||||||
|
\newmdtheoremenv[nobreak=true]{gevolg}{Gevolg}
|
||||||
|
\newmdtheoremenv[nobreak=true]{lemma}{Lemma}
|
||||||
|
\newmdtheoremenv[nobreak=true]{propositie}{Propositie}
|
||||||
|
\newmdtheoremenv[nobreak=true]{stelling}{Stelling}
|
||||||
|
\newmdtheoremenv[nobreak=true]{wet}{Wet}
|
||||||
|
\newmdtheoremenv[nobreak=true]{postulaat}{Postulaat}
|
||||||
|
\newmdtheoremenv{conclusie}{Conclusie}
|
||||||
|
\newmdtheoremenv{toemaatje}{Toemaatje}
|
||||||
|
\newmdtheoremenv{vermoeden}{Vermoeden}
|
||||||
|
\newtheorem*{herhaling}{Herhaling}
|
||||||
|
\newtheorem*{intermezzo}{Intermezzo}
|
||||||
|
\newtheorem*{notatie}{Notatie}
|
||||||
|
\newtheorem*{observatie}{Observatie}
|
||||||
|
\newtheorem*{oef}{Oefening}
|
||||||
|
\newtheorem*{opmerking}{Opmerking}
|
||||||
|
\newtheorem*{praktisch}{Praktisch}
|
||||||
|
\newtheorem*{probleem}{Probleem}
|
||||||
|
\newtheorem*{terminologie}{Terminologie}
|
||||||
|
\newtheorem*{toepassing}{Toepassing}
|
||||||
|
\newtheorem*{uovt}{UOVT}
|
||||||
|
\newtheorem*{vb}{Voorbeeld}
|
||||||
|
\newtheorem*{vraag}{Vraag}
|
||||||
|
|
||||||
|
\newmdtheoremenv[nobreak=true]{definition}{Definition}
|
||||||
|
\newtheorem*{eg}{Example}
|
||||||
|
\newtheorem*{notation}{Notation}
|
||||||
|
\newtheorem*{previouslyseen}{As previously seen}
|
||||||
|
\newtheorem*{remark}{Remark}
|
||||||
|
\newtheorem*{note}{Note}
|
||||||
|
\newtheorem*{problem}{Problem}
|
||||||
|
\newtheorem*{observe}{Observe}
|
||||||
|
\newtheorem*{property}{Property}
|
||||||
|
\newtheorem*{intuition}{Intuition}
|
||||||
|
\newmdtheoremenv[nobreak=true]{prop}{Proposition}
|
||||||
|
\newmdtheoremenv[nobreak=true]{theorem}{Theorem}
|
||||||
|
\newmdtheoremenv[nobreak=true]{corollary}{Corollary}
|
||||||
|
|
||||||
|
% End example and intermezzo environments with a small diamond (just like proof
|
||||||
|
% environments end with a small square)
|
||||||
|
\usepackage{etoolbox}
|
||||||
|
\AtEndEnvironment{vb}{\null\hfill$\diamond$}%
|
||||||
|
\AtEndEnvironment{intermezzo}{\null\hfill$\diamond$}%
|
||||||
|
% \AtEndEnvironment{opmerking}{\null\hfill$\diamond$}%
|
||||||
|
|
||||||
|
% Fix some spacing
|
||||||
|
% http://tex.stackexchange.com/questions/22119/how-can-i-change-the-spacing-before-theorems-with-amsthm
|
||||||
|
\makeatletter
|
||||||
|
\def\thm@space@setup{%
|
||||||
|
\thm@preskip=\parskip \thm@postskip=0pt
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
% Exercise
|
||||||
|
% Usage:
|
||||||
|
% \oefening{5}
|
||||||
|
% \suboefening{1}
|
||||||
|
% \suboefening{2}
|
||||||
|
% \suboefening{3}
|
||||||
|
% gives
|
||||||
|
% Oefening 5
|
||||||
|
% Oefening 5.1
|
||||||
|
% Oefening 5.2
|
||||||
|
% Oefening 5.3
|
||||||
|
\newcommand{\oefening}[1]{%
|
||||||
|
\def\@oefening{#1}%
|
||||||
|
\subsection*{Oefening #1}
|
||||||
|
}
|
||||||
|
|
||||||
|
\newcommand{\suboefening}[1]{%
|
||||||
|
\subsubsection*{Oefening \@oefening.#1}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
% \lecture starts a new lecture (les in dutch)
|
||||||
|
%
|
||||||
|
% Usage:
|
||||||
|
% \lecture{1}{di 12 feb 2019 16:00}{Inleiding}
|
||||||
|
%
|
||||||
|
% This adds a section heading with the number / title of the lecture and a
|
||||||
|
% margin paragraph with the date.
|
||||||
|
|
||||||
|
% I use \dateparts here to hide the year (2019). This way, I can easily parse
|
||||||
|
% the date of each lecture unambiguously while still having a human-friendly
|
||||||
|
% short format printed to the pdf.
|
||||||
|
|
||||||
|
\usepackage{xifthen}
|
||||||
|
\def\testdateparts#1{\dateparts#1\relax}
|
||||||
|
\def\dateparts#1 #2 #3 #4 #5\relax{
|
||||||
|
\marginpar{\small\textsf{\mbox{#1 #2 #3 #5}}}
|
||||||
|
}
|
||||||
|
|
||||||
|
\def\@lecture{}%
|
||||||
|
\newcommand{\lecture}[3]{
|
||||||
|
\ifthenelse{\isempty{#3}}{%
|
||||||
|
\def\@lecture{Lecture #1}%
|
||||||
|
}{%
|
||||||
|
\def\@lecture{Lecture #1: #3}%
|
||||||
|
}%
|
||||||
|
\subsection*{\@lecture}
|
||||||
|
\marginpar{\small\textsf{\mbox{#2}}}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
% These are the fancy headers
|
||||||
|
\usepackage{fancyhdr}
|
||||||
|
\pagestyle{fancy}
|
||||||
|
|
||||||
|
% LE: left even
|
||||||
|
% RO: right odd
|
||||||
|
% CE, CO: center even, center odd
|
||||||
|
% My name for when I print my lecture notes to use for an open book exam.
|
||||||
|
% \fancyhead[LE,RO]{Gilles Castel}
|
||||||
|
|
||||||
|
\fancyhead[RO,LE]{\@lecture} % Right odd, Left even
|
||||||
|
\fancyhead[RE,LO]{} % Right even, Left odd
|
||||||
|
|
||||||
|
\fancyfoot[RO,LE]{\thepage} % Right odd, Left even
|
||||||
|
\fancyfoot[RE,LO]{} % Right even, Left odd
|
||||||
|
\fancyfoot[C]{\leftmark} % Center
|
||||||
|
|
||||||
|
\makeatother
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
% Todonotes and inline notes in fancy boxes
|
||||||
|
\usepackage{todonotes}
|
||||||
|
\usepackage{tcolorbox}
|
||||||
|
|
||||||
|
% Make boxes breakable
|
||||||
|
\tcbuselibrary{breakable}
|
||||||
|
|
||||||
|
% Verbetering is correction in Dutch
|
||||||
|
% Usage:
|
||||||
|
% \begin{verbetering}
|
||||||
|
% Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod
|
||||||
|
% tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At
|
||||||
|
% vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren,
|
||||||
|
% no sea takimata sanctus est Lorem ipsum dolor sit amet.
|
||||||
|
% \end{verbetering}
|
||||||
|
\newenvironment{verbetering}{\begin{tcolorbox}[
|
||||||
|
arc=0mm,
|
||||||
|
colback=white,
|
||||||
|
colframe=green!60!black,
|
||||||
|
title=Opmerking,
|
||||||
|
fonttitle=\sffamily,
|
||||||
|
breakable
|
||||||
|
]}{\end{tcolorbox}}
|
||||||
|
|
||||||
|
% Noot is note in Dutch. Same as 'verbetering' but color of box is different
|
||||||
|
\newenvironment{noot}[1]{\begin{tcolorbox}[
|
||||||
|
arc=0mm,
|
||||||
|
colback=white,
|
||||||
|
colframe=white!60!black,
|
||||||
|
title=#1,
|
||||||
|
fonttitle=\sffamily,
|
||||||
|
breakable
|
||||||
|
]}{\end{tcolorbox}}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
% Figure support as explained in my blog post.
|
||||||
|
\usepackage{import}
|
||||||
|
\usepackage{xifthen}
|
||||||
|
\pdfminorversion=7
|
||||||
|
\usepackage{pdfpages}
|
||||||
|
\usepackage{transparent}
|
||||||
|
\newcommand{\incfig}[1]{%
|
||||||
|
\def\svgwidth{\columnwidth}
|
||||||
|
\import{./figures/}{#1.pdf_tex}
|
||||||
|
}
|
||||||
|
|
||||||
|
% Fix some stuff
|
||||||
|
% %http://tex.stackexchange.com/questions/76273/multiple-pdfs-with-page-group-included-in-a-single-page-warning
|
||||||
|
\pdfsuppresswarningpagegroup=1
|
||||||
|
|
||||||
|
|
||||||
|
% My name
|
||||||
|
\author{Gilles Castel}
|
9
scripts/compile-all-masters.py
Executable file
9
scripts/compile-all-masters.py
Executable file
|
@ -0,0 +1,9 @@
|
||||||
|
#!/bin/python3
|
||||||
|
from courses import Courses
|
||||||
|
|
||||||
|
for course in Courses():
|
||||||
|
lectures = course.lectures
|
||||||
|
|
||||||
|
r = lectures.parse_range_string('all')
|
||||||
|
lectures.update_lectures_in_master(r)
|
||||||
|
lectures.compile_master()
|
11
scripts/config.py
Normal file
11
scripts/config.py
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
from datetime import datetime
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
def get_week(d=datetime.today()):
|
||||||
|
return (int(d.strftime("%W")) + 52 - 5) % 52
|
||||||
|
|
||||||
|
CURRENT_COURSE_SYMLINK = Path('~/current_course').expanduser()
|
||||||
|
CURRENT_COURSE_ROOT = CURRENT_COURSE_SYMLINK.resolve()
|
||||||
|
CURRENT_COURSE_WATCH_FILE = Path('/tmp/current_course').resolve()
|
||||||
|
ROOT = Path('~/Documents/Kulak/bachelor_3/semester_2').expanduser()
|
||||||
|
DATE_FORMAT = '%a %d %b %Y %H:%M'
|
224
scripts/countdown.py
Executable file
224
scripts/countdown.py
Executable file
|
@ -0,0 +1,224 @@
|
||||||
|
#!/usr/bin/python3
|
||||||
|
import pickle
|
||||||
|
|
||||||
|
import os
|
||||||
|
import os.path
|
||||||
|
import sys
|
||||||
|
|
||||||
|
import re
|
||||||
|
import math
|
||||||
|
|
||||||
|
import sched
|
||||||
|
import datetime
|
||||||
|
import time
|
||||||
|
import pytz
|
||||||
|
from dateutil.parser import parse
|
||||||
|
|
||||||
|
import http.client as httplib
|
||||||
|
|
||||||
|
from googleapiclient.discovery import build
|
||||||
|
from google_auth_oauthlib.flow import InstalledAppFlow
|
||||||
|
from google.auth.transport.requests import Request
|
||||||
|
|
||||||
|
from courses import Courses
|
||||||
|
|
||||||
|
courses = Courses()
|
||||||
|
|
||||||
|
def authenticate():
|
||||||
|
print('Authenticating')
|
||||||
|
# If modifying these scopes, delete the file token.pickle.
|
||||||
|
SCOPES = ['https://www.googleapis.com/auth/calendar.readonly']
|
||||||
|
creds = None
|
||||||
|
# The file token.pickle stores the user's access and refresh tokens, and is
|
||||||
|
# created automatically when the authorization flow completes for the first
|
||||||
|
# time.
|
||||||
|
if os.path.exists('token.pickle'):
|
||||||
|
with open('token.pickle', 'rb') as token:
|
||||||
|
creds = pickle.load(token)
|
||||||
|
|
||||||
|
# If there are no (valid) credentials available, let the user log in.
|
||||||
|
if not creds or not creds.valid:
|
||||||
|
if creds and creds.expired and creds.refresh_token:
|
||||||
|
print('Refreshing credentials')
|
||||||
|
creds.refresh(Request())
|
||||||
|
else:
|
||||||
|
print('Need to allow access')
|
||||||
|
flow = InstalledAppFlow.from_client_secrets_file('credentials.json', SCOPES)
|
||||||
|
creds = flow.run_local_server(port=0)
|
||||||
|
# Save the credentials for the next run
|
||||||
|
with open('token.pickle', 'wb') as token:
|
||||||
|
pickle.dump(creds, token)
|
||||||
|
|
||||||
|
service = build('calendar', 'v3', credentials=creds)
|
||||||
|
return service
|
||||||
|
|
||||||
|
def join(*args):
|
||||||
|
return ' '.join(str(e) for e in args if e)
|
||||||
|
|
||||||
|
def truncate(string, length):
|
||||||
|
ellipsis = ' ...'
|
||||||
|
if len(string) < length:
|
||||||
|
return string
|
||||||
|
return string[:length - len(ellipsis)] + ellipsis
|
||||||
|
|
||||||
|
def summary(text):
|
||||||
|
return truncate(re.sub(r'X[0-9A-Za-z]+', '', text).strip(), 50)
|
||||||
|
|
||||||
|
def gray(text):
|
||||||
|
return '%{F#999999}' + text + '%{F-}'
|
||||||
|
|
||||||
|
def formatdd(begin, end):
|
||||||
|
minutes = math.ceil((end - begin).seconds / 60)
|
||||||
|
|
||||||
|
if minutes == 1:
|
||||||
|
return '1 minuut'
|
||||||
|
|
||||||
|
if minutes < 60:
|
||||||
|
return f'{minutes} min'
|
||||||
|
|
||||||
|
hours = math.floor(minutes/60)
|
||||||
|
rest_minutes = minutes % 60
|
||||||
|
|
||||||
|
if hours > 5 or rest_minutes == 0:
|
||||||
|
return f'{hours} uur'
|
||||||
|
|
||||||
|
return '{}:{:02d} uur'.format(hours, rest_minutes)
|
||||||
|
|
||||||
|
def location(text):
|
||||||
|
if not text:
|
||||||
|
return ''
|
||||||
|
match = re.search(r'\((.*)\)', text)
|
||||||
|
|
||||||
|
if not match:
|
||||||
|
return ''
|
||||||
|
|
||||||
|
return f'{gray("in")} {match.group(1)}'
|
||||||
|
|
||||||
|
def text(events, now):
|
||||||
|
current = next((e for e in events if e['start'] < now and now < e['end']), None)
|
||||||
|
|
||||||
|
if not current:
|
||||||
|
nxt = next((e for e in events if now <= e['start']), None)
|
||||||
|
if nxt:
|
||||||
|
return join(
|
||||||
|
summary(nxt['summary']),
|
||||||
|
gray('over'),
|
||||||
|
formatdd(now, nxt['start']),
|
||||||
|
location(nxt['location'])
|
||||||
|
)
|
||||||
|
return ''
|
||||||
|
|
||||||
|
nxt = next((e for e in events if e['start'] >= current['end']), None)
|
||||||
|
if not nxt:
|
||||||
|
return join(gray('Einde over'), formatdd(now, current['end']) + '!')
|
||||||
|
|
||||||
|
if current['end'] == nxt['start']:
|
||||||
|
return join(
|
||||||
|
gray('Einde over'),
|
||||||
|
formatdd(now, current['end']) + gray('.'),
|
||||||
|
gray('Hierna'),
|
||||||
|
summary(nxt['summary']),
|
||||||
|
location(nxt['location'])
|
||||||
|
)
|
||||||
|
|
||||||
|
return join(
|
||||||
|
gray('Einde over'),
|
||||||
|
formatdd(now, current['end']) + gray('.'),
|
||||||
|
gray('Hierna'),
|
||||||
|
summary(nxt['summary']),
|
||||||
|
location(nxt['location']),
|
||||||
|
gray('na een pauze van'),
|
||||||
|
formatdd(current['end'], nxt['start'])
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def activate_course(event):
|
||||||
|
course = next(
|
||||||
|
(course for course in courses
|
||||||
|
if course.info['title'].lower() in event['summary'].lower()),
|
||||||
|
None
|
||||||
|
)
|
||||||
|
|
||||||
|
if not course:
|
||||||
|
return
|
||||||
|
|
||||||
|
courses.current = course
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
scheduler = sched.scheduler(time.time, time.sleep)
|
||||||
|
|
||||||
|
print('Initializing')
|
||||||
|
if 'TZ' in os.environ:
|
||||||
|
TZ = pytz.timezone(os.environ['TZ'])
|
||||||
|
else:
|
||||||
|
print("Warning: TZ environ variable not set")
|
||||||
|
|
||||||
|
|
||||||
|
service = authenticate()
|
||||||
|
|
||||||
|
print('Authenticated')
|
||||||
|
# Call the Calendar API
|
||||||
|
now = datetime.datetime.now(tz=TZ)
|
||||||
|
|
||||||
|
morning = now.replace(hour=6, minute=0, microsecond=0)
|
||||||
|
evening= now.replace(hour=23, minute=59, microsecond=0)
|
||||||
|
|
||||||
|
print('Searching for events')
|
||||||
|
|
||||||
|
def get_events(calendar):
|
||||||
|
events_result = service.events().list(
|
||||||
|
calendarId=calendar,
|
||||||
|
timeMin=morning.isoformat(),
|
||||||
|
timeMax=evening.isoformat(),
|
||||||
|
singleEvents=True,
|
||||||
|
orderBy='startTime'
|
||||||
|
).execute()
|
||||||
|
events = events_result.get('items', [])
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
'summary': event['summary'],
|
||||||
|
'location': event.get('location', None),
|
||||||
|
'start': parse(event['start']['dateTime']),
|
||||||
|
'end': parse(event['end']['dateTime'])
|
||||||
|
}
|
||||||
|
for event in events
|
||||||
|
if 'dateTime' in event['start']
|
||||||
|
]
|
||||||
|
|
||||||
|
events = get_events('primary')
|
||||||
|
# events = get_events('primary') + get_events('school-calendar@import.calendar.google.com')
|
||||||
|
print('Done')
|
||||||
|
|
||||||
|
DELAY = 60
|
||||||
|
|
||||||
|
def print_message():
|
||||||
|
now = datetime.datetime.now(tz=TZ)
|
||||||
|
print(text(events, now))
|
||||||
|
if now < evening:
|
||||||
|
scheduler.enter(DELAY, 1, print_message)
|
||||||
|
|
||||||
|
for event in events:
|
||||||
|
# absolute entry, priority 1
|
||||||
|
scheduler.enterabs(event['start'].timestamp(), 1, activate_course, argument=(event, ))
|
||||||
|
|
||||||
|
# Immediate, priority 1
|
||||||
|
scheduler.enter(0, 1, print_message)
|
||||||
|
scheduler.run()
|
||||||
|
|
||||||
|
|
||||||
|
def wait_for_internet_connection(url, timeout=1):
|
||||||
|
while True:
|
||||||
|
conn = httplib.HTTPConnection(url, timeout=5)
|
||||||
|
try:
|
||||||
|
conn.request("HEAD", "/")
|
||||||
|
conn.close()
|
||||||
|
return True
|
||||||
|
except:
|
||||||
|
conn.close()
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
os.chdir(sys.path[0])
|
||||||
|
print('Waiting for connection')
|
||||||
|
wait_for_internet_connection('www.google.com')
|
||||||
|
main()
|
44
scripts/courses.py
Executable file
44
scripts/courses.py
Executable file
|
@ -0,0 +1,44 @@
|
||||||
|
#!/usr/bin/python3
|
||||||
|
from pathlib import Path
|
||||||
|
import yaml
|
||||||
|
|
||||||
|
from lectures import Lectures
|
||||||
|
from config import ROOT, CURRENT_COURSE_ROOT, CURRENT_COURSE_SYMLINK, CURRENT_COURSE_WATCH_FILE
|
||||||
|
|
||||||
|
class Course():
|
||||||
|
def __init__(self, path):
|
||||||
|
self.path = path
|
||||||
|
self.name = path.stem
|
||||||
|
|
||||||
|
self.info = yaml.load((path / 'info.yaml').open())
|
||||||
|
self._lectures = None
|
||||||
|
|
||||||
|
@property
|
||||||
|
def lectures(self):
|
||||||
|
if not self._lectures:
|
||||||
|
self._lectures = Lectures(self)
|
||||||
|
return self._lectures
|
||||||
|
|
||||||
|
def __eq__(self, other):
|
||||||
|
if other == None:
|
||||||
|
return False
|
||||||
|
return self.path == other.path
|
||||||
|
|
||||||
|
class Courses(list):
|
||||||
|
def __init__(self):
|
||||||
|
list.__init__(self, self.read_files())
|
||||||
|
|
||||||
|
def read_files(self):
|
||||||
|
course_directories = [x for x in ROOT.iterdir() if x.is_dir()]
|
||||||
|
_courses = [Course(path) for path in course_directories]
|
||||||
|
return sorted(_courses, key=lambda c: c.name)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def current(self):
|
||||||
|
return Course(CURRENT_COURSE_ROOT.resolve())
|
||||||
|
|
||||||
|
@current.setter
|
||||||
|
def current(self, course):
|
||||||
|
CURRENT_COURSE_SYMLINK.unlink()
|
||||||
|
CURRENT_COURSE_SYMLINK.symlink_to(course.path)
|
||||||
|
CURRENT_COURSE_WATCH_FILE.write_text('{}\n'.format(course.info['short']))
|
190
scripts/lectures.py
Executable file
190
scripts/lectures.py
Executable file
|
@ -0,0 +1,190 @@
|
||||||
|
#!/usr/bin/python3
|
||||||
|
|
||||||
|
import os
|
||||||
|
from datetime import datetime
|
||||||
|
from pathlib import Path
|
||||||
|
import locale
|
||||||
|
import re
|
||||||
|
import subprocess
|
||||||
|
|
||||||
|
|
||||||
|
from config import get_week, DATE_FORMAT, CURRENT_COURSE_ROOT
|
||||||
|
|
||||||
|
# TODO
|
||||||
|
locale.setlocale(locale.LC_TIME, "nl_BE.utf8")
|
||||||
|
|
||||||
|
|
||||||
|
def number2filename(n):
|
||||||
|
return 'lec_{0:02d}.tex'.format(n)
|
||||||
|
|
||||||
|
def filename2number(s):
|
||||||
|
return int(str(s).replace('.tex', '').replace('lec_', ''))
|
||||||
|
|
||||||
|
class Lecture():
|
||||||
|
def __init__(self, file_path, course):
|
||||||
|
with file_path.open() as f:
|
||||||
|
for line in f:
|
||||||
|
lecture_match = re.search(r'lecture\{(.*?)\}\{(.*?)\}\{(.*)\}', line)
|
||||||
|
if lecture_match:
|
||||||
|
break;
|
||||||
|
|
||||||
|
# number = int(lecture_match.group(1))
|
||||||
|
|
||||||
|
date_str = lecture_match.group(2)
|
||||||
|
date = datetime.strptime(date_str, DATE_FORMAT)
|
||||||
|
week = get_week(date)
|
||||||
|
|
||||||
|
title = lecture_match.group(3)
|
||||||
|
|
||||||
|
self.file_path = file_path
|
||||||
|
self.date = date
|
||||||
|
self.week = week
|
||||||
|
self.number = filename2number(file_path.stem)
|
||||||
|
self.title = title
|
||||||
|
self.course = course
|
||||||
|
|
||||||
|
def edit(self):
|
||||||
|
subprocess.Popen([
|
||||||
|
"x-terminal-emulator",
|
||||||
|
"-e", "zsh", "-i", "-c",
|
||||||
|
f"\\vim --servername kulak --remote-silent {str(self.file_path)}"
|
||||||
|
])
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return f'<Lecture {self.course.info["short"]} {self.number} "{self.title}">'
|
||||||
|
|
||||||
|
|
||||||
|
class Lectures(list):
|
||||||
|
def __init__(self, course):
|
||||||
|
self.course = course
|
||||||
|
self.root = course.path
|
||||||
|
self.master_file = self.root / 'master.tex'
|
||||||
|
list.__init__(self, self.read_files())
|
||||||
|
|
||||||
|
def read_files(self):
|
||||||
|
files = self.root.glob('lec_*.tex')
|
||||||
|
return sorted((Lecture(f, self.course) for f in files), key=lambda l: l.number)
|
||||||
|
|
||||||
|
def parse_lecture_spec(self, string):
|
||||||
|
if len(self) == 0:
|
||||||
|
return 0
|
||||||
|
|
||||||
|
if string.isdigit():
|
||||||
|
return int(string)
|
||||||
|
elif string == 'last':
|
||||||
|
return self[-1].number
|
||||||
|
elif string == 'prev':
|
||||||
|
return self[-1].number - 1
|
||||||
|
|
||||||
|
def parse_range_string(self, arg):
|
||||||
|
all_numbers = [lecture.number for lecture in self]
|
||||||
|
if 'all' in arg:
|
||||||
|
return all_numbers
|
||||||
|
|
||||||
|
if '-' in arg:
|
||||||
|
start, end = [self.parse_lecture_spec(bit) for bit in arg.split('-')]
|
||||||
|
return list(set(all_numbers) & set(range(start, end + 1)))
|
||||||
|
|
||||||
|
return [self.parse_lecture_spec(arg)]
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_header_footer(filepath):
|
||||||
|
part = 0
|
||||||
|
header = ''
|
||||||
|
footer = ''
|
||||||
|
with filepath.open() as f:
|
||||||
|
for line in f:
|
||||||
|
# order of if-statements is important here!
|
||||||
|
if 'end lectures' in line:
|
||||||
|
part = 2
|
||||||
|
|
||||||
|
if part == 0:
|
||||||
|
header += line
|
||||||
|
if part == 2:
|
||||||
|
footer += line
|
||||||
|
|
||||||
|
if 'start lectures' in line:
|
||||||
|
part = 1
|
||||||
|
return (header, footer)
|
||||||
|
|
||||||
|
def update_lectures_in_master(self, r):
|
||||||
|
header, footer = self.get_header_footer(self.master_file)
|
||||||
|
body = ''.join(
|
||||||
|
' ' * 4 + r'\input{' + number2filename(number) + '}\n' for number in r)
|
||||||
|
self.master_file.write_text(header + body + footer)
|
||||||
|
|
||||||
|
def new_lecture(self):
|
||||||
|
if len(self) != 0:
|
||||||
|
new_lecture_number = self[-1].number + 1
|
||||||
|
else:
|
||||||
|
new_lecture_number = 1
|
||||||
|
|
||||||
|
new_lecture_path = self.root / number2filename(new_lecture_number)
|
||||||
|
|
||||||
|
today = datetime.today()
|
||||||
|
date = today.strftime(DATE_FORMAT)
|
||||||
|
|
||||||
|
new_lecture_path.touch()
|
||||||
|
new_lecture_path.write_text(f'\\lecture{{{new_lecture_number}}}{{{date}}}{{}}\n')
|
||||||
|
|
||||||
|
if new_lecture_number == 1:
|
||||||
|
self.update_lectures_in_master([1])
|
||||||
|
else:
|
||||||
|
self.update_lectures_in_master([new_lecture_number - 1, new_lecture_number])
|
||||||
|
|
||||||
|
self.read_files()
|
||||||
|
|
||||||
|
|
||||||
|
l = Lecture(new_lecture_path, self.course)
|
||||||
|
|
||||||
|
return l
|
||||||
|
|
||||||
|
def compile_master(self):
|
||||||
|
subprocess.Popen(
|
||||||
|
['latexmk', '-g', '-f', str(self.master_file)],
|
||||||
|
cwd=str(self.root),
|
||||||
|
stdout=subprocess.DEVNULL,
|
||||||
|
stderr=subprocess.DEVNULL
|
||||||
|
)
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
import sys
|
||||||
|
args = sys.argv
|
||||||
|
command = args[1]
|
||||||
|
|
||||||
|
|
||||||
|
lectures = Lectures(Path.cwd())
|
||||||
|
|
||||||
|
if command == 'view':
|
||||||
|
lecture_range = args[2]
|
||||||
|
lecture_range = lectures.parse_range_string(lecture_range)
|
||||||
|
print(lecture_range)
|
||||||
|
lectures.update_lectures_in_master(lecture_range)
|
||||||
|
lectures.compile_master()
|
||||||
|
|
||||||
|
if command == 'new':
|
||||||
|
lectures.new_lecture()
|
||||||
|
|
||||||
|
if command == 'init':
|
||||||
|
from utils import beautify
|
||||||
|
course_title = beautify(lectures.root.stem)
|
||||||
|
lines = [r'\documentclass[a4paper]{article}',
|
||||||
|
r'\input{../preamble.tex}',
|
||||||
|
fr'\title{{{course_title}}}',
|
||||||
|
r'\begin{document}',
|
||||||
|
r' \maketitle',
|
||||||
|
r' \tableofcontents',
|
||||||
|
r' % start lectures',
|
||||||
|
r' % end lectures',
|
||||||
|
r'\end{document}'
|
||||||
|
]
|
||||||
|
lectures.master_file.touch()
|
||||||
|
lectures.master_file.write_text('\n'.join(lines))
|
||||||
|
|
||||||
|
(lectures.root / 'master.tex.latexmain').touch()
|
||||||
|
|
||||||
|
info_file = lectures.root / 'info.yaml'
|
||||||
|
info_file.touch()
|
||||||
|
info_file.write_text(f"title: '{course_title}'")
|
||||||
|
|
||||||
|
(lectures.root / 'figures').mkdir()
|
22
scripts/rofi-courses.py
Executable file
22
scripts/rofi-courses.py
Executable file
|
@ -0,0 +1,22 @@
|
||||||
|
#!/usr/bin/python3
|
||||||
|
from rofi import rofi
|
||||||
|
|
||||||
|
from courses import Courses
|
||||||
|
|
||||||
|
courses = Courses()
|
||||||
|
current = courses.current
|
||||||
|
|
||||||
|
try:
|
||||||
|
current_index = courses.index(current)
|
||||||
|
args = ['-a', current_index]
|
||||||
|
except ValueError:
|
||||||
|
args = []
|
||||||
|
|
||||||
|
code, index, selected = rofi('Select course', [c.info['title'] for c in courses], [
|
||||||
|
'-auto-select',
|
||||||
|
'-no-custom',
|
||||||
|
'-lines', len(courses)
|
||||||
|
] + args)
|
||||||
|
|
||||||
|
if index >= 0:
|
||||||
|
courses.current = courses[index]
|
22
scripts/rofi-lectures-view.py
Executable file
22
scripts/rofi-lectures-view.py
Executable file
|
@ -0,0 +1,22 @@
|
||||||
|
#!/usr/bin/python3
|
||||||
|
from courses import Courses
|
||||||
|
from rofi import rofi
|
||||||
|
|
||||||
|
lectures = Courses().current.lectures
|
||||||
|
|
||||||
|
commands = ['last', 'prev-last', 'all', 'prev']
|
||||||
|
options = ['Current lecture', 'Last two lectures', 'All lectures', 'Previous lectures']
|
||||||
|
|
||||||
|
key, index, selected = rofi('Select view', options, [
|
||||||
|
'-lines', 4,
|
||||||
|
'-auto-select'
|
||||||
|
])
|
||||||
|
|
||||||
|
if index >= 0:
|
||||||
|
command = commands[index]
|
||||||
|
else:
|
||||||
|
command = selected
|
||||||
|
|
||||||
|
lecture_range = lectures.parse_range_string(command)
|
||||||
|
lectures.update_lectures_in_master(lecture_range)
|
||||||
|
lectures.compile_master()
|
32
scripts/rofi-lectures.py
Executable file
32
scripts/rofi-lectures.py
Executable file
|
@ -0,0 +1,32 @@
|
||||||
|
#!/usr/bin/python3
|
||||||
|
from courses import Courses
|
||||||
|
from rofi import rofi
|
||||||
|
from utils import generate_short_title, MAX_LEN
|
||||||
|
|
||||||
|
lectures = Courses().current.lectures
|
||||||
|
|
||||||
|
sorted_lectures = sorted(lectures, key=lambda l: -l.number)
|
||||||
|
|
||||||
|
options = [
|
||||||
|
"{number: >2}. <b>{title: <{fill}}</b> <span size='smaller'>{date} ({week})</span>".format(
|
||||||
|
fill=MAX_LEN,
|
||||||
|
number=lecture.number,
|
||||||
|
title=generate_short_title(lecture.title),
|
||||||
|
date=lecture.date.strftime('%a %d %b'),
|
||||||
|
week=lecture.week
|
||||||
|
)
|
||||||
|
for lecture in sorted_lectures
|
||||||
|
]
|
||||||
|
|
||||||
|
key, index, selected = rofi('Select lecture', options, [
|
||||||
|
'-lines', 5,
|
||||||
|
'-markup-rows',
|
||||||
|
'-kb-row-down', 'Down',
|
||||||
|
'-kb-custom-1', 'Ctrl+n'
|
||||||
|
])
|
||||||
|
|
||||||
|
if key == 0:
|
||||||
|
sorted_lectures[index].edit()
|
||||||
|
elif key == 1:
|
||||||
|
new_lecture = lectures.new_lecture()
|
||||||
|
new_lecture.edit()
|
30
scripts/rofi.py
Normal file
30
scripts/rofi.py
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
import subprocess
|
||||||
|
|
||||||
|
def rofi(prompt, options, rofi_args=[], fuzzy=True):
|
||||||
|
optionstr = '\n'.join(option.replace('\n', ' ') for option in options)
|
||||||
|
args = ['rofi', '-sort', '-no-levenshtein-sort']
|
||||||
|
if fuzzy:
|
||||||
|
args += ['-matching', 'fuzzy']
|
||||||
|
args += ['-dmenu', '-p', prompt, '-format', 's', '-i']
|
||||||
|
args += rofi_args
|
||||||
|
args = [str(arg) for arg in args]
|
||||||
|
|
||||||
|
|
||||||
|
result = subprocess.run(args, input=optionstr, stdout=subprocess.PIPE, universal_newlines=True)
|
||||||
|
returncode = result.returncode
|
||||||
|
stdout = result.stdout.strip()
|
||||||
|
|
||||||
|
selected = stdout.strip()
|
||||||
|
try:
|
||||||
|
index = [opt.strip() for opt in options].index(selected)
|
||||||
|
except ValueError:
|
||||||
|
index = -1
|
||||||
|
|
||||||
|
if returncode == 0:
|
||||||
|
key = 0
|
||||||
|
elif returncode == 1:
|
||||||
|
key = -1
|
||||||
|
elif returncode > 9:
|
||||||
|
key = returncode - 9
|
||||||
|
|
||||||
|
return key, index, selected
|
14
scripts/utils.py
Normal file
14
scripts/utils.py
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
def beautify(string):
|
||||||
|
return string.replace('_', ' ').replace('-', ' ').title()
|
||||||
|
|
||||||
|
def unbeautify(string):
|
||||||
|
return string.replace(' ', '-').lower()
|
||||||
|
|
||||||
|
MAX_LEN = 40
|
||||||
|
def generate_short_title(title):
|
||||||
|
short_title = title or 'Untitled'
|
||||||
|
if len(title) >= MAX_LEN:
|
||||||
|
short_title = title[:MAX_LEN - len(' ... ')] + ' ... '
|
||||||
|
short_title = short_title.replace('$', '')
|
||||||
|
return short_title
|
||||||
|
|
Loading…
Reference in a new issue