diff --git a/Locale/en.json b/Locale/en.json index af775d7..894e660 100644 --- a/Locale/en.json +++ b/Locale/en.json @@ -1,6 +1,7 @@ { "CreatedOn": "Created on", "EditedOn": "Edited on", + "Unlisted": "Unlisted", "Categories": "Categories", "ReadFullPost": "Read the full post", "Comments": "Comments", diff --git a/Locale/it.json b/Locale/it.json index dc859ec..ee02612 100644 --- a/Locale/it.json +++ b/Locale/it.json @@ -1,6 +1,7 @@ { "CreatedOn": "Creato in data", "EditedOn": "Modificato in data", + "Unlisted": "Non in elenco", "Categories": "Categorie", "ReadFullPost": "Leggi il post intero", "Comments": "Commenti", diff --git a/Source/Build.py b/Source/Build.py index ac9c8f7..3aa9d39 100755 --- a/Source/Build.py +++ b/Source/Build.py @@ -17,6 +17,7 @@ from datetime import datetime from pathlib import Path from Modules.Config import * from Modules.Gemini import * +from Modules.Globals import * from Modules.Logging import * from Modules.Markdown import * from Modules.Site import * diff --git a/Source/Libs/mastodon/streaming.py b/Source/Libs/mastodon/streaming.py index 83472a8..8364274 100644 --- a/Source/Libs/mastodon/streaming.py +++ b/Source/Libs/mastodon/streaming.py @@ -4,7 +4,7 @@ https://github.com/tootsuite/mastodon/blob/master/docs/Using-the-API/Streaming-A """ import json -import six +from .. import six from . import Mastodon from .Mastodon import MastodonMalformedEventError, MastodonNetworkError, MastodonReadTimeout from requests.exceptions import ChunkedEncodingError, ReadTimeout diff --git a/Source/Modules/Elements.py b/Source/Modules/Elements.py index 9d65181..7c1607b 100644 --- a/Source/Modules/Elements.py +++ b/Source/Modules/Elements.py @@ -8,6 +8,7 @@ | ================================= """ from base64 import b64encode +from Modules.Globals import * from Modules.HTML import * from Modules.Utils import * @@ -32,6 +33,8 @@ RedirectPageTemplate = """\ + + {TitlePrefix}Redirect @@ -68,7 +71,7 @@ def GenHTMLTreeList(MetaList:str, Type:str='ul'): PrevDepth = CurDepth return f'<{Type}>{HTML}\n' -def MakeLinkableTitle(Line, Title, DashTitle, Type): +def MakeLinkableTitle(Line:str, Title:str, DashTitle:str, Type:str): if Type == 'md': Index = Title.split(' ')[0].count('#') return HTMLSectionTitleLine.format( @@ -83,7 +86,7 @@ def MakeLinkableTitle(Line, Title, DashTitle, Type): Rest=Line[Index+2:], DashTitle=DashTitle) -def GetTitle(FileName, Meta, Titles, Prefer='MetaTitle', BlogName=None): +def GetTitle(FileName:str, Meta:dict, Titles:list, Prefer='MetaTitle', BlogName=None): if Prefer == 'BodyTitle': Title = Titles[0].lstrip('#') if Titles else Meta['Title'] if Meta['Title'] else FileName elif Prefer == 'MetaTitle': @@ -94,27 +97,29 @@ def GetTitle(FileName, Meta, Titles, Prefer='MetaTitle', BlogName=None): Title += ' - ' + BlogName return Title -def GetDescription(Meta, BodyDescription, Prefer='MetaDescription'): +def GetDescription(Meta:dict, 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'): +def GetImage(Meta:dict, 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=''): +def MakeContentHeader(Meta:dict, Locale:dict, Categories=''): Header = '' for e in ['CreatedOn', 'EditedOn']: if Meta[e]: - Header += f'{Locale[e]}: {Meta[e]}
' + Header += f'{Locale[e]}: {Meta[e]}
' if Categories: - Header += f'{Locale["Categories"]}:{Categories.removesuffix(" ")}
' + Header += f'{Locale["Categories"]}:{Categories.removesuffix(" ")}
' + if Meta['Index'].lower() in PageIndexStrNeg: + Header += f'{Locale["Unlisted"]}
' return f'

{Header}

' def MakeCategoryLine(File, Meta): diff --git a/Source/Modules/Globals.py b/Source/Modules/Globals.py new file mode 100644 index 0000000..50b18ae --- /dev/null +++ b/Source/Modules/Globals.py @@ -0,0 +1,23 @@ +""" ================================= | +| This file is part of | +| staticoso | +| Just a simple Static Site Generator | +| | +| Licensed under the AGPLv3 license | +| Copyright (C) 2022, OctoSpacc | +| ================================= """ + +ReservedPaths = ('Site.ini', 'Assets', 'Pages', 'Posts', 'Templates', 'StaticParts', 'DynamicParts') +FileExtensions = { + 'Pages': ('htm', 'html', 'markdown', 'md', 'pug', 'txt'), + 'HTML': ('.htm', '.html'), + 'Markdown': ('.markdown', '.md'), + 'Tmp': ('htm', 'markdown', 'md', 'pug', 'txt')} + +PosStrBools = ('true', 'yes', 'on', '1', 'enabled') +NegStrBools = ('false', 'no', 'off', '0', 'disabled') + +PageIndexStrPos = tuple(list(PosStrBools) + ['all', 'listed', 'indexed', 'unlinked']) +PageIndexStrNeg = tuple(list(NegStrBools) + ['none', 'unlisted', 'unindexed', 'hidden']) + +InternalMacrosWraps = [['[', ']'], ['<', '>']] diff --git a/Source/Modules/HTML.py b/Source/Modules/HTML.py index 34812d6..270e700 100644 --- a/Source/Modules/HTML.py +++ b/Source/Modules/HTML.py @@ -58,21 +58,24 @@ def WriteImgAltAndTitle(HTML, AltToTitle, TitleToAlt): # Adds alt or title attr. return str(Soup) def AddToTagStartEnd(HTML, MatchStart, MatchEnd, AddStart, AddEnd): # This doesn't handle nested tags - StartPos = None + StartPos, DidStart, DidEnd = None, 0, 0 for i,e in enumerate(HTML): FilterStart = HTML[i:i+len(MatchStart)] FilterEnd = HTML[i:i+len(MatchEnd)] - if not AddStart and not AddEnd: - break - if FilterStart == MatchStart: + if DidStart == 0 and FilterStart == MatchStart: StartPos = i if AddStart: HTML = HTML[:i] + AddStart + HTML[i:] - AddStart = None - if FilterEnd == MatchEnd and StartPos and i > StartPos: + DidStart = 2 + if DidEnd == 0 and FilterEnd == MatchEnd and StartPos and i > StartPos: + StartPos = None if AddEnd: HTML = HTML[:i+len(MatchEnd)] + AddEnd + HTML[i+len(MatchEnd):] - AddEnd = None + DidEnd = 2 + if DidStart > 0: + DidStart -= 1 + if DidEnd > 0: + DidEnd -= 1 return HTML def SquareFnrefs(HTML): # Different combinations of formatting for Soup .prettify, .encode, .decode break different page elements, don't use this for now diff --git a/Source/Modules/Site.py b/Source/Modules/Site.py index aef0bf0..2ccbb20 100644 --- a/Source/Modules/Site.py +++ b/Source/Modules/Site.py @@ -13,6 +13,7 @@ from multiprocessing import Pool, cpu_count from Libs.bs4 import BeautifulSoup from Modules.Config import * from Modules.Elements import * +from Modules.Globals import * from Modules.HTML import * from Modules.Logging import * from Modules.Markdown import * @@ -31,7 +32,7 @@ def GetHTMLPagesList(Pages, BlogName, SiteRoot, PathPrefix, CallbackFile=None, U List, ToPop, LastParent = '', [], [] IndexPages = Pages.copy() for e in IndexPages: - if e[3]['Index'] == 'False' or e[3]['Index'] == 'None': + if e[3]['Index'].lower() in PageIndexStrNeg: IndexPages.remove(e) for i,e in enumerate(IndexPages): if Type and e[3]['Type'] != Type: @@ -43,7 +44,7 @@ def GetHTMLPagesList(Pages, BlogName, SiteRoot, PathPrefix, CallbackFile=None, U IndexPages = OrderPages(IndexPages) for i,e in enumerate(Unite): if e: - IndexPages.insert(i,[e,None,None,{'Type':Type,'Index':'True','Order':'Unite'}]) + IndexPages.insert(i, [e, None, None, {'Type':Type, 'Index':'True', 'Order':'Unite'}]) for File, Content, Titles, Meta in IndexPages: # Allow for the virtual "Pages/" prefix to be used in path filtering TmpPathFilter = PathFilter @@ -128,11 +129,12 @@ def FindPreprocLine(Line, Meta, Macros): # IgnoreBlocksStart += [l] return (Meta, Macros, Changed) -def PagePreprocessor(Path, TempPath, Type, SiteTemplate, SiteRoot, GlobalMacros, CategoryUncategorized, LightRun=False): +def PagePreprocessor(Path:str, TempPath:str, Type, SiteTemplate, SiteRoot, GlobalMacros, CategoryUncategorized, LightRun=False): File = ReadFile(Path) Path = Path.lower() Content, Titles, DashyTitles, HTMLTitlesFound, Macros, Meta, MetaDefault = '', [], [], False, '', '', { 'Template': SiteTemplate, + 'Head': '', 'Style': '', 'Type': Type, 'Index': 'Unspecified', @@ -250,24 +252,24 @@ def PagePreprocessor(Path, TempPath, Type, SiteTemplate, SiteRoot, GlobalMacros, Meta.update({i:MetaDefault[i]}) if Meta['UpdatedOn']: Meta['EditedOn'] = Meta['UpdatedOn'] - if Meta['Index'] in ('Default', 'Unspecified', 'Categories'): + if Meta['Index'].lower() in ('default', 'unspecified', 'categories'): if not Meta['Categories']: Meta['Categories'] = [CategoryUncategorized] - if Meta['Type'] == 'Page': + if Meta['Type'].lower() == 'page': Meta['Index'] = 'Categories' - elif Meta['Type'] == 'Post': + elif Meta['Type'].lower() == 'post': Meta['Index'] = 'True' if GlobalMacros: Meta['Macros'].update(GlobalMacros) Meta['Macros'].update(ReadConf(LoadConfStr('[Macros]\n' + Macros), 'Macros')) return [TempPath, Content, Titles, Meta] -def PagePostprocessor(FileType, Text, Meta): +def PagePostprocessor(FileType, Text:str, Meta:dict): for e in Meta['Macros']: Text = ReplWithEsc(Text, f"[: {e} :]", f"[:{e}:]") return Text -def OrderPages(Old): +def OrderPages(Old:list): New, NoOrder, Max = [], [], 0 for i,e in enumerate(Old): Curr = e[3]['Order'] @@ -285,10 +287,10 @@ def OrderPages(Old): New.remove(None) return New + NoOrder -def CanIndex(Index, For): - if Index in ('False', 'None'): +def CanIndex(Index:str, For:str): + if Index.lower() in PageIndexStrNeg: return False - elif Index in ('True', 'All', 'Unlinked'): + elif Index.lower() in PageIndexStrPos: return True else: return True if Index == For else False @@ -297,20 +299,26 @@ def PatchHTML(File, HTML, StaticPartsText, DynamicParts, DynamicPartsText, HTMLP HTMLTitles = FormatTitles(Titles) BodyDescription, BodyImage = '', '' if not File.lower().endswith('.txt'): - Soup = BeautifulSoup(Content, 'html.parser') - - if not BodyDescription and Soup.p: - BodyDescription = Soup.p.get_text()[:150].replace('\n', ' ').replace('"', "'") + '...' + Soup = MkSoup(Content) + if not BodyDescription:# and Soup.p: + #BodyDescription = Soup.p.get_text()[:150].replace('\n', ' ').replace('"', "'") + '...' + for t in Soup.find_all('p'): + if t.get_text(): + BodyDescription = t.get_text()[:150].replace('\n', ' ').replace('"', "'") + '...' + break if not BodyImage and Soup.img and Soup.img['src']: BodyImage = Soup.img['src'] #Content = SquareFnrefs(Content) - if '', '[', ']') + if '', '[', ']') if any(_ in Content for _ in ('', '', '', '')): Content = DictReplWithEsc( Content, { + '': '', + '--->': '', '': '', @@ -352,85 +360,130 @@ def PatchHTML(File, HTML, StaticPartsText, DynamicParts, DynamicPartsText, HTMLP if LightRun: HTML = None else: - HTML = DictReplWithEsc(HTML, { - '[staticoso:Site:Menu]': HTMLPagesList, - '': HTMLPagesList, - '[staticoso:Page:Lang]': Meta['Language'] if Meta['Language'] else SiteLang, - '': Meta['Language'] if Meta['Language'] else SiteLang, - '': Meta['Language'] if Meta['Language'] else SiteLang, - '[staticoso:Page:Chapters]': HTMLTitles, - '': HTMLTitles, - '[staticoso:Page:Title]': Title, - '': Title, - '[staticoso:Page:Description]': Description, - '': Description, - '[staticoso:Page:Image]': Image, - '': Image, - '[staticoso:Page:Path]': PagePath, - '': PagePath, - '[staticoso:Page:Style]': Meta['Style'], - '': Meta['Style'], + HTML = WrapDictReplWithEsc(HTML, { + #'[staticoso:Site:Menu]': HTMLPagesList, + #'': HTMLPagesList, + #'[staticoso:Page:Lang]': Meta['Language'] if Meta['Language'] else SiteLang, + #'': Meta['Language'] if Meta['Language'] else SiteLang, + #'': Meta['Language'] if Meta['Language'] else SiteLang, + #'[staticoso:Page:Chapters]': HTMLTitles, + #'': HTMLTitles, + #'[staticoso:Page:Title]': Title, + #'': Title, + #'[staticoso:Page:Description]': Description, + #'': Description, + #'[staticoso:Page:Image]': Image, + #'': Image, + #'[staticoso:Page:Path]': PagePath, + #'': PagePath, + #'[staticoso:PageHead]': Meta['Head'], + #'': Meta['Head'], + #'[staticoso:Page:Style]': Meta['Style'], + #'': Meta['Style'], + # #DEPRECATION # + 'staticoso:Site:Menu': HTMLPagesList, + 'staticoso:Page:Lang': Meta['Language'] if Meta['Language'] else SiteLang, + 'staticoso:Page:Chapters': HTMLTitles, + 'staticoso:Page:Title': Title, + 'staticoso:Page:Description': Description, + 'staticoso:Page:Image': Image, + 'staticoso:Page:Path': PagePath, + 'staticoso:Page:Style': Meta['Style'], + ################ + 'staticoso:SiteMenu': HTMLPagesList, + 'staticoso:PageLang': Meta['Language'] if Meta['Language'] else SiteLang, + 'staticoso:PageLanguage': Meta['Language'] if Meta['Language'] else SiteLang, + 'staticoso:PageSections': HTMLTitles, + 'staticoso:PageTitle': Title, + 'staticoso:PageDescription': Description, + 'staticoso:PageImage': Image, + 'staticoso:PagePath': PagePath, + 'staticoso:PageHead': Meta['Head'], + 'staticoso:PageStyle': Meta['Style'], # NOTE: Content is injected in page only at this point! Keep in mind for other substitutions - '[staticoso:Page:Content]': Content, - '': Content, - '[staticoso:Page:ContentInfo]': ContentHeader, - '': ContentHeader, - '[staticoso:BuildTime]': TimeNow, - '': TimeNow, - '': SiteDomain, - '[staticoso:Site:Name]': SiteName, - '': SiteName, - '[staticoso:Site:AbsoluteRoot]': SiteRoot, - '': SiteRoot, - '[staticoso:Site:RelativeRoot]': RelativeRoot, - '': RelativeRoot}) + #'[staticoso:Page:Content]': Content, + #'': Content, + #'[staticoso:Page:ContentInfo]': ContentHeader, + #'': ContentHeader, + #'[staticoso:BuildTime]': TimeNow, + #'': TimeNow, + #'': SiteDomain, + #'[staticoso:Site:Name]': SiteName, + #'': SiteName, + #'[staticoso:Site:AbsoluteRoot]': SiteRoot, + #'': SiteRoot, + #'[staticoso:Site:RelativeRoot]': RelativeRoot, + #'': RelativeRoot, + # #DEPRECATION # + 'staticoso:Page:Content': Content, + 'staticoso:Page:ContentInfo': ContentHeader, + 'staticoso:Site:Name': SiteName, + 'staticoso:Site:AbsoluteRoot': SiteRoot, + 'staticoso:Site:RelativeRoot': RelativeRoot, + ################ + 'staticoso:PageContent': Content, + 'staticoso:PageContentInfo': ContentHeader, + 'staticoso:BuildTime': TimeNow, + 'staticoso:SiteDomain': SiteDomain, + 'staticoso:SiteName': SiteName, + 'staticoso:SiteAbsoluteRoot': SiteRoot, + 'staticoso:SiteRelativeRoot': RelativeRoot, + }, InternalMacrosWraps) for e in Meta['Macros']: HTML = ReplWithEsc(HTML, f"[:{e}:]", Meta['Macros'][e]) for e in FolderRoots: - HTML = DictReplWithEsc(HTML, { - f"[staticoso:CustomPath:{e}]": FolderRoots[e], - f"": FolderRoots[e], - #DEPRECATED - f"[staticoso:Folder:{e}:AbsoluteRoot]": FolderRoots[e], - f"": FolderRoots[e]}) + HTML = WrapDictReplWithEsc(HTML, { + f'staticoso:CustomPath:{e}': FolderRoots[e], + f'staticoso:Folder:{e}:AbsoluteRoot': FolderRoots[e], #DEPRECATED + }, InternalMacrosWraps) for e in Categories: - HTML = DictReplWithEsc(HTML, { - f"[staticoso:Category:{e}]": Categories[e], - f"": Categories[e], - f"": Categories[e], - #DEPRECATED - f"[staticoso:Category:{e}]": Categories[e]}) + HTML = WrapDictReplWithEsc(HTML, { + f'staticoso:Category:{e}': Categories[e], + f'staticoso:CategoryList:{e}': Categories[e], + }, InternalMacrosWraps) + HTML = ReplWithEsc(HTML, f'[staticoso:Category:{e}]', Categories[e]) #DEPRECATED # TODO: Clean this doubling? ContentHTML = Content - ContentHTML = DictReplWithEsc(ContentHTML, { + ContentHTML = WrapDictReplWithEsc(ContentHTML, { + #'[staticoso:Page:Title]': Title, + #'': Title, + #'[staticoso:Page:Description]': Description, + #'': Description, + #'': SiteDomain, + #'[staticoso:Site:Name]': SiteName, + #'': SiteName, + #'[staticoso:Site:AbsoluteRoot]': SiteRoot, + #'': SiteRoot, + #'[staticoso:Site:RelativeRoot]': RelativeRoot, + #'': RelativeRoot, + # #DEPRECATION # '[staticoso:Page:Title]': Title, - '': Title, '[staticoso:Page:Description]': Description, + '[staticoso:Site:Name]': SiteName, + '[staticoso:Site:AbsoluteRoot]': SiteRoot, + '[staticoso:Site:RelativeRoot]': RelativeRoot, + ################ + '': Title, '': Description, '': SiteDomain, - '[staticoso:Site:Name]': SiteName, '': SiteName, - '[staticoso:Site:AbsoluteRoot]': SiteRoot, '': SiteRoot, - '[staticoso:Site:RelativeRoot]': RelativeRoot, - '': RelativeRoot}) + '': RelativeRoot, + }, InternalMacrosWraps) for e in Meta['Macros']: ContentHTML = ReplWithEsc(ContentHTML, f"[:{e}:]", Meta['Macros'][e]) for e in FolderRoots: - ContentHTML = DictReplWithEsc(ContentHTML, { - f"[staticoso:CustomPath:{e}]": FolderRoots[e], - f"": FolderRoots[e], - #DEPRECATED - f"[staticoso:Folder:{e}:AbsoluteRoot]": FolderRoots[e], - f"": FolderRoots[e]}) + ContentHTML = WrapDictReplWithEsc(ContentHTML, { + f'staticoso:CustomPath:{e}': FolderRoots[e], + f'staticoso:Folder:{e}:AbsoluteRoot': FolderRoots[e], #DEPRECATED + }, InternalMacrosWraps) for e in Categories: - ContentHTML = DictReplWithEsc(ContentHTML, { - f"[staticoso:Category:{e}]": Categories[e], - f"": Categories[e], - f"": Categories[e], - #DEPRECATED - f"[staticoso:Category:{e}]": Categories[e]}) + ContentHTML = WrapDictReplWithEsc(ContentHTML, { + f'staticoso:Category:{e}': Categories[e], + f'staticoso:CategoryList:{e}': Categories[e], + }, InternalMacrosWraps) + ContentHTML = ReplWithEsc(ContentHTML, f'[staticoso:Category:{e}]', Categories[e]) #DEPRECATED return HTML, ContentHTML, Description, Image diff --git a/Source/Modules/Utils.py b/Source/Modules/Utils.py index 96b037c..542d204 100644 --- a/Source/Modules/Utils.py +++ b/Source/Modules/Utils.py @@ -12,13 +12,7 @@ import json import os from datetime import datetime from pathlib import Path - -ReservedPaths = ('Site.ini', 'Assets', 'Pages', 'Posts', 'Templates', 'StaticParts', 'DynamicParts') -FileExtensions = { - 'Pages': ('htm', 'html', 'markdown', 'md', 'pug', 'txt'), - 'HTML': ('.htm', '.html'), - 'Markdown': ('.markdown', '.md'), - 'Tmp': ('htm', 'markdown', 'md', 'pug', 'txt')} +from Modules.Globals import * def SureList(e): return e if type(e) == list else [e] @@ -111,11 +105,18 @@ def ReplWithEsc(Str, Find, Repl, Esc='\\'): New += Repl + e return New -def DictReplWithEsc(Str, Dict, Esc='\\'): +def DictReplWithEsc(Str:str, Dict:dict, Esc:str='\\'): for Item in Dict: Str = ReplWithEsc(Str, Item, Dict[Item], Esc='\\') return Str +def WrapDictReplWithEsc(Str:str, Dict:dict, Wraps:list=[], Esc:str='\\'): + NewDict = {} + for Item in Dict: + for Wrap in Wraps: + NewDict.update({f'{Wrap[0]}{Item}{Wrap[1]}': Dict[Item]}) + return DictReplWithEsc(Str, NewDict, Esc) + def NumsFromFileName(Path): Name = Path.split('/')[-1] Split = len(Name) diff --git a/TODO b/TODO index faaf592..cb494ec 100644 --- a/TODO +++ b/TODO @@ -1,5 +1,10 @@ +- .html input pages bug: // metadata lines not being removed from final file after parsing +- Multi-line metadata flags +- Category-based feeds +- Meta tag generator showing software version (git commit hash) +- Customize date format - Misskey for ActivityPub -- Section marking in pages (for use with external translators) +- Section marking in pages? (for use with external translators) - Choosing to use HTML or CSS styling for default internal snippets - Pages transclusion + probably drop StaticParts (would be redundant) - User macros with arguments @@ -17,8 +22,8 @@ - Custom path for Posts and Categories - Support for YAML header in Markdown - Support for HTML comment lines () in any format -- Support for rST and AsciiDoc (?) -- Posts in draft state (will not be compiled) / show unlisted status for posts with Index = False +- Support for Wikitext, rST, AsciiDoc (?) +- Posts in draft state (will not be compiled) (?) - Check if external tools (pug-cli, html2gmi) are installed - Static code syntax highlighing - Override internal HTML snippets (meta lines, page lists, redirects, ...) with config file in Templates/NAME.ini