The terminal versions of the pages for the home directory of the shell server's root user, so each page's top command (cat README, cat README.en, cat CODE_OF_CONDUCT, cat CODE_OF_CONDUCT.en) works in the web terminal. Generated by server/make_files.py. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
335 lines
17 KiB
Python
335 lines
17 KiB
Python
# Builds README, README.en, CODE_OF_CONDUCT and CODE_OF_CONDUCT.en next to this script: the website's pages as
|
||
# terminal text with escape sequences, for the home directory of the shell server's root user.
|
||
# Usage: python3 server/make_files.py
|
||
import pathlib
|
||
import textwrap
|
||
|
||
ESC = '\x1b'
|
||
RESET = f'{ESC}[0m'
|
||
BOLD = f'{ESC}[1m'
|
||
DIM = f'{ESC}[38;2;31;154;63m' # the page's --dim
|
||
HOT = f'{ESC}[38;2;255;42;42m' # the page's --hot
|
||
WIDTH = 82 # the web terminal's width, as wide as the page column
|
||
INNER = WIDTH - 4 # text width inside "│ " ... " │"
|
||
SITE = 'https://berlin.ccc.de/kik/'
|
||
IMPRESSUM = 'https://berlin.ccc.de/page/impressum/'
|
||
TICKETS = 'https://tickets.berlin.ccc.de/cccb/kik-v0.1/'
|
||
CFP = 'https://cfp.berlin.ccc.de/kik-v01/cfp'
|
||
SCHEDULE = 'https://cfp.berlin.ccc.de/kik-v01/schedule'
|
||
MAP = 'https://www.openstreetmap.org/node/3872556357#map=20/52.5217003/13.3828041&layers=H'
|
||
|
||
|
||
def link(url, text, style=''):
|
||
return f'{ESC}]8;;{url}{ESC}\\{style}{text}{RESET if style else ""}{ESC}]8;;{ESC}\\'
|
||
|
||
|
||
class Line:
|
||
"""A line of text with its visible width, so boxes can be padded around styled text."""
|
||
def __init__(self, text='', width=None):
|
||
self.text, self.width = text, len(text) if width is None else width
|
||
|
||
def __add__(self, other):
|
||
return Line(self.text + other.text, self.width + other.width)
|
||
|
||
|
||
def styled(text, style):
|
||
return Line(f'{style}{text}{RESET}', len(text))
|
||
|
||
|
||
def linked(url, text, style=''):
|
||
return Line(link(url, text, style), len(text))
|
||
|
||
|
||
def joined(parts, gap=' '):
|
||
line = parts[0]
|
||
for part in parts[1:]:
|
||
line = line + Line(gap) + part
|
||
return line
|
||
|
||
|
||
def wrap(text, width, links=()):
|
||
"""Wraps plain text, then turns the given (text, url) pairs into links where they appear."""
|
||
lines = []
|
||
for raw in textwrap.wrap(text, width, break_on_hyphens=False, break_long_words=False):
|
||
rendered = raw
|
||
for needle, url in links:
|
||
rendered = rendered.replace(needle, link(url, needle))
|
||
lines.append(Line(rendered, len(raw)))
|
||
return lines
|
||
|
||
|
||
def paragraph(text, links=(), lead=None):
|
||
"""A paragraph, optionally starting with a bold lead-in like "Ticket:"."""
|
||
full = f'{lead} {text}' if lead else text
|
||
lines = wrap(full, INNER, links)
|
||
if lead:
|
||
first = lines[0]
|
||
lines[0] = Line(first.text.replace(lead, f'{BOLD}{lead}{RESET}', 1), first.width)
|
||
return lines
|
||
|
||
|
||
def pairs(items, label, links=()):
|
||
"""A key-value list like the page's: dim "Label :" column, values wrapped with a hanging indent."""
|
||
lines = []
|
||
for key, value in items:
|
||
wrapped = wrap(value, INNER - label, links)
|
||
lines.append(styled(f'{key} :'.ljust(label), DIM) + wrapped[0])
|
||
lines += [Line(' ' * label) + rest for rest in wrapped[1:]]
|
||
return lines
|
||
|
||
|
||
def bullets(items):
|
||
"""A list like the page's: dim "> " marker, bold title, text wrapped with a hanging indent."""
|
||
lines = []
|
||
for title, text in items:
|
||
wrapped = wrap(f'{title} {text}', INNER - 2)
|
||
first = wrapped[0]
|
||
lines.append(styled('> ', DIM) + Line(first.text.replace(title, f'{BOLD}{title}{RESET}', 1), first.width))
|
||
lines += [Line(' ') + rest for rest in wrapped[1:]]
|
||
return lines
|
||
|
||
|
||
def box(title, lines):
|
||
rule = WIDTH - 2 - len(f'─┤ {title} ├')
|
||
out = [Line('┌─') + styled('┤ ', DIM) + styled(title, BOLD) + styled(' ├', DIM) + Line('─' * rule + '┐')]
|
||
out += [Line('│ ') + line + Line(' ' * (INNER - line.width) + ' │') for line in lines]
|
||
out.append(Line('└' + '─' * (WIDTH - 2) + '┘'))
|
||
return out
|
||
|
||
|
||
def centered(line):
|
||
return Line(' ' * ((WIDTH - line.width) // 2)) + line
|
||
|
||
|
||
def nav(line):
|
||
return [styled('─' * WIDTH, DIM), line, styled('─' * WIDTH, DIM)]
|
||
|
||
|
||
def footer(parts):
|
||
line = linked(parts[0][1], parts[0][0], DIM)
|
||
for text, url in parts[1:]:
|
||
line = line + styled(' · ', DIM) + linked(url, text, DIM)
|
||
return [styled('─' * WIDTH, DIM), centered(line)]
|
||
|
||
|
||
def page(lines):
|
||
lines = [Line(), *lines, Line()]
|
||
for line in lines:
|
||
assert line.width <= WIDTH + 2, (line.width, line.text)
|
||
return ''.join(f'{line.text}\n' for line in lines)
|
||
|
||
|
||
def readme(t):
|
||
label = 3 + max(len(key) for key, _ in t['infos'] + t['route'])
|
||
return page([
|
||
styled('KHAOS IM ', BOLD) + styled('KIEZ', BOLD + HOT),
|
||
styled(t['date'], DIM),
|
||
Line(),
|
||
*nav(joined([linked(f'{SITE}{t["page"]}#{anchor}', f'[{n}] {title}') for n, title, anchor in t['sections']])),
|
||
Line(),
|
||
*box('{} {}'.format(*t['sections'][0][:2]), [
|
||
*paragraph(t['intro'][0]),
|
||
Line(),
|
||
*paragraph(t['intro'][1]),
|
||
Line(),
|
||
*pairs(t['infos'], label, links=[('cfp.berlin.ccc.de', 'https://cfp.berlin.ccc.de'), ('Code of Conduct', f'{SITE}{t["coc"]}')]),
|
||
]),
|
||
Line(),
|
||
*box('{} {}'.format(*t['sections'][1][:2]), [
|
||
*paragraph(t['ticket'], lead=t['ticket_lead']),
|
||
Line(),
|
||
linked(TICKETS, f'[ {t["get_ticket"]} ]', HOT),
|
||
Line(),
|
||
*paragraph(t['talks'], lead=t['talks_lead']),
|
||
Line(),
|
||
joined([linked(CFP, f'[ {t["submit"]} ]'), linked(SCHEDULE, f'[ {t["schedule"]} ]')]),
|
||
Line(),
|
||
*paragraph(t['other'], lead=t['other_lead']),
|
||
]),
|
||
Line(),
|
||
*box('{} {}'.format(*t['sections'][2][:2]), [
|
||
*pairs(t['route'], label, links=[('BBBike.de', 'http://www.bbbike.de/')]),
|
||
Line(),
|
||
linked(MAP, f'[ {t["map"]} ]'),
|
||
]),
|
||
Line(),
|
||
*footer(t['footer']),
|
||
])
|
||
|
||
|
||
def code_of_conduct(t):
|
||
return page([
|
||
*box('Code of Conduct', [
|
||
*paragraph(t['intro']),
|
||
Line(),
|
||
*bullets(t['rules']),
|
||
Line(),
|
||
*paragraph(t['house'], lead=t['house_lead']),
|
||
Line(),
|
||
*paragraph(t['problems'], lead=t['problems_lead']),
|
||
]),
|
||
Line(),
|
||
*footer(t['footer']),
|
||
])
|
||
|
||
|
||
DE = {
|
||
'page': '', 'coc': 'coc.html',
|
||
'date': 'CCC Berlin · 28. – 29.11.2026',
|
||
'sections': [(1, 'Infos', 'infos'), (2, 'Mitmachen', 'mitmachen'), (3, 'Anfahrt', 'anfahrt')],
|
||
'intro': [
|
||
'Khaos im Kiez ist ein kleines Chaos-Event, back to the roots: inspiriert von anderen kleinen Chaos-Events. '
|
||
'Ein Wochenende zum Chillen und um die Seite von Chaos-Events zu erleben, für die bei größeren Events oft '
|
||
'keine Zeit mehr bleibt.',
|
||
'Du kannst einen Talk oder Workshop halten, an deinen Projekten schrauben, gemeinsam hacken, löten, basteln '
|
||
'oder einfach vorbeikommen, zuhören und Leute kennenlernen. Alle sind willkommen, die Lust auf $Dinge, '
|
||
'Neugier und Chaos mitbringen.',
|
||
],
|
||
'infos': [
|
||
('Was', 'Khaos im Kiez (KiK)'),
|
||
('Wann', 'Sa 28.11. – So 29.11.2026, 11:00 – 23:59'),
|
||
('Wo', 'Marienstraße 11, 10117 Berlin'),
|
||
('Wer', 'Chaos Computer Club Berlin'),
|
||
('Preis', '0,00 € (Ticket trotzdem nötig, ~50 Plätze)'),
|
||
('Fahrplan', 'cfp.berlin.ccc.de'),
|
||
('Essen', 'Ewiges Frühstück, vegan'),
|
||
('Barrierefrei', 'Nicht barrierefrei.'),
|
||
('Regeln', 'Code of Conduct'),
|
||
],
|
||
'ticket_lead': 'Ticket:',
|
||
'ticket': 'Anmeldung über den Ticketshop. Das Ticket ist kostenlos, aber die Plätze sind begrenzt.',
|
||
'get_ticket': 'Ticket sichern',
|
||
'talks_lead': 'Talks & Workshops:',
|
||
'talks': 'Reiche deinen Beitrag im Call for Participation ein und schau dir später den Fahrplan an.',
|
||
'submit': 'Talk einreichen', 'schedule': 'Fahrplan',
|
||
'other_lead': 'Sonstiges:',
|
||
'other': 'Du willst anders helfen oder hast Fragen? Schreib an aprl [at] berlin [dot] ccc [dot] de.',
|
||
'route': [
|
||
('Adresse', 'Clubräume im Hinterhof der Marienstraße 11, 10117 Berlin'),
|
||
('Koordinaten', '52,52194° N, 13,38261° O'),
|
||
('Fahrrad', 'Im Innenhof gibt es eine begrenzte Zahl an Fahrradständern. Auf dem Weg liegen viele '
|
||
'Fahrradstraßen, nutz die am besten, z. B. mit BBBike.de.'),
|
||
('ÖPNV', 'Der Club liegt direkt beim S+U-Bahnhof Friedrichstraße (auch Regionalbahnen). Vom oberirdischen '
|
||
'S-Bahn-Steig (S3/S5/S7) nimmst du den Ausgang Schiffbauerdamm und gehst unter der Bahnbrücke über '
|
||
'die Spree. Über die Albrechtstraße kommst du zur Marienstraße.'),
|
||
('Fernverkehr', 'Aussteigen in Berlin Hauptbahnhof, dann mit S-Bahn oder Regionalbahn Richtung Osten eine '
|
||
'Station bis Friedrichstraße. Bahnhof Richtung Spree verlassen, über die Fußgängerbrücke und '
|
||
'der Albrechtstraße bis zur Stichstraße Marienstraße folgen.'),
|
||
('Flugzeug', 'BER: Airport Express (RE7, RB14) oder S9 bis Friedrichstraße, alternativ FEX bis Hauptbahnhof '
|
||
'(dann wie Fernverkehr). Achtung: Terminal 5 (früher SXF) ist nicht mit dem Rest des BER '
|
||
'verbunden, dort fährt nur die S9. Flugplatz Strausberg (EDAY): S7 direkt bis Friedrichstraße.'),
|
||
('Auto', 'Zufahrt von Hauptbahnhof oder Brandenburger Tor über die Luisenstraße. Von der Friedrichstraße '
|
||
'entweder über Schiffbauerdamm und Albrechtstraße (Außengastronomie und Kopfsteinpflaster) oder '
|
||
'über Reinhardstraße und Luisenstraße (die Albrechtstraße ist nur in Gegenrichtung befahrbar).'),
|
||
('Parken', 'Achtung: Es gibt kaum Parkplätze, und die Marienstraße und alle umliegenden Straßen sind '
|
||
'parkraumbewirtschaftet.'),
|
||
],
|
||
'map': 'Karte (OpenStreetMap)',
|
||
'footer': [('Impressum', IMPRESSUM), ('CCC Berlin', 'https://berlin.ccc.de/')],
|
||
}
|
||
|
||
EN = {
|
||
'page': 'en.html', 'coc': 'coc-en.html',
|
||
'date': 'CCC Berlin · 2026-11-28 – 2026-11-29',
|
||
'sections': [(1, 'Info', 'infos'), (2, 'Join in', 'join'), (3, 'Getting here', 'getting-here')],
|
||
'intro': [
|
||
'Khaos im Kiez is a small chaos event, back to the roots: inspired by other small chaos events. A weekend to '
|
||
'chill and to experience the side of chaos events that often gets no time at larger events.',
|
||
'Give a talk or workshop, work on your projects, hack together, solder, tinker, or simply drop by, listen and '
|
||
'meet people. Everyone who brings a love of $things, curiosity and a bit of chaos is welcome.',
|
||
],
|
||
'infos': [
|
||
('What', 'Khaos im Kiez (KiK)'),
|
||
('When', 'Sat 2026-11-28 – Sun 2026-11-29, 11:00 – 23:59'),
|
||
('Where', 'Marienstraße 11, 10117 Berlin, Germany'),
|
||
('Who', 'Chaos Computer Club Berlin'),
|
||
('Price', '€0.00 (ticket still required, ~50 seats)'),
|
||
('Schedule', 'cfp.berlin.ccc.de'),
|
||
('Food', 'Eternal breakfast, vegan'),
|
||
('Accessibility', 'Not accessible.'),
|
||
('Rules', 'Code of Conduct'),
|
||
],
|
||
'ticket_lead': 'Ticket:',
|
||
'ticket': 'Register via the ticket shop. The ticket is free, but seats are limited.',
|
||
'get_ticket': 'Get ticket',
|
||
'talks_lead': 'Talks & workshops:',
|
||
'talks': 'Submit your proposal via the call for participation and check the schedule later.',
|
||
'submit': 'Submit a talk', 'schedule': 'Schedule',
|
||
'other_lead': 'Other:',
|
||
'other': 'Want to help otherwise or have questions? Write to aprl [at] berlin [dot] ccc [dot] de.',
|
||
'route': [
|
||
('Address', 'Club rooms in the back yard of Marienstraße 11, 10117 Berlin'),
|
||
('Coordinates', '52.52194° N, 13.38261° E'),
|
||
('Bicycle', 'A limited number of bike racks are in the courtyard. Many bicycle streets lead there, preferably '
|
||
'use those, e.g. with BBBike.de.'),
|
||
('Local transit', 'The club is right next to S+U Friedrichstraße (regional trains stop there too). From the '
|
||
'elevated S-Bahn platform (S3/S5/S7) take the Schiffbauerdamm exit and walk across the Spree '
|
||
'beneath the railway bridge. Albrechtstraße leads to Marienstraße.'),
|
||
('Long distance', 'Get off at Berlin Hauptbahnhof, then take any S-Bahn or regional train eastbound one stop '
|
||
'to Friedrichstraße. Leave the station towards the Spree, cross the footbridge and follow '
|
||
'Albrechtstraße to the dead-end Marienstraße.'),
|
||
('Air', 'BER: Airport Express (RE7, RB14) or S9 to Friedrichstraße, or FEX to Hauptbahnhof (then as above). '
|
||
'Note: Terminal 5 (formerly SXF) is not connected to the rest of BER, only the S9 runs there. '
|
||
'Strausberg airfield (EDAY): S7 direct to Friedrichstraße.'),
|
||
('Car', 'Access from Hauptbahnhof or Brandenburger Tor via Luisenstraße. From Friedrichstraße either via '
|
||
'Schiffbauerdamm and Albrechtstraße (outdoor seating and cobblestones) or via Reinhardstraße and '
|
||
'Luisenstraße (Albrechtstraße is one-way the other direction).'),
|
||
('Parking', 'Warning: parking is scarce, and Marienstraße and all surrounding streets are subject to paid '
|
||
'parking zones.'),
|
||
],
|
||
'map': 'Map (OpenStreetMap)',
|
||
'footer': [('Legal notice', IMPRESSUM), ('CCC Berlin', 'https://berlin.ccc.de/')],
|
||
}
|
||
|
||
COC_DE = {
|
||
'intro': 'Khaos im Kiez ist eine Veranstaltung des Chaos Computer Club Berlin e.V. (CCCB). Damit sich alle '
|
||
'wohlfühlen, gelten diese Regeln für alle Teilnehmenden, Vortragenden und Helfenden.',
|
||
'rules': [
|
||
('Sei nett zueinander.', 'Geh respektvoll und freundlich miteinander um, auch wenn du anderer Meinung bist.'),
|
||
('Keine Diskriminierung.', 'Rassismus, Sexismus, Queerfeindlichkeit, Ableismus und andere menschenverachtende '
|
||
'Haltungen haben hier keinen Platz.'),
|
||
('Keine Belästigung.', 'Dazu gehören Beleidigungen, Bedrohungen, Stalking, unerwünschte körperliche '
|
||
'Berührungen und unerwünschte sexuelle Aufmerksamkeit. Ein Nein ist ein Nein.'),
|
||
('Frag vor dem Fotografieren.', 'Fotos, Video- und Tonaufnahmen von Personen nur mit deren Einverständnis. '
|
||
'Das gilt auch für Talks und Workshops.'),
|
||
('Achte auf Geräte und Eigentum.', 'Fass fremde Geräte nicht ohne Erlaubnis an. Geh pfleglich mit den Räumen '
|
||
'und dem Inventar um.'),
|
||
('Sei neugierig, nicht übergriffig.', 'Chaos ja, Grenzenüberschreitungen nein.'),
|
||
],
|
||
'house_lead': 'Hausrecht:',
|
||
'house': 'Die Orga hat Hausrecht. Bei Verstößen gegen diese Regeln kann sie Verwarnungen aussprechen, Personen '
|
||
'der Veranstaltung verweisen oder ein Hausverbot erteilen. Den Anweisungen der Orga ist Folge zu leisten.',
|
||
'problems_lead': 'Probleme?',
|
||
'problems': 'Wenn dir etwas passiert oder du etwas beobachtest, sprich eine Person aus der Orga an oder schreib an '
|
||
'aprl [at] berlin [dot] ccc [dot] de. Hinweise werden vertraulich behandelt.',
|
||
'footer': [('Startseite', SITE), ('Impressum', IMPRESSUM)],
|
||
}
|
||
|
||
COC_EN = {
|
||
'intro': 'Khaos im Kiez is an event of the Chaos Computer Club Berlin e.V. (CCCB). To make everyone feel '
|
||
'comfortable, these rules apply to all attendees, speakers and helpers.',
|
||
'rules': [
|
||
('Be nice to each other.', 'Treat each other respectfully and kindly, even when you disagree.'),
|
||
('No discrimination.', 'Racism, sexism, queerphobia, ableism and other hateful attitudes have no place here.'),
|
||
('No harassment.', 'This includes insults, threats, stalking, unwanted physical contact and unwanted sexual '
|
||
'attention. No means no.'),
|
||
('Ask before taking pictures.', 'Photos, video and audio recordings of people only with their consent. This '
|
||
'also applies to talks and workshops.'),
|
||
('Respect devices and property.', "Do not touch other people's devices without permission. Treat the rooms "
|
||
'and furniture with care.'),
|
||
('Be curious, not intrusive.', 'Chaos yes, crossing boundaries no.'),
|
||
],
|
||
'house_lead': 'House rules:',
|
||
'house': 'The organizers have the right to exclude people from the premises (Hausrecht). In case of violations '
|
||
"they may issue warnings, ask people to leave, or ban them from the event. Follow the organizers' "
|
||
'instructions.',
|
||
'problems_lead': 'Problems?',
|
||
'problems': 'If something happens to you or you notice something, talk to a member of the organizing team or '
|
||
'write to aprl [at] berlin [dot] ccc [dot] de. Reports are treated confidentially.',
|
||
'footer': [('Home', f'{SITE}en.html'), ('Legal notice', IMPRESSUM)],
|
||
}
|
||
|
||
out = pathlib.Path(__file__).parent
|
||
for name, text in [('README', readme(DE)), ('README.en', readme(EN)),
|
||
('CODE_OF_CONDUCT', code_of_conduct(COC_DE)), ('CODE_OF_CONDUCT.en', code_of_conduct(COC_EN))]:
|
||
(out / name).write_text(text, encoding='utf-8')
|