From de9f0e832b145f6efe445e63836a8b6af1c933da Mon Sep 17 00:00:00 2001 From: octospacc Date: Thu, 14 Jul 2022 20:38:45 +0200 Subject: [PATCH] Clean code --- Source/Build.py | 407 +------------------------------------ Source/Modules/Markdown.py | 14 ++ Source/Modules/Site.py | 395 +++++++++++++++++++++++++++++++++++ Source/Modules/Utils.py | 12 ++ 4 files changed, 424 insertions(+), 404 deletions(-) create mode 100644 Source/Modules/Markdown.py create mode 100644 Source/Modules/Site.py diff --git a/Source/Build.py b/Source/Build.py index c8f203b..cb86a75 100755 --- a/Source/Build.py +++ b/Source/Build.py @@ -15,15 +15,6 @@ from ast import literal_eval from datetime import datetime from pathlib import Path -# Our local Markdown patches conflict if the module is installed on the system, so first try to import from system -try: - from markdown import markdown -except ModuleNotFoundError: - from Libs.markdown import markdown - -from Libs import htmlmin -from Libs.bs4 import BeautifulSoup - try: from Modules.ActivityPub import * ActivityPub = True @@ -33,13 +24,11 @@ except: from Modules.Config import * from Modules.Gemini import * -from Modules.Pug import * +from Modules.Markdown import * +from Modules.Site import * from Modules.Sitemap import * from Modules.Utils import * -Extensions = { - 'Pages': ('md', 'pug')} - def ResetPublic(): for i in ('public', 'public.gmi'): try: @@ -47,404 +36,14 @@ def ResetPublic(): except FileNotFoundError: pass -def GetLevels(Path, AsNum=False, Add=0, Sub=0): - n = Path.count('/') + Add - Sub - return n if AsNum else '../' * n - -def DashifyTitle(Title, Done=[]): - return UndupeStr(DashifyStr(Title), Done, '-') - -def GetTitle(Meta, Titles, Prefer='MetaTitle', BlogName=None): - if Prefer == 'BodyTitle': - Title = Titles[0].lstrip('#') if Titles else Meta['Title'] if Meta['Title'] else 'Untitled' - elif Prefer == 'MetaTitle': - Title = Meta['Title'] if Meta['Title'] else Titles[0].lstrip('#') if Titles else 'Untitled' - elif Prefer == 'HTMLTitle': - Title = Meta['HTMLTitle'] if Meta['HTMLTitle'] else Meta['Title'] if Meta['Title'] else Titles[0].lstrip('#') if Titles else 'Untitled' - if Meta['Type'] == 'Post' and BlogName: - Title += ' - ' + BlogName - return Title - -def GetDescription(Meta, BodyDescription, Prefer='MetaDescription'): - if Prefer == 'BodyDescription': - Description = BodyDescription if BodyDescription else Meta['Description'] if Meta['Description'] else '' - elif Prefer == 'MetaDescription': - Description = Meta['Description'] if Meta['Description'] else BodyDescription if BodyDescription else '' - return Description - -def GetImage(Meta, BodyImage, Prefer='MetaImage'): - if Prefer == 'BodyImage': - Image = BodyImage if BodyImage else Meta['Image'] if Meta['Image'] else '' - elif Prefer == 'MetaImage': - Image = Meta['Image'] if Meta['Image'] else BodyImage if BodyImage else '' - return Image - -def MakeLinkableTitle(Line, Title, DashTitle, Type): - if Type == 'md': - Index = Title.split(' ')[0].count('#') - return '{}'.format(Index, DashTitle, Title[Index+1:], Index) - elif Type == 'pug': - NewLine = '' - Index = Line.find('h') - NewLine += Line[:Index] - NewLine += "{}(id='{}')".format(Line[Index:Index+2], DashTitle) - NewLine += Line[Index+2:] - return NewLine - -def MakeListTitle(File, Meta, Titles, Prefer, SiteRoot, BlogName, PathPrefix=''): - Title = GetTitle(Meta, Titles, Prefer, BlogName) - Link = False if Meta['Index'] == 'Unlinked' else True - if Link: - Title = '[{}]({})'.format( - Title, - '{}{}.html'.format(PathPrefix, StripExt(File))) - if Meta['Type'] == 'Post': - CreatedOn = Meta['CreatedOn'] if Meta['CreatedOn'] else '?' - Title = '[{}] {}'.format(CreatedOn, Title) - return Title - -def FormatTitles(Titles): - # TODO: Somehow titles written in Pug can end up here and don't work, they should be handled - MDTitles, DashyTitles = '', [] - for t in Titles: - n = t.split(' ')[0].count('#') - Heading = '- ' * n - Title = t.lstrip('#') - DashyTitle = DashifyTitle(Title, DashyTitles) - DashyTitles += [DashyTitle] - Title = '[{}](#{})'.format(Title, DashyTitle) - MDTitles += Heading + Title + '\n' - return markdown(MDTitles) - -def Preprocessor(Path, SiteRoot): - File = ReadFile(Path) - Content, Titles, DashyTitles, Meta = '', [], [], { - 'Template': 'Standard.html', - 'Style': '', - 'Type': '', - 'Index': 'True', - 'Title': '', - 'HTMLTitle': '', - 'Description': '', - 'Image': '', - 'Categories': [], - 'CreatedOn': '', - 'EditedOn': '', - 'Order': None} - for l in File.splitlines(): - ls = l.lstrip() - if ls.startswith('// '): - lss = ls[3:] - for Item in ('Template', 'Type', 'Index', 'Title', 'HTMLTitle', 'Description', 'Image', 'CreatedOn', 'EditedOn'): - ItemText = '{}: '.format(Item) - if lss.startswith(ItemText): - Meta[Item] = lss[len(ItemText):] - if lss.startswith('Categories: '): - for i in lss[len('Categories: '):].split(' '): - Meta['Categories'] += [i] - elif lss.startswith('Background: '): - Meta['Style'] += "#MainBox{Background:" + lss[len('Background: '):] + ";} " - elif lss.startswith('Style: '): - Meta['Style'] += lss[len('Style: '):] + ' ' - elif lss.startswith('Order: '): - Meta['Order'] = int(lss[len('Order: '):]) - else: - if Path.endswith('.md'): - if ls.startswith('#'): - DashTitle = DashifyTitle(l.lstrip('#'), DashyTitles) - DashyTitles += [DashTitle] - Titles += [l] - Content += MakeLinkableTitle(l, ls, DashTitle, 'md') + '\n' - else: - Content += l + '\n' - elif Path.endswith('.pug'): - if ls.startswith(('h1', 'h2', 'h3', 'h4', 'h5', 'h6')): - if ls[2:].startswith(("(class='NoTitle", '(class="NoTitle')): - Content += l + '\n' - else: - Title = '#'*int(ls[1]) + str(ls[3:]) - DashTitle = DashifyTitle(Title.lstrip('#'), DashyTitles) - DashyTitles += [DashTitle] - Titles += [Title] - # TODO: We should handle headers that for any reason already have parenthesis - if ls[2:] == '(': - Content += l + '\n' - else: - Content += MakeLinkableTitle(l, Title, DashTitle, 'pug') + '\n' - else: - Content += l + '\n' - return Content, Titles, Meta - -def MakeContentHeader(Meta, Locale, Categories=''): - Header = '' - for i in ['CreatedOn', 'EditedOn']: - if Meta[i]: - Header += '{} {} \n'.format(Locale[i], Meta[i]) - if Categories: - Header += '{}: {} \n'.format(Locale['Categories'], Categories) - return markdown(Header) - -def MakeCategoryLine(File, Meta): - Categories = '' - if Meta['Categories']: - for i in Meta['Categories']: - Categories += '[{}]({}{}.html) '.format(i, GetLevels(File) + 'Categories/', i) - return Categories - -def PatchHTML(File, HTML, PartsText, ContextParts, ContextPartsText, HTMLPagesList, PagePath, Content, Titles, Meta, SiteRoot, SiteName, BlogName, FolderRoots, Categories, SiteLang, Locale): - HTMLTitles = FormatTitles(Titles) - BodyDescription, BodyImage = '', '' - Parse = BeautifulSoup(Content, 'html.parser') - if not BodyDescription and Parse.p: - BodyDescription = Parse.p.get_text()[:150].replace('\n', ' ').replace('"', "'") + '...' - if not BodyImage and Parse.img and Parse.img['src']: - BodyImage = Parse.img['src'] - - Title = GetTitle(Meta, Titles, 'MetaTitle', BlogName) - Description = GetDescription(Meta, BodyDescription, 'MetaDescription') - Image = GetImage(Meta, BodyImage, 'MetaImage') - - for Line in HTML.splitlines(): - Line = Line.lstrip().rstrip() - if Line.startswith('[HTML:ContextPart:') and Line.endswith(']'): - Path = Line[len('[HTML:ContextPart:'):-1] - Section = Path.split('/')[-1] - if Section in ContextParts: - Part = ContextParts[Section] - Text = '' - if type(Part) == list: - for i in Part: - Text += ContextPartsText['{}/{}'.format(Path, i)] + '\n' - elif type(Part) == str: - Text = ContextPartsText['{}/{}'.format(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, '[HTML:Site:Menu]', HTMLPagesList) - HTML = ReplWithEsc(HTML, '[HTML:Page:Lang]', SiteLang) - HTML = ReplWithEsc(HTML, '[HTML:Page:Chapters]', HTMLTitles) - HTML = ReplWithEsc(HTML, '[HTML:Page:Title]', Title) - HTML = ReplWithEsc(HTML, '[HTML:Page:Description]', Description) - HTML = ReplWithEsc(HTML, '[HTML:Page:Image]', Image) - HTML = ReplWithEsc(HTML, '[HTML:Page:Path]', PagePath) - HTML = ReplWithEsc(HTML, '[HTML:Page:Style]', Meta['Style']) - HTML = ReplWithEsc(HTML, '[HTML:Page:Content]', Content) - HTML = ReplWithEsc(HTML, '[HTML:Page:ContentHeader]', MakeContentHeader(Meta, Locale, MakeCategoryLine(File, Meta))) - HTML = ReplWithEsc(HTML, '[HTML:Site:Name]', SiteName) - HTML = ReplWithEsc(HTML, '[HTML:Site:AbsoluteRoot]', SiteRoot) - HTML = ReplWithEsc(HTML, '[HTML:Site:RelativeRoot]', GetLevels(PagePath)) - for i in FolderRoots: - HTML = HTML.replace('[HTML:Folder:{}:AbsoluteRoot]'.format(i), FolderRoots[i]) - for i in Categories: - HTML = HTML.replace('[HTML:Category:{}]'.format(i), Categories[i]) - - # TODO: Clean this doubling? - ContentHTML = Content - ContentHTML = ContentHTML.replace('[HTML:Site:AbsoluteRoot]', SiteRoot) - ContentHTML = ContentHTML.replace('[HTML:Site:RelativeRoot]', GetLevels(PagePath)) - for i in FolderRoots: - ContentHTML = ContentHTML.replace('[HTML:Folder:{}:AbsoluteRoot]'.format(i), FolderRoots[i]) - for i in Categories: - ContentHTML = ContentHTML.replace('[HTML:Category:{}]'.format(i), Categories[i]) - SlimHTML = HTMLPagesList + ContentHTML - - return HTML, ContentHTML, SlimHTML, Description, Image - -def OrderPages(Old): - New, NoOrder, Max = [], [], 0 - for i,e in enumerate(Old): - Curr = e[3]['Order'] - if Curr: - if Curr > Max: - Max = Curr - else: - NoOrder += [e] - for i in range(Max+1): - New += [[]] - for i,e in enumerate(Old): - Curr = e[3]['Order'] - if Curr: - New[Curr] = e - while [] in New: - New.remove([]) - return New + NoOrder - -def CanIndex(Index, For): - if Index in ('False', 'None'): - return False - elif Index in ('True', 'All', 'Unlinked'): - return True - else: - return True if Index == For else False - -def GetHTMLPagesList(Pages, BlogName, SiteRoot, PathPrefix, Type='Page', Category=None, For='Menu'): - List, ToPop, LastParent = '', [], [] - IndexPages = Pages.copy() - for e in IndexPages: - if e[3]['Index'] == 'False' or e[3]['Index'] == 'None': - IndexPages.remove(e) - for i,e in enumerate(IndexPages): - if e[3]['Type'] != Type: - ToPop += [i] - ToPop = RevSort(ToPop) - for i in ToPop: - IndexPages.pop(i) - if Type == 'Page': - IndexPages = OrderPages(IndexPages) - for File, Content, Titles, Meta in IndexPages: - if Meta['Type'] == Type and CanIndex(Meta['Index'], For) and GetTitle(Meta, Titles, 'HTMLTitle', BlogName) != 'Untitled' and (not Category or Category in Meta['Categories']): - n = File.count('/') + 1 - if n > 1: - CurParent = File.split('/')[:-1] - for i,s in enumerate(CurParent): - if LastParent != CurParent: - LastParent = CurParent - Levels = '- ' * (n-1+i) - if StripExt(File).endswith('index'): - Title = MakeListTitle(File, Meta, Titles, 'HTMLTitle', SiteRoot, BlogName, PathPrefix) - else: - Title = CurParent[n-2+i] - List += Levels + Title + '\n' - if not (n > 1 and StripExt(File).endswith('index')): - Levels = '- ' * n - Title = MakeListTitle(File, Meta, Titles, 'HTMLTitle', SiteRoot, BlogName, PathPrefix) - List += Levels + Title + '\n' - return markdown(List) - def DelTmp(): - for Ext in Extensions['Pages']: + for Ext in FileExtensions['Pages']: for File in Path('public').rglob('*.{}'.format(Ext)): os.remove(File) for Dir in ('public', 'public.gmi'): for File in Path(Dir).rglob('*.tmp'): os.remove(File) -def RevSort(List): - List.sort() - List.reverse() - return List - -def DoMinify(HTML): - return htmlmin.minify( - input=HTML, - remove_comments=True, - remove_empty_space=True, - remove_all_empty_space=False, - reduce_empty_attributes=True, - reduce_boolean_attributes=True, - remove_optional_attribute_quotes=True, - convert_charrefs=True, - keep_pre=True) - -def MakeSite(TemplatesText, PartsText, ContextParts, ContextPartsText, SiteName, BlogName, SiteTagline, SiteDomain, SiteRoot, FolderRoots, SiteLang, Locale, Minify, Sorting, MarkdownExts, AutoCategories): - PagesPaths, PostsPaths, Pages, MadePages, Categories = [], [], [], [], {} - for Ext in Extensions['Pages']: - for File in Path('Pages').rglob('*.{}'.format(Ext)): - PagesPaths += [FileToStr(File, 'Pages/')] - for File in Path('Posts').rglob('*.{}'.format(Ext)): - PostsPaths += [FileToStr(File, 'Posts/')] - - if Sorting['Pages'] == 'Standard': - PagesPaths.sort() - elif Sorting['Pages'] == 'Inverse': - PagesPaths = RevSort(PagesPaths) - if Sorting['Posts'] == 'Standard': - PostsPaths.sort() - elif Sorting['Posts'] == 'Inverse': - PostsPaths = RevSort(PostsPaths) - - print("[I] Preprocessing Source Pages") - for Type in ['Page', 'Post']: - if Type == 'Page': - Files = PagesPaths - elif Type == 'Post': - Files = PostsPaths - for File in Files: - Content, Titles, Meta = Preprocessor('{}s/{}'.format(Type, File), SiteRoot) - if Type != 'Page': - File = Type + 's/' + File - if not Meta['Type']: - Meta['Type'] = Type - Pages += [[File, Content, Titles, Meta]] - for Cat in Meta['Categories']: - Categories.update({Cat:''}) - PugCompileList(Pages) - - if Categories: - print("[I] Generating Category Lists") - for Cat in Categories: - for Type in ('Page', 'Post'): - Categories[Cat] += GetHTMLPagesList( - Pages=Pages, - BlogName=BlogName, - SiteRoot=SiteRoot, - PathPrefix=GetLevels('Categories/'), - Type=Type, - Category=Cat, - For='Categories') - - if AutoCategories: - Dir = 'public/Categories' - for Cat in Categories: - Exists = False - for File in Path(Dir).rglob(str(Cat)+'.*'): - Exists = True - break - if not Exists: - File = 'Categories/{}.md'.format(Cat) - FilePath = 'public/{}'.format(File) - WriteFile(FilePath, """\ -// Title: {Category} -// Type: Page - -# {Category} - -
[HTML:Category:{Category}]
-""".format(Category=Cat)) - Content, Titles, Meta = Preprocessor(FilePath, SiteRoot) - Pages += [[File, Content, Titles, Meta]] - - print("[I] Writing Pages") - for File, Content, Titles, Meta in Pages: - HTMLPagesList = GetHTMLPagesList( - Pages=Pages, - BlogName=BlogName, - SiteRoot=SiteRoot, - PathPrefix=GetLevels(File), - Type='Page', - For='Menu') - 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']], - PartsText=PartsText, - ContextParts=ContextParts, - ContextPartsText=ContextPartsText, - HTMLPagesList=HTMLPagesList, - PagePath=PagePath[len('public/'):], - Content=Content, - Titles=Titles, - Meta=Meta, - SiteRoot=SiteRoot, - SiteName=SiteName, - BlogName=BlogName, - FolderRoots=FolderRoots, - Categories=Categories, - SiteLang=SiteLang, - Locale=Locale) - if Minify: - HTML = DoMinify(HTML) - WriteFile(PagePath, HTML) - MadePages += [[File, Content, Titles, Meta, ContentHTML, SlimHTML, Description, Image]] - - return MadePages - def SetSorting(Sorting): Default = { 'Pages':'Standard', diff --git a/Source/Modules/Markdown.py b/Source/Modules/Markdown.py new file mode 100644 index 0000000..25a39b9 --- /dev/null +++ b/Source/Modules/Markdown.py @@ -0,0 +1,14 @@ +""" ================================= | +| This file is part of | +| staticoso | +| Just a simple Static Site Generator | +| | +| Licensed under the AGPLv3 license | +| Copyright (C) 2022, OctoSpacc | +| ================================= """ + +# Our local Markdown patches conflict if the module is installed on the system, so first try to import from system +try: + from markdown import markdown +except ModuleNotFoundError: + from Libs.markdown import markdown diff --git a/Source/Modules/Site.py b/Source/Modules/Site.py new file mode 100644 index 0000000..3141b99 --- /dev/null +++ b/Source/Modules/Site.py @@ -0,0 +1,395 @@ +""" ================================= | +| This file is part of | +| staticoso | +| Just a simple Static Site Generator | +| | +| Licensed under the AGPLv3 license | +| Copyright (C) 2022, OctoSpacc | +| ================================= """ + +from Libs import htmlmin +from Libs.bs4 import BeautifulSoup +from Modules.Markdown import * +from Modules.Pug import * +from Modules.Utils import * + +def DashifyTitle(Title, Done=[]): + return UndupeStr(DashifyStr(Title), Done, '-') + +def MakeLinkableTitle(Line, Title, DashTitle, Type): + if Type == 'md': + Index = Title.split(' ')[0].count('#') + return '{}'.format(Index, DashTitle, Title[Index+1:], Index) + elif Type == 'pug': + NewLine = '' + Index = Line.find('h') + NewLine += Line[:Index] + NewLine += "{}(id='{}')".format(Line[Index:Index+2], DashTitle) + NewLine += Line[Index+2:] + return NewLine + +def GetTitle(Meta, Titles, Prefer='MetaTitle', BlogName=None): + if Prefer == 'BodyTitle': + Title = Titles[0].lstrip('#') if Titles else Meta['Title'] if Meta['Title'] else 'Untitled' + elif Prefer == 'MetaTitle': + Title = Meta['Title'] if Meta['Title'] else Titles[0].lstrip('#') if Titles else 'Untitled' + elif Prefer == 'HTMLTitle': + Title = Meta['HTMLTitle'] if Meta['HTMLTitle'] else Meta['Title'] if Meta['Title'] else Titles[0].lstrip('#') if Titles else 'Untitled' + if Meta['Type'] == 'Post' and BlogName: + Title += ' - ' + BlogName + return Title + +def GetDescription(Meta, BodyDescription, Prefer='MetaDescription'): + if Prefer == 'BodyDescription': + Description = BodyDescription if BodyDescription else Meta['Description'] if Meta['Description'] else '' + elif Prefer == 'MetaDescription': + Description = Meta['Description'] if Meta['Description'] else BodyDescription if BodyDescription else '' + return Description + +def GetImage(Meta, BodyImage, Prefer='MetaImage'): + if Prefer == 'BodyImage': + Image = BodyImage if BodyImage else Meta['Image'] if Meta['Image'] else '' + elif Prefer == 'MetaImage': + Image = Meta['Image'] if Meta['Image'] else BodyImage if BodyImage else '' + return Image + +def MakeContentHeader(Meta, Locale, Categories=''): + Header = '' + for i in ['CreatedOn', 'EditedOn']: + if Meta[i]: + Header += '{} {} \n'.format(Locale[i], Meta[i]) + if Categories: + Header += '{}: {} \n'.format(Locale['Categories'], Categories) + return markdown(Header) + +def MakeCategoryLine(File, Meta): + Categories = '' + if Meta['Categories']: + for i in Meta['Categories']: + Categories += '[{}]({}{}.html) '.format(i, GetPathLevels(File) + 'Categories/', i) + return Categories + +def GetHTMLPagesList(Pages, BlogName, SiteRoot, PathPrefix, Type='Page', Category=None, For='Menu'): + List, ToPop, LastParent = '', [], [] + IndexPages = Pages.copy() + for e in IndexPages: + if e[3]['Index'] == 'False' or e[3]['Index'] == 'None': + IndexPages.remove(e) + for i,e in enumerate(IndexPages): + if e[3]['Type'] != Type: + ToPop += [i] + ToPop = RevSort(ToPop) + for i in ToPop: + IndexPages.pop(i) + if Type == 'Page': + IndexPages = OrderPages(IndexPages) + for File, Content, Titles, Meta in IndexPages: + if Meta['Type'] == Type and CanIndex(Meta['Index'], For) and GetTitle(Meta, Titles, 'HTMLTitle', BlogName) != 'Untitled' and (not Category or Category in Meta['Categories']): + n = File.count('/') + 1 + if n > 1: + CurParent = File.split('/')[:-1] + for i,s in enumerate(CurParent): + if LastParent != CurParent: + LastParent = CurParent + Levels = '- ' * (n-1+i) + if StripExt(File).endswith('index'): + Title = MakeListTitle(File, Meta, Titles, 'HTMLTitle', SiteRoot, BlogName, PathPrefix) + else: + Title = CurParent[n-2+i] + List += Levels + Title + '\n' + if not (n > 1 and StripExt(File).endswith('index')): + Levels = '- ' * n + Title = MakeListTitle(File, Meta, Titles, 'HTMLTitle', SiteRoot, BlogName, PathPrefix) + List += Levels + Title + '\n' + return markdown(List) + +def Preprocessor(Path, SiteRoot): + File = ReadFile(Path) + Content, Titles, DashyTitles, Meta = '', [], [], { + 'Template': 'Standard.html', + 'Style': '', + 'Type': '', + 'Index': 'True', + 'Title': '', + 'HTMLTitle': '', + 'Description': '', + 'Image': '', + 'Categories': [], + 'CreatedOn': '', + 'EditedOn': '', + 'Order': None} + for l in File.splitlines(): + ls = l.lstrip() + if ls.startswith('// '): + lss = ls[3:] + for Item in ('Template', 'Type', 'Index', 'Title', 'HTMLTitle', 'Description', 'Image', 'CreatedOn', 'EditedOn'): + ItemText = '{}: '.format(Item) + if lss.startswith(ItemText): + Meta[Item] = lss[len(ItemText):] + if lss.startswith('Categories: '): + for i in lss[len('Categories: '):].split(' '): + Meta['Categories'] += [i] + elif lss.startswith('Background: '): + Meta['Style'] += "#MainBox{Background:" + lss[len('Background: '):] + ";} " + elif lss.startswith('Style: '): + Meta['Style'] += lss[len('Style: '):] + ' ' + elif lss.startswith('Order: '): + Meta['Order'] = int(lss[len('Order: '):]) + else: + if Path.endswith('.md'): + if ls.startswith('#'): + DashTitle = DashifyTitle(l.lstrip('#'), DashyTitles) + DashyTitles += [DashTitle] + Titles += [l] + Content += MakeLinkableTitle(l, ls, DashTitle, 'md') + '\n' + else: + Content += l + '\n' + elif Path.endswith('.pug'): + if ls.startswith(('h1', 'h2', 'h3', 'h4', 'h5', 'h6')): + if ls[2:].startswith(("(class='NoTitle", '(class="NoTitle')): + Content += l + '\n' + else: + Title = '#'*int(ls[1]) + str(ls[3:]) + DashTitle = DashifyTitle(Title.lstrip('#'), DashyTitles) + DashyTitles += [DashTitle] + Titles += [Title] + # TODO: We should handle headers that for any reason already have parenthesis + if ls[2:] == '(': + Content += l + '\n' + else: + Content += MakeLinkableTitle(l, Title, DashTitle, 'pug') + '\n' + else: + Content += l + '\n' + return Content, Titles, Meta + +def MakeListTitle(File, Meta, Titles, Prefer, SiteRoot, BlogName, PathPrefix=''): + Title = GetTitle(Meta, Titles, Prefer, BlogName) + Link = False if Meta['Index'] == 'Unlinked' else True + if Link: + Title = '[{}]({})'.format( + Title, + '{}{}.html'.format(PathPrefix, StripExt(File))) + if Meta['Type'] == 'Post': + CreatedOn = Meta['CreatedOn'] if Meta['CreatedOn'] else '?' + Title = '[{}] {}'.format(CreatedOn, Title) + return Title + +def FormatTitles(Titles): + # TODO: Somehow titles written in Pug can end up here and don't work, they should be handled + MDTitles, DashyTitles = '', [] + for t in Titles: + n = t.split(' ')[0].count('#') + Heading = '- ' * n + Title = t.lstrip('#') + DashyTitle = DashifyTitle(Title, DashyTitles) + DashyTitles += [DashyTitle] + Title = '[{}](#{})'.format(Title, DashyTitle) + MDTitles += Heading + Title + '\n' + return markdown(MDTitles) + +def OrderPages(Old): + New, NoOrder, Max = [], [], 0 + for i,e in enumerate(Old): + Curr = e[3]['Order'] + if Curr: + if Curr > Max: + Max = Curr + else: + NoOrder += [e] + for i in range(Max+1): + New += [[]] + for i,e in enumerate(Old): + Curr = e[3]['Order'] + if Curr: + New[Curr] = e + while [] in New: + New.remove([]) + return New + NoOrder + +def CanIndex(Index, For): + if Index in ('False', 'None'): + return False + elif Index in ('True', 'All', 'Unlinked'): + return True + else: + return True if Index == For else False + +def PatchHTML(File, HTML, PartsText, ContextParts, ContextPartsText, HTMLPagesList, PagePath, Content, Titles, Meta, SiteRoot, SiteName, BlogName, FolderRoots, Categories, SiteLang, Locale): + HTMLTitles = FormatTitles(Titles) + BodyDescription, BodyImage = '', '' + Parse = BeautifulSoup(Content, 'html.parser') + if not BodyDescription and Parse.p: + BodyDescription = Parse.p.get_text()[:150].replace('\n', ' ').replace('"', "'") + '...' + if not BodyImage and Parse.img and Parse.img['src']: + BodyImage = Parse.img['src'] + + Title = GetTitle(Meta, Titles, 'MetaTitle', BlogName) + Description = GetDescription(Meta, BodyDescription, 'MetaDescription') + Image = GetImage(Meta, BodyImage, 'MetaImage') + + for Line in HTML.splitlines(): + Line = Line.lstrip().rstrip() + if Line.startswith('[HTML:ContextPart:') and Line.endswith(']'): + Path = Line[len('[HTML:ContextPart:'):-1] + Section = Path.split('/')[-1] + if Section in ContextParts: + Part = ContextParts[Section] + Text = '' + if type(Part) == list: + for i in Part: + Text += ContextPartsText['{}/{}'.format(Path, i)] + '\n' + elif type(Part) == str: + Text = ContextPartsText['{}/{}'.format(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, '[HTML:Site:Menu]', HTMLPagesList) + HTML = ReplWithEsc(HTML, '[HTML:Page:Lang]', SiteLang) + HTML = ReplWithEsc(HTML, '[HTML:Page:Chapters]', HTMLTitles) + HTML = ReplWithEsc(HTML, '[HTML:Page:Title]', Title) + HTML = ReplWithEsc(HTML, '[HTML:Page:Description]', Description) + HTML = ReplWithEsc(HTML, '[HTML:Page:Image]', Image) + HTML = ReplWithEsc(HTML, '[HTML:Page:Path]', PagePath) + HTML = ReplWithEsc(HTML, '[HTML:Page:Style]', Meta['Style']) + HTML = ReplWithEsc(HTML, '[HTML:Page:Content]', Content) + HTML = ReplWithEsc(HTML, '[HTML:Page:ContentHeader]', MakeContentHeader(Meta, Locale, MakeCategoryLine(File, Meta))) + HTML = ReplWithEsc(HTML, '[HTML:Site:Name]', SiteName) + HTML = ReplWithEsc(HTML, '[HTML:Site:AbsoluteRoot]', SiteRoot) + HTML = ReplWithEsc(HTML, '[HTML:Site:RelativeRoot]', GetPathLevels(PagePath)) + for i in FolderRoots: + HTML = HTML.replace('[HTML:Folder:{}:AbsoluteRoot]'.format(i), FolderRoots[i]) + for i in Categories: + HTML = HTML.replace('[HTML:Category:{}]'.format(i), Categories[i]) + + # TODO: Clean this doubling? + ContentHTML = Content + ContentHTML = ContentHTML.replace('[HTML:Site:AbsoluteRoot]', SiteRoot) + ContentHTML = ContentHTML.replace('[HTML:Site:RelativeRoot]', GetPathLevels(PagePath)) + for i in FolderRoots: + ContentHTML = ContentHTML.replace('[HTML:Folder:{}:AbsoluteRoot]'.format(i), FolderRoots[i]) + for i in Categories: + ContentHTML = ContentHTML.replace('[HTML:Category:{}]'.format(i), Categories[i]) + SlimHTML = HTMLPagesList + ContentHTML + + return HTML, ContentHTML, SlimHTML, Description, Image + +def DoMinify(HTML): + return htmlmin.minify( + input=HTML, + remove_comments=True, + remove_empty_space=True, + remove_all_empty_space=False, + reduce_empty_attributes=True, + reduce_boolean_attributes=True, + remove_optional_attribute_quotes=True, + convert_charrefs=True, + keep_pre=True) + +def MakeSite(TemplatesText, PartsText, ContextParts, ContextPartsText, SiteName, BlogName, SiteTagline, SiteDomain, SiteRoot, FolderRoots, SiteLang, Locale, Minify, Sorting, MarkdownExts, AutoCategories): + PagesPaths, PostsPaths, Pages, MadePages, Categories = [], [], [], [], {} + for Ext in FileExtensions['Pages']: + for File in Path('Pages').rglob('*.{}'.format(Ext)): + PagesPaths += [FileToStr(File, 'Pages/')] + for File in Path('Posts').rglob('*.{}'.format(Ext)): + PostsPaths += [FileToStr(File, 'Posts/')] + + if Sorting['Pages'] == 'Standard': + PagesPaths.sort() + elif Sorting['Pages'] == 'Inverse': + PagesPaths = RevSort(PagesPaths) + if Sorting['Posts'] == 'Standard': + PostsPaths.sort() + elif Sorting['Posts'] == 'Inverse': + PostsPaths = RevSort(PostsPaths) + + print("[I] Preprocessing Source Pages") + for Type in ['Page', 'Post']: + if Type == 'Page': + Files = PagesPaths + elif Type == 'Post': + Files = PostsPaths + for File in Files: + Content, Titles, Meta = Preprocessor('{}s/{}'.format(Type, File), SiteRoot) + if Type != 'Page': + File = Type + 's/' + File + if not Meta['Type']: + Meta['Type'] = Type + Pages += [[File, Content, Titles, Meta]] + for Cat in Meta['Categories']: + Categories.update({Cat:''}) + PugCompileList(Pages) + + if Categories: + print("[I] Generating Category Lists") + for Cat in Categories: + for Type in ('Page', 'Post'): + Categories[Cat] += GetHTMLPagesList( + Pages=Pages, + BlogName=BlogName, + SiteRoot=SiteRoot, + PathPrefix=GetPathLevels('Categories/'), + Type=Type, + Category=Cat, + For='Categories') + + if AutoCategories: + Dir = 'public/Categories' + for Cat in Categories: + Exists = False + for File in Path(Dir).rglob(str(Cat)+'.*'): + Exists = True + break + if not Exists: + File = 'Categories/{}.md'.format(Cat) + FilePath = 'public/{}'.format(File) + WriteFile(FilePath, """\ +// Title: {Category} +// Type: Page + +# {Category} + +
[HTML:Category:{Category}]
+""".format(Category=Cat)) + Content, Titles, Meta = Preprocessor(FilePath, SiteRoot) + Pages += [[File, Content, Titles, Meta]] + + print("[I] Writing Pages") + for File, Content, Titles, Meta in Pages: + HTMLPagesList = GetHTMLPagesList( + Pages=Pages, + BlogName=BlogName, + SiteRoot=SiteRoot, + PathPrefix=GetPathLevels(File), + Type='Page', + For='Menu') + 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']], + PartsText=PartsText, + ContextParts=ContextParts, + ContextPartsText=ContextPartsText, + HTMLPagesList=HTMLPagesList, + PagePath=PagePath[len('public/'):], + Content=Content, + Titles=Titles, + Meta=Meta, + SiteRoot=SiteRoot, + SiteName=SiteName, + BlogName=BlogName, + FolderRoots=FolderRoots, + Categories=Categories, + SiteLang=SiteLang, + Locale=Locale) + if Minify: + HTML = DoMinify(HTML) + WriteFile(PagePath, HTML) + MadePages += [[File, Content, Titles, Meta, ContentHTML, SlimHTML, Description, Image]] + + return MadePages diff --git a/Source/Modules/Utils.py b/Source/Modules/Utils.py index 7081f35..93072b0 100644 --- a/Source/Modules/Utils.py +++ b/Source/Modules/Utils.py @@ -12,6 +12,9 @@ import os from datetime import datetime from pathlib import Path +FileExtensions = { + 'Pages': ('md', 'pug')} + def ReadFile(p): try: with open(p, 'r') as f: @@ -63,6 +66,10 @@ def DashifyStr(s, Limit=32): Str += c return '-' + Str +def GetPathLevels(Path, AsNum=False, Add=0, Sub=0): + n = Path.count('/') + Add - Sub + return n if AsNum else '../' * n + # https://stackoverflow.com/a/34445090 def FindAllIndex(Str, Sub): i = Str.find(Sub) @@ -86,6 +93,11 @@ def ReplWithEsc(Str, Find, Repl, Esc='\\'): New += Repl + e return New +def RevSort(List): + List.sort() + List.reverse() + return List + def GetFullDate(Date): if not Date: return None