Source

footnotes-ocaml / footnotes.ml

(* compile with: ocamlopt -o footnotes str.cmxa footnotes.ml *)

let sep_line = "@footnote:";;		(* the seperator line *)

let ref_regexp = Str.regexp "\\[\\([0-9]+\\)\\]";;

let ref_of_int n = "[" ^ string_of_int n ^ "]";;

let output_endline c s = output_string c s; output_char c '\n';;

exception End_of_body;;

(* footnotes: read from channel inc and print with renumbered references
 *            and reordered footnotes to channel outc *)
let footnotes inc outc =
  let refs = Hashtbl.create 1000000 in
  let sub_ref l ref =
    let old_n = int_of_string (Str.matched_group 1 l) in
    ref_of_int
      (try Hashtbl.find refs old_n
      with Not_found ->
	let n = Hashtbl.length refs + 1 in
	Hashtbl.add refs old_n n;
	n)
  in
  try
    while true do
      let l = input_line inc in
      if l = sep_line then raise End_of_body;
      output_endline outc (Str.global_substitute ref_regexp (sub_ref l) l)
    done
  with
    End_of_file ->
      failwith "Missing footnotes part"
  | End_of_body ->
      output_endline outc sep_line;
      let foots = Array.make (Hashtbl.length refs) None in
      let add_foot l =
	if Str.string_match ref_regexp l 0 then
	  let old_n = int_of_string (Str.matched_group 1 l) in
	  try
	    let n = Hashtbl.find refs old_n in
	    foots.(n-1) <- Some (Str.string_after l (Str.match_end ()))
	  with
	    Not_found -> prerr_endline ("unreferenced footnote: " ^ l)
	else prerr_endline ("malformed footnote: " ^ l)
      in
      try
	while true do
	  let l = input_line inc in
	  add_foot l
	done
      with End_of_file ->
	let print_foot idx elt =
	  output_string outc (ref_of_int (idx + 1));
	  match elt with
	    None   -> output_endline outc " ### missing footnote ###"
	  | Some l -> output_endline outc l in
	Array.iteri print_foot foots;;

(* main: call footnotes either with given files or with stdin *)
let main () =
  if Array.length Sys.argv > 1 then
    let do_file f =
      let c = open_in f in
      (try footnotes c stdout with e -> close_in c; raise e);
      close_in c
    in
    Arg.parse [] do_file "Usage: footnotes [file...]"
  else footnotes stdin stdout;;

main ();;
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.