mirror of
https://gitlab.com/octtspacc/staticoso
synced 2025-03-12 01:00:10 +01:00
XML feeds without libs; Check system tools; Provide software version
This commit is contained in:
parent
1e07f92452
commit
8e892e096d
@ -179,7 +179,7 @@ def BuildMain(Args, FeedEntries):
|
||||
# os.chdir(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:
|
||||
logging.info(f"Compiling: {SiteName}")
|
||||
|
||||
@ -246,6 +246,12 @@ def BuildMain(Args, FeedEntries):
|
||||
SiteDomain = Flags['SiteDomain'] = SiteDomain.removesuffix('/')
|
||||
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:
|
||||
logging.info("Build mode: Differential")
|
||||
LimitFiles = GetModifiedFiles(OutDir)
|
||||
@ -347,13 +353,13 @@ if __name__ == '__main__':
|
||||
|
||||
ConfigLogging(Args.Logging)
|
||||
|
||||
try:
|
||||
import lxml
|
||||
from Modules.Feed import *
|
||||
FeedEntries = Args.FeedEntries if Args.FeedEntries else 'Default'
|
||||
except:
|
||||
logging.warning("⚠ Can't load the XML libraries. XML Feeds Generation is Disabled. Make sure the 'lxml' library is installed.")
|
||||
FeedEntries = 0
|
||||
#try:
|
||||
# import lxml
|
||||
from Modules.Feed import *
|
||||
FeedEntries = Args.FeedEntries if Args.FeedEntries else 'Default'
|
||||
#except:
|
||||
# logging.warning("⚠ Can't load the XML libraries. XML Feeds Generation is Disabled. Make sure the 'lxml' library is installed.")
|
||||
# FeedEntries = 0
|
||||
|
||||
BuildMain(Args=Args, FeedEntries=FeedEntries)
|
||||
logging.info(f"✅ Done! ({round(time.time()-StartTime, 3)}s)")
|
||||
|
@ -15,6 +15,7 @@ DefConf = {
|
||||
"Threads": 0,
|
||||
"DiffBuild": False,
|
||||
"OutDir": "public",
|
||||
"SiteName": "Untitled Site",
|
||||
"SiteLang": "en",
|
||||
"SiteTemplate": "Default.html",
|
||||
"ActivityPubTypeFilter": "Post",
|
||||
@ -22,7 +23,7 @@ DefConf = {
|
||||
"CategoriesUncategorized": "Uncategorized",
|
||||
"FeedCategoryFilter": "Blog",
|
||||
"FeedEntries": 10,
|
||||
"JournalRedirect": False
|
||||
"JournalRedirect": False,
|
||||
}
|
||||
|
||||
def LoadConfFile(File):
|
||||
|
@ -29,18 +29,19 @@ CategoryPageTemplate = """\
|
||||
|
||||
<div><staticoso:Category:{Name}></div>
|
||||
"""
|
||||
RedirectPageTemplate = """\
|
||||
RedirectPageTemplate = f"""\
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8"/>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
|
||||
<title>{TitlePrefix}Redirect</title>
|
||||
<link rel="canonical" href="{SiteDomain}/{DestURL}"/>
|
||||
<meta http-equiv="refresh" content="0; url='{DestURL}'"/>
|
||||
<title>{{TitlePrefix}}Redirect</title>
|
||||
<link rel="canonical" href="{{SiteDomain}}/{{DestURL}}"/>
|
||||
<meta http-equiv="refresh" content="0; url='{{DestURL}}'"/>
|
||||
<meta name="generator" content="{staticosoNameVersion()}"/>
|
||||
</head>
|
||||
<body>
|
||||
<p><a href="{DestURL}">{StrClick}</a> {StrRedirect}.</p>
|
||||
<p><a href="{{DestURL}}">{{StrClick}}</a> {{StrRedirect}}.</p>
|
||||
</body>
|
||||
</html>
|
||||
"""
|
||||
|
@ -7,56 +7,153 @@
|
||||
| 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 Libs.feedgen.feed import FeedGenerator
|
||||
from datetime import datetime
|
||||
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)
|
||||
CategoryFilter = Flags['FeedCategoryFilter']
|
||||
MaxEntries = Flags['FeedEntries']
|
||||
|
||||
Feed = FeedGenerator()
|
||||
Link = Flags['SiteDomain'] if Flags['SiteDomain'] else ' '
|
||||
Feed.id(Link)
|
||||
Feed.title(Flags['SiteName'] if Flags['SiteName'] else 'Untitled Site')
|
||||
Feed.link(href=Link, rel='alternate')
|
||||
Feed.description(Flags['SiteTagline'] if Flags['SiteTagline'] else ' ')
|
||||
if Flags['SiteDomain']:
|
||||
Feed.logo(Flags['SiteDomain'] + '/favicon.png')
|
||||
Feed.language(Flags['SiteLang'])
|
||||
if FeedGenerator:
|
||||
Feed = FeedGenerator()
|
||||
Link = Flags['SiteDomain'] if Flags['SiteDomain'] else ' '
|
||||
Feed.id(Link)
|
||||
Feed.link(href=Link, rel='alternate')
|
||||
Feed.title(Flags['SiteName'])
|
||||
Feed.description(Flags['SiteTagline'] if Flags['SiteTagline'] else ' ')
|
||||
if Flags['SiteDomain']:
|
||||
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 = []
|
||||
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]
|
||||
MaxEntries -= 1
|
||||
DoPages.reverse()
|
||||
|
||||
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 == '*')))):
|
||||
Entry = Feed.add_entry()
|
||||
FileName = File.split('/')[-1]
|
||||
File = f"{StripExt(File)}.html"
|
||||
Content = ReadFile(f"{Flags['OutDir']}/{File}")
|
||||
Link = Flags['SiteDomain'] + '/' + File if Flags['SiteDomain'] else ' '
|
||||
File = f'{StripExt(File)}.html'
|
||||
Link = Flags['SiteDomain'] + '/' + File if Flags['SiteDomain'] else File
|
||||
Title = Meta['Title'] if Meta['Title'] else Titles[0].lstrip('#') if Titles else File.split('/')[-1]
|
||||
Title = Title.lstrip().rstrip()
|
||||
CreatedOn = GetFullDate(Meta['CreatedOn'])
|
||||
EditedOn = GetFullDate(Meta['EditedOn'])
|
||||
Entry.id(Link)
|
||||
Title = Meta['Title'] if Meta['Title'] else Titles[0].lstrip('#') if Titles else FileName
|
||||
Entry.title(Title.lstrip().rstrip())
|
||||
Entry.description(Description)
|
||||
Entry.link(href=Link, rel='alternate')
|
||||
if not FullSite: # Avoid making an enormous site feed file...
|
||||
Entry.content(ContentHTML, type='html')
|
||||
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)
|
||||
if FullSite: # Avoid making an enormous site feed file...
|
||||
ContentHTML = ''
|
||||
if FeedGenerator:
|
||||
Entry = Feed.add_entry()
|
||||
Entry.id(Link)
|
||||
Entry.link(href=Link, rel='alternate')
|
||||
Entry.title(Title)
|
||||
Entry.description(Description)
|
||||
if ContentHTML:
|
||||
Entry.content(ContentHTML, type='html')
|
||||
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"):
|
||||
os.mkdir(f"{Flags['OutDir']}/feed")
|
||||
FeedType = 'site.' if FullSite else ''
|
||||
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']))
|
||||
if FeedGenerator:
|
||||
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
|
||||
|
@ -7,8 +7,7 @@
|
||||
| Copyright (C) 2022-2023, OctoSpacc |
|
||||
| ================================== """
|
||||
|
||||
import json
|
||||
import os
|
||||
import json, os, subprocess
|
||||
from datetime import datetime
|
||||
from multiprocessing import Pool, cpu_count
|
||||
from pathlib import Path
|
||||
@ -22,6 +21,14 @@ def SureList(e):
|
||||
def staticosoBaseDir():
|
||||
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'):
|
||||
try:
|
||||
with open(p, m) as f:
|
||||
|
3
App/TODO
3
App/TODO
@ -2,7 +2,6 @@
|
||||
- .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)
|
||||
@ -25,7 +24,6 @@
|
||||
- Support for HTML comment lines (<!-- -->) in any format
|
||||
- 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
|
||||
- Specify input folder(s)
|
||||
@ -39,7 +37,6 @@
|
||||
- Change FolderRoots arg name to CustomPaths
|
||||
- Accept Macros as CLI arguments + Deprecate FolderRoots (Macros make it redundant)
|
||||
- Fix ordering menu in Site.ini (not working for inner pages)
|
||||
- Feed generation optionally without native libraries
|
||||
- JSON feeds
|
||||
- Full XML sitemap
|
||||
- SCSS support
|
||||
|
Loading…
x
Reference in New Issue
Block a user