XML feeds without libs; Check system tools; Provide software version

This commit is contained in:
2023-06-06 15:52:31 +02:00
parent 1e07f92452
commit 8e892e096d
6 changed files with 160 additions and 51 deletions

View File

@@ -179,7 +179,7 @@ def BuildMain(Args, FeedEntries):
# os.chdir(Args.InputDir) # os.chdir(Args.InputDir)
# print(f"[I] Current directory: {Args.InputDir}") # print(f"[I] Current directory: {Args.InputDir}")
SiteName = Flags['SiteName'] = OptChoose('', Args.SiteName, ReadConf(SiteConf, 'Site', 'Name')) SiteName = Flags['SiteName'] = DefConfOptChoose('SiteName', Args.SiteName, ReadConf(SiteConf, 'Site', 'Name'))
if SiteName: if SiteName:
logging.info(f"Compiling: {SiteName}") logging.info(f"Compiling: {SiteName}")
@@ -246,6 +246,12 @@ def BuildMain(Args, FeedEntries):
SiteDomain = Flags['SiteDomain'] = SiteDomain.removesuffix('/') SiteDomain = Flags['SiteDomain'] = SiteDomain.removesuffix('/')
Locale = LoadLocale(SiteLang) Locale = LoadLocale(SiteLang)
if not InSystemPath('pug'):
logging.warning("⚠ `pug` not found in system PATH. If you have any .pug pages to be compiled, the program will fail.")
if Flags['GemtextOutput'] and not InSystemPath('html2gmi'):
logging.warning("⚠ `html2gmi` not found in system PATH. Gemtext generation will be disabled.")
Flags['GemtextOutput'] = False
if DiffBuild: if DiffBuild:
logging.info("Build mode: Differential") logging.info("Build mode: Differential")
LimitFiles = GetModifiedFiles(OutDir) LimitFiles = GetModifiedFiles(OutDir)
@@ -347,13 +353,13 @@ if __name__ == '__main__':
ConfigLogging(Args.Logging) ConfigLogging(Args.Logging)
try: #try:
import lxml # import lxml
from Modules.Feed import * from Modules.Feed import *
FeedEntries = Args.FeedEntries if Args.FeedEntries else 'Default' FeedEntries = Args.FeedEntries if Args.FeedEntries else 'Default'
except: #except:
logging.warning("⚠ Can't load the XML libraries. XML Feeds Generation is Disabled. Make sure the 'lxml' library is installed.") # logging.warning("⚠ Can't load the XML libraries. XML Feeds Generation is Disabled. Make sure the 'lxml' library is installed.")
FeedEntries = 0 # FeedEntries = 0
BuildMain(Args=Args, FeedEntries=FeedEntries) BuildMain(Args=Args, FeedEntries=FeedEntries)
logging.info(f"✅ Done! ({round(time.time()-StartTime, 3)}s)") logging.info(f"✅ Done! ({round(time.time()-StartTime, 3)}s)")

View File

@@ -15,6 +15,7 @@ DefConf = {
"Threads": 0, "Threads": 0,
"DiffBuild": False, "DiffBuild": False,
"OutDir": "public", "OutDir": "public",
"SiteName": "Untitled Site",
"SiteLang": "en", "SiteLang": "en",
"SiteTemplate": "Default.html", "SiteTemplate": "Default.html",
"ActivityPubTypeFilter": "Post", "ActivityPubTypeFilter": "Post",
@@ -22,7 +23,7 @@ DefConf = {
"CategoriesUncategorized": "Uncategorized", "CategoriesUncategorized": "Uncategorized",
"FeedCategoryFilter": "Blog", "FeedCategoryFilter": "Blog",
"FeedEntries": 10, "FeedEntries": 10,
"JournalRedirect": False "JournalRedirect": False,
} }
def LoadConfFile(File): def LoadConfFile(File):

View File

@@ -29,18 +29,19 @@ CategoryPageTemplate = """\
<div><staticoso:Category:{Name}></div> <div><staticoso:Category:{Name}></div>
""" """
RedirectPageTemplate = """\ RedirectPageTemplate = f"""\
<!DOCTYPE html> <!DOCTYPE html>
<html> <html>
<head> <head>
<meta charset="UTF-8"/> <meta charset="UTF-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0"/> <meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<title>{TitlePrefix}Redirect</title> <title>{{TitlePrefix}}Redirect</title>
<link rel="canonical" href="{SiteDomain}/{DestURL}"/> <link rel="canonical" href="{{SiteDomain}}/{{DestURL}}"/>
<meta http-equiv="refresh" content="0; url='{DestURL}'"/> <meta http-equiv="refresh" content="0; url='{{DestURL}}'"/>
<meta name="generator" content="{staticosoNameVersion()}"/>
</head> </head>
<body> <body>
<p><a href="{DestURL}">{StrClick}</a> {StrRedirect}.</p> <p><a href="{{DestURL}}">{{StrClick}}</a> {{StrRedirect}}.</p>
</body> </body>
</html> </html>
""" """

View File

@@ -7,56 +7,153 @@
| Copyright (C) 2022-2023, OctoSpacc | | Copyright (C) 2022-2023, OctoSpacc |
| ================================== """ | ================================== """
# TODO: Either switch feed generation lib, or rewrite the 'lxml' module, so that no modules have to be compiled and the program is 100% portable from datetime import datetime
from Libs.feedgen.feed import FeedGenerator
from Modules.Utils import * from Modules.Utils import *
def MakeFeed(Flags:dict, Pages:list, FullSite=False): FeedGenerator = None
try:
import lxml
from Libs.feedgen.feed import FeedGenerator
except:
logging.warning("⚠ Can't load the native XML libraries. XML Feeds Generation will use the interpreted module.")
def MakeFeed(Flags:dict, Pages:list, FullSite:bool=False):
f = NameSpace(Flags) f = NameSpace(Flags)
CategoryFilter = Flags['FeedCategoryFilter'] CategoryFilter = Flags['FeedCategoryFilter']
MaxEntries = Flags['FeedEntries'] MaxEntries = Flags['FeedEntries']
Feed = FeedGenerator() if FeedGenerator:
Link = Flags['SiteDomain'] if Flags['SiteDomain'] else ' ' Feed = FeedGenerator()
Feed.id(Link) Link = Flags['SiteDomain'] if Flags['SiteDomain'] else ' '
Feed.title(Flags['SiteName'] if Flags['SiteName'] else 'Untitled Site') Feed.id(Link)
Feed.link(href=Link, rel='alternate') Feed.link(href=Link, rel='alternate')
Feed.description(Flags['SiteTagline'] if Flags['SiteTagline'] else ' ') Feed.title(Flags['SiteName'])
if Flags['SiteDomain']: Feed.description(Flags['SiteTagline'] if Flags['SiteTagline'] else ' ')
Feed.logo(Flags['SiteDomain'] + '/favicon.png') if Flags['SiteDomain']:
Feed.language(Flags['SiteLang']) Feed.logo(f'{Flags["SiteDomain"]}/favicon.png')
Feed.language(Flags['SiteLang'])
else:
FeedData = {
'Link': Flags['SiteDomain'],
'Title': Flags['SiteName'],
'Description': Flags['SiteTagline'],
'Language': Flags['SiteLang'],
'Entries': [],
}
DoPages = [] DoPages = []
for e in Pages: for e in Pages:
if FullSite or (not FullSite and MaxEntries != 0 and e[3]['Type'] == 'Post' and e[3]['Feed'] == 'True'): # No entry limit if site feed # No entry limit if site feed
if FullSite or (not FullSite and MaxEntries != 0 and e[3]['Type'] == 'Post' and e[3]['Feed'] == 'True'):
DoPages += [e] DoPages += [e]
MaxEntries -= 1 MaxEntries -= 1
DoPages.reverse() DoPages.reverse()
for File, Content, Titles, Meta, ContentHTML, SlimHTML, Description, Image in DoPages: for File, Content, Titles, Meta, ContentHTML, SlimHTML, Description, Image in DoPages:
if FullSite or (not FullSite and Meta['Type'] == 'Post' and (not CategoryFilter or (CategoryFilter and (CategoryFilter in Meta['Categories'] or CategoryFilter == '*')))): if FullSite or (not FullSite and Meta['Type'] == 'Post' and (not CategoryFilter or (CategoryFilter and (CategoryFilter in Meta['Categories'] or CategoryFilter == '*')))):
Entry = Feed.add_entry() File = f'{StripExt(File)}.html'
FileName = File.split('/')[-1] Link = Flags['SiteDomain'] + '/' + File if Flags['SiteDomain'] else File
File = f"{StripExt(File)}.html" Title = Meta['Title'] if Meta['Title'] else Titles[0].lstrip('#') if Titles else File.split('/')[-1]
Content = ReadFile(f"{Flags['OutDir']}/{File}") Title = Title.lstrip().rstrip()
Link = Flags['SiteDomain'] + '/' + File if Flags['SiteDomain'] else ' '
CreatedOn = GetFullDate(Meta['CreatedOn']) CreatedOn = GetFullDate(Meta['CreatedOn'])
EditedOn = GetFullDate(Meta['EditedOn']) EditedOn = GetFullDate(Meta['EditedOn'])
Entry.id(Link) if FullSite: # Avoid making an enormous site feed file...
Title = Meta['Title'] if Meta['Title'] else Titles[0].lstrip('#') if Titles else FileName ContentHTML = ''
Entry.title(Title.lstrip().rstrip()) if FeedGenerator:
Entry.description(Description) Entry = Feed.add_entry()
Entry.link(href=Link, rel='alternate') Entry.id(Link)
if not FullSite: # Avoid making an enormous site feed file... Entry.link(href=Link, rel='alternate')
Entry.content(ContentHTML, type='html') Entry.title(Title)
if CreatedOn: Entry.description(Description)
Entry.pubDate(CreatedOn) if ContentHTML:
EditedOn = EditedOn if EditedOn else CreatedOn if CreatedOn and not EditedOn else '1970-01-01T00:00+00:00' Entry.content(ContentHTML, type='html')
Entry.updated(EditedOn) if CreatedOn:
Entry.pubDate(CreatedOn)
EditedOn = EditedOn if EditedOn else CreatedOn if CreatedOn and not EditedOn else '1970-01-01T00:00+00:00'
Entry.updated(EditedOn)
else:
FeedData['Entries'] += [{
'Link': Link,
'Title': Title,
'Description': Description,
'Content': ContentHTML,
'PublishedOn': CreatedOn,
'UpdatedOn': EditedOn,
}]
if not os.path.exists(f"{Flags['OutDir']}/feed"): if not os.path.exists(f"{Flags['OutDir']}/feed"):
os.mkdir(f"{Flags['OutDir']}/feed") os.mkdir(f"{Flags['OutDir']}/feed")
FeedType = 'site.' if FullSite else '' FeedType = 'site.' if FullSite else ''
Feed.atom_file(f"{Flags['OutDir']}/feed/{FeedType}atom.xml", pretty=(not Flags['MinifyOutput'])) if FeedGenerator:
Feed.rss_file(f"{Flags['OutDir']}/feed/{FeedType}rss.xml", pretty=(not Flags['MinifyOutput'])) Feed.atom_file(f"{Flags['OutDir']}/feed/{FeedType}atom.xml", pretty=(not Flags['MinifyOutput']))
Feed.rss_file(f"{Flags['OutDir']}/feed/{FeedType}rss.xml", pretty=(not Flags['MinifyOutput']))
else:
Feeds = PyFeedGenerator(FeedData)
for Format in ('atom', 'rss'):
WriteFile(f"{Flags['OutDir']}/feed/{FeedType}{Format}.xml", Feeds[Format])
def PyFeedGenerator(Data:dict, Format:bool=None):
XmlEntries = {'atom': '', 'rss': ''}
XmlExtra, AtomExtra, RssExtra = '', '', ''
XmlHeader = '<?xml version="1.0" encoding="UTF-8"?>'
XmlLang = f'xml:lang="{Data["Language"]}"'
XmlTitle = f'<title>{Data["Title"]}</title>'
XmlExtra += XmlTitle
if Data['Description']:
AtomExtra += f'<subtitle>{Data["Description"]}</subtitle>'
RssExtra += f'<description>{Data["Description"]}</description>'
if Data['Link']:
IconUrl = f'{Data["Link"]}/favicon.png'
AtomExtra += f'<id>{Data["Link"]}</id><link href="{Data["Link"]}"/><logo>{IconUrl}</logo>'
RssExtra += f'<link>{Data["Link"]}</link><image>{XmlTitle}<url>{IconUrl}</url><link>{Data["Link"]}</link></image>'
Entries = Data['Entries'] if Data['Entries'] else ()
for Entry in Data['Entries']:
XmlEntryExtra, AtomEntryExtra, RssEntryExtra = '', '', ''
XmlEntryExtra += f'<title>{Entry["Title"]}</title>'
if Entry['Description']:
RssEntryExtra += f'<description>{Entry["Description"]}</description>'
if Entry['Content']:
AtomEntryExtra += f'<content type="html">{Entry["Content"]}</content>'
RssEntryExtra += f'<content:encoded>{Entry["Content"]}</content:encoded>'
if Entry['PublishedOn']:
AtomEntryExtra += f'<published>{Entry["PublishedOn"]}</published>'
RssEntryExtra += f'<pubDate>{Entry["PublishedOn"]}</pubDate>'
if Entry['UpdatedOn']:
AtomEntryExtra += f'<updated>{Entry["UpdatedOn"]}</updated>'
XmlEntries['atom'] += f'''
<entry>
<id>{Entry['Link']}</id>
<link href="{Entry['Link']}"/>
{XmlEntryExtra}
{AtomEntryExtra}
</entry>
'''
XmlEntries['rss'] += f'''
<item>
<guid>{Entry['Link']}</guid>
<link>{Entry['Link']}</link>
{XmlEntryExtra}
{RssEntryExtra}
</item>
'''
Feeds = {'atom': f'''{XmlHeader}
<feed xmlns="http://www.w3.org/2005/Atom" {XmlLang}>
{XmlExtra}
{AtomExtra}
<updated>{datetime.now()}</updated>
<generator uri="https://gitlab.com/octtspacc/staticoso" version="{staticosoNameVersion().split(" ")[1]}">staticoso</generator>
{XmlEntries['atom']}
</feed>''', 'rss': f'''{XmlHeader}
<rss xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/" version="2.0" {XmlLang}>
<channel>
{XmlExtra}
{RssExtra}
<language>{Data["Language"]}</language>
<lastBuildDate>{datetime.now()}</lastBuildDate>
<generator>{staticosoNameVersion()}</generator>
{XmlEntries['rss']}
</channel>
</rss>'''}
if Format:
Feeds = Feeds[Format.lower()]
return Feeds

View File

@@ -7,8 +7,7 @@
| Copyright (C) 2022-2023, OctoSpacc | | Copyright (C) 2022-2023, OctoSpacc |
| ================================== """ | ================================== """
import json import json, os, subprocess
import os
from datetime import datetime from datetime import datetime
from multiprocessing import Pool, cpu_count from multiprocessing import Pool, cpu_count
from pathlib import Path from pathlib import Path
@@ -22,6 +21,14 @@ def SureList(e):
def staticosoBaseDir(): def staticosoBaseDir():
return f"{os.path.dirname(os.path.abspath(__file__))}/../../" return f"{os.path.dirname(os.path.abspath(__file__))}/../../"
def staticosoNameVersion():
Version = subprocess.run(('sh', '-c', 'git log | head -n 1'), stdout=subprocess.PIPE).stdout.decode().strip()
Version = ' v.' + Version.split(' ')[1][:8] if Version else ''
return f'staticoso{Version}'
def InSystemPath(Exec:str):
return subprocess.run(('sh', '-c', f'which {Exec}'), stdout=subprocess.PIPE).stdout.decode().strip()
def ReadFile(p:str, m:str='r'): def ReadFile(p:str, m:str='r'):
try: try:
with open(p, m) as f: with open(p, m) as f:

View File

@@ -2,7 +2,6 @@
- .html input pages bug: // metadata lines not being removed from final file after parsing - .html input pages bug: // metadata lines not being removed from final file after parsing
- Multi-line metadata flags - Multi-line metadata flags
- Category-based feeds - Category-based feeds
- Meta tag generator showing software version (git commit hash)
- Customize date format - Customize date format
- Misskey for ActivityPub - Misskey for ActivityPub
- Section marking in pages? (for use with external translators) - Section marking in pages? (for use with external translators)
@@ -25,7 +24,6 @@
- Support for HTML comment lines (<!-- -->) in any format - Support for HTML comment lines (<!-- -->) in any format
- Support for Wikitext, rST, AsciiDoc (?) - Support for Wikitext, rST, AsciiDoc (?)
- Posts in draft state (will not be compiled) (?) - Posts in draft state (will not be compiled) (?)
- Check if external tools (pug-cli, html2gmi) are installed
- Static code syntax highlighing - Static code syntax highlighing
- Override internal HTML snippets (meta lines, page lists, redirects, ...) with config file in Templates/NAME.ini - Override internal HTML snippets (meta lines, page lists, redirects, ...) with config file in Templates/NAME.ini
- Specify input folder(s) - Specify input folder(s)
@@ -39,7 +37,6 @@
- Change FolderRoots arg name to CustomPaths - Change FolderRoots arg name to CustomPaths
- Accept Macros as CLI arguments + Deprecate FolderRoots (Macros make it redundant) - Accept Macros as CLI arguments + Deprecate FolderRoots (Macros make it redundant)
- Fix ordering menu in Site.ini (not working for inner pages) - Fix ordering menu in Site.ini (not working for inner pages)
- Feed generation optionally without native libraries
- JSON feeds - JSON feeds
- Full XML sitemap - Full XML sitemap
- SCSS support - SCSS support