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