initial commit
This commit is contained in:
commit
e43f252e44
177
fmj.tm
Normal file
177
fmj.tm
Normal file
@ -0,0 +1,177 @@
|
|||||||
|
enum Writable(
|
||||||
|
Stdout,
|
||||||
|
File(path:Path),
|
||||||
|
)
|
||||||
|
func write(self:Writable, text:Text, newline:Bool=no)
|
||||||
|
when self is Stdout
|
||||||
|
say(text, newline)
|
||||||
|
return
|
||||||
|
is File(path)
|
||||||
|
_ := path.append(text)
|
||||||
|
if newline
|
||||||
|
_ := path.append("\n")
|
||||||
|
return
|
||||||
|
|
||||||
|
struct Option(option: Text, value: Text)
|
||||||
|
func parse(line: Text, expected: Text -> Option?)
|
||||||
|
val : Text = ""
|
||||||
|
if not line.starts_with(expected, &val)
|
||||||
|
return none
|
||||||
|
|
||||||
|
if val == ""
|
||||||
|
return none
|
||||||
|
|
||||||
|
foundequals : Bool = no
|
||||||
|
cursor := 1
|
||||||
|
while yes
|
||||||
|
if val[cursor] == " "
|
||||||
|
cursor += 1
|
||||||
|
continue
|
||||||
|
|
||||||
|
if not foundequals and val[cursor] == "="
|
||||||
|
foundequals = yes
|
||||||
|
cursor += 1
|
||||||
|
continue
|
||||||
|
|
||||||
|
if foundequals and cursor <= val.length
|
||||||
|
return Option(expected, val.slice(cursor))
|
||||||
|
|
||||||
|
return none
|
||||||
|
return none
|
||||||
|
|
||||||
|
fmjconfpath := ".fmjconf"
|
||||||
|
fmj_extension := "fmj"
|
||||||
|
|
||||||
|
description := "description"
|
||||||
|
title := "title"
|
||||||
|
link := "link"
|
||||||
|
default_author := "default_author"
|
||||||
|
author := "author"
|
||||||
|
category := "category"
|
||||||
|
pub_date := "pub_date"
|
||||||
|
|
||||||
|
default_author_value : Text? = none
|
||||||
|
|
||||||
|
func write_feed_header(out: Writable, config: Text?)
|
||||||
|
out.write("<?xml version=\"1.0\" encoding=\"UTF-8\" ?><rss version=\"2.0\"><channel>")
|
||||||
|
|
||||||
|
found_title : Bool = no
|
||||||
|
found_link : Bool = no
|
||||||
|
found_description : Bool = no
|
||||||
|
found_default_author : Bool = no
|
||||||
|
if config
|
||||||
|
for line in config.lines()
|
||||||
|
opt : Option? = none
|
||||||
|
opt = Option.parse(line, title)
|
||||||
|
if not found_title and opt
|
||||||
|
out.write("<title>$(opt!.value)</title>")
|
||||||
|
found_title = yes
|
||||||
|
continue
|
||||||
|
opt = Option.parse(line, link)
|
||||||
|
if not found_link and opt
|
||||||
|
out.write("<link>$(opt!.value)</link>")
|
||||||
|
found_link = yes
|
||||||
|
continue
|
||||||
|
opt = Option.parse(line, description)
|
||||||
|
if not found_description and opt
|
||||||
|
out.write("<description>$(opt!.value)</description>")
|
||||||
|
found_description = yes
|
||||||
|
continue
|
||||||
|
opt = Option.parse(line, default_author)
|
||||||
|
if not found_default_author and opt
|
||||||
|
default_author_value = opt!.value
|
||||||
|
found_default_author = yes
|
||||||
|
continue
|
||||||
|
|
||||||
|
if not found_title
|
||||||
|
out.write("<title>fmj generated feed</title>")
|
||||||
|
|
||||||
|
if not found_link
|
||||||
|
out.write("<link>http://example.com</link>")
|
||||||
|
|
||||||
|
if not found_description
|
||||||
|
out.write("<description>an rss feed generated by fmj</description>")
|
||||||
|
|
||||||
|
out.write("<generator>fmj</generator>")
|
||||||
|
|
||||||
|
func write_feed_footer(out : Writable)
|
||||||
|
out.write("</channel></rss>")
|
||||||
|
|
||||||
|
func write_feed_item(out : Writable, path : Path)
|
||||||
|
if not (path.extension() == fmj_extension)
|
||||||
|
return
|
||||||
|
lines := path.read()!.lines()
|
||||||
|
has_requirements : Bool = no
|
||||||
|
for line in lines
|
||||||
|
if Option.parse(line, title) or Option.parse(line, description)
|
||||||
|
has_requirements = yes
|
||||||
|
break
|
||||||
|
if not has_requirements
|
||||||
|
return
|
||||||
|
|
||||||
|
found_title : Bool = no
|
||||||
|
found_description : Bool = no
|
||||||
|
found_link : Bool = no
|
||||||
|
found_pub_date : Bool = no
|
||||||
|
found_author : Bool = no
|
||||||
|
|
||||||
|
out.write("<item>")
|
||||||
|
for line in lines
|
||||||
|
opt : Option? = none
|
||||||
|
opt = Option.parse(line, title)
|
||||||
|
if not found_title and opt
|
||||||
|
out.write("<title>$(opt!.value)</title>")
|
||||||
|
found_title = yes
|
||||||
|
continue
|
||||||
|
opt = Option.parse(line, link)
|
||||||
|
if not found_link and opt
|
||||||
|
out.write("<link>$(opt!.value)</link>")
|
||||||
|
found_link = yes
|
||||||
|
continue
|
||||||
|
opt = Option.parse(line, description)
|
||||||
|
if not found_description and opt
|
||||||
|
out.write("<description>$(opt!.value)</description>")
|
||||||
|
found_description = yes
|
||||||
|
continue
|
||||||
|
opt = Option.parse(line, author)
|
||||||
|
if not found_author and opt
|
||||||
|
out.write("<author>$(opt!.value)</description>")
|
||||||
|
found_author = yes
|
||||||
|
continue
|
||||||
|
opt = Option.parse(line, pub_date)
|
||||||
|
if not found_pub_date and opt
|
||||||
|
out.write("<pubDate>$(opt!.value)</pubDate>")
|
||||||
|
found_pub_date = yes
|
||||||
|
continue
|
||||||
|
opt = Option.parse(line, category)
|
||||||
|
if opt
|
||||||
|
out.write("<category>$(opt.value)</category>")
|
||||||
|
continue
|
||||||
|
if not found_author and default_author_value
|
||||||
|
out.write("<author>$(default_author_value)</author>")
|
||||||
|
out.write("</item>")
|
||||||
|
|
||||||
|
func main(dir: Path, output: Text = "rss.xml")
|
||||||
|
usingStdout : Bool = no
|
||||||
|
outfile : Path? = none
|
||||||
|
if output == "-"
|
||||||
|
usingStdout = yes
|
||||||
|
else
|
||||||
|
outfile = Path.from_text(output)
|
||||||
|
|
||||||
|
if not usingStdout and outfile!.exists()
|
||||||
|
_ := outfile!.remove()
|
||||||
|
|
||||||
|
out : Writable = if usingStdout
|
||||||
|
Writable.Stdout
|
||||||
|
else
|
||||||
|
Writable.File(outfile!)
|
||||||
|
|
||||||
|
config := dir.child(fmjconfpath).read()
|
||||||
|
|
||||||
|
out.write_feed_header(config)
|
||||||
|
|
||||||
|
for path in dir.children()
|
||||||
|
out.write_feed_item(path)
|
||||||
|
|
||||||
|
out.write_feed_footer()
|
||||||
24
readme.md
Normal file
24
readme.md
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
# fmj
|
||||||
|
fmj is an rss feed generator. it takes a directory of config files that describe the posts and outputs xml.
|
||||||
|
|
||||||
|
the file format is more concise than handwriting xml:
|
||||||
|
```fmj
|
||||||
|
title = s/discord/???
|
||||||
|
description = on replacing discord
|
||||||
|
link = https://bear.o7moon.dev/sdiscord/
|
||||||
|
|
||||||
|
anything that isnt one of the defined options is a comment
|
||||||
|
```
|
||||||
|
|
||||||
|
## usage examples:
|
||||||
|
- `fmj tests/examples \\-` will output to stdout
|
||||||
|
- `fmj tests/noconf filename.xml` will output to `filename.xml`
|
||||||
|
- `fmj tests/defaultauthor` will output to `rss.xml`
|
||||||
|
## building / platform support:
|
||||||
|
this repo is a port of the [previous c implementation](https://git.tilde.town/moss/fmj) to [tomo](https://github.com/bruce-hill/tomo), which is a relatively recent language and has some rough edges (the requirement to write \\- instead of - for stdout comes from a conflict with the language's built-in argument parsing) but ive found it to be nicer than c for this small program and i used it as a chance to both learn a new language and make a small improvement to the c implementation (this one handles utf-8! due to tomo's Text type also handling it).
|
||||||
|
|
||||||
|
`fmj.tm` can be built with the tomo compiler like this, if you have it installed:
|
||||||
|
`````
|
||||||
|
```
|
||||||
|
tomo -e fmj.tm
|
||||||
|
```
|
||||||
3
tests/defaultauthor/.fmjconf
Normal file
3
tests/defaultauthor/.fmjconf
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
title = default author test case
|
||||||
|
description = contains a post with an author option and one without, to test the default author option from the .fmjconf file
|
||||||
|
default_author = alice
|
||||||
2
tests/defaultauthor/alices_post.fmj
Normal file
2
tests/defaultauthor/alices_post.fmj
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
title = alice's post
|
||||||
|
description = this post implicitly has alice as the author due to the .fmjconf
|
||||||
3
tests/defaultauthor/bobs_post.fmj
Normal file
3
tests/defaultauthor/bobs_post.fmj
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
title = bob's post
|
||||||
|
author = bob
|
||||||
|
description = bob wrote this post.
|
||||||
2
tests/example/.fmjconf
Normal file
2
tests/example/.fmjconf
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
title = example rss feed !!!!!!!!!
|
||||||
|
description = this is an example generated by fmj as a test case
|
||||||
3
tests/example/test1.fmj
Normal file
3
tests/example/test1.fmj
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
title = TEST TITLE !!!!!!
|
||||||
|
|
||||||
|
this is a comment
|
||||||
1
tests/noconf/test1.fmj
Normal file
1
tests/noconf/test1.fmj
Normal file
@ -0,0 +1 @@
|
|||||||
|
title = this is a test where the config file doesnt exist
|
||||||
Loading…
x
Reference in New Issue
Block a user