1#!/usr/bin/env python 2 3# This Source Code Form is subject to the terms of the Mozilla Public 4# License, v. 2.0. If a copy of the MPL was not distributed with this 5# file, You can obtain one at https://mozilla.org/MPL/2.0/. 6 7import re 8import os 9import sys 10 11from io import BytesIO 12 13GECKO_DIR = os.path.dirname(__file__.replace('\\', '/')) 14sys.path.insert(0, os.path.join(os.path.dirname(GECKO_DIR), "properties")) 15 16import build 17 18 19# Matches lines like `GK_ATOM(foo, "foo", 0x12345678, true, nsStaticAtom, PseudoElementAtom)`. 20PATTERN = re.compile('^GK_ATOM\(([^,]*),[^"]*"([^"]*)",\s*(0x[0-9a-f]+),\s*[^,]*,\s*([^,]*),\s*([^)]*)\)', 21 re.MULTILINE) 22FILE = "include/nsGkAtomList.h" 23 24 25def map_atom(ident): 26 if ident in {"box", "loop", "match", "mod", "ref", 27 "self", "type", "use", "where", "in"}: 28 return ident + "_" 29 return ident 30 31 32class Atom: 33 def __init__(self, ident, value, hash, ty, atom_type): 34 self.ident = "nsGkAtoms_{}".format(ident) 35 self.original_ident = ident 36 self.value = value 37 self.hash = hash 38 # The Gecko type: "nsStaticAtom", "nsCSSPseudoElementStaticAtom", or 39 # "nsAnonBoxPseudoStaticAtom". 40 self.ty = ty 41 # The type of atom: "Atom", "PseudoElement", "NonInheritingAnonBox", 42 # or "InheritingAnonBox". 43 self.atom_type = atom_type 44 45 if self.is_pseudo_element() or self.is_anon_box() or self.is_tree_pseudo_element(): 46 self.pseudo_ident = (ident.split("_", 1))[1] 47 48 if self.is_anon_box(): 49 assert self.is_inheriting_anon_box() or self.is_non_inheriting_anon_box() 50 51 def type(self): 52 return self.ty 53 54 def capitalized_pseudo(self): 55 return self.pseudo_ident[0].upper() + self.pseudo_ident[1:] 56 57 def is_pseudo_element(self): 58 return self.atom_type == "PseudoElementAtom" 59 60 def is_anon_box(self): 61 if self.is_tree_pseudo_element(): 62 return False 63 return self.is_non_inheriting_anon_box() or self.is_inheriting_anon_box() 64 65 def is_non_inheriting_anon_box(self): 66 assert not self.is_tree_pseudo_element() 67 return self.atom_type == "NonInheritingAnonBoxAtom" 68 69 def is_inheriting_anon_box(self): 70 if self.is_tree_pseudo_element(): 71 return False 72 return self.atom_type == "InheritingAnonBoxAtom" 73 74 def is_tree_pseudo_element(self): 75 return self.value.startswith(":-moz-tree-") 76 77 78def collect_atoms(objdir): 79 atoms = [] 80 path = os.path.abspath(os.path.join(objdir, FILE)) 81 print("cargo:rerun-if-changed={}".format(path)) 82 with open(path) as f: 83 content = f.read() 84 for result in PATTERN.finditer(content): 85 atoms.append(Atom(result.group(1), result.group(2), result.group(3), 86 result.group(4), result.group(5))) 87 return atoms 88 89 90class FileAvoidWrite(BytesIO): 91 """File-like object that buffers output and only writes if content changed.""" 92 def __init__(self, filename): 93 BytesIO.__init__(self) 94 self.name = filename 95 96 def write(self, buf): 97 if isinstance(buf, str): 98 buf = buf.encode('utf-8') 99 BytesIO.write(self, buf) 100 101 def close(self): 102 buf = self.getvalue() 103 BytesIO.close(self) 104 try: 105 with open(self.name, 'rb') as f: 106 old_content = f.read() 107 if old_content == buf: 108 print("{} is not changed, skip".format(self.name)) 109 return 110 except IOError: 111 pass 112 with open(self.name, 'wb') as f: 113 f.write(buf) 114 115 def __enter__(self): 116 return self 117 118 def __exit__(self, type, value, traceback): 119 if not self.closed: 120 self.close() 121 122 123PRELUDE = ''' 124/* This Source Code Form is subject to the terms of the Mozilla Public 125 * License, v. 2.0. If a copy of the MPL was not distributed with this 126 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ 127 128// Autogenerated file created by components/style/gecko/regen_atoms.py. 129// DO NOT EDIT DIRECTLY 130'''[1:] 131 132RULE_TEMPLATE = ''' 133 ("{atom}") => {{{{ 134 #[allow(unsafe_code)] #[allow(unused_unsafe)] 135 unsafe {{ $crate::string_cache::Atom::from_index_unchecked({index}) }} 136 }}}}; 137'''[1:] 138 139MACRO_TEMPLATE = ''' 140/// Returns a static atom by passing the literal string it represents. 141#[macro_export] 142macro_rules! atom {{ 143{body}\ 144}} 145''' 146 147def write_atom_macro(atoms, file_name): 148 with FileAvoidWrite(file_name) as f: 149 f.write(PRELUDE) 150 macro_rules = [RULE_TEMPLATE.format(atom=atom.value, name=atom.ident, index=i) 151 for (i, atom) in enumerate(atoms)] 152 f.write(MACRO_TEMPLATE.format(body=''.join(macro_rules))) 153 154 155def write_pseudo_elements(atoms, target_filename): 156 pseudos = [] 157 for atom in atoms: 158 if atom.type() == "nsCSSPseudoElementStaticAtom" or atom.type() == "nsCSSAnonBoxPseudoStaticAtom": 159 pseudos.append(atom) 160 161 pseudo_definition_template = os.path.join(GECKO_DIR, "pseudo_element_definition.mako.rs") 162 print("cargo:rerun-if-changed={}".format(pseudo_definition_template)) 163 contents = build.render(pseudo_definition_template, PSEUDOS=pseudos) 164 165 with FileAvoidWrite(target_filename) as f: 166 f.write(contents) 167 168 169def generate_atoms(dist, out): 170 atoms = collect_atoms(dist) 171 write_atom_macro(atoms, os.path.join(out, "atom_macro.rs")) 172 write_pseudo_elements(atoms, os.path.join(out, "pseudo_element_definition.rs")) 173 174 175if __name__ == "__main__": 176 if len(sys.argv) != 3: 177 print("Usage: {} dist out".format(sys.argv[0])) 178 exit(2) 179 generate_atoms(sys.argv[1], sys.argv[2]) 180