Compare commits
10 Commits
7e490453b7
...
10888bc8b6
Author | SHA1 | Date | |
---|---|---|---|
![]() |
10888bc8b6 | ||
![]() |
57304917a2 | ||
![]() |
4a93a47e50 | ||
![]() |
97e4d08503 | ||
![]() |
6e97efbf0c | ||
![]() |
583f725b21 | ||
![]() |
28ca660dab | ||
![]() |
521112d3d6 | ||
![]() |
6ad15ec7eb | ||
![]() |
2f038028dc |
1
.gitignore
vendored
1
.gitignore
vendored
@ -9,3 +9,4 @@
|
||||
_build
|
||||
*.htm
|
||||
index.html
|
||||
/.svn
|
||||
|
3
.gitmodules
vendored
3
.gitmodules
vendored
@ -1,3 +0,0 @@
|
||||
[submodule "text-parse-ml"]
|
||||
path = text-parse
|
||||
url = https://git.chaotic.ninja/yakumo.izuru/text-parse-ml
|
15
Makefile
15
Makefile
@ -1,6 +1,6 @@
|
||||
OS=`uname -s`
|
||||
MACHINE=`uname -m`
|
||||
DATE=`date -r _build/default/cli/txt.exe +%Y%m%d`
|
||||
DATE=`date -r _build/default/cmd/txt/txt.exe +%Y%m%d`
|
||||
COMMIT=`git rev-parse --short HEAD`
|
||||
PREFIX=/usr/local
|
||||
|
||||
@ -10,19 +10,22 @@ LD=cc
|
||||
all:
|
||||
@dune build
|
||||
deps:
|
||||
@opam install dune ocurl cmdliner=1.0.4 msgpck
|
||||
cli:
|
||||
@dune build cli/txt.exe
|
||||
@opam install dune ocurl cmdliner msgpck
|
||||
txt:
|
||||
@dune build cmd/txt/txt.exe
|
||||
clean:
|
||||
@dune clean
|
||||
dist:
|
||||
@dune build
|
||||
@cp _build/default/cli/txt.exe txt.exe
|
||||
@cp _build/default/cmd/txt/txt.exe txt.exe
|
||||
@strip txt.exe
|
||||
@tar czvf "kosuzu-${OS}-${MACHINE}-${DATE}-${COMMIT}" txt.exe readme.txt
|
||||
@rm txt.exe
|
||||
|
||||
txt_init:
|
||||
@dune build cmd/txt_init/txt_init.exe
|
||||
install:
|
||||
@dune install --prefix ${PREFIX}
|
||||
uninstall:
|
||||
@dune uninstall --prefix ${PREFIX}
|
||||
.PHONY: cli
|
||||
.PHONY: txt txt_init
|
||||
|
17
README.md
17
README.md
@ -1,16 +1,5 @@
|
||||
# Logarion
|
||||
Text archival and exchange.
|
||||
# Kosuzu
|
||||
Text archival and exchange, named after [Kosuzu Motoori](https://en.touhouwiki.net/wiki/Kosuzu_Motoori) from [Forbidden Scrollery](https://en.touhouwiki.net/wiki/Forbidden_Scrollery).
|
||||
|
||||
## Contact
|
||||
* [Mailing list](https://lists.tildeverse.org/postorius/lists/logarion.lists.tildeverse.org/)
|
||||
|
||||
## References
|
||||
* [Building from source](https://logarion.chaotic.ninja/9egbae.htm)
|
||||
* [Creating texts & publishing on the net](https://logarion.chaotic.ninja/hvhhwf.htm)
|
||||
* [Exploring & pulling texts from Logarion repositories](https://logarion.chaotic.ninja/3sqd84.htm)
|
||||
* [Header format](https://logarion.chaotic.ninja/d41e68.htm)
|
||||
* [Txt uniform resource names](https://logarion.chaotic.ninja/h1a9tg.htm)
|
||||
|
||||
## Maintainers
|
||||
* orbifx (original author, former maintainer)
|
||||
* Izuru Yakumo (contributor, current maintainer)
|
||||
* [Mailing list](mailto:kosuzu-dev@chaotic.ninja)
|
||||
|
6
cli/dune
6
cli/dune
@ -1,6 +0,0 @@
|
||||
(executable
|
||||
(name txt)
|
||||
(public_name txt)
|
||||
(modules txt authors convert conversion edit file index last listing
|
||||
new topics html atom gemini peers pull read recent unfile)
|
||||
(libraries text_parse.converter text_parse.parsers logarion msgpck curl str cmdliner ocmd))
|
24
cli/read.ml
24
cli/read.ml
@ -1,24 +0,0 @@
|
||||
open Logarion
|
||||
|
||||
open Cmdliner
|
||||
|
||||
|
||||
let id = Arg.(value & pos 0 string "" & info [] ~docv:"text ID")
|
||||
let recurse = Arg.(value & flag & info ["R"] ~doc:"recurse, include subdirs")
|
||||
let reverse = Arg.(value & flag & info ["r"] ~doc:"reverse order")
|
||||
let time = Arg.(value & flag & info ["t"] ~doc:"sort by time, newest first")
|
||||
let number = Arg.(value & opt (some int) None & info ["n"] ~docv:"number" ~doc:"number of entries to list")
|
||||
let authed = Arg.(value & opt (some string) None & info ["authored"] ~docv:"comma-separated names" ~doc:"texts by authors")
|
||||
let topics = Arg.(value & opt (some string) None & info ["topics"] ~docv:"comma-separated topics" ~doc:"texts with topics")
|
||||
|
||||
let read_t = Term.(const (Archive.apply_sys_util "PAGER" "less") $ recurse $ time $ reverse $ number $ authed $ topics $ id)
|
||||
|
||||
let cmd =
|
||||
let doc = "Read a text" in
|
||||
let man = [
|
||||
`S Manpage.s_description;
|
||||
`P "Deprecated. This subcommand will be removed in a future release of Logarion";
|
||||
`P "This invokes the PAGER utility ('less' if unset) on an article of the archive" ]
|
||||
in
|
||||
let info = Cmd.info "read" ~version:"%%VERSION%%" ~doc ~man in
|
||||
Cmd.v info read_t
|
31
cli/txt.ml
31
cli/txt.ml
@ -1,31 +0,0 @@
|
||||
open Cmdliner
|
||||
|
||||
let subs = [
|
||||
Authors.cmd; (* Done *)
|
||||
Convert.cmd; (* Done *)
|
||||
Edit.cmd; (* Done *)
|
||||
File.cmd; (* Done *)
|
||||
Index.cmd; (* Done *)
|
||||
Last.cmd; (* Done *)
|
||||
Listing.cmd; (* Done *)
|
||||
New.cmd; (* Done *)
|
||||
Peers.cmd; (* Done *)
|
||||
Pull.cmd; (* Done *)
|
||||
Read.cmd; (* Done *)
|
||||
Recent.cmd; (* Done *)
|
||||
Topics.cmd; (* Done *)
|
||||
Unfile.cmd; (* Done *)
|
||||
]
|
||||
|
||||
let default_cmd = Term.(ret (const (`Help (`Pager, None))))
|
||||
|
||||
let txt =
|
||||
let doc = "Discover, collect and exchange texts" in
|
||||
let man = [
|
||||
`S "CONTACT";
|
||||
`P "<mailto:logarion-dev@chaotic.ninja>"; ]
|
||||
in
|
||||
Cmd.group (Cmd.info "txt" ~version:"%%VERSION%%" ~doc ~man) ~default:default_cmd subs
|
||||
|
||||
let main () = exit (Cmd.eval txt)
|
||||
let () = main ()
|
@ -11,23 +11,23 @@ let opt_element tag_name content =
|
||||
|
||||
module P = Parsers.Plain_text.Make (Converter.Html)
|
||||
|
||||
let id txt = "<id>urn:txtid:" ^ Logarion.(txt.Text.id) ^ "</id>\n"
|
||||
let title text = "<title>" ^ esc text.Logarion.Text.title ^ "</title>\n"
|
||||
let id txt = "<id>urn:txtid:" ^ Kosuzu.(txt.Text.id) ^ "</id>\n"
|
||||
let title text = "<title>" ^ esc text.Kosuzu.Text.title ^ "</title>\n"
|
||||
|
||||
let authors text =
|
||||
let u acc addr = acc ^ element "uri" addr in
|
||||
let open Logarion in
|
||||
let open Kosuzu in
|
||||
let fn txt a =
|
||||
a ^ "<author>" ^ (opt_element "name" @@ esc txt.Person.name)
|
||||
^ (List.fold_left u "" txt.Person.addresses)
|
||||
^ "</author>\n" in
|
||||
Person.Set.fold fn text.Text.authors ""
|
||||
|
||||
let updated txt = let open Logarion in
|
||||
let updated txt = let open Kosuzu in
|
||||
"<updated>"^ Date.(txt.Text.date |> listing |> rfc_string) ^"</updated>\n"
|
||||
|
||||
let htm_entry base_url text =
|
||||
let open Logarion in
|
||||
let open Kosuzu in
|
||||
let u = Text.short_id text in
|
||||
"<entry>\n<link rel=\"alternate\" href=\"" ^ base_url ^ "/" ^ u ^ ".htm\" />\n"
|
||||
^ title text ^ id text ^ updated text ^ authors text
|
||||
@ -36,7 +36,7 @@ let htm_entry base_url text =
|
||||
^ "</entry>\n"
|
||||
|
||||
let gmi_entry base_url text =
|
||||
let open Logarion in
|
||||
let open Kosuzu in
|
||||
let u = Text.short_id text in
|
||||
"<entry>\n<link rel=\"alternate\" href=\"" ^ base_url ^ "/" ^ u ^ ".gmi\" />\n"
|
||||
^ title text ^ id text ^ updated text ^ authors text
|
||||
@ -45,14 +45,14 @@ let gmi_entry base_url text =
|
||||
^ "</entry>\n"
|
||||
|
||||
let base_url kv protocol = try
|
||||
let locs = Logarion.Store.KV.find "Locations" kv in
|
||||
let locs = Kosuzu.Store.KV.find "Locations" kv in
|
||||
let _i = Str.(search_forward (regexp (protocol ^ "://[^;]*")) locs 0) in
|
||||
Str.(matched_string locs)
|
||||
with Not_found -> Printf.eprintf "Missing location for %s, add it to txt.conf\n" protocol; ""
|
||||
|
||||
let indices alternate_type c =
|
||||
let file name = Logarion.File_store.file (Filename.concat c.Conversion.dir name) in
|
||||
let title = try Logarion.Store.KV.find "Title" c.Conversion.kv with Not_found -> "" in
|
||||
let file name = Kosuzu.File_store.file (Filename.concat c.Conversion.dir name) in
|
||||
let title = try Kosuzu.Store.KV.find "Title" c.Conversion.kv with Not_found -> "" in
|
||||
let entry, fname, protocol_regexp = match alternate_type with
|
||||
| "text/gemini" -> gmi_entry, "gmi.atom", "gemini"
|
||||
| "text/html" | _ -> htm_entry, "feed.atom", "https?"
|
||||
@ -64,7 +64,7 @@ let indices alternate_type c =
|
||||
^ title ^ {|</title><link rel="alternate" type="|} ^ alternate_type ^ {|" href="|}
|
||||
^ base_url ^ {|/" /><link rel="self" type="application/atom+xml" href="|}
|
||||
^ self ^ {|" /><id>urn:txtid:|} ^ c.Conversion.id ^ "</id><updated>"
|
||||
^ Logarion.Date.now () ^ "</updated>\n"
|
||||
^ Kosuzu.Date.now () ^ "</updated>\n"
|
||||
^ List.fold_left (fun acc t -> acc ^ entry base_url t) "" c.texts
|
||||
^ "</feed>"
|
||||
|
@ -1,4 +1,4 @@
|
||||
open Logarion
|
||||
open Kosuzu
|
||||
let authors r topics_opt =
|
||||
let predicates = Archive.(predicate topics topics_opt) in
|
||||
let predicate text = List.fold_left (fun a e -> a && e text) true predicates in
|
||||
@ -18,5 +18,5 @@ let cmd =
|
||||
`S Manpage.s_description;
|
||||
`P "List author names" ]
|
||||
in
|
||||
let info = Cmd.info "authors" ~version:"%%VERSION%%" ~doc ~man in
|
||||
let info = Cmd.info "authors" ~doc ~man in
|
||||
Cmd.v info authors_t
|
@ -1,4 +1,4 @@
|
||||
open Logarion
|
||||
open Kosuzu
|
||||
|
||||
module Rel = struct
|
||||
|
||||
@ -60,7 +60,7 @@ type t = {
|
||||
|
||||
type fn_t = {
|
||||
ext: string;
|
||||
page: (t -> Logarion.Text.t -> string) option;
|
||||
page: (t -> Kosuzu.Text.t -> string) option;
|
||||
indices: (t -> unit) option;
|
||||
}
|
||||
|
@ -1,6 +1,5 @@
|
||||
open Logarion
|
||||
open Kosuzu
|
||||
|
||||
(*TODO: move to converters (style, feed checks)*)
|
||||
let is_older s d = try Unix.((stat d).st_mtime < (stat s).st_mtime) with _-> true
|
||||
|
||||
let convert cs r (text, files) = match Text.str "Content-Type" text with
|
||||
@ -92,5 +91,5 @@ let cmd =
|
||||
`P "If path is a directory must contain an index.pck.";
|
||||
`P "Run `txt index` first." ]
|
||||
in
|
||||
let info = Cmd.info "convert" ~version: "%%VERSION%%" ~doc ~man in
|
||||
let info = Cmd.info "convert" ~doc ~man in
|
||||
Cmd.v info convert_t
|
6
cmd/txt/dune
Normal file
6
cmd/txt/dune
Normal file
@ -0,0 +1,6 @@
|
||||
(executable
|
||||
(name txt)
|
||||
(public_name txt)
|
||||
(modules txt authors convert conversion edit file index last listing
|
||||
new topics html atom gemini peers pull recent unfile)
|
||||
(libraries text_parse.converter text_parse.parsers kosuzu msgpck curl str cmdliner))
|
@ -7,14 +7,16 @@ let number = Arg.(value & opt (some int) None & info ["n"] ~docv: "number" ~doc:
|
||||
let authed = Arg.(value & opt (some string) None & info ["authored"] ~docv: "Comma-separated names" ~doc: "Texts by authors")
|
||||
let topics = Arg.(value & opt (some string) None & info ["topics"] ~docv: "Comma-separated topics" ~doc: "Texts by topics")
|
||||
|
||||
let edit_t = Term.(const (Logarion.Archive.apply_sys_util "EDITOR" "nano") $ recurse $ time $ reverse $ number $ authed $ topics $ id)
|
||||
let edit_t = Term.(const (Kosuzu.Archive.apply_sys_util "EDITOR" "nano") $ recurse $ time $ reverse $ number $ authed $ topics $ id)
|
||||
|
||||
let cmd =
|
||||
let doc = "Edit a text" in
|
||||
let man = [
|
||||
`S Manpage.s_description;
|
||||
`P "Launches EDITOR (nano if environment variable is unset) with text path as parameter.";
|
||||
`P "If -R is used, the ID search space includes texts found in subdirectories, too." ]
|
||||
`P "If -R is used, the ID search space includes texts found in subdirectories, too.";
|
||||
`S Manpage.s_environment;
|
||||
`P "EDITOR - Default editor name" ]
|
||||
in
|
||||
let info = Cmd.info "edit" ~version:"%%VERSION%%" ~doc ~man in
|
||||
let info = Cmd.info "edit" ~doc ~man in
|
||||
Cmd.v info edit_t
|
@ -1,4 +1,4 @@
|
||||
open Logarion
|
||||
open Kosuzu
|
||||
let file files =
|
||||
let dirs, files = File_store.split_filetypes files in
|
||||
let _link_as_named dir file = Unix.link file (Filename.concat dir file) in
|
||||
@ -19,5 +19,5 @@ let cmd =
|
||||
`P "Files all texts in parameter in every directory in parameter, using hardlinks";
|
||||
`P "Use it to create sub-repositories for sharing or converting" ]
|
||||
in
|
||||
let info = Cmd.info "file" ~version:"%%VERSION%%" ~doc ~man in
|
||||
let info = Cmd.info "file" ~doc ~man in
|
||||
Cmd.v info file_t
|
@ -7,18 +7,18 @@ module GeminiConverter = struct
|
||||
end
|
||||
|
||||
let page _conversion text =
|
||||
let open Logarion.Text in
|
||||
let open Kosuzu.Text in
|
||||
"# " ^ text.title
|
||||
^ "\nAuthors: " ^ Logarion.Person.Set.to_string text.authors
|
||||
^ "\nDate: " ^ Logarion.Date.(pretty_date @@ listing text.date)
|
||||
^ "\nAuthors: " ^ Kosuzu.Person.Set.to_string text.authors
|
||||
^ "\nDate: " ^ Kosuzu.Date.(pretty_date @@ listing text.date)
|
||||
^ let module T = Parsers.Plain_text.Make (GeminiConverter) in
|
||||
"\n" ^ T.of_string text.body ""
|
||||
|
||||
let date_index title meta_list =
|
||||
List.fold_left
|
||||
(fun a m ->
|
||||
a ^ "=> " ^ Logarion.Text.short_id m ^ ".gmi " ^
|
||||
Logarion.(Date.(pretty_date (listing m.date)) ^ " " ^ m.title) ^ "\n")
|
||||
a ^ "=> " ^ Kosuzu.Text.short_id m ^ ".gmi " ^
|
||||
Kosuzu.(Date.(pretty_date (listing m.date)) ^ " " ^ m.title) ^ "\n")
|
||||
("# " ^ title ^ "\n\n## Posts by date\n\n") meta_list
|
||||
|
||||
let to_dated_links ?(limit) meta_list =
|
||||
@ -33,9 +33,9 @@ let to_dated_links ?(limit) meta_list =
|
||||
List.fold_left
|
||||
(fun a m ->
|
||||
a
|
||||
^ "=> " ^ Logarion.Text.short_id m ^ ".gmi "
|
||||
^ Logarion.(Date.(pretty_date (listing m.Text.date))) ^ " "
|
||||
^ m.Logarion.Text.title ^ "\n")
|
||||
^ "=> " ^ Kosuzu.Text.short_id m ^ ".gmi "
|
||||
^ Kosuzu.(Date.(pretty_date (listing m.Text.date))) ^ " "
|
||||
^ m.Kosuzu.Text.title ^ "\n")
|
||||
"" meta_list
|
||||
|
||||
let topic_link root topic =
|
||||
@ -43,7 +43,7 @@ let topic_link root topic =
|
||||
"=> index." ^ replaced_space root ^ ".gmi " ^ String.capitalize_ascii topic ^ "\n"
|
||||
|
||||
let text_item path meta =
|
||||
let open Logarion in
|
||||
let open Kosuzu in
|
||||
"=> " ^ path ^ Text.short_id meta ^ ".gmi "
|
||||
^ Date.(pretty_date (listing meta.Text.date)) ^ " "
|
||||
^ meta.Text.title ^ "\n"
|
||||
@ -51,15 +51,15 @@ let text_item path meta =
|
||||
let listing_index topic_map topic_roots path metas =
|
||||
let rec item_group topics =
|
||||
List.fold_left (fun acc topic -> acc ^ sub_groups topic ^ items topic) "" topics
|
||||
and sub_groups topic = match Logarion.Topic_set.Map.find_opt topic topic_map with
|
||||
and sub_groups topic = match Kosuzu.Topic_set.Map.find_opt topic topic_map with
|
||||
| None -> ""
|
||||
| Some (_, subtopics) -> item_group (Logarion.String_set.elements subtopics)
|
||||
| Some (_, subtopics) -> item_group (Kosuzu.String_set.elements subtopics)
|
||||
and items topic =
|
||||
let items =
|
||||
let open Logarion in
|
||||
let open Kosuzu in
|
||||
List.fold_left
|
||||
(fun a e ->
|
||||
if String_set.mem topic (String_set.map (Logarion.Topic_set.topic) (Text.set "Topics" e))
|
||||
if String_set.mem topic (String_set.map (Kosuzu.Topic_set.topic) (Text.set "Topics" e))
|
||||
then text_item path e ^ a else a) "" metas in
|
||||
match items with
|
||||
| "" -> ""
|
||||
@ -76,7 +76,7 @@ let topic_main_index r title topic_roots metas =
|
||||
^ (if topic_roots <> [] then ("## Main topics\n\n" ^ fold_topic_roots topic_roots) else "")
|
||||
^ "\n## Latest\n\n" ^ to_dated_links ~limit:10 metas
|
||||
^ "\n=> index.date.gmi More by date\n\n"
|
||||
^ let peers = Logarion.Store.KV.find "Peers" r.Conversion.kv in
|
||||
^ let peers = Kosuzu.Store.KV.find "Peers" r.Conversion.kv in
|
||||
if peers = "" then "" else
|
||||
List.fold_left (fun a s -> Printf.sprintf "%s=> %s\n" a s) "## Peers\n\n"
|
||||
(Str.split (Str.regexp ";\n") peers)
|
||||
@ -86,7 +86,7 @@ let topic_sub_index title topic_map topic_root metas =
|
||||
^ listing_index topic_map [topic_root] "" metas
|
||||
|
||||
let indices r =
|
||||
let open Logarion in
|
||||
let open Kosuzu in
|
||||
let file name = File_store.file (Filename.concat r.Conversion.dir name) in
|
||||
let index_name = try Store.KV.find "Gemini-index" r.kv with Not_found -> "index.gmi" in
|
||||
let title = try Store.KV.find "Title" r.Conversion.kv with Not_found -> "" in
|
@ -6,7 +6,7 @@ let empty_templates = { header = None; footer = None }
|
||||
let default_opts = { templates = empty_templates; style = "" }
|
||||
|
||||
let init kv =
|
||||
let open Logarion in
|
||||
let open Kosuzu in
|
||||
let to_string key kv = match Store.KV.find key kv with
|
||||
| fname -> Some (File_store.to_string fname)
|
||||
| exception Not_found -> None in
|
||||
@ -17,12 +17,12 @@ let init kv =
|
||||
{ templates = { header; footer}; style }
|
||||
|
||||
let wrap conv htm text_title body =
|
||||
let site_title = try Logarion.Store.KV.find "Title" conv.Conversion.kv with Not_found -> "" in
|
||||
let site_title = try Kosuzu.Store.KV.find "Title" conv.Conversion.kv with Not_found -> "" in
|
||||
let replace x = let open Str in
|
||||
global_replace (regexp "{{archive-title}}") site_title x
|
||||
|> global_replace (regexp "{{text-title}}") text_title
|
||||
in
|
||||
let feed = try Logarion.Store.KV.find "HTM-feed" conv.Conversion.kv
|
||||
let feed = try Kosuzu.Store.KV.find "HTM-feed" conv.Conversion.kv
|
||||
with Not_found -> if Sys.file_exists (Filename.concat conv.Conversion.dir "feed.atom")
|
||||
then "feed.atom" else "" in
|
||||
let header = match htm.templates.header with
|
||||
@ -31,7 +31,7 @@ let wrap conv htm text_title body =
|
||||
(if feed <> "" then sprintf "<a href='%s' id='feed'>feed</a>" feed else ""))
|
||||
in
|
||||
let footer = match htm.templates.footer with None -> "" | Some x -> replace x in
|
||||
Printf.sprintf "<!DOCTYPE HTML>\n<html>\n<head>\n<link rel=\"icon\" href=\"/favicon.ico\">\n<title>%s%s</title>\n%s\n%s\n<meta name=\"generator\" content=\"Logarion\">\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\">\n</head>\n<body>\n%s%s%s</body>\n</html>"
|
||||
Printf.sprintf "<!DOCTYPE HTML>\n<html>\n<head>\n<link rel=\"icon\" href=\"/favicon.ico\">\n<title>%s%s</title>\n%s\n%s\n<meta name=\"generator\" content=\"Kosuzu\">\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\">\n</head>\n<body>\n%s%s%s</body>\n</html>"
|
||||
text_title (if site_title <> "" then (" • " ^ site_title) else "")
|
||||
htm.style
|
||||
(if feed <> "" then Printf.sprintf "<link rel='alternate' href='%s' type='application/atom+xml'>" feed else "")
|
||||
@ -51,7 +51,7 @@ module HtmlConverter = struct
|
||||
end
|
||||
|
||||
let page htm conversion text =
|
||||
let open Logarion in
|
||||
let open Kosuzu in
|
||||
let open Text in
|
||||
let module T = Parsers.Plain_text.Make (HtmlConverter) in
|
||||
let sep_append ?(sep=", ") a x = match a,x with "",_ -> x | _, "" -> a | _ -> a ^ sep ^ x in
|
||||
@ -95,8 +95,8 @@ let to_dated_links ?(limit) meta_list =
|
||||
| h::t -> if i < limit then reduced (h::acc) (i+1) t else acc in
|
||||
List.rev @@ reduced [] 0 meta_list in
|
||||
List.fold_left
|
||||
(fun a m -> Printf.sprintf "%s <li> %s <a href=\"%s.htm\">%s</a>" a Logarion.(Date.(pretty_date (listing m.Text.date)))
|
||||
(Logarion.Text.short_id m) m.Logarion.Text.title)
|
||||
(fun a m -> Printf.sprintf "%s <li> %s <a href=\"%s.htm\">%s</a>" a Kosuzu.(Date.(pretty_date (listing m.Text.date)))
|
||||
(Kosuzu.Text.short_id m) m.Kosuzu.Text.title)
|
||||
"" meta_list
|
||||
|
||||
let date_index ?(limit) conv htm meta_list =
|
||||
@ -111,7 +111,7 @@ let fold_topic_roots topic_roots =
|
||||
^ "</ul></nav>"
|
||||
|
||||
let fold_topics topic_map topic_roots metas =
|
||||
let open Logarion in
|
||||
let open Kosuzu in
|
||||
let rec unordered_list root topic =
|
||||
List.fold_left (fun a x -> a ^ list_item root x) "<ul>" topic
|
||||
^ "</ul>"
|
||||
@ -128,7 +128,7 @@ and list_item root t =
|
||||
^ "</ul></nav>"
|
||||
|
||||
let text_item path meta =
|
||||
let open Logarion in
|
||||
let open Kosuzu in
|
||||
"<time>" ^ Date.(pretty_date (listing meta.Text.date))
|
||||
^ {|</time> <a href="|} ^ path ^ Text.short_id meta ^ {|.htm">|} ^ meta.Text.title
|
||||
^ "</a><br>"
|
||||
@ -136,15 +136,15 @@ let text_item path meta =
|
||||
let listing_index topic_map topic_roots path metas =
|
||||
let rec item_group topics =
|
||||
List.fold_left (fun acc topic -> acc ^ sub_groups topic ^ items topic) "" topics
|
||||
and sub_groups topic = match Logarion.Topic_set.Map.find_opt topic topic_map with
|
||||
and sub_groups topic = match Kosuzu.Topic_set.Map.find_opt topic topic_map with
|
||||
| None -> ""
|
||||
| Some (_, subtopics) -> item_group (Logarion.String_set.elements subtopics)
|
||||
| Some (_, subtopics) -> item_group (Kosuzu.String_set.elements subtopics)
|
||||
and items topic =
|
||||
let items =
|
||||
let open Logarion in
|
||||
let open Kosuzu in
|
||||
List.fold_left
|
||||
(fun a e ->
|
||||
if String_set.mem topic (String_set.map (Logarion.Topic_set.topic) (Text.set "Topics" e))
|
||||
if String_set.mem topic (String_set.map (Kosuzu.Topic_set.topic) (Text.set "Topics" e))
|
||||
then text_item path e ^ a else a) "" metas in
|
||||
match items with
|
||||
| "" -> ""
|
||||
@ -156,10 +156,10 @@ let topic_main_index conv htm topic_roots metas =
|
||||
(fold_topic_roots topic_roots
|
||||
^ "<nav><h1>Latest</h1><ul>" ^ to_dated_links ~limit:10 metas
|
||||
^ {|</ul></nav><hr><a href="index.date.htm">More by date</a>|}
|
||||
^ let peers = try Logarion.Store.KV.find "Peers" conv.kv with Not_found -> "" in
|
||||
^ let peers = try Kosuzu.Store.KV.find "Peers" conv.kv with Not_found -> "" in
|
||||
(if peers = "" then "" else
|
||||
List.fold_left (fun a s -> Printf.sprintf {|%s<li><a href="%s">%s</a>|} a s s) "<h1>Peers</h1><ul>"
|
||||
(Str.split (Str.regexp ";\n") (Logarion.Store.KV.find "Peers" conv.kv))
|
||||
(Str.split (Str.regexp ";\n") (Kosuzu.Store.KV.find "Peers" conv.kv))
|
||||
^ "</ul>"))
|
||||
|
||||
let topic_sub_index conv htm topic_map topic_root metas =
|
||||
@ -168,8 +168,8 @@ let topic_sub_index conv htm topic_map topic_root metas =
|
||||
^ listing_index topic_map [topic_root] "" metas)
|
||||
|
||||
let indices htm c =
|
||||
let file name = Logarion.File_store.file (Filename.concat c.Conversion.dir name) in
|
||||
let index_name = try Logarion.Store.KV.find "HTM-index" c.Conversion.kv with Not_found -> "index.html" in
|
||||
let file name = Kosuzu.File_store.file (Filename.concat c.Conversion.dir name) in
|
||||
let index_name = try Kosuzu.Store.KV.find "HTM-index" c.Conversion.kv with Not_found -> "index.html" in
|
||||
if index_name <> "" then file index_name (topic_main_index c htm c.topic_roots c.texts);
|
||||
file "index.date.htm" (date_index c htm c.texts);
|
||||
List.iter
|
@ -1,4 +1,4 @@
|
||||
open Logarion
|
||||
open Kosuzu
|
||||
|
||||
let text_editor name x =
|
||||
let fname, out = Filename.open_temp_file name "" in
|
||||
@ -85,7 +85,9 @@ let cmd =
|
||||
`P "* n info section with: title for the index, the authors, locations (URLs) the texts can be accessed.";
|
||||
`P "* listing of texts with: ID, date, title, authors, topics.";
|
||||
`P "* list of other text repositories (peers)";
|
||||
`S Manpage.s_environment;
|
||||
`P "EDITOR - Default editor name";
|
||||
`S Manpage.s_see_also;
|
||||
`P "MessagePack format. https://msgpack.org" ] in
|
||||
let info = Cmd.info "index" ~version:"%%VERSION%%" ~doc ~man in
|
||||
let info = Cmd.info "index" ~doc ~man in
|
||||
Cmd.v info index_t
|
@ -1,4 +1,4 @@
|
||||
open Logarion
|
||||
open Kosuzu
|
||||
|
||||
let last a ((t,_) as pair) = match a with
|
||||
| None -> Some pair
|
||||
@ -31,5 +31,5 @@ let cmd =
|
||||
`S Manpage.s_description;
|
||||
`P "Print the filename of most recent text" ]
|
||||
in
|
||||
let info = Cmd.info "last" ~version:"%%VERSION%%" ~doc ~man in
|
||||
let info = Cmd.info "last" ~doc ~man in
|
||||
Cmd.v info last_t
|
@ -1,4 +1,4 @@
|
||||
open Logarion
|
||||
open Kosuzu
|
||||
module FS = File_store
|
||||
module A = Archive
|
||||
|
||||
@ -40,5 +40,5 @@ let cmd =
|
||||
`P "If directory argument is omitted, TXTDIR is used, where empty value defaults to ~/.local/share/texts.";
|
||||
`P "If -R is used, list header information for texts found in subdirectories, too." ]
|
||||
in
|
||||
let info = Cmd.info "list" ~version:"%%VERSION%%" ~doc ~man in
|
||||
let info = Cmd.info "list" ~doc ~man in
|
||||
Cmd.v info listing_t
|
@ -1,29 +1,29 @@
|
||||
open Logarion
|
||||
open Kosuzu
|
||||
open Cmdliner
|
||||
|
||||
let new_txt title topics_opt interactive =
|
||||
let kv = Logarion.File_store.of_kv_file () in
|
||||
let authors = Person.Set.of_string (try Logarion.Store.KV.find "Authors" kv
|
||||
let new_txt title topics_opt =
|
||||
let kv = Kosuzu.File_store.of_kv_file () in
|
||||
let authors = Person.Set.of_string (try Kosuzu.Store.KV.find "Authors" kv
|
||||
with Not_found -> Sys.getenv "USER") in
|
||||
let text = { (Text.blank ()) with title; authors } in
|
||||
let text = try Text.with_str_set text "Topics" (Option.get topics_opt) with _->text in
|
||||
match File_store.with_text text with
|
||||
| Error s -> prerr_endline s
|
||||
| Ok (filepath, _note) ->
|
||||
if interactive then (Sys.command ("$EDITOR " ^ filepath) |> ignore);
|
||||
print_endline filepath
|
||||
|
||||
let title = Arg.(value & pos 0 string "" & info [] ~docv: "title" ~doc: "Title for new article")
|
||||
let topics = Arg.(value & opt (some string) None & info ["t"; "topics"] ~docv: "Comma-separated topics" ~doc: "Topics for new article")
|
||||
let inter = Arg.(value & flag & info ["i"; "interactive"] ~doc: "Prompt through the steps of creation")
|
||||
|
||||
let new_t = Term.(const new_txt $ title $ topics $ inter)
|
||||
let new_t = Term.(const new_txt $ title $ topics)
|
||||
|
||||
let cmd =
|
||||
let doc = "Create a new article" in
|
||||
let man = [
|
||||
`S Manpage.s_description;
|
||||
`P "Create a new article, with title 'Draft' when none provided" ]
|
||||
`P "Create a new article";
|
||||
`S Manpage.s_environment;
|
||||
`P "USER - The login name of the user, used if the Authors field is blank" ]
|
||||
in
|
||||
let info = Cmd.info "new" ~version:"%%VERSION%%" ~doc ~man in
|
||||
let info = Cmd.info "new" ~doc ~man in
|
||||
Cmd.v info new_t
|
@ -1,20 +1,20 @@
|
||||
let print_peers_of_peer p =
|
||||
let open Logarion.Header_pack in
|
||||
let open Kosuzu.Header_pack in
|
||||
match Msgpck.to_list p.peers with [] -> ()
|
||||
| ps -> print_endline @@
|
||||
List.fold_left (fun a x -> Printf.sprintf "%s %s" a (Msgpck.to_string x)) "peers: " ps
|
||||
|
||||
type filter_t = { authors: Logarion.Person.Set.t; topics: Logarion.String_set.t }
|
||||
type filter_t = { authors: Kosuzu.Person.Set.t; topics: Kosuzu.String_set.t }
|
||||
|
||||
let print_peer () peer =
|
||||
let open Logarion.Peers in
|
||||
let open Kosuzu.Peers in
|
||||
Printf.printf "%s" peer.path;
|
||||
List.iter (Printf.printf "\t%s\n") peer.pack.info.locations
|
||||
|
||||
let remove_repo id =
|
||||
let repopath = Filename.concat Logarion.Peers.text_dir id in
|
||||
let repopath = Filename.concat Kosuzu.Peers.text_dir id in
|
||||
match Sys.is_directory repopath with
|
||||
| false -> Printf.eprintf "No repository %s in %s" id Logarion.Peers.text_dir
|
||||
| false -> Printf.eprintf "No repository %s in %s" id Kosuzu.Peers.text_dir
|
||||
| true ->
|
||||
let cmd = Printf.sprintf "rm -r %s" repopath in
|
||||
Printf.printf "Run: %s ? (y/N) %!" cmd;
|
||||
@ -25,8 +25,8 @@ let remove_repo id =
|
||||
let peers = function
|
||||
| Some id -> remove_repo id
|
||||
| None ->
|
||||
Printf.printf "Peers in %s\n" Logarion.Peers.text_dir;
|
||||
Logarion.Peers.fold print_peer ()
|
||||
Printf.printf "Peers in %s\n" Kosuzu.Peers.text_dir;
|
||||
Kosuzu.Peers.fold print_peer ()
|
||||
|
||||
open Cmdliner
|
||||
let remove = Arg.(value & opt (some string) None & info ["remove"] ~docv:"Repository ID" ~doc:"Remove repository texts and from future pulling")
|
||||
@ -38,5 +38,5 @@ let cmd =
|
||||
`S Manpage.s_description;
|
||||
`P "List current peers and associated information" ]
|
||||
in
|
||||
let info = Cmd.info "peers" ~version:"%%VERSION%%" ~doc ~man in
|
||||
let info = Cmd.info "peers" ~doc ~man in
|
||||
Cmd.v info peers_t
|
@ -2,15 +2,6 @@ let writer accum data =
|
||||
Buffer.add_string accum data;
|
||||
String.length data
|
||||
|
||||
let showContent content =
|
||||
Printf.printf "%s" (Buffer.contents content);
|
||||
flush stdout
|
||||
|
||||
let showInfo connection =
|
||||
Printf.printf "Time: %f for: %s\n"
|
||||
(Curl.get_totaltime connection)
|
||||
(Curl.get_effectiveurl connection)
|
||||
|
||||
let getContent connection url =
|
||||
Curl.set_url connection url;
|
||||
Curl.perform connection
|
||||
@ -25,8 +16,6 @@ let curl_pull url =
|
||||
Curl.set_followlocation connection true;
|
||||
Curl.set_url connection url;
|
||||
Curl.perform connection;
|
||||
(* showContent result;*)
|
||||
(* showInfo connection;*)
|
||||
Curl.cleanup connection;
|
||||
Ok result
|
||||
with
|
||||
@ -38,18 +27,18 @@ let curl_pull url =
|
||||
Error (Printf.sprintf "Caught exception: %s" s)
|
||||
|
||||
let newer time id dir =
|
||||
match Logarion.File_store.to_text @@ Filename.(concat dir (Logarion.Id.short id) ^ ".txt") with
|
||||
match Kosuzu.File_store.to_text @@ Filename.(concat dir (Kosuzu.Id.short id) ^ ".txt") with
|
||||
| Error x -> prerr_endline x; true
|
||||
| Ok txt -> time > (Logarion.(Header_pack.date (Date.listing txt.date)))
|
||||
| Ok txt -> time > (Kosuzu.(Header_pack.date (Date.listing txt.date)))
|
||||
| exception (Sys_error _) -> true
|
||||
|
||||
let print_peers p =
|
||||
let open Logarion.Header_pack in
|
||||
let open Kosuzu.Header_pack in
|
||||
match Msgpck.to_list p.peers with [] -> ()
|
||||
| ps -> print_endline @@
|
||||
List.fold_left (fun a x -> Printf.sprintf "%s %s" a (Msgpck.to_string x)) "peers: " ps
|
||||
|
||||
type filter_t = { authors: Logarion.Person.Set.t; topics: Logarion.String_set.t }
|
||||
type filter_t = { authors: Kosuzu.Person.Set.t; topics: Kosuzu.String_set.t }
|
||||
|
||||
let print_pull_start width total title dir =
|
||||
Printf.printf "%*d/%s %s => %s %!" width 0 total title dir
|
||||
@ -62,14 +51,14 @@ let printers total title dir =
|
||||
print_pull_start width total title dir;
|
||||
print_pull width total
|
||||
|
||||
let fname dir text = Filename.concat dir (Logarion.Text.short_id text ^ ".txt")
|
||||
let fname dir text = Filename.concat dir (Kosuzu.Text.short_id text ^ ".txt")
|
||||
|
||||
let pull_text url dir id =
|
||||
let u = Filename.concat url ((Logarion.Id.short id) ^ ".txt") in
|
||||
let u = Filename.concat url ((Kosuzu.Id.short id) ^ ".txt") in
|
||||
match curl_pull u with
|
||||
| Error msg -> Printf.eprintf "Failed getting %s: %s" u msg
|
||||
| Ok txt -> let txt = Buffer.contents txt in
|
||||
match Logarion.Text.of_string txt with
|
||||
match Kosuzu.Text.of_string txt with
|
||||
| Error s -> prerr_endline s
|
||||
| Ok text ->
|
||||
let file = open_out_gen [Open_creat; Open_trunc; Open_wronly] 0o640 (fname dir text) in
|
||||
@ -77,7 +66,7 @@ let pull_text url dir id =
|
||||
|
||||
let per_text url dir filter print i id time title authors topics _refs _reps = match id with
|
||||
| "" -> Printf.eprintf "\nInvalid id for %s\n" title
|
||||
| id -> let open Logarion in
|
||||
| id -> let open Kosuzu in
|
||||
print i;
|
||||
if newer time id dir
|
||||
&& (String_set.empty = filter.topics
|
||||
@ -86,48 +75,41 @@ let per_text url dir filter print i id time title authors topics _refs _reps = m
|
||||
|| Person.Set.exists (fun t -> List.mem (Person.to_string t) authors) filter.authors)
|
||||
then pull_text url dir id
|
||||
|
||||
(*TODO: integrate in lib*)
|
||||
let validate_id_length s = String.length s <= 32
|
||||
let validate_id_chars s = try
|
||||
String.iter (function 'a'..'z'|'A'..'Z'|'0'..'9'-> () | _ -> raise (Invalid_argument "")) s;
|
||||
true
|
||||
with Invalid_argument _ -> false
|
||||
|
||||
let pull_index url authors_opt topics_opt =
|
||||
let index_url = Filename.concat url "index.pck" in
|
||||
match curl_pull index_url with
|
||||
| Error s -> prerr_endline s; false
|
||||
| Ok body ->
|
||||
match Logarion.Header_pack.of_string (Buffer.contents body) with
|
||||
match Kosuzu.Header_pack.of_string (Buffer.contents body) with
|
||||
| Error s -> Printf.printf "Error with %s: %s\n" url s; false
|
||||
| Ok pk when pk.info.id = "" ->
|
||||
Printf.printf "Empty ID index.pck, skipping %s\n" url; false
|
||||
| Ok pk when not (validate_id_length pk.info.id) ->
|
||||
| Ok pk when not (Kosuzu.Validate.validate_id_length pk.info.id) ->
|
||||
Printf.printf "Index pack ID longer than 32 characters, skipping %s\n" url; false
|
||||
| Ok pk when not (validate_id_chars pk.info.id) ->
|
||||
| Ok pk when not (Kosuzu.Validate.validate_id_chars pk.info.id) ->
|
||||
Printf.printf "Index pack contains invalid ID characters, skipping %s\n" url; false
|
||||
| Ok pk ->
|
||||
let dir = Filename.concat Logarion.Peers.text_dir pk.info.id in
|
||||
Logarion.File_store.with_dir dir;
|
||||
let dir = Filename.concat Kosuzu.Peers.text_dir pk.info.id in
|
||||
Kosuzu.File_store.with_dir dir;
|
||||
let file = open_out_gen [Open_creat; Open_trunc; Open_wronly] 0o640
|
||||
(Filename.concat dir "index.pck") in
|
||||
output_string file ( Logarion.Header_pack.string {
|
||||
output_string file ( Kosuzu.Header_pack.string {
|
||||
pk with info = { pk.info with locations = url::pk.info.locations }});
|
||||
close_out file;
|
||||
let filter = let open Logarion in {
|
||||
let filter = let open Kosuzu in {
|
||||
authors = (match authors_opt with Some s -> Person.Set.of_string s | None -> Person.Set.empty);
|
||||
topics =( match topics_opt with Some s -> String_set.of_string s | None -> String_set.empty);
|
||||
} in
|
||||
let name = match pk.info.title with "" -> url | title -> title in
|
||||
let print = printers (string_of_int @@ Logarion.Header_pack.numof_texts pk) name dir in
|
||||
try Logarion.Header_pack.iteri (per_text url dir filter print) pk; print_newline (); true
|
||||
let print = printers (string_of_int @@ Kosuzu.Header_pack.numof_texts pk) name dir in
|
||||
try Kosuzu.Header_pack.iteri (per_text url dir filter print) pk; print_newline (); true
|
||||
with Invalid_argument msg -> Printf.printf "\nFailed to parse %s: %s\n%!" url msg; false
|
||||
|
||||
let pull_list auths topics =
|
||||
Curl.global_init Curl.CURLINIT_GLOBALALL;
|
||||
let pull got_one peer_url = if got_one then got_one else
|
||||
(pull_index peer_url auths topics) in
|
||||
let open Logarion in
|
||||
let open Kosuzu in
|
||||
let fold_locations init peer =
|
||||
ignore @@ List.fold_left pull init peer.Peers.pack.Header_pack.info.locations;
|
||||
false
|
||||
@ -151,5 +133,5 @@ let cmd =
|
||||
`S Manpage.s_description;
|
||||
`P "Pull texts from known repositories." ]
|
||||
in
|
||||
let info = Cmd.info "pull" ~version:"%%VERSION%%" ~doc ~man in
|
||||
let info = Cmd.info "pull" ~doc ~man in
|
||||
Cmd.v info pull_t
|
@ -1,4 +1,4 @@
|
||||
open Logarion
|
||||
open Kosuzu
|
||||
module FS = File_store
|
||||
module A = Archive
|
||||
|
||||
@ -19,5 +19,5 @@ let cmd =
|
||||
`P "List header information of most recent texts.";
|
||||
`P "If -R is used, list header information for texts found in subdirectories, too, along with their filepaths" ]
|
||||
in
|
||||
let info = Cmd.info "recent" ~version:"%%VERSION%%" ~doc ~man in
|
||||
let info = Cmd.info "recent" ~doc ~man in
|
||||
Cmd.v info recent_t
|
@ -1,4 +1,4 @@
|
||||
open Logarion
|
||||
open Kosuzu
|
||||
let topics r authors_opt =
|
||||
let predicates = Archive.(predicate authored authors_opt) in
|
||||
let predicate text = List.fold_left (fun a e -> a && e text) true predicates in
|
||||
@ -17,5 +17,5 @@ let cmd =
|
||||
`S Manpage.s_description;
|
||||
`P "List of topics" ]
|
||||
in
|
||||
let info = Cmd.info "topics" ~version:"%%VERSION%%" ~doc ~man in
|
||||
let info = Cmd.info "topics" ~doc ~man in
|
||||
Cmd.v info topics_t
|
36
cmd/txt/txt.ml
Normal file
36
cmd/txt/txt.ml
Normal file
@ -0,0 +1,36 @@
|
||||
open Cmdliner
|
||||
|
||||
let subs = [
|
||||
Authors.cmd;
|
||||
Convert.cmd;
|
||||
Edit.cmd;
|
||||
File.cmd;
|
||||
Index.cmd;
|
||||
Last.cmd;
|
||||
Listing.cmd;
|
||||
New.cmd;
|
||||
Peers.cmd;
|
||||
Pull.cmd;
|
||||
Recent.cmd;
|
||||
Topics.cmd;
|
||||
Unfile.cmd;
|
||||
]
|
||||
|
||||
let default_cmd = Term.(ret (const (`Help (`Pager, None))))
|
||||
|
||||
let txt =
|
||||
let doc = "Discover, collect and exchange texts" in
|
||||
let man = [
|
||||
`S Manpage.s_authors;
|
||||
`P "orbifx <mailto:fox@orbitalfox.eu>";
|
||||
`P "Izuru Yakumo <mailto:yakumo.izuru@chaotic.ninja>";
|
||||
`S Manpage.s_bugs;
|
||||
`P "Please report them at <mailto:kosuzu-dev@chaotic.ninja>";
|
||||
`S Manpage.s_see_also;
|
||||
`P "This program is named after Kosuzu Motoori from Touhou Suzunaan: Forbidden Scrollery";
|
||||
`P "https://en.touhouwiki.net/wiki/Forbidden_Scrollery" ]
|
||||
in
|
||||
Cmd.group (Cmd.info "txt" ~version:"%%VERSION%%" ~doc ~man) ~default:default_cmd subs
|
||||
|
||||
let main () = exit (Cmd.eval txt)
|
||||
let () = main ()
|
@ -1,4 +1,4 @@
|
||||
open Logarion
|
||||
open Kosuzu
|
||||
|
||||
let unfile files =
|
||||
let dirs, files = File_store.split_filetypes files in
|
||||
@ -17,5 +17,5 @@ let cmd =
|
||||
`S Manpage.s_description;
|
||||
`P "Unfile texts in parameter from directories in parameter, by removing hardlinks" ]
|
||||
in
|
||||
let info = Cmd.info "unfile" ~version:"%%VERSION%%" ~doc ~man in
|
||||
let info = Cmd.info "unfile" ~doc ~man in
|
||||
Cmd.v info unfile_t
|
5
cmd/txt_init/dune
Normal file
5
cmd/txt_init/dune
Normal file
@ -0,0 +1,5 @@
|
||||
(executable
|
||||
(name txt_init)
|
||||
(public_name txt_init)
|
||||
(modules txt_init)
|
||||
(libraries kosuzu))
|
17
cmd/txt_init/txt_init.ml
Normal file
17
cmd/txt_init/txt_init.ml
Normal file
@ -0,0 +1,17 @@
|
||||
let init_repo =
|
||||
print_endline "Initializing repository...";
|
||||
print_endline "It's required for the repository name and id.";
|
||||
print_endline "Create one? (y/n)";
|
||||
match input_line stdin with
|
||||
|"y"->
|
||||
let title =
|
||||
print_endline "Title for repository: ";
|
||||
input_line stdin in
|
||||
let authors =
|
||||
print_endline "Authors (format: name <name@email> <http://website>): ";
|
||||
input_line stdin in
|
||||
Kosuzu.File_store.file "txt.conf"
|
||||
(Printf.sprintf "Id:%s\nTitle: %s\nAuthors: %s\n" (Kosuzu.Id.generate ()) title authors);
|
||||
Kosuzu.File_store.of_kv_file ()
|
||||
| _ ->
|
||||
print_endline "Aborting..."; exit 1
|
@ -1,11 +0,0 @@
|
||||
ID: 3sqd84
|
||||
Date: 2022-11-06T13:01:19Z
|
||||
Title: Exploring & pulling texts from Logarion repositories
|
||||
Authors: orbifx
|
||||
Topics: Logarion
|
||||
|
||||
Logarion repositories are collections of text files, accompanied by a special index file. These collections can exist on any server and accessed by any transport protocol. Logarion's client currently supports a plethora of protocols, HTTP, FTP, Gopher to name a few examples.
|
||||
|
||||
A remote repository can be registered and texts copied locally. To add a new remote run: `txt pull <url>`, where <url> is the address of the remote repository. The program will connect to the server, copy the `index.pck` file and use it to download each text file. It will not redownload previous texts, unless their Date or Date-Edited dates are newer than the previous ones.
|
||||
|
||||
The text files are by default downloaded to `.local/share/texts/peers/`. A new directory is created for each peer's unique id and the index & texts are stored in it. Running `txt pull` with no URL, will refetch indices from all previously pulled repositories, and new text files will be downloaded automatically.
|
@ -1,15 +0,0 @@
|
||||
ID: 9egbae
|
||||
Date: 2023-06-17T23:27:44Z
|
||||
Title: Building from source
|
||||
Authors: Izuru Yakumo
|
||||
Topics: Logarion
|
||||
|
||||
Requirements:
|
||||
* git
|
||||
* ocaml (>=4.13)
|
||||
|
||||
1. git clone git://git.chaotic.ninja/yakumo_izuru/logarion
|
||||
2. cd logarion
|
||||
3. git submodule update --init
|
||||
4. make deps
|
||||
5. make tgz
|
23
doc/Makefile
23
doc/Makefile
@ -1,23 +0,0 @@
|
||||
all: clean build
|
||||
|
||||
build: index generate
|
||||
|
||||
clean:
|
||||
find htm -type f -name "*.html" -print -delete
|
||||
find htm -type f -name "*.htm" -print -delete
|
||||
find . -type f -name "*.atom" -print -delete
|
||||
copy-index: index
|
||||
cp index.pck htm
|
||||
generate: relink copy-index
|
||||
txt convert --type=htm htm
|
||||
index:
|
||||
txt index .
|
||||
link:
|
||||
txt file *.txt htm
|
||||
delink:
|
||||
rm htm/*.txt
|
||||
serve:
|
||||
darkhttpd htm
|
||||
upload:
|
||||
rsync -acv --del htm/ webdev@chaotic.ninja:/var/www/logarion
|
||||
relink: delink link
|
@ -1,26 +0,0 @@
|
||||
ID: d41e68
|
||||
Date: 2023-10-18T18:45:33+00:00
|
||||
Title: Header format
|
||||
Authors: orbifx <fox@orbitalfox.eu>
|
||||
Topics: Logarion
|
||||
|
||||
ID: Unique identifier
|
||||
Date: ISO8601 date of creation
|
||||
Topics: Comma seperated list of topic names & phrases
|
||||
Title: A title for the text, ideally less than 70 characters
|
||||
Authors: List of name with optional set of <address>
|
||||
Date-edited: ISO8601, use only when text edited
|
||||
References: list of text ID links
|
||||
|
||||
A blank line must follow the last header field.
|
||||
|
||||
|
||||
Example
|
||||
|
||||
ID: 11bcd
|
||||
Title: A Logarion exemplar header
|
||||
Authors: John Doe <http://example.com> <jd@example.com>
|
||||
Date: 1970-11-06T12:29:50-00:00
|
||||
Topic: Logarion, examples
|
||||
|
||||
Hello world!
|
@ -1,4 +0,0 @@
|
||||
<hr>
|
||||
<p>
|
||||
<a href="git://git.chaotic.ninja/yakumo_izuru/logarion">Source code</a>
|
||||
</p>
|
@ -1,17 +0,0 @@
|
||||
ID: h1a9tg
|
||||
Date: 2022-11-20T13:28:57Z
|
||||
Authors: orbifx <orbifx@orbifx.indy>
|
||||
Title: Txt uniform resource names
|
||||
Topics: Logarion
|
||||
|
||||
Logarion texts are transport agnostic. URIs should therefore avoid using URLs and use URNs instead. Some definitions of Uniform Resource:
|
||||
- Locator (URL) <http://en.wikipedia.org/wiki/Url>
|
||||
- Identifier (URI) <http://en.wikipedia.org/wiki/Uniform_Resource_Identifier>
|
||||
- Name (URN) <http://en.wikipedia.org/wiki/Uniform_Resource_Name>
|
||||
|
||||
|
||||
Links enclosed in angled brackets <> of the format:
|
||||
|
||||
urn:txtid:abcdef
|
||||
|
||||
where `abcdef` is the id of the text are understood by `txt` and handled accordingly. For example when converting to HTML or Gemini, the URNs are converted to relative URLs which browsers can understand.
|
@ -1,2 +0,0 @@
|
||||
<h1>Logarion</h1>
|
||||
<p>discover, collect & exchange plain text files</p>
|
Binary file not shown.
@ -1,8 +0,0 @@
|
||||
body{color:#111;background:#faeed6;margin:auto;padding:1ch;max-width:80ch}
|
||||
@media(min-resolution:130dpi){body{font-size:1.5em}}
|
||||
article>header{margin:auto;padding:1ch}
|
||||
dt{float:left;text-align:right;padding-right:1ch;min-width:8ch;opacity:0.4;clear:left}
|
||||
:target::before{content:"☞"}
|
||||
body>h1{font-family:cursive}
|
||||
h1,h2{color:#ff6600}
|
||||
h3,h4{color:#333}
|
@ -1,77 +0,0 @@
|
||||
ID: hvhhwf
|
||||
Date: 2022-11-06T13:19:57Z
|
||||
Title: Creating texts & publishing on the net
|
||||
Authors: orbifx
|
||||
Topics: Logarion
|
||||
|
||||
# New
|
||||
|
||||
To create new text files, use "txt new". For example:
|
||||
txt new "Hello world"
|
||||
It's important to enclose the title with quotation marks if it contains spaces. The command will return the filename of the new text. The filename starts with a part of the ID and the title of the text. Use the file name to open it with your text editor.
|
||||
|
||||
Alternatively add the -i flag to have the text editor launched to edit the newly created file:
|
||||
txt new -i "Some title"
|
||||
|
||||
Text files will be stored in either:
|
||||
1. The directory pointed at by txtdir if defined
|
||||
2. $HOME/.local/share/texts, if directory exists
|
||||
3. The current working directory, if all else fails
|
||||
|
||||
The simplest approach is to put all texts in the local-share directory and override that on occasion with
|
||||
$txtdir. For example:
|
||||
txtdir=. txt new "Hello world"
|
||||
|
||||
|
||||
# Publish
|
||||
|
||||
Texts created with "new" are treated as personal until published. To publish a text, use `txt publish [id]` where [id] is the text of the text to publish. Publication requires a `txt.conf` file which must exist in either:
|
||||
1. The current working directory
|
||||
2. $HOME/.config/txt/txt.conf
|
||||
|
||||
With the above in place, `txt publish [id]` will add the text file with [id] in the publication-directory and reproduce the `index.pck` in that directory. If Pubdir is not defined in `txt.conf` then the environmental variable `txtpubdir` is used. If that is also undefined, the current working directory is used as a publication directory.
|
||||
|
||||
Logarion is protocol agnostic, so publish looks for the existence of directories to copy the files, ready for publication. At the time of writing the three directories are `public_html`, `public_gemini` and `public_gopher`. For each of these directories, `txt publish [id]` will copy the text file, revise the `index.pck` and also convert produce converted files, such .htm for public_html.
|
||||
|
||||
## txt.conf keys
|
||||
|
||||
Id:
|
||||
A random, unique, alphanumeric string for distinguishing the repository (atleast 6 characters of Crockford's Base32 recommended)
|
||||
|
||||
Title:
|
||||
a human-friendly title
|
||||
|
||||
Authors:
|
||||
comma seperated list of names and, optionally, addresses
|
||||
|
||||
Topics:
|
||||
topics the repository aims to cover
|
||||
|
||||
Locations:
|
||||
list of URIs the repositories can be accessed
|
||||
|
||||
Peers:
|
||||
list of peer URIs
|
||||
|
||||
Pubdir:
|
||||
(optional) the directory that contains publication subdirectories
|
||||
|
||||
|
||||
## HTML
|
||||
|
||||
There are some special settings for HTML publication:
|
||||
|
||||
HTM-style:
|
||||
path to a CSS style. It will be inserted in every .htm file. To link to a single CSS consider using `@import`
|
||||
|
||||
HTM-header:
|
||||
path to a file, inserted in every .htm file, right after the body tag
|
||||
|
||||
HTM-footer:
|
||||
path to a file, inserted in every .htm file, right before the body tag
|
||||
|
||||
HTM-index:
|
||||
if defined, determines the filename for the index files. Left undefined, defaults to `index.html`
|
||||
|
||||
HTM-feed:
|
||||
if defined, this will overrite the feed URI used in HTML files. If left undefined the default `feed.atom` is used
|
BIN
doc/index.pck
BIN
doc/index.pck
Binary file not shown.
@ -1,8 +0,0 @@
|
||||
body{color:#111;background:#faeed6;margin:auto;padding:1ch;max-width:80ch}
|
||||
@media(min-resolution:130dpi){body{font-size:1.5em}}
|
||||
article>header{margin:auto;padding:1ch}
|
||||
dt{float:left;text-align:right;padding-right:1ch;min-width:8ch;opacity:0.4;clear:left}
|
||||
:target::before{content:"☞"}
|
||||
body>h1{font-family:cursive}
|
||||
h1,h2{color:#ff6600}
|
||||
h3,h4{color:#333}
|
@ -1,8 +0,0 @@
|
||||
Id: 17f4e3
|
||||
Title: Logarion
|
||||
Authors: orbifx <fox@orbifalfox.eu>, Izuru Yakumo <yakumo.izuru@chaotic.ninja>
|
||||
HTM-style: main.css
|
||||
HTM-header: header.html
|
||||
HTM-footer: footer.html
|
||||
HTM-feed:
|
||||
Topics: Logarion
|
12
dune-project
12
dune-project
@ -1,16 +1,16 @@
|
||||
(lang dune 2.0)
|
||||
(name logarion)
|
||||
(version 1.4.0)
|
||||
(name kosuzu)
|
||||
(version 1.4.3)
|
||||
(license EUPL-1.2)
|
||||
(authors "orbifx <fox@orbitalfox.eu>")
|
||||
(bug_reports "mailto:logarion-dev@chaotic.ninja")
|
||||
(bug_reports "mailto:kosuzu-dev@chaotic.ninja")
|
||||
(maintainers "Izuru Yakumo <yakumo.izuru@chaotic.ninja>")
|
||||
(homepage "https://suzunaan.chaotic.ninja/logarion/")
|
||||
(source (uri https://git.chaotic.ninja/yakumo.izuru/logarion))
|
||||
(homepage "https://suzunaan.chaotic.ninja/kosuzu/")
|
||||
(source (uri git+https://git.chaotic.ninja/yakumo.izuru/kosuzu))
|
||||
|
||||
(generate_opam_files true)
|
||||
|
||||
(package
|
||||
(name logarion)
|
||||
(name kosuzu)
|
||||
(synopsis "Texts archival and exchange")
|
||||
(depends ocaml dune ocurl msgpck cmdliner))
|
||||
|
@ -1,12 +1,12 @@
|
||||
# This file is generated by dune, edit dune-project instead
|
||||
opam-version: "2.0"
|
||||
version: "1.4.0"
|
||||
version: "1.4.3"
|
||||
synopsis: "Texts archival and exchange"
|
||||
maintainer: ["Izuru Yakumo <yakumo.izuru@chaotic.ninja>"]
|
||||
authors: ["orbifx <fox@orbitalfox.eu>"]
|
||||
license: "EUPL-1.2"
|
||||
homepage: "https://suzunaan.chaotic.ninja/logarion/"
|
||||
bug-reports: "mailto:logarion-dev@chaotic.ninja"
|
||||
homepage: "https://suzunaan.chaotic.ninja/kosuzu/"
|
||||
bug-reports: "mailto:kosuzu-dev@chaotic.ninja"
|
||||
depends: ["ocaml" "dune" "ocurl" "msgpck" "cmdliner"]
|
||||
build: [
|
||||
["dune" "subst"] {pinned}
|
||||
@ -22,4 +22,4 @@ build: [
|
||||
"@doc" {with-doc}
|
||||
]
|
||||
]
|
||||
dev-repo: "https://git.chaotic.ninja/yakumo.izuru/logarion"
|
||||
dev-repo: "git+https://git.chaotic.ninja/yakumo.izuru/kosuzu"
|
4
lib/dune
4
lib/dune
@ -1,4 +1,4 @@
|
||||
(library
|
||||
(name logarion)
|
||||
(public_name logarion)
|
||||
(name kosuzu)
|
||||
(public_name kosuzu)
|
||||
(libraries text_parse text_parse.parsers unix str msgpck))
|
||||
|
5
lib/validate.ml
Normal file
5
lib/validate.ml
Normal file
@ -0,0 +1,5 @@
|
||||
let validate_id_length s = String.length s <= 32
|
||||
let validate_id_chars s = try
|
||||
String.iter (function 'a'..'z'|'A'..'Z'|'0'..'9'-> () | _ -> raise (Invalid_argument "")) s;
|
||||
true
|
||||
with Invalid_argument _ -> false
|
Loading…
x
Reference in New Issue
Block a user