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("") 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("$(opt!.value)") found_title = yes continue opt = Option.parse(line, link) if not found_link and opt out.write("$(opt!.value)") found_link = yes continue opt = Option.parse(line, description) if not found_description and opt out.write("$(opt!.value)") 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("fmj generated feed") if not found_link out.write("http://example.com") if not found_description out.write("an rss feed generated by fmj") out.write("fmj") func write_feed_footer(out : Writable) out.write("") 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("") for line in lines opt : Option? = none opt = Option.parse(line, title) if not found_title and opt out.write("$(opt!.value)") found_title = yes continue opt = Option.parse(line, link) if not found_link and opt out.write("$(opt!.value)") found_link = yes continue opt = Option.parse(line, description) if not found_description and opt out.write("$(opt!.value)") found_description = yes continue opt = Option.parse(line, author) if not found_author and opt out.write("$(opt!.value)") found_author = yes continue opt = Option.parse(line, pub_date) if not found_pub_date and opt out.write("$(opt!.value)") found_pub_date = yes continue opt = Option.parse(line, category) if opt out.write("$(opt.value)") continue if not found_author and default_author_value out.write("$(default_author_value)") out.write("") 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()