mirror of
https://github.com/CatalaLang/catala.git
synced 2024-11-08 07:51:43 +03:00
Clerk: add support for basic configuration files
Not much there at the moment, but being able to specify the include directories is already pretty useful to run clerk directly e.g. on `catala-examples`. (you had to explicitely specify variable `CATALA_INCLUDE`, the `-I` flags or to go through `make` without that)
This commit is contained in:
parent
31adaa019f
commit
05752988e6
112
build_system/clerk_config.ml
Normal file
112
build_system/clerk_config.ml
Normal file
@ -0,0 +1,112 @@
|
|||||||
|
(* This file is part of the Catala build system, a specification language for
|
||||||
|
tax and social benefits computation rules. Copyright (C) 2024 Inria,
|
||||||
|
contributors: Louis Gesbert <louis.gesbert@inria.fr>
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
||||||
|
use this file except in compliance with the License. You may obtain a copy of
|
||||||
|
the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
License for the specific language governing permissions and limitations under
|
||||||
|
the License. *)
|
||||||
|
|
||||||
|
open Catala_utils
|
||||||
|
open Otoml
|
||||||
|
|
||||||
|
type t = {
|
||||||
|
catala_opts : string list;
|
||||||
|
build_dir : File.t;
|
||||||
|
include_dirs : File.t list;
|
||||||
|
}
|
||||||
|
|
||||||
|
let default = { catala_opts = []; build_dir = "_build"; include_dirs = [] }
|
||||||
|
|
||||||
|
let toml_to_config toml =
|
||||||
|
{
|
||||||
|
catala_opts = Helpers.find_strings_exn toml ["build"; "catala_opts"];
|
||||||
|
build_dir = Helpers.find_string_exn toml ["build"; "build_dir"];
|
||||||
|
include_dirs = Helpers.find_strings_exn toml ["project"; "include_dirs"];
|
||||||
|
}
|
||||||
|
|
||||||
|
let config_to_toml t =
|
||||||
|
table
|
||||||
|
[
|
||||||
|
( "build",
|
||||||
|
table
|
||||||
|
[
|
||||||
|
"catala_opts", array (List.map string t.catala_opts);
|
||||||
|
"build_dir", string t.build_dir;
|
||||||
|
] );
|
||||||
|
"project", table ["include_dirs", array (List.map string t.include_dirs)];
|
||||||
|
]
|
||||||
|
|
||||||
|
let default_toml = config_to_toml default
|
||||||
|
|
||||||
|
(* joins default and supplied conf, ensuring types match. The filename is for
|
||||||
|
error reporting *)
|
||||||
|
let rec join ?(rpath = []) fname t1 t2 =
|
||||||
|
match t1, t2 with
|
||||||
|
| TomlString _, TomlString _
|
||||||
|
| TomlInteger _, TomlInteger _
|
||||||
|
| TomlFloat _, TomlFloat _
|
||||||
|
| TomlBoolean _, TomlBoolean _
|
||||||
|
| TomlOffsetDateTime _, TomlOffsetDateTime _
|
||||||
|
| TomlLocalDateTime _, TomlLocalDateTime _
|
||||||
|
| TomlLocalDate _, TomlLocalDate _
|
||||||
|
| TomlLocalTime _, TomlLocalTime _
|
||||||
|
| TomlArray _, TomlArray _
|
||||||
|
| TomlTableArray _, TomlTableArray _ ->
|
||||||
|
t2
|
||||||
|
| TomlTable tt1, TomlTable tt2 | TomlInlineTable tt1, TomlInlineTable tt2 ->
|
||||||
|
let m1 = String.Map.of_list tt1 in
|
||||||
|
let m2 = String.Map.of_list tt2 in
|
||||||
|
TomlTable
|
||||||
|
(String.Map.merge
|
||||||
|
(fun key t1 t2 ->
|
||||||
|
match t1, t2 with
|
||||||
|
| None, Some _ ->
|
||||||
|
Message.error
|
||||||
|
"While parsing %a: invalid key @{<red>%S@} at @{<bold>%s@}"
|
||||||
|
File.format fname key
|
||||||
|
(if rpath = [] then "file root"
|
||||||
|
else String.concat "." (List.rev rpath))
|
||||||
|
| Some t1, Some t2 -> Some (join ~rpath:(key :: rpath) fname t1 t2)
|
||||||
|
| Some t1, None -> Some t1
|
||||||
|
| None, None -> assert false)
|
||||||
|
m1 m2
|
||||||
|
|> String.Map.bindings)
|
||||||
|
| _ ->
|
||||||
|
Message.error
|
||||||
|
"While parsing %a: Wrong type for config value @{<bold>%s@}, was \
|
||||||
|
expecting @{<bold>%s@}"
|
||||||
|
File.format fname
|
||||||
|
(String.concat "." (List.rev rpath))
|
||||||
|
(match t1 with
|
||||||
|
| TomlString _ -> "a string"
|
||||||
|
| TomlInteger _ -> "an integer"
|
||||||
|
| TomlFloat _ -> "a float"
|
||||||
|
| TomlBoolean _ -> "a boolean"
|
||||||
|
| TomlOffsetDateTime _ -> "an offsetdatetime"
|
||||||
|
| TomlLocalDateTime _ -> "a localdatetime"
|
||||||
|
| TomlLocalDate _ -> "a localdate"
|
||||||
|
| TomlLocalTime _ -> "a localtime"
|
||||||
|
| TomlArray _ | TomlTableArray _ -> "an array"
|
||||||
|
| TomlTable _ | TomlInlineTable _ -> "a table")
|
||||||
|
|
||||||
|
let read f =
|
||||||
|
let toml =
|
||||||
|
try Parser.from_file f
|
||||||
|
with Parse_error (Some (li, col), msg) ->
|
||||||
|
Message.error
|
||||||
|
~pos:(Pos.from_info f li col li (col + 1))
|
||||||
|
"Error in Clerk configuration:@ %a" Format.pp_print_text msg
|
||||||
|
in
|
||||||
|
toml_to_config (join f default_toml toml)
|
||||||
|
|
||||||
|
let write f t =
|
||||||
|
let toml = config_to_toml t in
|
||||||
|
File.with_out_channel f @@ fun oc -> Printer.to_channel oc toml
|
27
build_system/clerk_config.mli
Normal file
27
build_system/clerk_config.mli
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
(* This file is part of the Catala build system, a specification language for
|
||||||
|
tax and social benefits computation rules. Copyright (C) 2024 Inria,
|
||||||
|
contributors: Louis Gesbert <louis.gesbert@inria.fr>
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
||||||
|
use this file except in compliance with the License. You may obtain a copy of
|
||||||
|
the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
License for the specific language governing permissions and limitations under
|
||||||
|
the License. *)
|
||||||
|
|
||||||
|
open Catala_utils
|
||||||
|
|
||||||
|
type t = {
|
||||||
|
catala_opts : string list;
|
||||||
|
build_dir : File.t;
|
||||||
|
include_dirs : File.t list;
|
||||||
|
}
|
||||||
|
|
||||||
|
val default : t
|
||||||
|
val read : File.t -> t
|
||||||
|
val write : File.t -> t -> unit
|
@ -1,7 +1,7 @@
|
|||||||
(* This file is part of the Catala build system, a specification language for
|
(* This file is part of the Catala build system, a specification language for
|
||||||
tax and social benefits computation rules. Copyright (C) 2020 Inria,
|
tax and social benefits computation rules. Copyright (C) 2020 Inria,
|
||||||
contributors: Denis Merigoux <denis.merigoux@inria.fr>, Emile Rolley
|
contributors: Denis Merigoux <denis.merigoux@inria.fr>, Emile Rolley
|
||||||
<emile.rolley@tuta.io>
|
<emile.rolley@tuta.io>, Louis Gesbert <louis.gesbert@inria.fr>
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
||||||
use this file except in compliance with the License. You may obtain a copy of
|
use this file except in compliance with the License. You may obtain a copy of
|
||||||
@ -101,7 +101,7 @@ module Cli = struct
|
|||||||
val debug : bool Term.t
|
val debug : bool Term.t
|
||||||
|
|
||||||
val term :
|
val term :
|
||||||
(chdir:File.t option ->
|
(config_file:File.t option ->
|
||||||
catala_exe:File.t option ->
|
catala_exe:File.t option ->
|
||||||
catala_opts:string list ->
|
catala_opts:string list ->
|
||||||
build_dir:File.t option ->
|
build_dir:File.t option ->
|
||||||
@ -112,12 +112,14 @@ module Cli = struct
|
|||||||
'a) ->
|
'a) ->
|
||||||
'a Term.t
|
'a Term.t
|
||||||
end = struct
|
end = struct
|
||||||
let chdir =
|
let config_file =
|
||||||
Arg.(
|
Arg.(
|
||||||
value
|
value
|
||||||
& opt (some string) None
|
& opt (some file) None
|
||||||
& info ["C"] ~docv:"DIR"
|
& info ["config"] ~docv:"FILE"
|
||||||
~doc:"Change to the given directory before processing")
|
~doc:
|
||||||
|
"Clerk configuration file to use, instead of looking up \
|
||||||
|
\"clerk.toml\" in parent directories.")
|
||||||
|
|
||||||
let color =
|
let color =
|
||||||
Arg.(
|
Arg.(
|
||||||
@ -148,7 +150,7 @@ module Cli = struct
|
|||||||
Term.(
|
Term.(
|
||||||
const
|
const
|
||||||
(fun
|
(fun
|
||||||
chdir
|
config_file
|
||||||
catala_exe
|
catala_exe
|
||||||
catala_opts
|
catala_opts
|
||||||
build_dir
|
build_dir
|
||||||
@ -157,9 +159,9 @@ module Cli = struct
|
|||||||
debug
|
debug
|
||||||
ninja_output
|
ninja_output
|
||||||
->
|
->
|
||||||
f ~chdir ~catala_exe ~catala_opts ~build_dir ~include_dirs ~color
|
f ~config_file ~catala_exe ~catala_opts ~build_dir ~include_dirs
|
||||||
~debug ~ninja_output)
|
~color ~debug ~ninja_output)
|
||||||
$ chdir
|
$ config_file
|
||||||
$ catala_exe
|
$ catala_exe
|
||||||
$ catala_opts
|
$ catala_opts
|
||||||
$ build_dir
|
$ build_dir
|
||||||
@ -300,36 +302,18 @@ end
|
|||||||
|
|
||||||
(** Some functions that poll the surrounding systems (think [./configure]) *)
|
(** Some functions that poll the surrounding systems (think [./configure]) *)
|
||||||
module Poll = struct
|
module Poll = struct
|
||||||
let project_root_absrel : (File.t option * File.t) Lazy.t =
|
(** This module is sensitive to the CWD at first use. Therefore it's expected
|
||||||
lazy
|
that [chdir] has been run beforehand to the project root. *)
|
||||||
(let open File in
|
let root = lazy (Sys.getcwd ())
|
||||||
let home = try Sys.getenv "HOME" with Not_found -> "" in
|
|
||||||
let rec lookup dir rel =
|
|
||||||
if
|
|
||||||
Sys.file_exists (dir / "catala.opam")
|
|
||||||
|| Sys.file_exists (dir / ".git")
|
|
||||||
|| Sys.file_exists (dir / "clerk.toml")
|
|
||||||
then Some dir, rel
|
|
||||||
else if dir = home then None, Filename.current_dir_name
|
|
||||||
else
|
|
||||||
let parent = Filename.dirname dir in
|
|
||||||
if parent = dir then None, Filename.current_dir_name
|
|
||||||
else lookup parent (rel / Filename.parent_dir_name)
|
|
||||||
in
|
|
||||||
lookup (Sys.getcwd ()) Filename.current_dir_name)
|
|
||||||
|
|
||||||
let project_root = lazy (fst (Lazy.force project_root_absrel))
|
|
||||||
let project_root_relative = lazy (snd (Lazy.force project_root_absrel))
|
|
||||||
|
|
||||||
(** Scans for a parent directory being the root of the Catala source repo *)
|
(** Scans for a parent directory being the root of the Catala source repo *)
|
||||||
let catala_project_root : File.t option Lazy.t =
|
let catala_project_root : File.t option Lazy.t =
|
||||||
lazy
|
root
|
||||||
(match Lazy.force project_root with
|
|> Lazy.map
|
||||||
| Some root
|
@@ fun root ->
|
||||||
when Sys.file_exists File.(root / "catala.opam")
|
if File.(exists (root / "catala.opam") && exists (root / "dune-project"))
|
||||||
&& Sys.file_exists File.(root / "dune-project") ->
|
then Some root
|
||||||
Some root
|
else None
|
||||||
| _ -> None)
|
|
||||||
|
|
||||||
let exec_dir : File.t = Catala_utils.Cli.exec_dir
|
let exec_dir : File.t = Catala_utils.Cli.exec_dir
|
||||||
let clerk_exe : File.t Lazy.t = lazy (Unix.realpath Sys.executable_name)
|
let clerk_exe : File.t Lazy.t = lazy (Unix.realpath Sys.executable_name)
|
||||||
@ -339,14 +323,14 @@ module Poll = struct
|
|||||||
(let f = File.(exec_dir / "catala") in
|
(let f = File.(exec_dir / "catala") in
|
||||||
if Sys.file_exists f then Unix.realpath f
|
if Sys.file_exists f then Unix.realpath f
|
||||||
else
|
else
|
||||||
match Lazy.force project_root with
|
match catala_project_root with
|
||||||
| Some root when Sys.file_exists File.(root / "catala.opam") ->
|
| (lazy (Some root)) ->
|
||||||
Unix.realpath
|
Unix.realpath
|
||||||
File.(root / "_build" / "default" / "compiler" / "catala.exe")
|
File.(root / "_build" / "default" / "compiler" / "catala.exe")
|
||||||
| _ -> File.check_exec "catala")
|
| _ -> File.check_exec "catala")
|
||||||
|
|
||||||
let build_dir : ?dir:File.t -> unit -> File.t =
|
let build_dir : dir:File.t -> unit -> File.t =
|
||||||
fun ?(dir = "_build") () ->
|
fun ~dir () ->
|
||||||
let d = File.clean_path dir in
|
let d = File.clean_path dir in
|
||||||
File.ensure_dir d;
|
File.ensure_dir d;
|
||||||
d
|
d
|
||||||
@ -434,14 +418,6 @@ module Poll = struct
|
|||||||
lazy (snd (Lazy.force ocaml_include_and_lib_flags))
|
lazy (snd (Lazy.force ocaml_include_and_lib_flags))
|
||||||
end
|
end
|
||||||
|
|
||||||
(* Adjusts paths specified from the command-line relative to the user cwd to be
|
|
||||||
instead relative to the project root *)
|
|
||||||
let fix_path =
|
|
||||||
let from_dir = Sys.getcwd () in
|
|
||||||
fun d ->
|
|
||||||
let to_dir = Lazy.force Poll.project_root_relative in
|
|
||||||
Catala_utils.File.reverse_path ~from_dir ~to_dir d
|
|
||||||
|
|
||||||
(**{1 Building rules}*)
|
(**{1 Building rules}*)
|
||||||
|
|
||||||
(** Ninja variable names *)
|
(** Ninja variable names *)
|
||||||
@ -793,8 +769,10 @@ let gen_ninja_file catala_exe catala_flags build_dir include_dirs test_flags dir
|
|||||||
|
|
||||||
(** {1 Driver} *)
|
(** {1 Driver} *)
|
||||||
|
|
||||||
|
(* Last argument is a continuation taking as arguments [build_dir], the
|
||||||
|
[fix_path] function, and the ninja file name *)
|
||||||
let ninja_init
|
let ninja_init
|
||||||
~chdir
|
~config_file
|
||||||
~catala_exe
|
~catala_exe
|
||||||
~catala_opts
|
~catala_opts
|
||||||
~build_dir
|
~build_dir
|
||||||
@ -802,14 +780,58 @@ let ninja_init
|
|||||||
~color
|
~color
|
||||||
~debug
|
~debug
|
||||||
~ninja_output :
|
~ninja_output :
|
||||||
extra:def Seq.t -> test_flags:string list -> (File.t -> File.t -> 'a) -> 'a
|
extra:def Seq.t ->
|
||||||
=
|
test_flags:string list ->
|
||||||
|
(File.t -> (File.t -> File.t) -> File.t -> 'a) ->
|
||||||
|
'a =
|
||||||
let _options = Catala_utils.Global.enforce_options ~debug ~color () in
|
let _options = Catala_utils.Global.enforce_options ~debug ~color () in
|
||||||
let chdir =
|
let default_config_file = "clerk.toml" in
|
||||||
match chdir with None -> Lazy.force Poll.project_root | some -> some
|
let set_root_dir dir =
|
||||||
|
Message.debug "Entering directory %a" File.format dir;
|
||||||
|
Sys.chdir dir
|
||||||
in
|
in
|
||||||
Option.iter Sys.chdir chdir;
|
(* fix_path adjusts paths specified from the command-line relative to the user
|
||||||
let build_dir = Poll.build_dir ?dir:build_dir () in
|
cwd to be instead relative to the project root *)
|
||||||
|
let fix_path, config =
|
||||||
|
let from_dir = Sys.getcwd () in
|
||||||
|
match config_file with
|
||||||
|
| None -> (
|
||||||
|
match
|
||||||
|
File.(find_in_parents (fun dir -> exists (dir / default_config_file)))
|
||||||
|
with
|
||||||
|
| Some (root, rel) ->
|
||||||
|
set_root_dir root;
|
||||||
|
( Catala_utils.File.reverse_path ~from_dir ~to_dir:rel,
|
||||||
|
Clerk_config.read default_config_file )
|
||||||
|
| None -> (
|
||||||
|
match
|
||||||
|
File.(
|
||||||
|
find_in_parents (function dir ->
|
||||||
|
exists (dir / "catala.opam") || exists (dir / ".git")))
|
||||||
|
with
|
||||||
|
| Some (root, rel) ->
|
||||||
|
set_root_dir root;
|
||||||
|
( Catala_utils.File.reverse_path ~from_dir ~to_dir:rel,
|
||||||
|
Clerk_config.default )
|
||||||
|
| None -> Fun.id, Clerk_config.default))
|
||||||
|
| Some f ->
|
||||||
|
let root = Filename.dirname f in
|
||||||
|
let config = Clerk_config.read f in
|
||||||
|
set_root_dir root;
|
||||||
|
( (fun d ->
|
||||||
|
let r = Catala_utils.File.reverse_path ~from_dir ~to_dir:root d in
|
||||||
|
Message.debug "%a => %a" File.format d File.format r;
|
||||||
|
r),
|
||||||
|
config )
|
||||||
|
in
|
||||||
|
let build_dir =
|
||||||
|
let dir =
|
||||||
|
match build_dir with None -> config.build_dir | Some dir -> dir
|
||||||
|
in
|
||||||
|
Poll.build_dir ~dir ()
|
||||||
|
in
|
||||||
|
let catala_opts = config.catala_opts @ catala_opts in
|
||||||
|
let include_dirs = config.include_dirs @ include_dirs in
|
||||||
let with_ninja_output k =
|
let with_ninja_output k =
|
||||||
match ninja_output with
|
match ninja_output with
|
||||||
| Some f -> k f
|
| Some f -> k f
|
||||||
@ -837,7 +859,7 @@ let ninja_init
|
|||||||
]
|
]
|
||||||
in
|
in
|
||||||
Nj.format nin_ppf ninja_contents);
|
Nj.format nin_ppf ninja_contents);
|
||||||
k build_dir nin_file
|
k build_dir fix_path nin_file
|
||||||
|
|
||||||
let cleaned_up_env () =
|
let cleaned_up_env () =
|
||||||
let passthrough_vars =
|
let passthrough_vars =
|
||||||
@ -879,7 +901,7 @@ open Cmdliner
|
|||||||
let build_cmd =
|
let build_cmd =
|
||||||
let run ninja_init (targets : string list) (ninja_flags : string list) =
|
let run ninja_init (targets : string list) (ninja_flags : string list) =
|
||||||
ninja_init ~extra:Seq.empty ~test_flags:[]
|
ninja_init ~extra:Seq.empty ~test_flags:[]
|
||||||
@@ fun _build_dir nin_file ->
|
@@ fun _build_dir fix_path nin_file ->
|
||||||
let targets =
|
let targets =
|
||||||
List.map
|
List.map
|
||||||
(fun f ->
|
(fun f ->
|
||||||
@ -920,7 +942,7 @@ let test_cmd =
|
|||||||
set_report_verbosity verbosity;
|
set_report_verbosity verbosity;
|
||||||
Clerk_report.set_display_flags ~use_patdiff ();
|
Clerk_report.set_display_flags ~use_patdiff ();
|
||||||
ninja_init ~extra:Seq.empty ~test_flags
|
ninja_init ~extra:Seq.empty ~test_flags
|
||||||
@@ fun build_dir nin_file ->
|
@@ fun build_dir fix_path nin_file ->
|
||||||
let targets =
|
let targets =
|
||||||
let fs = if files_or_folders = [] then ["."] else files_or_folders in
|
let fs = if files_or_folders = [] then ["."] else files_or_folders in
|
||||||
List.map File.(fun f -> (build_dir / fix_path f) ^ "@test") fs
|
List.map File.(fun f -> (build_dir / fix_path f) ^ "@test") fs
|
||||||
@ -1005,7 +1027,7 @@ let run_cmd =
|
|||||||
(List.map (fun file -> file ^ "@interpret") files_or_folders)))
|
(List.map (fun file -> file ^ "@interpret") files_or_folders)))
|
||||||
in
|
in
|
||||||
ninja_init ~extra ~test_flags:[]
|
ninja_init ~extra ~test_flags:[]
|
||||||
@@ fun _build_dir nin_file ->
|
@@ fun _build_dir _fix_path nin_file ->
|
||||||
let ninja_cmd = ninja_cmdline ninja_flags nin_file [] in
|
let ninja_cmd = ninja_cmdline ninja_flags nin_file [] in
|
||||||
Message.debug "executing '%s'..." (String.concat " " ninja_cmd);
|
Message.debug "executing '%s'..." (String.concat " " ninja_cmd);
|
||||||
raise (Catala_utils.Cli.Exit_with (run_ninja ~clean_up_env:false ninja_cmd))
|
raise (Catala_utils.Cli.Exit_with (run_ninja ~clean_up_env:false ninja_cmd))
|
||||||
|
@ -8,8 +8,9 @@
|
|||||||
ninja_utils
|
ninja_utils
|
||||||
cmdliner
|
cmdliner
|
||||||
re
|
re
|
||||||
ocolor)
|
ocolor
|
||||||
(modules clerk_scan clerk_report clerk_runtest clerk_driver))
|
otoml)
|
||||||
|
(modules clerk_scan clerk_report clerk_runtest clerk_config clerk_driver))
|
||||||
|
|
||||||
(rule
|
(rule
|
||||||
(target custom_linking.sexp)
|
(target custom_linking.sexp)
|
||||||
|
@ -51,6 +51,7 @@ depends: [
|
|||||||
"conf-pandoc" {cataladevmode}
|
"conf-pandoc" {cataladevmode}
|
||||||
"z3" {catalaz3mode}
|
"z3" {catalaz3mode}
|
||||||
"conf-ninja"
|
"conf-ninja"
|
||||||
|
"otoml" {>= "1.0"}
|
||||||
]
|
]
|
||||||
depopts: ["z3"]
|
depopts: ["z3"]
|
||||||
conflicts: [
|
conflicts: [
|
||||||
|
@ -66,6 +66,8 @@ let clean_path p =
|
|||||||
in
|
in
|
||||||
if p = "" then "." else p
|
if p = "" then "." else p
|
||||||
|
|
||||||
|
let exists = Sys.file_exists
|
||||||
|
|
||||||
let rec ensure_dir dir =
|
let rec ensure_dir dir =
|
||||||
match Sys.is_directory dir with
|
match Sys.is_directory dir with
|
||||||
| true -> ()
|
| true -> ()
|
||||||
@ -104,6 +106,20 @@ let reverse_path ?(from_dir = Sys.getcwd ()) ~to_dir f =
|
|||||||
String.concat Filename.dir_sep
|
String.concat Filename.dir_sep
|
||||||
(aux (path_to_list f) rbase (path_to_list to_dir))
|
(aux (path_to_list f) rbase (path_to_list to_dir))
|
||||||
|
|
||||||
|
let find_in_parents predicate =
|
||||||
|
let home = try Sys.getenv "HOME" with Not_found -> "" in
|
||||||
|
let rec lookup dir rel =
|
||||||
|
if predicate dir then Some dir, rel
|
||||||
|
else if dir = home then None, Filename.current_dir_name
|
||||||
|
else
|
||||||
|
let parent = Filename.dirname dir in
|
||||||
|
if parent = dir then None, Filename.current_dir_name
|
||||||
|
else lookup parent (rel / Filename.parent_dir_name)
|
||||||
|
in
|
||||||
|
match lookup (Sys.getcwd ()) Filename.current_dir_name with
|
||||||
|
| Some dir, rel -> Some (dir, rel)
|
||||||
|
| None, _ -> None
|
||||||
|
|
||||||
let with_out_channel filename f =
|
let with_out_channel filename f =
|
||||||
ensure_dir (Filename.dirname filename);
|
ensure_dir (Filename.dirname filename);
|
||||||
let oc = open_out filename in
|
let oc = open_out filename in
|
||||||
|
@ -89,6 +89,9 @@ val ensure_dir : t -> unit
|
|||||||
(** Creates the directory (and parents recursively) if it doesn't exist already.
|
(** Creates the directory (and parents recursively) if it doesn't exist already.
|
||||||
Errors out if the file exists but is not a directory *)
|
Errors out if the file exists but is not a directory *)
|
||||||
|
|
||||||
|
val exists : t -> bool
|
||||||
|
(** Alias for Sys.file_exists*)
|
||||||
|
|
||||||
val check_file : t -> t option
|
val check_file : t -> t option
|
||||||
(** Returns its argument if it exists and is a plain file, [None] otherwise.
|
(** Returns its argument if it exists and is a plain file, [None] otherwise.
|
||||||
Does not do resolution like [check_directory]. *)
|
Does not do resolution like [check_directory]. *)
|
||||||
@ -122,6 +125,12 @@ val reverse_path : ?from_dir:t -> to_dir:t -> t -> t
|
|||||||
leading to [f] from [to_dir]. The results attempts to be relative to
|
leading to [f] from [to_dir]. The results attempts to be relative to
|
||||||
[to_dir]. *)
|
[to_dir]. *)
|
||||||
|
|
||||||
|
val find_in_parents : (t -> bool) -> (t * t) option
|
||||||
|
(** Checks for the first directory matching the given predicate from the current
|
||||||
|
directory upwards. Recursion stops at home. Returns a pair [dir, rel_path],
|
||||||
|
where [dir] is the ancestor directory matching the predicate, and [rel_path]
|
||||||
|
is a path pointing to it from the current dir. *)
|
||||||
|
|
||||||
val ( /../ ) : t -> t -> t
|
val ( /../ ) : t -> t -> t
|
||||||
(** Sugar for [parent a / b] *)
|
(** Sugar for [parent a / b] *)
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user