1- """Builds the CSSWG directory index.
2-
3- It also sets up redirects from shortnames to the current work spec, by building
4- an index.html with a <meta refresh>.
1+ """
2+ Builds the GitHub Pages root page,
3+ which now is just a redirect to the drafts server listing.
54"""
65
7- import json
8- import os
9- import os .path
10- import re
11- import sys
12- import subprocess
13- from collections import defaultdict
14-
15- from html .parser import HTMLParser
16-
17- from bikeshed import Spec , constants
18-
19- import jinja2
20-
21- jinja_env = jinja2 .Environment (
22- loader = jinja2 .PackageLoader ("build-index" , "templates" ),
23- autoescape = jinja2 .select_autoescape (),
24- trim_blocks = True ,
25- lstrip_blocks = True
26- )
27-
28-
29- def title_from_html (file ):
30- class HTMLTitleParser (HTMLParser ):
31- def __init__ (self ):
32- super ().__init__ ()
33- self .in_title = False
34- self .title = ""
35- self .done = False
36-
37- def handle_starttag (self , tag , attrs ):
38- if tag == "title" :
39- self .in_title = True
40-
41- def handle_data (self , data ):
42- if self .in_title :
43- self .title += data
44-
45- def handle_endtag (self , tag ):
46- if tag == "title" and self .in_title :
47- self .in_title = False
48- self .done = True
49- self .reset ()
50-
51- parser = HTMLTitleParser ()
52- with open (file , encoding = "UTF-8" ) as f :
53- for line in f :
54- parser .feed (line )
55- if parser .done :
56- break
57- if not parser .done :
58- parser .close ()
59-
60- return parser .title if parser .done else None
61-
62-
63- def get_date_authored_timestamp_from_git (path ):
64- source = os .path .realpath (path )
65- proc = subprocess .run (["git" , "log" , "-1" , "--format=%at" , source ],
66- capture_output = True , encoding = "utf_8" )
67- return int (proc .stdout .splitlines ()[- 1 ])
68-
69-
70- def get_bs_spec_metadata (folder_name , path ):
71- spec = Spec (path )
72- spec .assembleDocument ()
73-
74- level = int (spec .md .level ) if spec .md .level else 0
75-
76- if spec .md .shortname == "css-animations-2" :
77- shortname = "css-animations"
78- elif spec .md .shortname == "css-gcpm-4" :
79- shortname = "css-gcpm"
80- elif spec .md .shortname == "css-transitions-2" :
81- shortname = "css-transitions"
82- elif spec .md .shortname == "scroll-animations-1" :
83- shortname = "scroll-animations"
84- else :
85- # Fix CSS snapshots (e.g. "css-2022")
86- snapshot_match = re .match (
87- "^css-(20[0-9]{2})$" , spec .md .shortname )
88- if snapshot_match :
89- shortname = "css-snapshot"
90- level = int (snapshot_match .group (1 ))
91- else :
92- shortname = spec .md .shortname
93-
94- return {
95- "timestamp" : get_date_authored_timestamp_from_git (path ),
96- "shortname" : shortname ,
97- "level" : level ,
98- "title" : spec .md .title ,
99- "workStatus" : spec .md .workStatus
100- }
101-
102-
103- def get_html_spec_metadata (folder_name , path ):
104- match = re .match ("^([a-z0-9-]+)-([0-9]+)$" , folder_name )
105- if match and match .group (1 ) == "css" :
106- shortname = "css-snapshot"
107- title = f"CSS Snapshot { match .group (2 )} "
108- else :
109- shortname = match .group (1 ) if match else folder_name
110- title = title_from_html (path )
111-
112- return {
113- "shortname" : shortname ,
114- "level" : int (match .group (2 )) if match else 0 ,
115- "title" : title ,
116- "workStatus" : "completed" # It's a good heuristic
117- }
118-
119-
120- def create_symlink (shortname , spec_folder ):
121- """Creates a <shortname> symlink pointing to the given <spec_folder>.
122- """
123-
124- if spec_folder in timestamps :
125- timestamps [shortname ] = timestamps [spec_folder ]
126-
127- try :
128- os .symlink (spec_folder , shortname )
129- except OSError :
130- pass
131-
132-
133- CURRENT_WORK_EXCEPTIONS = {
134- "css-conditional" : 5 ,
135- "css-easing" : 2 ,
136- "css-grid" : 2 ,
137- "css-snapshot" : None , # always choose the last spec
138- "css-values" : 4 ,
139- "css-writing-modes" : 4 ,
140- "web-animations" : 2
141- }
142-
143- # ------------------------------------------------------------------------------
144-
145-
146- constants .setErrorLevel ("nothing" )
147-
148- specgroups = defaultdict (list )
149- timestamps = defaultdict (list )
150-
151- for entry in os .scandir ("." ):
152- if entry .is_dir (follow_symlinks = False ):
153- # Not actual specs, just examples.
154- if entry .name in ["css-module" ]:
155- continue
156-
157- bs_file = os .path .join (entry .path , "Overview.bs" )
158- html_file = os .path .join (entry .path , "Overview.html" )
159- if os .path .exists (bs_file ):
160- metadata = get_bs_spec_metadata (entry .name , bs_file )
161- timestamps [entry .name ] = metadata ["timestamp" ]
162- elif os .path .exists (html_file ):
163- metadata = get_html_spec_metadata (entry .name , html_file )
164- else :
165- # Not a spec
166- continue
167-
168- metadata ["dir" ] = entry .name
169- metadata ["currentWork" ] = False
170- specgroups [metadata ["shortname" ]].append (metadata )
171-
172- # Reorder the specs with common shortname based on their level (or year, for
173- # CSS snapshots), and determine which spec is the current work.
174- for shortname , specgroup in specgroups .items ():
175- if len (specgroup ) == 1 :
176- if shortname != specgroup [0 ]["dir" ]:
177- create_symlink (shortname , specgroup [0 ]["dir" ])
178- else :
179- specgroup .sort (key = lambda spec : spec ["level" ])
180-
181- # TODO: This algorithm for determining which spec is the current work
182- # is wrong in a number of cases. Try and come up with a better
183- # algorithm, rather than maintaining a list of exceptions.
184- for spec in specgroup :
185- if shortname in CURRENT_WORK_EXCEPTIONS :
186- if CURRENT_WORK_EXCEPTIONS [shortname ] == spec ["level" ]:
187- spec ["currentWork" ] = True
188- currentWorkDir = spec ["dir" ]
189- break
190- elif spec ["workStatus" ] != "completed" :
191- spec ["currentWork" ] = True
192- currentWorkDir = spec ["dir" ]
193- break
194- else :
195- specgroup [- 1 ]["currentWork" ] = True
196- currentWorkDir = specgroup [- 1 ]["dir" ]
197-
198- if shortname != currentWorkDir :
199- create_symlink (shortname , currentWorkDir )
200- if shortname == "css-snapshot" :
201- create_symlink ("css" , currentWorkDir )
2026
203- with open ('./timestamps.json' , 'w' ) as f :
204- json .dump (dict (sorted (timestamps .items ())), f , indent = 2 )
2057
2068with open ("./index.html" , mode = 'w' , encoding = "UTF-8" ) as f :
207- template = jinja_env .get_template ("index.html.j2" )
208- f .write (template .render (specgroups = specgroups ))
9+ f .write ("""
10+ <!doctype html>
11+ <meta charset=utf-8>
12+ <title>Redirecting to the Drafts listing...</title>
13+ <meta http-equiv=Refresh content="0; url='https://drafts.csswg.org'">
14+ <script>
15+ window.location.href = "https://drafts.csswg.org";
16+ </script>
17+ """ )
0 commit comments