├── .github └── workflows │ └── build.yml ├── .gitignore ├── CHANGES.md ├── Makefile ├── README.md ├── dune-project ├── examples ├── Makefile ├── example.ml └── test.agda ├── snippetor ├── snippetor.opam └── src ├── dune └── snippetor.ml /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Build 2 | on: [push] 3 | jobs: 4 | build: 5 | runs-on: ubuntu-latest 6 | steps: 7 | - name: Checkout code 8 | uses: actions/checkout@v4 9 | - name: Install packages 10 | run: sudo apt-get -y install ocaml ocaml-dune 11 | - name: Build 12 | run: dune build 13 | - name: Test 14 | run: | 15 | dune test 16 | make -C examples 17 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *~ 2 | _build 3 | .merlin 4 | examples/include 5 | -------------------------------------------------------------------------------- /CHANGES.md: -------------------------------------------------------------------------------- 1 | 0.2.0 (unreleased) 2 | ===== 3 | 4 | - Make prefixing with ! optional in commands. 5 | 6 | 0.1.0 (2022-09-07) 7 | ===== 8 | 9 | - Initial release. 10 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | all: 2 | @dune build 3 | 4 | clean: 5 | @dune clean 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Snippettor 2 | ========== 3 | 4 | Snippettor is a tool to extract excerpts from code (for now [OCaml](https://ocaml.org/) and [Agda](https://wiki.portal.chalmers.se/agda/) are supported). I use it to include portions of code in my LaTeX files 5 | (e.g. [this course](http://pp.mimram.fr/)), while ensuring that it is compiling. 6 | 7 | Usage 8 | ----- 9 | 10 | In order to specify snippets in OCaml code, you should enclose them with comments `(* BEGIN name *)` and `(* END *)` where `name` is the name you want to give to the snippet. For instance, consider the following file [`example.ml`](example/example.ml): 11 | 12 | ```ocaml 13 | (* BEGIN suc *) 14 | let rec suc n = 15 | if n = 0 then 1 else 1 + suc (n-1) 16 | (* END *) 17 | 18 | (* BEGIN fact *) 19 | let rec fact n = 20 | if n = 0 then 1 else n * fact (n - 1) 21 | (* END *) 22 | 23 | let () = 24 | assert (fact 5 = 120) 25 | ``` 26 | 27 | If you run the command 28 | 29 | ```sh 30 | snippetor example.ml 31 | ``` 32 | 33 | it will produce the following files in the `include` directory: 34 | 35 | - `example.ml.suc` which contains the code for the `suc` function, 36 | - `example.ml.fact` which contains the code for the `fact` function, 37 | - `example.ml` which contains the code without the comments specific to 38 | snippetor. 39 | 40 | The usage for Agda files is similar, excepting that comments are of the form `-- BEGIN name` and `-- END`. 41 | 42 | More options 43 | ------------ 44 | 45 | - You can use comments of the form `(* INDENT -2 *)` in order to unindent the following block of code by two blanks. 46 | - You can use `(* HIDE *)` to hide the following line within a snippet (and `(* HIDE 5 *)` to hide the following 5 lines). 47 | -------------------------------------------------------------------------------- /dune-project: -------------------------------------------------------------------------------- 1 | (lang dune 2.0) 2 | (name snippetor) 3 | (version 0.1.0) 4 | (generate_opam_files true) 5 | (authors "Samuel Mimram ") 6 | (maintainers "Samuel Mimram ") 7 | (source (github "smimram/snippetor")) 8 | (homepage "https://github.com/smimram/snippetor/") 9 | (bug_reports "https://github.com/smimram/snippetor/issues") 10 | (package 11 | (name snippetor) 12 | (synopsis "Extract excerpts from code") 13 | (description "Snippettor is a tool to extract excerpts from code (for now OCaml and Agda are supported). It can typically be used in order to include portions of code in LaTeX files, while ensuring that it is compiling.") 14 | ) 15 | -------------------------------------------------------------------------------- /examples/Makefile: -------------------------------------------------------------------------------- 1 | SNIPPETOR = ../snippetor 2 | 3 | all: 4 | @mkdir -p include 5 | @$(SNIPPETOR) example.ml 6 | @$(SNIPPETOR) test.agda 7 | 8 | clean: 9 | rm -rf include 10 | -------------------------------------------------------------------------------- /examples/example.ml: -------------------------------------------------------------------------------- 1 | (* BEGIN id *) 2 | (* This is the identity. *) 3 | let id x = x 4 | (* END *) 5 | 6 | (* BEGIN suc *) 7 | let rec suc n = 8 | if n = 0 then 1 else 1 + suc (n-1) 9 | (* END *) 10 | 11 | (* BEGIN fact *) 12 | let rec fact n = 13 | if n = 0 then 1 else n * fact (n - 1) 14 | (* END *) 15 | 16 | let () = 17 | assert (fact 5 = 120) 18 | -------------------------------------------------------------------------------- /examples/test.agda: -------------------------------------------------------------------------------- 1 | module _ (A : Set) where 2 | -- INENT -2 3 | -- BEGIN id 4 | id : A → A 5 | id x = x 6 | -- END 7 | -------------------------------------------------------------------------------- /snippetor: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | export DIR=$(dirname "$0") 4 | dune exec --root=$DIR src/snippetor.exe "$@" 5 | -------------------------------------------------------------------------------- /snippetor.opam: -------------------------------------------------------------------------------- 1 | # This file is generated by dune, edit dune-project instead 2 | opam-version: "2.0" 3 | version: "0.1.0" 4 | synopsis: "Extract excerpts from code" 5 | description: 6 | "Snippettor is a tool to extract excerpts from code (for now OCaml and Agda are supported). It can typically be used in order to include portions of code in LaTeX files, while ensuring that it is compiling." 7 | maintainer: ["Samuel Mimram "] 8 | authors: ["Samuel Mimram "] 9 | homepage: "https://github.com/smimram/snippetor/" 10 | bug-reports: "https://github.com/smimram/snippetor/issues" 11 | depends: [ 12 | "dune" {>= "2.0"} 13 | ] 14 | build: [ 15 | ["dune" "subst"] {pinned} 16 | [ 17 | "dune" 18 | "build" 19 | "-p" 20 | name 21 | "-j" 22 | jobs 23 | "@install" 24 | "@runtest" {with-test} 25 | "@doc" {with-doc} 26 | ] 27 | ] 28 | dev-repo: "git+https://github.com/smimram/snippetor.git" 29 | -------------------------------------------------------------------------------- /src/dune: -------------------------------------------------------------------------------- 1 | (executable 2 | (public_name snippetor) 3 | (libraries str)) 4 | -------------------------------------------------------------------------------- /src/snippetor.ml: -------------------------------------------------------------------------------- 1 | let fname = ref "" 2 | type mode = Agda | OCaml 3 | let mode = ref OCaml 4 | 5 | let () = 6 | let filename s = 7 | fname := s; 8 | match Filename.extension s with 9 | | ".ml" -> mode := OCaml 10 | | ".agda" -> mode := Agda 11 | | _ -> () 12 | in 13 | Arg.parse 14 | [ 15 | "--agda", Arg.Unit (fun () -> mode := Agda), "Agda mode."; 16 | "--ocaml", Arg.Unit (fun () -> mode := OCaml), "OCaml mode." 17 | ] 18 | filename "snippetor [options] file"; 19 | if !fname = "" then 20 | ( 21 | Printf.eprintf "Please provide a file name.\n%!"; 22 | exit 1 23 | ) 24 | 25 | module RE = struct 26 | let regexp s = 27 | let s = 28 | match !mode with 29 | | Agda -> "[ ]*-- !?" ^ s 30 | | OCaml -> "[ ]*(\\* !?" ^ s 31 | in 32 | Str.regexp s 33 | 34 | let hide = regexp "HIDE[ ]*\\([0-9]*\\)" 35 | 36 | let indent = regexp "INDENT[ ]*\\([-]?[0-9]+\\)" 37 | 38 | let start = regexp "BEGIN[ ]*\\([-a-zA-Z0-9]+\\)" 39 | 40 | let stop = regexp "END[ ]*\\([-a-zA-Z0-9]*\\)" 41 | end 42 | 43 | let () = 44 | let fname = !fname in 45 | Printf.printf "Preprocessing %s... %!" fname; 46 | let ic = open_in fname in 47 | let outdir = "include/" in 48 | let ocni = open_out (outdir^fname^".noimport") in 49 | let ocni_first = ref true in 50 | let ocs = ref ["include", open_out (outdir^fname); "include", open_out (outdir^fname^".include")] in 51 | let output l = 52 | List.iter (fun (_,oc) -> output_string oc (l^"\n")) !ocs; 53 | if String.length l < 11 || String.sub l 0 11 <> "open import" then 54 | if not (!ocni_first && l = "") then 55 | ( 56 | ocni_first := false; 57 | output_string ocni (l^"\n") 58 | ) 59 | in 60 | let hide = ref 0 in 61 | let indent = ref 0 in 62 | ( 63 | try 64 | while true do 65 | let l = input_line ic in 66 | if Str.string_match RE.hide l 0 then 67 | let n = Str.matched_group 1 l in 68 | let n = if n = "" then 1 else int_of_string n in 69 | hide := n 70 | else if Str.string_match RE.indent l 0 then 71 | let n = Str.matched_group 1 l in 72 | let n = int_of_string n in 73 | indent := n 74 | else if Str.string_match RE.start l 0 then 75 | let s = Str.matched_group 1 l in 76 | let oc = open_out (outdir^fname^"."^s) in 77 | ocs := (s, oc) :: !ocs 78 | else if Str.string_match RE.stop l 0 then 79 | let s = Str.matched_group 1 l in 80 | indent := 0; 81 | if s = "" then ocs := List.tl !ocs 82 | else ocs := List.filter (fun (s',_) -> s' <> s) !ocs 83 | else if !hide > 0 then decr hide else 84 | let l = 85 | if !indent = 0 then l 86 | else if !indent > 0 then String.make !indent ' ' ^ l 87 | else if String.length l >= - !indent then String.sub l (- !indent) (String.length l + !indent) 88 | else "" 89 | in 90 | output l 91 | done 92 | with 93 | | End_of_file -> () 94 | ); 95 | close_in ic; 96 | List.iter (fun (_,oc) -> close_out oc) !ocs; 97 | Printf.printf "done.\n%!" 98 | --------------------------------------------------------------------------------