class Siter: def __init__(self, argv): # Declare and optionally create the dirs and files Siter uses self.dirs = Dirs() self.files = Files(self.dirs) # Set defaults and load user settings from args and config files self.settings = Settings(argv, self.files) # Optional packages self.imports = Imports() # Token processing utilities self.tokenizer = Tokenizer(self.settings.EvalHint, self.settings.TagOpen, self.settings.TagClose) # Copy static files if self.dirs.static.exists(): self.dirs.static.copy_to(self.dirs.out) # Variables, macros, and functions self.bindings = BindingCollection(self) # Set built-in global bindings self.__set_global_bindings() # Get user global bindings, if any if self.files.defs.exists(): self.__set_file_bindings(self.files.defs, False) def __set_global_bindings(self): self.bindings.add_function(self.settings.Def, [1, 2, 3], Functions.declare_binding, protected = True, lazy = True) self.bindings.add_function(self.settings.If, [2, 3], Functions.if_check, protected = True, lazy = True) self.bindings.add_function(self.settings.Generated, [1], Functions.gen_time, protected = True) self.bindings.add_function(self.settings.Code, [1, 2, 3], Functions.highlight_code, protected = True) self.bindings.add_function(self.settings.Markdown, [1], Functions.markdown, protected = True) self.bindings.add_function(self.settings.Lowercase, [1], Functions.lowercase, protected = True) def __set_local_bindings(self, read_file, read_dir): self.bindings.add_function(self.settings.Modified, [1], lambda _, args: \ Functions.mod_time(_, [read_file] + args)) self.bindings.add_variable(self.settings.Root, self.tokenizer.tokenize( read_dir.path_to(self.dirs.pages))) def __set_file_bindings(self, read_file, set_content): content = read_file.get_content() content_tokens = self.tokenizer.tokenize(content) content_tokens = self.__evaluate_collection(content_tokens) if set_content: self.bindings.add_variable(self.settings.Content, content_tokens, protected = True) def __evaluate_collection(self, collection): eval_tokens = TokenCollection() for token in collection: if token.t_type is TokenType.Block: evaluated = self.evaluate_block(token) if evaluated: eval_tokens.add_collection(evaluated) else: eval_tokens.add_token(token) return eval_tokens def evaluate_block(self, block): # Get the binding's name name = block.capture_call() if name is None: # This block does not call a binding return self.__evaluate_collection(block.tokens) if not self.bindings.contains(name): # Name is unknown, discard block Util.warning('Use of unknown binding {}:\n{}'.format(name, block)) return None binding = self.bindings.get(name) eval_tokens = TokenCollection() if type(binding) is VariableBinding: eval_binding = self.__evaluate_collection(binding.tokens) eval_tokens.add_collection(eval_binding) elif type(binding) is MacroBinding: args = block.capture_args(binding.num_params == 1) if len(args) != binding.num_params: Util.warning('Macro {} takes {} args, got {}:\n{}' .format(name, binding.num_params, len(args), block)) return None self.bindings.push() # Bind each parameter to the supplied argument for arg, param in zip(args, binding.params): self.bindings.add_variable(param.resolve(), TokenCollection([arg])) eval_binding = self.__evaluate_collection(binding.tokens) eval_tokens.add_collection(eval_binding) self.bindings.pop() elif type(binding) is FunctionBinding: args = block.capture_args(binding.num_params == [1]) if len(args) not in binding.num_params: Util.warning('Function {} takes {} args, got {}:\n{}' .format(name, binding.num_params, len(args), block)) return None if binding.lazy: # Feed block tokens directly to function result = binding.func(self, args) if result: result = self.evaluate_block(result) eval_tokens.add_collection(result) else: # Evaluate and resolve each argument arguments = [self.evaluate_block(a).resolve() for a in args] body = binding.func(self, arguments) eval_tokens.add_collection(self.tokenizer.tokenize(body)) # Trim leading and trailing whitespace eval_tokens.trim() # Run page content through Markdown if binding.protected \ and name == self.settings.Content \ and self.imports.Md: content = eval_tokens.resolve() md = self.imports.Md.markdown(content, output_format = 'html5') md_token = Token(TokenType.Text, md) eval_tokens = TokenCollection([md_token]) return eval_tokens def __apply_template(self): if self.files.page_html.exists(): content = self.files.page_html.get_content() else: content = '<!DOCTYPE html><html><body>{}{}{}{}</body></html>' \ .format(self.settings.TagOpen, self.settings.EvalHint, self.settings.Content, self.settings.TagClose) tokens = self.tokenizer.tokenize(content) tokens = self.__evaluate_collection(tokens) return tokens.resolve() def __work(self, read_dir, write_dir): counter = 0 for in_file in read_dir.list_files(): out_file = write_dir.add_file(in_file.get_name(), FileMode.Create) Util.message('Writing', out_file.get_path()) self.bindings.push() self.__set_local_bindings(in_file, read_dir) self.__set_file_bindings(in_file, True) # Load template and replace variables and functions with bindings final = self.__apply_template() out_file.write(final) self.bindings.pop() counter += 1 for read_subdir in read_dir.list_dirs(): write_subdir = write_dir.add_dir(read_subdir.get_name(), FileMode.Create) counter += self.__work(read_subdir, write_subdir) return counter def run(self): start = time.perf_counter() count = self.__work(self.dirs.pages, self.dirs.out) elapsed = round(time.perf_counter() - start + 0.05, 1) Util.message('Done', '{} {} in {}s' \ .format(count, 'page' if count == 1 else 'pages', elapsed))