Documented legifrance_catala

This commit is contained in:
Denis Merigoux 2020-05-23 17:35:40 +02:00
parent ca6fd2173f
commit 9e6c1cc9ee
5 changed files with 171 additions and 83 deletions

View File

@ -152,9 +152,6 @@ let get_article_expiration_date (json : article) : Unix.tm =
|> Yojson.Basic.Util.to_int |> api_timestamp_to_localtime
with Yojson.Basic.Util.Type_error (msg, obj) -> raise_article_parsing_error json msg obj
let date_compare (d1 : Unix.tm) (d2 : Unix.tm) : int =
int_of_float (fst (Unix.mktime d1)) - int_of_float (fst (Unix.mktime d2))
let get_article_new_version (json : article) : string =
let expiration_date = get_article_expiration_date json in
let get_version_date_debut (version : Yojson.Basic.t) : Unix.tm =
@ -168,9 +165,9 @@ let get_article_new_version (json : article) : string =
|> Yojson.Basic.Util.member "articleVersions"
|> Yojson.Basic.Util.to_list
|> List.filter (fun version ->
date_compare expiration_date (get_version_date_debut version) <= 0)
Date.date_compare expiration_date (get_version_date_debut version) <= 0)
|> List.sort (fun version1 version2 ->
date_compare (get_version_date_debut version1) (get_version_date_debut version2))
Date.date_compare (get_version_date_debut version1) (get_version_date_debut version2))
|> List.hd |> Yojson.Basic.Util.member "id" |> Yojson.Basic.Util.to_string
with Yojson.Basic.Util.Type_error (msg, obj) -> raise_article_parsing_error json msg obj

View File

@ -0,0 +1,59 @@
(* This file is part of the Catala compiler, a specification language for tax and social benefits
computation rules. Copyright (C) 2020 Inria, contributor: Denis Merigoux
<denis.merigoux@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. *)
(** Command line arguments specification of [legifrance_catala] *)
open Cmdliner
let file =
Arg.(
required
& pos 0 (some string) None
& info [] ~docv:"FILE"
~doc:"Name of the Catala master file you want to get LegiFrance information on")
let client_id =
Arg.(
required
& pos 1 (some string) None
& info [] ~docv:"CLIENT_ID" ~doc:"LegiFrance Oauth cliend id")
let client_secret =
Arg.(
required
& pos 2 (some string) None
& info [] ~docv:"CLIENT_SECRET" ~doc:"LegiFrance Oauth cliend secret")
let debug = Arg.(value & flag & info [ "d"; "debug" ] ~doc:"Prints debug information")
(** Arguments : [file debug cliend_id client_secret] *)
let catala_legifrance_t f = Term.(const f $ file $ debug $ client_id $ client_secret)
let info =
let doc = "LegiFrance interaction tool for Catala" in
let man =
[
`S Manpage.s_authors;
`P "Denis Merigoux <denis.merigoux@inria.fr>";
`S Manpage.s_bugs;
`P "Please file bug reports at https://gitlab.inria.fr/verifisc/catala/issues";
]
in
let exits = Term.default_exits @ [ Term.exit_info ~doc:"on error" 1 ] in
Term.info "legifrance_catala"
~version:
( match Build_info.V1.version () with
| None -> "n/a"
| Some v -> Build_info.V1.Version.to_string v )
~doc ~exits ~man

View File

@ -0,0 +1,52 @@
(* This file is part of the Catala compiler, a specification language for tax and social benefits
computation rules. Copyright (C) 2020 Inria, contributor: Denis Merigoux
<denis.merigoux@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. *)
(** Helper functions to interact with {!Unix.tm} dates *)
(** Parses a date formatted as [DD/MM/YYYY] into an {!Unix.tm}*)
let parse_expiration_date (expiration_date : string) : Unix.tm =
try
let extract_article_title = Re.Pcre.regexp "([0-9]{2})\\/([0-9]{2})\\/([0-9]{4})" in
let get_substring =
Re.Pcre.get_substring (Re.Pcre.exec ~rex:extract_article_title expiration_date)
in
snd
(Unix.mktime
{
Unix.tm_mday = int_of_string (get_substring 1);
Unix.tm_mon = int_of_string (get_substring 2);
Unix.tm_year = int_of_string (get_substring 3) - 1900;
Unix.tm_sec = 0;
Unix.tm_min = 0;
Unix.tm_hour = 0;
Unix.tm_wday = 0;
Unix.tm_yday = 0;
Unix.tm_isdst = false;
})
with _ ->
Catala.Cli.error_print
(Printf.sprintf "Error while parsing expiration date argument (%s)" expiration_date);
exit 0
(** Prints an [Unix.tm] under the ISO formatting [YYYY-MM-DD] *)
let print_tm (d : Unix.tm) : string =
if d.Unix.tm_year + 1900 = 2999 then "undefined date"
else Printf.sprintf "%d-%02d-%02d" (1900 + d.Unix.tm_year) (1 + d.Unix.tm_mon) d.Unix.tm_mday
(** Returns true if [d] is set in the year [2999] *)
let is_infinity (d : Unix.tm) : bool = d.Unix.tm_year + 1900 = 2999
(** [date_compare d1 d2] compares the timestamps of [d1] and [d2]*)
let date_compare (d1 : Unix.tm) (d2 : Unix.tm) : int =
int_of_float (fst (Unix.mktime d1)) - int_of_float (fst (Unix.mktime d2))

View File

@ -0,0 +1,35 @@
(* This file is part of the Catala compiler, a specification language for tax and social benefits
computation rules. Copyright (C) 2020 Inria, contributor: Denis Merigoux
<denis.merigoux@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. *)
(** Simple and inefficient diff algorithm based on longest common subsequences *)
(** The diff algorithm works on comparable items *)
module type Comparable = sig
type t
val compare : t -> t -> int
end
(** Functor that produces a [Diff] module given a comparable type *)
module Make : functor (X : Comparable) -> sig
type item = X.t
type diff = Deleted of item list | Added of item list | Equal of item list
type t = diff list
val get_diff : item array -> item array -> t
(** This is the main function : [get_diff a1 a2] compares two arrays of items and outputs a list
of chunks tagged with [Deteted], [Added] or [Removed] *)
end

View File

@ -12,82 +12,11 @@
or implied. See the License for the specific language governing permissions and limitations under
the License. *)
open Cmdliner
let file =
Arg.(
required
& pos 0 (some string) None
& info [] ~docv:"FILE"
~doc:"Name of the Catala master file you want to get LegiFrance information on")
let client_id =
Arg.(
required
& pos 1 (some string) None
& info [] ~docv:"CLIENT_ID" ~doc:"LegiFrance Oauth cliend id")
let client_secret =
Arg.(
required
& pos 2 (some string) None
& info [] ~docv:"CLIENT_SECRET" ~doc:"LegiFrance Oauth cliend secret")
let debug = Arg.(value & flag & info [ "d"; "debug" ] ~doc:"Prints debug information")
let catala_legifrance_t f = Term.(const f $ file $ debug $ client_id $ client_secret)
let info =
let doc = "LegiFrance interaction tool for Catala" in
let man =
[
`S Manpage.s_authors;
`P "Denis Merigoux <denis.merigoux@inria.fr>";
`S Manpage.s_bugs;
`P "Please file bug reports at https://gitlab.inria.fr/verifisc/catala/issues";
]
in
let exits = Term.default_exits @ [ Term.exit_info ~doc:"on error" 1 ] in
Term.info "legifrance_catala"
~version:
( match Build_info.V1.version () with
| None -> "n/a"
| Some v -> Build_info.V1.Version.to_string v )
~doc ~exits ~man
let parse_expiration_date (expiration_date : string) : Unix.tm =
try
let extract_article_title = Re.Pcre.regexp "([0-9]{2})\\/([0-9]{2})\\/([0-9]{4})" in
let get_substring =
Re.Pcre.get_substring (Re.Pcre.exec ~rex:extract_article_title expiration_date)
in
snd
(Unix.mktime
{
Unix.tm_mday = int_of_string (get_substring 1);
Unix.tm_mon = int_of_string (get_substring 2);
Unix.tm_year = int_of_string (get_substring 3) - 1900;
Unix.tm_sec = 0;
Unix.tm_min = 0;
Unix.tm_hour = 0;
Unix.tm_wday = 0;
Unix.tm_yday = 0;
Unix.tm_isdst = false;
})
with _ ->
Catala.Cli.error_print
(Printf.sprintf "Error while parsing expiration date argument (%s)" expiration_date);
exit 0
let print_tm (d : Unix.tm) : string =
if d.Unix.tm_year + 1900 = 2999 then "undefined date"
else Printf.sprintf "%d-%02d-%02d" (1900 + d.Unix.tm_year) (1 + d.Unix.tm_mon) d.Unix.tm_mday
let is_infinity (d : Unix.tm) : bool = d.Unix.tm_year + 1900 = 2999
(** Main logic for interacting with LegiFrance when traversing Catala source files *)
type new_article_version = NotAvailable | Available of string
(* Returns the ID of the future version of the article if any *)
(** Returns the ID of the future version of the article if any *)
let check_article_expiration (article_catala : Catala.Ast.law_article)
(access_token : Api.access_token) : new_article_version option =
match article_catala.Catala.Ast.law_article_id with
@ -100,18 +29,18 @@ let check_article_expiration (article_catala : Catala.Ast.law_article)
(Catala.Pos.unmark article_catala.Catala.Ast.law_article_name)
(Catala.Pos.to_string
(Catala.Pos.get_position article_catala.Catala.Ast.law_article_name))
(print_tm api_article_expiration_date)
(Date.print_tm api_article_expiration_date)
( match article_catala.Catala.Ast.law_article_expiration_date with
| None -> ""
| Some source_exp_date -> ", " ^ source_exp_date ^ " according to source code" )
in
let new_version_available = not (is_infinity api_article_expiration_date) in
let new_version_available = not (Date.is_infinity api_article_expiration_date) in
let source_code_expiration =
match article_catala.Catala.Ast.law_article_expiration_date with
| None -> false
| Some source_exp_date ->
let source_exp_date = parse_expiration_date source_exp_date in
not (is_infinity source_exp_date)
let source_exp_date = Date.parse_expiration_date source_exp_date in
not (Date.is_infinity source_exp_date)
in
if new_version_available || source_code_expiration then begin
Catala.Cli.warning_print msg;
@ -133,9 +62,13 @@ type article_text_acc = {
new_version : string option;
current_version : string option;
}
(** Accumulator type when traversing the Catala source files *)
module Diff = Diff.Make (String)
(** Diff algorithm for a list of words *)
(** [compare_article_to_version token text version] retrieves the text of the article whose
LegiFrance ID is [version] and produces a diff with the expected [text]*)
let compare_article_to_version (access_token : Api.access_token) (text : string) (version : string)
: Diff.t option =
let new_article = Api.retrieve_article access_token version in
@ -149,6 +82,8 @@ let compare_article_to_version (access_token : Api.access_token) (text : string)
in
if not all_equal then Some diff else None
(** Compares [article_text_acc.current_version] and [article_text_acc.new_version] by accessing
LegiFrance and display differences if any *)
let compare_to_versions (article_text_acc : article_text_acc) (access_token : Api.access_token) :
unit =
let print_diff msg diff =
@ -194,6 +129,8 @@ let compare_to_versions (article_text_acc : article_text_acc) (access_token : Ap
diff )
| None -> ()
(** Fill an [@@Include ...@@] tag inside the Catala source file with the legislative contents
retrieved from LegiFrance *)
let include_legislative_text (id : string Catala.Pos.marked) (access_token : Api.access_token) :
unit =
let excerpt = Api.retrieve_law_excerpt access_token (Catala.Pos.unmark id) in
@ -227,6 +164,13 @@ let include_legislative_text (id : string Catala.Pos.marked) (access_token : Api
close_in ic;
close_out oc
(** Parses the Catala master source file and checks each article:
- if the article has a LegiFrance ID, checks the text of the article in the source code vs the
text from LegiFrance;
- if the article has an expiration date, display the difference between the current version of
the article and the next one on LegiFrance;
- fill each [@@Include ...@@] tag with the contents retrieved from LegiFrance *)
let driver (file : string) (debug : bool) (client_id : string) (client_secret : string) =
if debug then Catala.Cli.debug_flag := true;
let access_token = Api.get_token client_id client_secret in
@ -271,4 +215,5 @@ let driver (file : string) (debug : bool) (client_id : string) (client_secret :
compare_to_versions article_text_acc access_token;
exit 0
let main () = Cmdliner.Term.exit @@ Cmdliner.Term.eval (catala_legifrance_t driver, info)
(** Hook for the executable *)
let main () = Cmdliner.Term.exit @@ Cmdliner.Term.eval (Cli.catala_legifrance_t driver, Cli.info)