diff --git a/Source/Build.py b/Source/Build.py index e76a5aa..9d691de 100755 --- a/Source/Build.py +++ b/Source/Build.py @@ -38,7 +38,7 @@ def ResetPublic(): def DelTmp(): for Ext in FileExtensions['Tmp']: - for File in Path('public').rglob('*.{}'.format(Ext)): + for File in Path('public').rglob(f"*.{Ext}"): os.remove(File) for Dir in ('public', 'public.gmi'): for File in Path(Dir).rglob('*.tmp'): @@ -185,7 +185,7 @@ def Main(Args, FeedEntries): print("[I] Generating Gemtext") GemtextCompileList( Pages, - Header=Args.GemtextHeader if Args.GemtextHeader else '# {}\n\n'.format(SiteName) if SiteName else '') + Header=Args.GemtextHeader if Args.GemtextHeader else f"# {SiteName}\n\n" if SiteName else '') print("[I] Last Steps") DelTmp() diff --git a/Source/Libs/markdown/extensions/attr_list_patch.py b/Source/Libs/markdown/extensions/attr_list_patch.py new file mode 100644 index 0000000..b2e2ab2 --- /dev/null +++ b/Source/Libs/markdown/extensions/attr_list_patch.py @@ -0,0 +1,173 @@ +""" +Attribute List Extension for Python-Markdown +============================================ + +Adds attribute list syntax. Inspired by +[maruku](http://maruku.rubyforge.org/proposal.html#attribute_lists)'s +feature of the same name. + +See +for documentation. + +Original code Copyright 2011 [Waylan Limberg](http://achinghead.com/). + +All changes Copyright 2011-2014 The Python Markdown Project + +License: [BSD](https://opensource.org/licenses/bsd-license.php) + +""" + +from . import Extension +from ..treeprocessors import Treeprocessor +from xml.etree import ElementTree +import re + + +def _handle_double_quote(s, t): + k, v = t.split('=', 1) + return k, v.strip('"') + + +def _handle_single_quote(s, t): + k, v = t.split('=', 1) + return k, v.strip("'") + + +def _handle_key_value(s, t): + return t.split('=', 1) + + +def _handle_word(s, t): + print(s, t) + if t.startswith('.'): + return '.', t[1:] + if t.startswith('#'): + return 'id', t[1:] + return t, t + + +_scanner = re.Scanner([ + (r'[^ =]+=".*?"', _handle_double_quote), + (r"[^ =]+='.*?'", _handle_single_quote), + (r'[^ =]+=[^ =]+', _handle_key_value), + (r'[^ =]+', _handle_word), + (r' ', None) +]) + + +def get_attrs(str): + """ Parse attribute list and return a list of attribute tuples. """ + return _scanner.scan(str)[0] + + +def isheader(elem): + return elem.tag in ['h1', 'h2', 'h3', 'h4', 'h5', 'h6'] + + +class AttrListTreeprocessor(Treeprocessor): + + BASE_RE = r'\{\:[ ]*([^\n ][^\n]*)[ ]*\:\}' + HEADER_RE = re.compile(r'[ ]+{}[ ]*$'.format(BASE_RE)) + BLOCK_RE = re.compile(r'\n[ ]*{}[ ]*$'.format(BASE_RE)) + INLINE_RE = re.compile(r'^{}'.format(BASE_RE)) + NAME_RE = re.compile(r'[^A-Z_a-z\u00c0-\u00d6\u00d8-\u00f6\u00f8-\u02ff' + r'\u0370-\u037d\u037f-\u1fff\u200c-\u200d' + r'\u2070-\u218f\u2c00-\u2fef\u3001-\ud7ff' + r'\uf900-\ufdcf\ufdf0-\ufffd' + r'\:\-\.0-9\u00b7\u0300-\u036f\u203f-\u2040]+') + + def run(self, doc): + for elem in doc.iter(): + if self.md.is_block_level(elem.tag): + # Block level: check for attrs on last line of text + RE = self.BLOCK_RE + if isheader(elem) or elem.tag in ['dt', 'td', 'th']: + # header, def-term, or table cell: check for attrs at end of element + RE = self.HEADER_RE + if len(elem) and elem.tag == 'li': + # special case list items. children may include a ul or ol. + pos = None + # find the ul or ol position + for i, child in enumerate(elem): + if child.tag in ['ul', 'ol']: + pos = i + break + if pos is None and elem[-1].tail: + # use tail of last child. no ul or ol. + m = RE.search(elem[-1].tail) + if m: + self.assign_attrs(elem, m.group(1)) + elem[-1].tail = elem[-1].tail[:m.start()] + elif pos is not None and pos > 0 and elem[pos-1].tail: + # use tail of last child before ul or ol + m = RE.search(elem[pos-1].tail) + if m: + self.assign_attrs(elem, m.group(1)) + elem[pos-1].tail = elem[pos-1].tail[:m.start()] + elif elem.text: + # use text. ul is first child. + m = RE.search(elem.text) + if m: + self.assign_attrs(elem, m.group(1)) + elem.text = elem.text[:m.start()] + elif len(elem) and elem[-1].tail: + # has children. Get from tail of last child + m = RE.search(elem[-1].tail) + if m: + self.assign_attrs(elem, m.group(1)) + elem[-1].tail = elem[-1].tail[:m.start()] + if isheader(elem): + # clean up trailing #s + elem[-1].tail = elem[-1].tail.rstrip('#').rstrip() + elif elem.text: + # no children. Get from text. + m = RE.search(elem.text) + if m: + self.assign_attrs(elem, m.group(1)) + elem.text = elem.text[:m.start()] + if isheader(elem): + # clean up trailing #s + elem.text = elem.text.rstrip('#').rstrip() + else: + # inline: check for attrs at start of tail + if elem.tail: + m = self.INLINE_RE.match(elem.tail) + if m: + self.assign_attrs(elem, m.group(1)) + elem.tail = elem.tail[m.end():] +{+ +} + def assign_attrs(self, elem, attrs): + """ Assign attrs to element. """ + for k, v in get_attrs(attrs): + print(0, k, v) + if k == '.': + # add to class + cls = elem.get('class') + if cls: + elem.set('class', '{} {}'.format(cls, v)) + else: + elem.set('class', v) + elif k.startswith('{=') and k.endswith('=}'): + #elem.set(self.sanitize_name(k), f'" {v} {self.sanitize_name(k)}1="') + print(elem.keys()) + #elem.set(elem, v) + else: + # assign attr k with v + elem.set(self.sanitize_name(k), v) + + def sanitize_name(self, name): + """ + Sanitize name as 'an XML Name, minus the ":"'. + See https://www.w3.org/TR/REC-xml-names/#NT-NCName + """ + return self.NAME_RE.sub('_', name) + + +class AttrListExtension(Extension): + def extendMarkdown(self, md): + md.treeprocessors.register(AttrListTreeprocessor(md), 'attr_list', 8) + md.registerExtension(self) + + +def makeExtension(**kwargs): # pragma: no cover + return AttrListExtension(**kwargs) diff --git a/Source/Modules/Site.py b/Source/Modules/Site.py index f879ced..cde57d8 100644 --- a/Source/Modules/Site.py +++ b/Source/Modules/Site.py @@ -185,14 +185,14 @@ def Preprocessor(Path, SiteRoot): Content += MakeLinkableTitle(l, Title, DashTitle, 'pug') + '\n' else: Content += l + '\n' - #if Macros: Meta['Macros'] = ReadConf(LoadConfStr('[Macros]\n' + Macros), 'Macros') - # Macros = '[Macros]\n' + Macros - # Macros = LoadConfStr(Macros) - # Macros = ReadConf(Macros, 'Macros') - # Meta['Macros'] = Macros return Content, Titles, Meta +def Postprocessor(FileType, Text, Meta): + for e in Meta['Macros']: + Text = ReplWithEsc(Text, f"[: {e} :]", f"[:{e}:]") + return Text + def MakeListTitle(File, Meta, Titles, Prefer, SiteRoot, BlogName, PathPrefix=''): Title = GetTitle(Meta, Titles, Prefer, BlogName) Link = False if Meta['Index'] == 'Unlinked' else True @@ -267,15 +267,16 @@ def PatchHTML(File, HTML, PartsText, ContextParts, ContextPartsText, HTMLPagesLi Part = ContextParts[Section] Text = '' if type(Part) == list: - for i in Part: - Text += ContextPartsText['{}/{}'.format(Path, i)] + '\n' + for e in Part: + Text += ContextPartsText[f"{Path}/{e}"] + '\n' elif type(Part) == str: - Text = ContextPartsText['{}/{}'.format(Path, Part)] + Text = ContextPartsText[f"{Path}/{Part}"] else: Text = '' - HTML = HTML.replace('[HTML:ContextPart:{}]'.format(Path), Text) - for i in PartsText: - HTML = HTML.replace('[HTML:Part:{}]'.format(i), PartsText[i]) + HTML = ReplWithEsc(HTML, f"[HTML:ContextPart:{Path}]", Text) + + for e in PartsText: + HTML = ReplWithEsc(HTML, f"[HTML:Part:{e}]", PartsText[e]) HTML = ReplWithEsc(HTML, '[HTML:Site:Menu]', HTMLPagesList) HTML = ReplWithEsc(HTML, '[HTML:Page:Lang]', SiteLang) HTML = ReplWithEsc(HTML, '[HTML:Page:Chapters]', HTMLTitles) @@ -290,22 +291,22 @@ def PatchHTML(File, HTML, PartsText, ContextParts, ContextPartsText, HTMLPagesLi HTML = ReplWithEsc(HTML, '[HTML:Site:AbsoluteRoot]', SiteRoot) HTML = ReplWithEsc(HTML, '[HTML:Site:RelativeRoot]', GetPathLevels(PagePath)) for e in Meta['Macros']: - HTML = HTML.replace(f"{{{{{e}}}}}", Meta['Macros'][e]).replace(f"{{{{ {e} }}}}", Meta['Macros'][e]) + HTML = ReplWithEsc(HTML, f"[:{e}:]", Meta['Macros'][e]) for e in FolderRoots: - HTML = HTML.replace('[HTML:Folder:{}:AbsoluteRoot]'.format(e), FolderRoots[e]) + HTML = ReplWithEsc(HTML, f"[HTML:Folder:{e}:AbsoluteRoot]", FolderRoots[e]) for e in Categories: - HTML = HTML.replace('[HTML:Category:{}]'.format(e), Categories[e]) + HTML = ReplWithEsc(HTML, f"[HTML:Category:{e}]", Categories[e]) # TODO: Clean this doubling? ContentHTML = Content ContentHTML = ContentHTML.replace('[HTML:Site:AbsoluteRoot]', SiteRoot) ContentHTML = ContentHTML.replace('[HTML:Site:RelativeRoot]', GetPathLevels(PagePath)) for e in Meta['Macros']: - ContentHTML = ContentHTML.replace(f"{{{{{e}}}}}", Meta['Macros'][e]).replace(f"{{{{ {e} }}}}", Meta['Macros'][e]) + ContentHTML = ContentHTML.replace(f"[:{e}:]", Meta['Macros'][e]) for e in FolderRoots: - ContentHTML = ContentHTML.replace('[HTML:Folder:{}:AbsoluteRoot]'.format(e), FolderRoots[e]) + ContentHTML = ContentHTML.replace(f"[HTML:Folder:{e}:AbsoluteRoot]", FolderRoots[e]) for e in Categories: - ContentHTML = ContentHTML.replace('[HTML:Category:{}]'.format(e), Categories[e]) + ContentHTML = ContentHTML.replace(f"[HTML:Category:{e}]", Categories[e]) SlimHTML = HTMLPagesList + ContentHTML return HTML, ContentHTML, SlimHTML, Description, Image @@ -325,9 +326,9 @@ def DoMinifyHTML(HTML): def MakeSite(TemplatesText, PartsText, ContextParts, ContextPartsText, ConfMenu, SiteName, BlogName, SiteTagline, SiteDomain, SiteRoot, FolderRoots, SiteLang, Locale, Minify, NoScripts, Sorting, MarkdownExts, AutoCategories): PagesPaths, PostsPaths, Pages, MadePages, Categories = [], [], [], [], {} for Ext in FileExtensions['Pages']: - for File in Path('Pages').rglob('*.{}'.format(Ext)): + for File in Path('Pages').rglob(f"*.{Ext}"): PagesPaths += [FileToStr(File, 'Pages/')] - for File in Path('Posts').rglob('*.{}'.format(Ext)): + for File in Path('Posts').rglob(f"*.{Ext}"): PostsPaths += [FileToStr(File, 'Posts/')] if Sorting['Pages'] == 'Standard': @@ -346,9 +347,9 @@ def MakeSite(TemplatesText, PartsText, ContextParts, ContextPartsText, ConfMenu, elif Type == 'Post': Files = PostsPaths for File in Files: - Content, Titles, Meta = Preprocessor('{}s/{}'.format(Type, File), SiteRoot) + Content, Titles, Meta = Preprocessor(f"{Type}s/{File}", SiteRoot) if Type != 'Page': - File = Type + 's/' + File + File = f"{Type}s/{File}" if not Meta['Type']: Meta['Type'] = Type Pages += [[File, Content, Titles, Meta]] @@ -379,8 +380,8 @@ def MakeSite(TemplatesText, PartsText, ContextParts, ContextPartsText, ConfMenu, Exists = True break if not Exists: - File = 'Categories/{}.md'.format(Cat) - FilePath = 'public/{}'.format(File) + File = f"Categories/{Cat}.md" + FilePath = f"public/{File}" WriteFile(FilePath, """\ // Title: {Category} // Type: Page @@ -400,6 +401,12 @@ def MakeSite(TemplatesText, PartsText, ContextParts, ContextPartsText, ConfMenu, print("[I] Writing Pages") for File, Content, Titles, Meta in Pages: + PagePath = 'public/{}.html'.format(StripExt(File)) + if File.endswith('.md'): + Content = markdown(Postprocessor('md', Content, Meta), extensions=MarkdownExts) + elif File.endswith(('.pug')): + Content = Postprocessor('pug', ReadFile(PagePath), Meta) + HTMLPagesList = GetHTMLPagesList( Pages=Pages, BlogName=BlogName, @@ -409,11 +416,7 @@ def MakeSite(TemplatesText, PartsText, ContextParts, ContextPartsText, ConfMenu, Type='Page', For='Menu', MarkdownExts=MarkdownExts) - PagePath = 'public/{}.html'.format(StripExt(File)) - if File.endswith('.md'): - Content = markdown(Content, extensions=MarkdownExts) - elif File.endswith(('.pug')): - Content = ReadFile(PagePath) + HTML, ContentHTML, SlimHTML, Description, Image = PatchHTML( File=File, HTML=TemplatesText[Meta['Template']],