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>.
5+ """
6+
7+ import json
8+ import os
9+ import os .path
10+ import re
11+ import sys
12+ from collections import defaultdict
13+
14+ from html .parser import HTMLParser
15+
16+ from bikeshed import Spec , constants
17+
18+ import jinja2
19+
20+ jinja_env = jinja2 .Environment (
21+ loader = jinja2 .PackageLoader ("build-index" , "templates" ),
22+ autoescape = jinja2 .select_autoescape (),
23+ trim_blocks = True ,
24+ lstrip_blocks = True
25+ )
26+
27+
28+ def title_from_html (file ):
29+ class HTMLTitleParser (HTMLParser ):
30+ def __init__ (self ):
31+ super ().__init__ ()
32+ self .in_title = False
33+ self .title = ""
34+ self .done = False
35+
36+ def handle_starttag (self , tag , attrs ):
37+ if tag == "title" :
38+ self .in_title = True
39+
40+ def handle_data (self , data ):
41+ if self .in_title :
42+ self .title += data
43+
44+ def handle_endtag (self , tag ):
45+ if tag == "title" and self .in_title :
46+ self .in_title = False
47+ self .done = True
48+ self .reset ()
49+
50+ parser = HTMLTitleParser ()
51+ with open (file , encoding = "UTF-8" ) as f :
52+ for line in f :
53+ parser .feed (line )
54+ if parser .done :
55+ break
56+ if not parser .done :
57+ parser .close ()
58+
59+ return parser .title if parser .done else None
60+
61+
62+ def get_bs_spec_metadata (folder_name , path ):
63+ spec = Spec (path )
64+ spec .assembleDocument ()
65+
66+ level = int (spec .md .level ) if spec .md .level else 0
67+
68+ if spec .md .shortname == "css-animations-2" :
69+ shortname = "css-animations"
70+ elif spec .md .shortname == "css-gcpm-4" :
71+ shortname = "css-gcpm"
72+ elif spec .md .shortname == "css-transitions-2" :
73+ shortname = "css-transitions"
74+ elif spec .md .shortname == "scroll-animations-1" :
75+ shortname = "scroll-animations"
76+ else :
77+ # Fix CSS snapshots (e.g. "css-2022")
78+ snapshot_match = re .match (
79+ "^css-(20[0-9]{2})$" , spec .md .shortname )
80+ if snapshot_match :
81+ shortname = "css-snapshot"
82+ level = int (snapshot_match .group (1 ))
83+ else :
84+ shortname = spec .md .shortname
85+
86+ return {
87+ "shortname" : shortname ,
88+ "level" : level ,
89+ "title" : spec .md .title ,
90+ "workStatus" : spec .md .workStatus
91+ }
92+
93+
94+ def get_html_spec_metadata (folder_name , path ):
95+ match = re .match ("^([a-z0-9-]+)-([0-9]+)$" , folder_name )
96+ if match and match .group (1 ) == "css" :
97+ shortname = "css-snapshot"
98+ title = f"CSS Snapshot { match .group (2 )} "
99+ else :
100+ shortname = match .group (1 ) if match else folder_name
101+ title = title_from_html (path )
102+
103+ return {
104+ "shortname" : shortname ,
105+ "level" : int (match .group (2 )) if match else 0 ,
106+ "title" : title ,
107+ "workStatus" : "completed" # It's a good heuristic
108+ }
109+
110+
111+ def build_redirect (shortname , spec_folder ):
112+ """Builds redirects from the shortname to the current work for that spec.
113+
114+ Since Github Actions doesn't allow anything like mod_rewrite, we do this by
115+ creating an empty index.html in the shortname folder that redirects to the
116+ correct spec.
117+ """
118+
119+ template = jinja_env .get_template ("redirect.html.j2" )
120+ contents = template .render (spec_folder = spec_folder )
121+
122+ folder = os .path .join ("./csswg-drafts" , shortname )
123+ try :
124+ os .mkdir (folder )
125+ except FileExistsError :
126+ pass
127+
128+ index = os .path .join (folder , "index.html" )
129+ with open (index , mode = 'w' , encoding = "UTF-8" ) as f :
130+ f .write (contents )
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+
150+ for entry in os .scandir ("./csswg-drafts" ):
151+ if entry .is_dir ():
152+ # Not actual specs, just examples.
153+ if entry .name in ["css-module" ]:
154+ continue
155+
156+ bs_file = os .path .join (entry .path , "Overview.bs" )
157+ html_file = os .path .join (entry .path , "Overview.html" )
158+ if os .path .exists (bs_file ):
159+ metadata = get_bs_spec_metadata (entry .name , bs_file )
160+ elif os .path .exists (html_file ):
161+ metadata = get_html_spec_metadata (entry .name , html_file )
162+ else :
163+ # Not a spec
164+ continue
165+
166+ metadata ["dir" ] = entry .name
167+ metadata ["currentWork" ] = False
168+ specgroups [metadata ["shortname" ]].append (metadata )
169+
170+ # Reorder the specs with common shortname based on their level (or year, for
171+ # CSS snapshots), and determine which spec is the current work.
172+ for shortname , specgroup in specgroups .items ():
173+ if len (specgroup ) == 1 :
174+ if shortname != specgroup [0 ]["dir" ]:
175+ build_redirect (shortname , specgroup [0 ]["dir" ])
176+ else :
177+ specgroup .sort (key = lambda spec : spec ["level" ])
178+
179+ # TODO: This algorithm for determining which spec is the current work
180+ # is wrong in a number of cases. Try and come up with a better
181+ # algorithm, rather than maintaining a list of exceptions.
182+ for spec in specgroup :
183+ if shortname in CURRENT_WORK_EXCEPTIONS :
184+ if CURRENT_WORK_EXCEPTIONS [shortname ] == spec ["level" ]:
185+ spec ["currentWork" ] = True
186+ currentWorkDir = spec ["dir" ]
187+ break
188+ elif spec ["workStatus" ] != "completed" :
189+ spec ["currentWork" ] = True
190+ currentWorkDir = spec ["dir" ]
191+ break
192+ else :
193+ specgroup [- 1 ]["currentWork" ] = True
194+ currentWorkDir = specgroup [- 1 ]["dir" ]
195+
196+ if shortname != currentWorkDir :
197+ build_redirect (shortname , currentWorkDir )
198+ if shortname == "css-snapshot" :
199+ build_redirect ("css" , currentWorkDir )
200+
201+ with open ("./csswg-drafts/index.html" , mode = 'w' , encoding = "UTF-8" ) as f :
202+ template = jinja_env .get_template ("index.html.j2" )
203+ f .write (template .render (specgroups = specgroups ))
0 commit comments