├── .github └── workflows │ └── main.yml ├── .gitignore ├── CHANGES.md ├── LICENSE.txt ├── Makefile ├── README.md ├── dune ├── dune-project ├── example ├── dune └── quine.ml ├── integration-test ├── dune-lang-1 │ ├── Makefile │ ├── common.inc │ ├── dune-project │ ├── etc │ │ └── hostname │ ├── foo.opam │ ├── root.inc │ └── src │ │ ├── common.inc │ │ ├── dune │ │ ├── etc │ │ └── hostname │ │ ├── foo.ml │ │ └── src.inc ├── dune-lang-3 │ ├── Makefile │ ├── common.inc │ ├── dune-project │ ├── etc │ │ └── hostname │ ├── foo.opam │ ├── root.inc │ └── src │ │ ├── common.inc │ │ ├── dune │ │ ├── etc │ │ └── hostname │ │ ├── foo.ml │ │ └── src.inc ├── ocamlbuild │ ├── .gitignore │ ├── Makefile │ ├── _tags │ ├── common.inc │ ├── etc │ │ └── hostname │ ├── foo.opam │ ├── root.inc │ └── src │ │ ├── common.inc │ │ ├── etc │ │ └── hostname │ │ ├── foo.ml │ │ └── src.inc └── ocamlopt │ ├── .gitignore │ ├── Makefile │ ├── common.inc │ ├── etc │ └── hostname │ ├── foo.opam │ ├── root.inc │ └── src │ ├── common.inc │ ├── etc │ └── hostname │ ├── foo.ml │ └── src.inc ├── ppx_blob.opam ├── src ├── dune └── ppx_blob.ml └── test ├── common.inc ├── dune ├── root.inc ├── subdir ├── dune ├── sub.inc └── test.ml ├── test.ml └── test └── common.inc /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: main 2 | 3 | on: 4 | - pull_request 5 | - push 6 | 7 | jobs: 8 | build: 9 | strategy: 10 | fail-fast: false 11 | matrix: 12 | config: 13 | - {os: macos-latest, ocaml: 4.10.x} # < OCaml 4.10 is no longer unavailable 14 | - {os: macos-latest, ocaml: 4.14.x} 15 | - {os: macos-latest, ocaml: 5.2.x} 16 | - {os: ubuntu-latest, ocaml: 4.08.x} 17 | - {os: ubuntu-latest, ocaml: 4.09.x} 18 | - {os: ubuntu-latest, ocaml: 4.10.x} 19 | - {os: ubuntu-latest, ocaml: 4.11.x} 20 | - {os: ubuntu-latest, ocaml: 4.12.x} 21 | - {os: ubuntu-latest, ocaml: 4.13.x} 22 | - {os: ubuntu-latest, ocaml: 4.14.x} 23 | - {os: ubuntu-latest, ocaml: 5.0.x} 24 | - {os: ubuntu-latest, ocaml: 5.2.x} 25 | - {os: windows-latest, ocaml: 4.14.x} # < OCaml 4.14 will not compile 26 | - {os: windows-latest, ocaml: 5.2.x} 27 | runs-on: ${{ matrix.config.os }} 28 | steps: 29 | - name: Checkout code 30 | uses: actions/checkout@v4 31 | - name: Use OCaml ${{ matrix.config.ocaml }} 32 | uses: ocaml/setup-ocaml@v3 33 | with: 34 | ocaml-compiler: ${{ matrix.config.ocaml }} 35 | - run: opam pin add --no-action ppx_blob.dev . 36 | - run: opam install --deps-only --with-test . 37 | - run: opam exec -- dune build @all @runtest 38 | integration-test: 39 | strategy: 40 | fail-fast: false 41 | matrix: 42 | test-dir: 43 | - dune-lang-1 44 | - dune-lang-3 45 | - ocamlbuild 46 | - ocamlopt 47 | runs-on: ubuntu-latest 48 | steps: 49 | - name: Checkout code 50 | uses: actions/checkout@v4 51 | - name: Use OCaml ${{ matrix.ocaml-compiler }} 52 | uses: ocaml/setup-ocaml@v3 53 | with: 54 | ocaml-compiler: 4.11.x 55 | - run: opam pin add ppx_blob.dev . 56 | - run: opam pin add foo "integration-test/${{ matrix.test-dir }}" 57 | - run: opam exec -- make clean run 58 | working-directory: "integration-test/${{ matrix.test-dir }}" 59 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ._d/ 2 | ppx_blob 3 | example/quine 4 | _build 5 | _opam 6 | .merlin 7 | *.install 8 | -------------------------------------------------------------------------------- /CHANGES.md: -------------------------------------------------------------------------------- 1 | 0.8.0 2024-04-04 2 | --------------------------------- 3 | 4 | - Fix the handling of paths relative to the source file in environments with 5 | `(lang dune 3.0)` (#24). 6 | 7 | 0.7.2 2020-11-24 8 | --------------------------------- 9 | 10 | - Port to ppxlib (#20). 11 | 12 | 0.7.1 2020-09-15 13 | --------------------------------- 14 | 15 | - Use `Ast_411` from ocaml-migrate-parsetree for compatibility with new OCaml syntax (#19). 16 | 17 | 0.7.0 2020-03-24 18 | --------------------------------- 19 | 20 | - Use `Ast_408` from ocaml-migrate-parsetree for compatibility with new OCaml syntax (#17). 21 | 22 | 0.6 2019-12-31 23 | --------------------------------- 24 | 25 | - Migrate to dune and dune-release 26 | 27 | 0.4 2018-02-10 28 | --------------------------------- 29 | 30 | - Check if file exists relative to working directory (#9) 31 | 32 | 0.3 2017-08-22 33 | --------------------------------- 34 | 35 | - Migrate to jbuilder and ocaml-migrate-parsetree (#7) 36 | 37 | 0.2 2016-04-18 38 | --------------------------------- 39 | 40 | - Merge pull request #3 from aantron/updates 41 | - Port to 4.03 and some fixes 42 | 43 | 0.1 2014-10-11 44 | --------------------------------- 45 | 46 | - Version 0.1 for opam 47 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | This is free and unencumbered software released into the public domain. 2 | 3 | Anyone is free to copy, modify, publish, use, compile, sell, or 4 | distribute this software, either in source code form or as a compiled 5 | binary, for any purpose, commercial or non-commercial, and by any 6 | means. 7 | 8 | In jurisdictions that recognize copyright laws, the author or authors 9 | of this software dedicate any and all copyright interest in the 10 | software to the public domain. We make this dedication for the benefit 11 | of the public at large and to the detriment of our heirs and 12 | successors. We intend this dedication to be an overt act of 13 | relinquishment in perpetuity of all present and future rights to this 14 | software under copyright law. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | For more information, please refer to 25 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: all 2 | all: 3 | dune build @install @example 4 | 5 | .PHONY: test 6 | test: 7 | dune runtest 8 | 9 | .PHONY: clean 10 | clean: 11 | dune clean 12 | 13 | .PHONY: tag 14 | tag: 15 | dune-release tag 16 | 17 | .PHONY: distrib 18 | distrib: 19 | dune-release distrib 20 | 21 | .PHONY: publish 22 | publish: 23 | dune-release publish 24 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ppx_blob 2 | 3 | This is an OCaml PPX to include a binary blob from a file as a string. Writing 4 | `[%blob "filename"]` will replace the string with the contents of the file at compile 5 | time. This allows the inclusion of arbitrary, possibly compressed, data, without the need 6 | to respect OCaml's lexical conventions. 7 | 8 | The filename can be relative to either the source file where `[%blob]` appears, or 9 | relative to the current working directory. If both files exist, the former takes 10 | precedence. 11 | 12 | ## Integration with build systems 13 | 14 | ### Dune 15 | 16 | Add `(preprocessor_deps (file path/to/file))` to your library or executable stanza. See 17 | [test/dune](test/dune) for an example. This will make sure the file is copied to the build 18 | directory and therefore visible to `ppx_blob`. 19 | 20 | ## Development 21 | 22 | Requirements: 23 | 24 | - OCaml 4.08+ 25 | - Packages: see [ppx_blob.opam](ppx_blob.opam). 26 | 27 | Run `make` and `make test`. 28 | -------------------------------------------------------------------------------- /dune: -------------------------------------------------------------------------------- 1 | (dirs :standard \ integration-test) 2 | -------------------------------------------------------------------------------- /dune-project: -------------------------------------------------------------------------------- 1 | (lang dune 1.11) 2 | (name ppx_blob) 3 | (explicit_js_mode) 4 | -------------------------------------------------------------------------------- /example/dune: -------------------------------------------------------------------------------- 1 | (executable 2 | (name quine) 3 | (preprocess (pps ppx_blob))) 4 | 5 | (alias 6 | (name example) 7 | (deps quine.exe)) 8 | -------------------------------------------------------------------------------- /example/quine.ml: -------------------------------------------------------------------------------- 1 | print_string [%blob "quine.ml"] 2 | -------------------------------------------------------------------------------- /integration-test/dune-lang-1/Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: run 2 | run: 3 | dune exec --root . ./src/foo.exe 4 | 5 | .PHONY: clean 6 | clean: 7 | dune clean --root . 8 | -------------------------------------------------------------------------------- /integration-test/dune-lang-1/common.inc: -------------------------------------------------------------------------------- 1 | included-common-root 2 | -------------------------------------------------------------------------------- /integration-test/dune-lang-1/dune-project: -------------------------------------------------------------------------------- 1 | (lang dune 1.0) 2 | -------------------------------------------------------------------------------- /integration-test/dune-lang-1/etc/hostname: -------------------------------------------------------------------------------- 1 | included-hostname 2 | -------------------------------------------------------------------------------- /integration-test/dune-lang-1/foo.opam: -------------------------------------------------------------------------------- 1 | opam-version: "2.0" 2 | authors: "John Whitington" 3 | maintainer: "contact@coherentgraphics.co.uk" 4 | homepage: "https://github.com/johnwhitington/ppx_blob" 5 | dev-repo: "git+https://github.com/johnwhitington/ppx_blob.git" 6 | bug-reports: "https://github.com/johnwhitington/ppx_blob/issues/" 7 | doc: "https://johnwhitington.github.io/ppx_blob/" 8 | build: [ 9 | ["dune" "build" "-p" name "-j" jobs] 10 | ["dune" "runtest" "-p" name "-j" jobs] {with-test} 11 | ] 12 | depends: [ 13 | "ocaml" 14 | "dune" {= "1.11.4"} 15 | "ppx_blob" 16 | ] 17 | synopsis: "Test case for ppx_blob" 18 | description: "Test case for ppx_blob" 19 | license: "Unlicense" 20 | -------------------------------------------------------------------------------- /integration-test/dune-lang-1/root.inc: -------------------------------------------------------------------------------- 1 | included-root 2 | -------------------------------------------------------------------------------- /integration-test/dune-lang-1/src/common.inc: -------------------------------------------------------------------------------- 1 | included-common-src 2 | -------------------------------------------------------------------------------- /integration-test/dune-lang-1/src/dune: -------------------------------------------------------------------------------- 1 | (executable 2 | (name foo) 3 | (preprocess (pps ppx_blob)) 4 | (preprocessor_deps (file ../root.inc) (file src.inc) 5 | (file ../common.inc) (file common.inc) 6 | (file ../etc/hostname) (file etc/hostname))) 7 | -------------------------------------------------------------------------------- /integration-test/dune-lang-1/src/etc/hostname: -------------------------------------------------------------------------------- 1 | included-hostname 2 | -------------------------------------------------------------------------------- /integration-test/dune-lang-1/src/foo.ml: -------------------------------------------------------------------------------- 1 | let check ~path ~expected ~actual = 2 | let actual = String.trim actual in 3 | if String.equal expected actual then ( 4 | Printf.printf "PASS: path: %s\n" path; 5 | Ok ()) 6 | else ( 7 | Printf.printf "FAIL: path: %s, expected: %s, actual: %s\n" path expected 8 | actual; 9 | Error ()) 10 | 11 | let () = 12 | if 13 | List.exists Result.is_error 14 | [ 15 | (* Path relative to this file *) 16 | check ~path:"../root.inc" ~expected:"included-root" 17 | ~actual:[%blob "../root.inc"]; 18 | check ~path:"src.inc" ~expected:"included-src" ~actual:[%blob "src.inc"]; 19 | (* Path relative to build directory *) 20 | check ~path:"root.inc" ~expected:"included-root" 21 | ~actual:[%blob "root.inc"]; 22 | check ~path:"src/src.inc" ~expected:"included-src" 23 | ~actual:[%blob "src/src.inc"]; 24 | (* Ambiguous path *) 25 | check ~path:"common.inc" ~expected:"included-common-src" 26 | ~actual:[%blob "common.inc"]; 27 | check ~path:"../common.inc" ~expected:"included-common-root" 28 | ~actual:[%blob "../common.inc"]; 29 | (* Absolute path *) 30 | check ~path:"/etc/hostname" 31 | ~expected:(input_line (open_in "/etc/hostname")) 32 | ~actual:[%blob "/etc/hostname"]; 33 | ] 34 | then exit 1 35 | -------------------------------------------------------------------------------- /integration-test/dune-lang-1/src/src.inc: -------------------------------------------------------------------------------- 1 | included-src 2 | -------------------------------------------------------------------------------- /integration-test/dune-lang-3/Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: run 2 | run: 3 | dune exec --root . ./src/foo.exe 4 | 5 | .PHONY: clean 6 | clean: 7 | dune clean --root . 8 | -------------------------------------------------------------------------------- /integration-test/dune-lang-3/common.inc: -------------------------------------------------------------------------------- 1 | included-common-root 2 | -------------------------------------------------------------------------------- /integration-test/dune-lang-3/dune-project: -------------------------------------------------------------------------------- 1 | (lang dune 3.0) 2 | -------------------------------------------------------------------------------- /integration-test/dune-lang-3/etc/hostname: -------------------------------------------------------------------------------- 1 | included-hostname 2 | -------------------------------------------------------------------------------- /integration-test/dune-lang-3/foo.opam: -------------------------------------------------------------------------------- 1 | opam-version: "2.0" 2 | authors: "John Whitington" 3 | maintainer: "contact@coherentgraphics.co.uk" 4 | homepage: "https://github.com/johnwhitington/ppx_blob" 5 | dev-repo: "git+https://github.com/johnwhitington/ppx_blob.git" 6 | bug-reports: "https://github.com/johnwhitington/ppx_blob/issues/" 7 | doc: "https://johnwhitington.github.io/ppx_blob/" 8 | build: [ 9 | ["dune" "build" "-p" name "-j" jobs] 10 | ["dune" "runtest" "-p" name "-j" jobs] {with-test} 11 | ] 12 | depends: [ 13 | "ocaml" 14 | "dune" {>= "3"} 15 | "ppx_blob" 16 | ] 17 | synopsis: "Test case for ppx_blob" 18 | description: "Test case for ppx_blob" 19 | license: "Unlicense" 20 | -------------------------------------------------------------------------------- /integration-test/dune-lang-3/root.inc: -------------------------------------------------------------------------------- 1 | included-root 2 | -------------------------------------------------------------------------------- /integration-test/dune-lang-3/src/common.inc: -------------------------------------------------------------------------------- 1 | included-common-src 2 | -------------------------------------------------------------------------------- /integration-test/dune-lang-3/src/dune: -------------------------------------------------------------------------------- 1 | (executable 2 | (name foo) 3 | (preprocess (pps ppx_blob)) 4 | (preprocessor_deps (file ../root.inc) (file src.inc) 5 | (file ../common.inc) (file common.inc) 6 | (file ../etc/hostname) (file etc/hostname))) 7 | -------------------------------------------------------------------------------- /integration-test/dune-lang-3/src/etc/hostname: -------------------------------------------------------------------------------- 1 | included-hostname 2 | -------------------------------------------------------------------------------- /integration-test/dune-lang-3/src/foo.ml: -------------------------------------------------------------------------------- 1 | let check ~path ~expected ~actual = 2 | let actual = String.trim actual in 3 | if String.equal expected actual then ( 4 | Printf.printf "PASS: path: %s\n" path; 5 | Ok ()) 6 | else ( 7 | Printf.printf "FAIL: path: %s, expected: %s, actual: %s\n" path expected 8 | actual; 9 | Error ()) 10 | 11 | let () = 12 | if 13 | List.exists Result.is_error 14 | [ 15 | (* Path relative to this file *) 16 | check ~path:"../root.inc" ~expected:"included-root" 17 | ~actual:[%blob "../root.inc"]; 18 | check ~path:"src.inc" ~expected:"included-src" ~actual:[%blob "src.inc"]; 19 | (* Path relative to build directory *) 20 | check ~path:"root.inc" ~expected:"included-root" 21 | ~actual:[%blob "root.inc"]; 22 | check ~path:"src/src.inc" ~expected:"included-src" 23 | ~actual:[%blob "src/src.inc"]; 24 | (* Ambiguous path *) 25 | check ~path:"common.inc" ~expected:"included-common-src" 26 | ~actual:[%blob "common.inc"]; 27 | check ~path:"../common.inc" ~expected:"included-common-root" 28 | ~actual:[%blob "../common.inc"]; 29 | (* Absolute path *) 30 | check ~path:"/etc/hostname" 31 | ~expected:(input_line (open_in "/etc/hostname")) 32 | ~actual:[%blob "/etc/hostname"]; 33 | ] 34 | then exit 1 35 | -------------------------------------------------------------------------------- /integration-test/dune-lang-3/src/src.inc: -------------------------------------------------------------------------------- 1 | included-src 2 | -------------------------------------------------------------------------------- /integration-test/ocamlbuild/.gitignore: -------------------------------------------------------------------------------- 1 | *.native 2 | -------------------------------------------------------------------------------- /integration-test/ocamlbuild/Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: run 2 | run: 3 | ocamlbuild -use-ocamlfind src/foo.native 4 | ./foo.native 5 | 6 | .PHONY: clean 7 | clean: 8 | ocamlbuild -clean 9 | -------------------------------------------------------------------------------- /integration-test/ocamlbuild/_tags: -------------------------------------------------------------------------------- 1 | true: package(ppx_blob) 2 | -------------------------------------------------------------------------------- /integration-test/ocamlbuild/common.inc: -------------------------------------------------------------------------------- 1 | included-common-root 2 | -------------------------------------------------------------------------------- /integration-test/ocamlbuild/etc/hostname: -------------------------------------------------------------------------------- 1 | included-hostname 2 | -------------------------------------------------------------------------------- /integration-test/ocamlbuild/foo.opam: -------------------------------------------------------------------------------- 1 | opam-version: "2.0" 2 | authors: "John Whitington" 3 | maintainer: "contact@coherentgraphics.co.uk" 4 | homepage: "https://github.com/johnwhitington/ppx_blob" 5 | dev-repo: "git+https://github.com/johnwhitington/ppx_blob.git" 6 | bug-reports: "https://github.com/johnwhitington/ppx_blob/issues/" 7 | doc: "https://johnwhitington.github.io/ppx_blob/" 8 | build: [ 9 | ["dune" "build" "-p" name "-j" jobs] 10 | ["dune" "runtest" "-p" name "-j" jobs] {with-test} 11 | ] 12 | depends: [ 13 | "ocaml" 14 | "ocamlbuild" 15 | "ocamlfind" 16 | "ppx_blob" 17 | ] 18 | synopsis: "Test case for ppx_blob" 19 | description: "Test case for ppx_blob" 20 | license: "Unlicense" 21 | -------------------------------------------------------------------------------- /integration-test/ocamlbuild/root.inc: -------------------------------------------------------------------------------- 1 | included-root 2 | -------------------------------------------------------------------------------- /integration-test/ocamlbuild/src/common.inc: -------------------------------------------------------------------------------- 1 | included-common-src 2 | -------------------------------------------------------------------------------- /integration-test/ocamlbuild/src/etc/hostname: -------------------------------------------------------------------------------- 1 | included-hostname 2 | -------------------------------------------------------------------------------- /integration-test/ocamlbuild/src/foo.ml: -------------------------------------------------------------------------------- 1 | let check ~path ~expected ~actual = 2 | let actual = String.trim actual in 3 | if String.equal expected actual then ( 4 | Printf.printf "PASS: path: %s\n" path; 5 | Ok ()) 6 | else ( 7 | Printf.printf "FAIL: path: %s, expected: %s, actual: %s\n" path expected 8 | actual; 9 | Error ()) 10 | 11 | let () = 12 | if 13 | List.exists Result.is_error 14 | [ 15 | (* Path relative to this file but outside of build directory *) 16 | check ~path:"../../root.inc" ~expected:"included-root" 17 | ~actual:[%blob "../../root.inc"]; 18 | check ~path:"../../src.inc" ~expected:"included-src" 19 | ~actual:[%blob "../../src/src.inc"]; 20 | (* Path relative to build directory but outside of it *) 21 | check ~path:"../root.inc" ~expected:"included-root" 22 | ~actual:[%blob "../root.inc"]; 23 | check ~path:"../src/src.inc" ~expected:"included-src" 24 | ~actual:[%blob "../src/src.inc"]; 25 | (* Ambiguous path *) 26 | check ~path:"../common.inc" ~expected:"included-common-root" 27 | ~actual:[%blob "../common.inc"]; 28 | check ~path:"../../common.inc" ~expected:"included-common-root" 29 | ~actual:[%blob "../../common.inc"]; 30 | (* Absolute path *) 31 | check ~path:"/etc/hostname" 32 | ~expected:(input_line (open_in "/etc/hostname")) 33 | ~actual:[%blob "/etc/hostname"]; 34 | ] 35 | then exit 1 36 | -------------------------------------------------------------------------------- /integration-test/ocamlbuild/src/src.inc: -------------------------------------------------------------------------------- 1 | included-src 2 | -------------------------------------------------------------------------------- /integration-test/ocamlopt/.gitignore: -------------------------------------------------------------------------------- 1 | *.cm[ix] 2 | *.native 3 | *.o 4 | -------------------------------------------------------------------------------- /integration-test/ocamlopt/Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: run 2 | run: 3 | ocamlopt -ppx "$(shell ocamlfind query ppx_blob)/ppx.exe --as-ppx" \ 4 | -o foo.native \ 5 | src/foo.ml 6 | ./foo.native 7 | 8 | .PHONY: clean 9 | clean: 10 | rm -f foo.native src/foo.{o,cmx,cmi} 11 | -------------------------------------------------------------------------------- /integration-test/ocamlopt/common.inc: -------------------------------------------------------------------------------- 1 | included-common-root 2 | -------------------------------------------------------------------------------- /integration-test/ocamlopt/etc/hostname: -------------------------------------------------------------------------------- 1 | included-hostname 2 | -------------------------------------------------------------------------------- /integration-test/ocamlopt/foo.opam: -------------------------------------------------------------------------------- 1 | opam-version: "2.0" 2 | authors: "John Whitington" 3 | maintainer: "contact@coherentgraphics.co.uk" 4 | homepage: "https://github.com/johnwhitington/ppx_blob" 5 | dev-repo: "git+https://github.com/johnwhitington/ppx_blob.git" 6 | bug-reports: "https://github.com/johnwhitington/ppx_blob/issues/" 7 | doc: "https://johnwhitington.github.io/ppx_blob/" 8 | depends: [ 9 | "ocaml" 10 | "ocamlfind" 11 | "ppx_blob" 12 | ] 13 | synopsis: "Test case for ppx_blob" 14 | description: "Test case for ppx_blob" 15 | license: "Unlicense" 16 | -------------------------------------------------------------------------------- /integration-test/ocamlopt/root.inc: -------------------------------------------------------------------------------- 1 | included-root 2 | -------------------------------------------------------------------------------- /integration-test/ocamlopt/src/common.inc: -------------------------------------------------------------------------------- 1 | included-common-src 2 | -------------------------------------------------------------------------------- /integration-test/ocamlopt/src/etc/hostname: -------------------------------------------------------------------------------- 1 | included-hostname 2 | -------------------------------------------------------------------------------- /integration-test/ocamlopt/src/foo.ml: -------------------------------------------------------------------------------- 1 | let check ~path ~expected ~actual = 2 | let actual = String.trim actual in 3 | if String.equal expected actual then ( 4 | Printf.printf "PASS: path: %s\n" path; 5 | Ok ()) 6 | else ( 7 | Printf.printf "FAIL: path: %s, expected: %s, actual: %s\n" path expected 8 | actual; 9 | Error ()) 10 | 11 | let () = 12 | if 13 | List.exists Result.is_error 14 | [ 15 | (* Path relative to this file *) 16 | check ~path:"../root.inc" ~expected:"included-root" 17 | ~actual:[%blob "../root.inc"]; 18 | check ~path:"src.inc" ~expected:"included-src" ~actual:[%blob "src.inc"]; 19 | (* Path relative to build directory *) 20 | check ~path:"root.inc" ~expected:"included-root" 21 | ~actual:[%blob "root.inc"]; 22 | check ~path:"src/src.inc" ~expected:"included-src" 23 | ~actual:[%blob "src/src.inc"]; 24 | (* Ambiguous path *) 25 | check ~path:"common.inc" ~expected:"included-common-src" 26 | ~actual:[%blob "common.inc"]; 27 | check ~path:"../common.inc" ~expected:"included-common-root" 28 | ~actual:[%blob "../common.inc"]; 29 | (* Absolute path *) 30 | check ~path:"/etc/hostname" 31 | ~expected:(input_line (open_in "/etc/hostname")) 32 | ~actual:[%blob "/etc/hostname"]; 33 | ] 34 | then exit 1 35 | -------------------------------------------------------------------------------- /integration-test/ocamlopt/src/src.inc: -------------------------------------------------------------------------------- 1 | included-src 2 | -------------------------------------------------------------------------------- /ppx_blob.opam: -------------------------------------------------------------------------------- 1 | opam-version: "2.0" 2 | authors: "John Whitington" 3 | maintainer: "contact@coherentgraphics.co.uk" 4 | homepage: "https://github.com/johnwhitington/ppx_blob" 5 | dev-repo: "git+https://github.com/johnwhitington/ppx_blob.git" 6 | bug-reports: "https://github.com/johnwhitington/ppx_blob/issues/" 7 | doc: "https://johnwhitington.github.io/ppx_blob/" 8 | license: "Unlicense" 9 | build: [ 10 | ["dune" "build" "-p" name "-j" jobs] 11 | ["dune" "runtest" "-p" name "-j" jobs] {with-test} 12 | ] 13 | depends: [ 14 | "ocaml" {>= "4.08"} 15 | "dune" {>= "1.11"} 16 | "ppxlib" {>= "0.9.0"} 17 | "alcotest" {with-test} 18 | ] 19 | synopsis: "Include a file as a string at compile time" 20 | description: 21 | "ppx_blob allows you to include a binary blob from a file as a string. Writing `[%blob \"filename\"]` will replace the string with the contents of the file at compile time. This allows the inclusion of arbitary, possibly compressed, data, without the need to respect OCaml's lexical conventions." 22 | -------------------------------------------------------------------------------- /src/dune: -------------------------------------------------------------------------------- 1 | (library 2 | (name ppx_blob) 3 | (public_name ppx_blob) 4 | (kind ppx_rewriter) 5 | (libraries ppxlib)) 6 | -------------------------------------------------------------------------------- /src/ppx_blob.ml: -------------------------------------------------------------------------------- 1 | let location_errorf ~loc = 2 | Format.ksprintf (fun err -> 3 | raise (Ocaml_common.Location.Error (Ocaml_common.Location.error ~loc err)) 4 | ) 5 | 6 | (* Same as [List.find_map] introduced in OCaml 4.10. *) 7 | let rec find_map f = function 8 | | [] -> None 9 | | x :: l -> 10 | (match f x with 11 | | Some _ as result -> result 12 | | None -> find_map f l) 13 | 14 | (* Return the list of paths we should try using, in order. *) 15 | let get_candidate_paths ~loc path = 16 | let source_dir = loc.Ocaml_common.Location.loc_start.pos_fname |> Filename.dirname in 17 | if Filename.is_relative path then 18 | let absolute_path = Filename.concat source_dir path in 19 | (* Try the path relative to the source file first, then the one relative to the 20 | current working directory (typically, the build directory). *) 21 | [absolute_path; path] 22 | else 23 | (* The user passed an absolute path. Use as is. *) 24 | [path] 25 | 26 | let read_file path = 27 | try 28 | let file = open_in_bin path in 29 | Fun.protect 30 | ~finally:(fun () -> close_in_noerr file) 31 | (fun () -> 32 | Some (really_input_string file (in_channel_length file))) 33 | with _ -> 34 | None 35 | 36 | let get_blob ~loc path = 37 | match find_map read_file (get_candidate_paths ~loc path) with 38 | | Some blob -> blob 39 | | None -> location_errorf ~loc "[%%blob] could not find or load file %s" path 40 | 41 | let expand ~ctxt path = 42 | let open Ppxlib in 43 | let loc = Expansion_context.Extension.extension_point_loc ctxt in 44 | Ast_builder.Default.estring ~loc (get_blob ~loc path) 45 | 46 | let extension = 47 | let open Ppxlib in 48 | Extension.V3.declare "blob" Extension.Context.expression 49 | Ast_pattern.(single_expr_payload (estring __)) 50 | expand 51 | 52 | let rule = Ppxlib.Context_free.Rule.extension extension 53 | 54 | let () = 55 | Ppxlib.Driver.register_transformation ~rules:[rule] "ppx_blob" 56 | -------------------------------------------------------------------------------- /test/common.inc: -------------------------------------------------------------------------------- 1 | included-common-root 2 | -------------------------------------------------------------------------------- /test/dune: -------------------------------------------------------------------------------- 1 | (tests 2 | (names test) 3 | (libraries alcotest) 4 | (preprocess (pps ppx_blob)) 5 | (preprocessor_deps 6 | (file root.inc) 7 | (file common.inc) 8 | (file test/common.inc) 9 | (file subdir/sub.inc))) 10 | -------------------------------------------------------------------------------- /test/root.inc: -------------------------------------------------------------------------------- 1 | included-root 2 | -------------------------------------------------------------------------------- /test/subdir/dune: -------------------------------------------------------------------------------- 1 | (tests 2 | (names test) 3 | (libraries alcotest) 4 | (preprocess (pps ppx_blob)) 5 | (preprocessor_deps 6 | (file ../root.inc) 7 | (file ../common.inc) 8 | (file sub.inc))) 9 | -------------------------------------------------------------------------------- /test/subdir/sub.inc: -------------------------------------------------------------------------------- 1 | included-sub 2 | -------------------------------------------------------------------------------- /test/subdir/test.ml: -------------------------------------------------------------------------------- 1 | let suite = [ 2 | ("relative to source file: root", `Quick, fun () -> 3 | Alcotest.(check string) "blob" "included-root\n" [%blob "../root.inc"] 4 | ); 5 | ("relative to source file: subdir", `Quick, fun () -> 6 | Alcotest.(check string) "blob" "included-sub\n" [%blob "../subdir/sub.inc"] 7 | ); 8 | ("relative to build directory: root", `Quick, fun () -> 9 | Alcotest.(check string) "blob" "included-root\n" [%blob "test/root.inc"] 10 | ); 11 | ("relative to build directory: subdir", `Quick, fun () -> 12 | Alcotest.(check string) "blob" "included-sub\n" [%blob "test/subdir/sub.inc"] 13 | ); 14 | ] 15 | 16 | let () = 17 | Alcotest.run "ppx_blob" [ 18 | "basic", suite 19 | ] 20 | -------------------------------------------------------------------------------- /test/test.ml: -------------------------------------------------------------------------------- 1 | let suite = [ 2 | ("relative to source file: root", `Quick, fun () -> 3 | Alcotest.(check string) "blob" "included-root\n" [%blob "root.inc"] 4 | ); 5 | ("relative to source file: subdir", `Quick, fun () -> 6 | Alcotest.(check string) "blob" "included-sub\n" [%blob "subdir/sub.inc"] 7 | ); 8 | ("relative to build directory: root", `Quick, fun () -> 9 | Alcotest.(check string) "blob" "included-root\n" [%blob "test/root.inc"] 10 | ); 11 | ("relative to build directory: subdir", `Quick, fun () -> 12 | Alcotest.(check string) "blob" "included-sub\n" [%blob "test/subdir/sub.inc"] 13 | ); 14 | ("ambiguous path", `Quick, fun () -> 15 | Alcotest.(check string) "blob" "included-common-sub\n" [%blob "test/common.inc"] 16 | ); 17 | ] 18 | 19 | let () = 20 | Alcotest.run "ppx_blob" [ 21 | "basic", suite 22 | ] 23 | -------------------------------------------------------------------------------- /test/test/common.inc: -------------------------------------------------------------------------------- 1 | included-common-sub 2 | --------------------------------------------------------------------------------