From 3110035e348e636796051ae7417cf16bc3d6226c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Loi=CC=88c=20Correnson?= <loic.correnson@cea.fr> Date: Wed, 26 Feb 2020 17:11:21 +0100 Subject: [PATCH] [server] generator for enumerated values Application to logs, properties kind & status --- src/plugins/server/data.mli | 88 ++++++++--- src/plugins/server/kernel_ast.ml | 52 ------- src/plugins/server/kernel_main.ml | 42 ++++-- src/plugins/server/kernel_properties.ml | 186 +++++++++++++++++++----- src/plugins/server/request.ml | 24 ++- src/plugins/server/states.ml | 18 ++- src/plugins/server/syntax.ml | 38 ++++- src/plugins/server/syntax.mli | 16 +- 8 files changed, 321 insertions(+), 143 deletions(-) diff --git a/src/plugins/server/data.mli b/src/plugins/server/data.mli index eeda161e364..6950b98de51 100644 --- a/src/plugins/server/data.mli +++ b/src/plugins/server/data.mli @@ -94,10 +94,6 @@ module Jtext : S with type t = json (** Rich text encoding, see [Jbuffer] *) (** {2 Records} *) (* -------------------------------------------------------------------------- *) -type 'a record (** Records of type 'a *) -type 'a signature (** Opened signature for record of type ['a] *) -type ('a,'b) field (** Field of type ['b] for a record of type ['a] *) - (** Record factory. You shall start by declaring a (ghost) type [r] and call @@ -122,6 +118,10 @@ type ('a,'b) field (** Field of type ['b] for a record of type ['a] *) module Record : sig + type 'a record (** Records of type 'a *) + type 'a signature (** Opened signature for record of type ['a] *) + type ('a,'b) field (** Field of type ['b] for a record of type ['a] *) + (** Data with [type t = r record]. Also contains getters and setters for fields. *) module type S = @@ -149,7 +149,72 @@ sig ('r,'a option) field (** Publish and close an opened record *) - val publish : 'a signature -> (module S with type r = 'a) + val publish : 'a signature -> + (module S with type r = 'a) + +end + +(* -------------------------------------------------------------------------- *) +(** {2 Enums} *) +(* -------------------------------------------------------------------------- *) + +(** Enum factory. + + You shall start by declaring a dictionnary with + [Enum.dictionary] for your values. + Then, populate the dictionary with [Enum.tag] values. + Finally, you shall call [Enum.publish] to obtain a new data module + for your type. + + You have two options for computing tags: either you provide values + when declaring tags, and these tags will be associated to registered + values for both directions; + alternatively you might provide a [~tag] function to [Enum.publish]. + + The difficulty when providing values only at tag definition is to ensure + that all possible value has been registered. + + The conversion values from and to json may fail when no value has been + registered with tags. +*) + +module Enum : +sig + + type 'a dictionary + type 'a tag + type 'a prefix = string -> 'a tag + + val name : 'a tag -> string + + (** Creates an opened, empty dictionnary. *) + val dictionary : + page:Doc.page -> name:string -> descr:Markdown.text -> + unit -> 'a dictionary + + (** Register a new tag in the dictionnary. + The provided value, if any, will be used for decoding json tags. + If would be used also for encoding values to json tags if no [~tag] + function is provided when publishing the dictionnary. + Registered values must be hashable with [Hashtbl.hash] function. *) + val tag : 'a dictionary -> + name:string -> descr:Markdown.text -> ?value:'a -> + unit -> 'a tag + + (** Register a new prefix tag in the dictionnary. + To decoding from json is provided to prefix tags. + Encoding is done by emitting tags with form ['prefix:*']. + The variable part of the prefix is documented as ['prefix:xxx'] + when [~var:"xxx"] is provided. *) + val prefix : 'a dictionary -> + prefix:string -> ?var:string -> descr:Markdown.text -> unit -> 'a prefix + + (** Publish the dictionnary. To more tag nor prefix can be added after. + If no [~tag] function is provided, registered values with tags + are used. *) + val publish : 'a dictionary -> + ?tag:('a -> 'a tag) -> + unit -> (module S with type t = 'a) end @@ -196,19 +261,6 @@ end (** Builds a {i projectified} index on types with {i unique} identifiers *) module Identified(A : IdentifiedType) : Index with type t = A.t -(* -------------------------------------------------------------------------- *) -(** {2 Dictionary} *) -(* -------------------------------------------------------------------------- *) - -module type Enum = -sig - type t - val values : (t * string * Markdown.text) list - include Info -end - -module Dictionary(E : Enum) : S_collection with type t = E.t - (* -------------------------------------------------------------------------- *) (** {2 Error handling} *) (* -------------------------------------------------------------------------- *) diff --git a/src/plugins/server/kernel_ast.ml b/src/plugins/server/kernel_ast.ml index 915f9e124e7..3861b8cfc8e 100644 --- a/src/plugins/server/kernel_ast.ml +++ b/src/plugins/server/kernel_ast.ml @@ -163,58 +163,6 @@ let () = Request.register ~page ~input:(module Kf) ~output:(module Jtext) (fun kf -> Jbuffer.to_json PP.pp_global (Kernel_function.get_global kf)) -(* -------------------------------------------------------------------------- *) -(* --- Properties --- *) -(* -------------------------------------------------------------------------- *) - -module Property = struct - type p - let signature = - Record.signature ~page ~name:"property" - ~descr:(Md.plain "logical property") () - - let name = Record.field signature ~name:"name" - ~descr:(Md.plain "name") (module Jstring) - let property = Record.field signature ~name:"property" - ~descr:(Md.plain "logical property") (module Jstring) - let status = Record.field signature ~name:"status" - ~descr:(Md.plain "logical status") (module Jstring) - let file = Record.field signature ~name:"file" - ~descr:(Md.plain "file") (module Jstring) - let kf = Record.field signature ~name:"function" - ~descr:(Md.plain "kernel function") (module Kf.Joption) - let kinstr = Record.field signature ~name:"kinstr" - ~descr:(Md.plain "kinstr") (module Ki) - - module R = (val (Record.publish signature) : Record.S with type r = p) - include R - - let make ip = - let st = Property_status.Feedback.get ip in - let st = Format.asprintf "%a" Property_status.Feedback.pretty st in - let p = Format.asprintf "%a" Property.pretty ip in - let loc = Property.location ip in - let path = Filepath.(Normalized.to_pretty_string (fst loc).pos_path) in - default |> set property p |> set status st - |> set kf (Property.get_kf ip) - |> set kinstr (Property.get_kinstr ip) - |> set name (Property.Names.get_prop_name_id ip) - |> set file path -end - -let get_properties () = - Property_status.fold (fun ip acc -> Property.make ip :: acc) [] - -let () = - Request.register - ~page - ~kind:`GET - ~name:"kernel.ast.getProperties" - ~descr:(Md.plain "Collect all logical properties") - ~input:(module Junit) - ~output:(module Jlist (Property)) - get_properties - (* -------------------------------------------------------------------------- *) (* --- Files --- *) (* -------------------------------------------------------------------------- *) diff --git a/src/plugins/server/kernel_main.ml b/src/plugins/server/kernel_main.ml index 22866e37d79..28e9a6574f0 100644 --- a/src/plugins/server/kernel_main.ml +++ b/src/plugins/server/kernel_main.ml @@ -110,21 +110,35 @@ module LogSource = Collection(RawSource) module RawKind = struct - type t = Log.kind - let page = page - let name = "kind" - let descr = Md.plain "Frama-C message category." - let values = [ - Log.Error, "ERROR", Md.plain "User Error" ; - Log.Warning, "WARNING", Md.plain "User Warning" ; - Log.Feedback, "FEEDBACK", Md.plain "Analyzer Feedback" ; - Log.Result, "RESULT", Md.plain "Analyzer Result" ; - Log.Failure, "FAILURE", Md.plain "Analyzer Failure" ; - Log.Debug, "DEBUG", Md.plain "Analyser Debug" ; - ] + let kinds = Enum.dictionary ~page + ~name:"kind" + ~descr:(Md.plain "Frama-C message category.") + () + + let t_kind value name descr = + Enum.tag kinds ~name ~descr:(Md.plain descr) ~value () + + let t_error = t_kind Log.Error "ERROR" "User Error" + let t_warning = t_kind Log.Warning "WARNING" "User Warning" + let t_feedback = t_kind Log.Feedback "FEEDBACK" "Plugin Feedback" + let t_result = t_kind Log.Result "RESULT" "Plugin Result" + let t_failure = t_kind Log.Failure "FAILURE" "Plugin Failure" + let t_debug = t_kind Log.Debug "DEBUG" "Analyser Debug" + + let tag = function + | Log.Error -> t_error + | Log.Warning -> t_warning + | Log.Feedback -> t_feedback + | Log.Result -> t_result + | Log.Failure -> t_failure + | Log.Debug -> t_debug + + let data = Enum.publish kinds ~tag () + + include (val data : S with type t = Log.kind) end -module LogKind = Dictionary(RawKind) +module LogKind = Collection(RawKind) (* -------------------------------------------------------------------------- *) (* --- Log Events --- *) @@ -135,7 +149,7 @@ struct type rlog - let jlog : rlog signature = Record.signature ~page + let jlog : rlog Record.signature = Record.signature ~page ~name:"log" ~descr:(Md.plain "Message event record.") () let kind = Record.field jlog ~name:"kind" diff --git a/src/plugins/server/kernel_properties.ml b/src/plugins/server/kernel_properties.ml index bc5461a2585..fe71601a655 100644 --- a/src/plugins/server/kernel_properties.ml +++ b/src/plugins/server/kernel_properties.ml @@ -36,43 +36,159 @@ let page = Doc.page `Kernel ~title:"Property Services" ~filename:"properties.md" module PropKind = struct - type t = string - let syntax = Sy.publish ~page + let kinds = Enum.dictionary ~page ~name:"propkind" ~descr:(Md.plain "Property Kind") - ~synopsis:Sy.string () + () + + let t_kind name descr = Enum.tag kinds ~name ~descr:(Md.plain descr) () + let t_clause name = t_kind name (Printf.sprintf "Clause `@%s`" name) + let t_loop name = + t_kind ("loop-" ^ name) (Printf.sprintf "Clause `@loop %s`" name) + + let t_behavior = t_kind "behavior" "Contract behavior" + let t_complete = t_kind "complete" "Complete behaviors clause" + let t_disjoint = t_kind "disjoint" "Disjoint behaviors clause" + + let t_assumes = t_clause "assumes" + let t_requires = t_clause "requires" + let t_breaks = t_clause "breaks" + let t_continues = t_clause "continues" + let t_returns = t_clause "returns" + let t_exits = t_clause "exits" + let t_ensures = t_clause "ensures" + let t_terminates = t_clause "terminates" + let t_allocates = t_clause "allocates" + let t_decreases = t_clause "decreases" + let t_assigns = t_clause "assigns" + let t_froms = t_kind "froms" "Clause `@assigns … \\from …`" + let t_ext = Enum.prefix kinds ~prefix:"ext" ~var:"<clause>" + ~descr:(Md.plain "ACSL extension `<clause>`") () + + let t_assert = t_clause "assert" + let t_loop_invariant = t_loop "invariant" + let t_loop_assigns = t_loop "assigns" + let t_loop_variant = t_loop "variant" + let t_loop_allocates = t_loop "allocates" + let t_loop_pragma = t_loop "pragma" + let t_loop_ext = Enum.prefix kinds ~prefix:"loop-ext" ~var:"<clause>" + ~descr:(Md.plain "ACSL loop extension `loop <clause>`") () + + let t_reachable = t_kind "reachable" "Reachable statement" + let t_code_contract = t_kind "code-contract" "Statement Contract" + let t_code_invariant = t_kind "code-invariant" "Generalized loop invariant" + let t_type_invariant = t_kind "type-invariant" "Type invariant" + let t_global_invariant = t_kind "global-invariant" "Global invariant" + + let t_axiomatic = t_kind "axiomatic" "Axiomatic definitions" + let t_axiom = t_kind "axiom" "Logical axiom" + let t_lemma = t_kind "lemma" "Logical lemma" + let t_other = Enum.prefix kinds ~prefix:"prop" ~var:"<prop>" + ~descr:(Md.plain "Plugin Specific properties") () + open Property - let kind = function - | IPPredicate _ -> "predicate" - | IPExtended { ie_ext={ ext_name } } -> ext_name - | IPAxiomatic _ -> "axiomatic" - | IPAxiom _ -> "axiom" - | IPLemma _ -> "lemma" - | IPBehavior _ -> "behavior" - | IPComplete _ -> "complete" - | IPDisjoint _ -> "disjoint" + + let rec tag = function + | IPPredicate { ip_kind } -> + begin match ip_kind with + | PKRequires _ -> t_requires + | PKAssumes _ -> t_assumes + | PKEnsures(_,Normal) -> t_ensures + | PKEnsures(_,Exits) -> t_exits + | PKEnsures(_,Breaks) -> t_breaks + | PKEnsures(_,Continues) -> t_continues + | PKEnsures(_,Returns) -> t_returns + | PKTerminates -> t_terminates + end + | IPExtended { ie_ext={ ext_name } } -> t_ext ext_name + | IPAxiomatic _ -> t_axiomatic + | IPAxiom _ -> t_axiom + | IPLemma _ -> t_lemma + | IPBehavior _ -> t_behavior + | IPComplete _ -> t_complete + | IPDisjoint _ -> t_disjoint | IPCodeAnnot { ica_ca={ annot_content } } -> begin match annot_content with - | AAssert _ -> "assert" - | AStmtSpec _ -> "stmt-contract" - | AInvariant(_,false,_) -> "code-invariant" - | AInvariant(_,true,_) -> "loop-invariant" - | AVariant _ -> "loop-variant" - | AAssigns _ -> "loop-assigns" - | AAllocation _ -> "loop-allocatation" - | APragma _ -> "loop-pragma" - | AExtended(_,_,{ext_name}) -> "loop-" ^ ext_name + | AAssert _ -> t_assert + | AStmtSpec _ -> t_code_contract + | AInvariant(_,false,_) -> t_code_invariant + | AInvariant(_,true,_) -> t_loop_invariant + | AVariant _ -> t_loop_variant + | AAssigns _ -> t_loop_assigns + | AAllocation _ -> t_loop_allocates + | APragma _ -> t_loop_pragma + | AExtended(_,_,{ext_name}) -> t_loop_ext ext_name end - | IPAllocation _ -> "allocation" - | IPAssigns _ -> "assigns" - | IPFrom _ -> "froms" - | IPDecrease _ -> "decrease" - | IPReachable _ -> "reachable" - | IPPropertyInstance _ -> "instance" - | IPTypeInvariant _ -> "type-invariant" - | IPGlobalInvariant _ -> "invariant" - | IPOther { io_name } -> io_name - let to_json = Jstring.to_json + | IPAllocation _ -> t_allocates + | IPAssigns _ -> t_assigns + | IPFrom _ -> t_froms + | IPDecrease _ -> t_decreases + | IPReachable _ -> t_reachable + | IPPropertyInstance { ii_ip } -> tag ii_ip + | IPTypeInvariant _ -> t_type_invariant + | IPGlobalInvariant _ -> t_global_invariant + | IPOther { io_name } -> t_other io_name + + + let data = Enum.publish kinds ~tag () + + include (val data : S with type t = Property.t) +end + +(* -------------------------------------------------------------------------- *) +(* --- Property Status --- *) +(* -------------------------------------------------------------------------- *) + +module PropStatus = +struct + + let status = Enum.dictionary ~page ~name:"status" + ~descr:(Md.plain "Property Status (consolidated)") () + + let t_status value name descr = + Enum.tag status ~name ~descr:(Md.plain descr) ~value () + + open Property_status.Feedback + + let t_unknown = + t_status Unknown "unknown" "Unknown status" + let t_never_tried = + t_status Never_tried "never-tried" "Unknown status (never tried)" + let t_inconsistent = + t_status Inconsistent "inconsistent" "Inconsistent status" + let t_valid = + t_status Valid "valid" "Valid property" + let t_valid_under_hyp = + t_status Valid_under_hyp "valid_under_hyp" "Valid (under hypotheses)" + let t_considered_valid = + t_status Considered_valid "considered_valid" "Valid (external assumption)" + let t_invalid = + t_status Invalid "invalid" "Invalid property (counter example found)" + let t_invalid_under_hyp = + t_status Invalid_under_hyp "invalid_under_hyp" "Invalid property (under hypotheses)" + let t_invalid_but_dead = + t_status Invalid_but_dead "invalid_but_dead" "Dead property (but invalid)" + let t_valid_but_dead = + t_status Valid_but_dead "valid_but_dead" "Dead property (but valid)" + let t_unknown_but_dead = + t_status Unknown_but_dead "unknown_but_dead" "Dead property (but unknown)" + + let tag = function + | Valid -> t_valid + | Invalid -> t_invalid + | Unknown -> t_unknown + | Never_tried -> t_never_tried + | Valid_under_hyp -> t_valid_under_hyp + | Valid_but_dead -> t_valid_but_dead + | Considered_valid -> t_considered_valid + | Invalid_under_hyp -> t_invalid_under_hyp + | Invalid_but_dead -> t_invalid_but_dead + | Unknown_but_dead -> t_unknown_but_dead + | Inconsistent -> t_inconsistent + + let data = Enum.publish status ~tag () + + include (val data : S with type t = Property_status.Feedback.t) end (* -------------------------------------------------------------------------- *) @@ -89,14 +205,12 @@ let () = States.column ~model ~name:"descr" let () = States.column ~model ~name:"kind" ~descr:(Md.plain "Kind") ~data:(module PropKind) - ~get:(PropKind.kind) () + ~get:(fun ip -> ip) () let () = States.column ~model ~name:"status" ~descr:(Md.plain "Status") - ~data:(module Jstring) - ~get:(fun ip -> - let st = Property_status.Feedback.get ip - in Format.asprintf "%a" Property_status.Feedback.pretty st) () + ~data:(module PropStatus) + ~get:(Property_status.Feedback.get) () let () = States.column ~model ~name:"function" ~descr:(Md.plain "Function") diff --git a/src/plugins/server/request.ml b/src/plugins/server/request.ml index 19dc53b676b..ebd45441bbc 100644 --- a/src/plugins/server/request.ml +++ b/src/plugins/server/request.ml @@ -205,7 +205,11 @@ let param (type a b) (s : (unit,b) signature) ~name ~descr ?default (input : a input) : a param = let module D = (val input) in let syntax = if default = None then D.syntax else Syntax.option D.syntax in - let fd = Syntax.{ name ; syntax ; descr } in + let fd = Syntax.{ + fd_name = name ; + fd_syntax = syntax ; + fd_descr = descr ; + } in s.input <- Pfields (fd :: fds_input s) ; fun rq -> try D.of_json (Fmap.find name rq.param) @@ -217,7 +221,11 @@ let param (type a b) (s : (unit,b) signature) ~name ~descr let param_opt (type a b) (s : (unit,b) signature) ~name ~descr (input : a input) : a option param = let module D = (val input) in - let fd = Syntax.{ name ; syntax = Syntax.option D.syntax ; descr } in + let fd = Syntax.{ + fd_name = name ; + fd_syntax = Syntax.option D.syntax ; + fd_descr = descr ; + } in s.input <- Pfields (fd :: fds_input s) ; fun rq -> try Some(D.of_json (Fmap.find name rq.param)) @@ -238,7 +246,11 @@ let fds_output s : Syntax.field list = let result (type a b) (s : (a,unit) signature) ~name ~descr ?default (output : b output) : b result = let module D = (val output) in - let fd = Syntax.{ name ; syntax = D.syntax ; descr } in + let fd = Syntax.{ + fd_name = name ; + fd_syntax = D.syntax ; + fd_descr = descr ; + } in s.output <- Rfields (fd :: fds_output s) ; begin match default with @@ -250,7 +262,11 @@ let result (type a b) (s : (a,unit) signature) ~name ~descr let result_opt (type a b) (s : (a,unit) signature) ~name ~descr (output : b output) : b option result = let module D = (val output) in - let fd = Syntax.{ name ; syntax = option D.syntax ; descr } in + let fd = Syntax.{ + fd_name = name ; + fd_syntax = option D.syntax ; + fd_descr = descr ; + } in s.output <- Rfields (fd :: fds_output s) ; fun rq opt -> match opt with None -> () | Some v -> diff --git a/src/plugins/server/states.ml b/src/plugins/server/states.ml index 7ba8567e3ee..2cbb15085cd 100644 --- a/src/plugins/server/states.ml +++ b/src/plugins/server/states.ml @@ -98,9 +98,13 @@ let column (type a b) ~(model : a model) ~name ~descr let module D = (val data) in if name = "key" || name = "index" then raise (Invalid_argument "Server.States.column: invalid name") ; - if List.exists (fun (fd,_) -> fd.Syntax.name = name) !model then + if List.exists (fun (fd,_) -> fd.Syntax.fd_name = name) !model then raise (Invalid_argument "Server.States.column: duplicate name") ; - let fd = Syntax.{ name ; syntax = D.syntax ; descr } in + let fd = Syntax.{ + fd_name = name ; + fd_syntax = D.syntax ; + fd_descr = descr ; + } in model := (fd , fun a -> D.to_json (get a)) :: !model module Kmap = Map.Make(String) @@ -257,12 +261,12 @@ let register_array ~page ~name ~descr ?(details=[]) ~key let columns = !model in let description = [ Block [Text descr] ; - Syntax.fields ~title:(Printf.sprintf "Array %s" name) + Syntax.fields ~title:"Columns" begin Syntax.{ - name="key" ; - syntax=Syntax.ident ; - descr=plain "entry identifier" ; + fd_name = "key" ; + fd_syntax = Syntax.ident ; + fd_descr = plain "entry identifier" ; } :: List.rev (List.map fst columns) end ; Block details @@ -270,7 +274,7 @@ let register_array ~page ~name ~descr ?(details=[]) ~key let mref = Doc.publish ~page:page ~name:name ~title ~index description [] in let signal = Request.signal ~page ~name:(name ^ ".sig") ~descr:(plain "Signal for array " @ href mref) () in - let getter = List.map (fun (fd,to_js) -> fd.Syntax.name , to_js) columns in + let getter = List.map Syntax.(fun (fd,to_js) -> fd.fd_name , to_js) columns in let array = { key ; iter ; getter ; signal ; current = None ; projects = Hashtbl.create 0 diff --git a/src/plugins/server/syntax.ml b/src/plugins/server/syntax.ml index bfc7218a57a..7c7a4778f4b 100644 --- a/src/plugins/server/syntax.ml +++ b/src/plugins/server/syntax.ml @@ -74,6 +74,8 @@ let publish ~page ~name ~descr ~synopsis ?(details = []) () = let _href = Doc.publish ~page ~name:id ~title ~index content [] in atom dlink +(* -------------------------------------------------------------------------- *) + let unit = atom @@ Markdown.plain "-" let any = atom @@ Markdown.emph "any" let int = atom @@ Markdown.emph "int" @@ -101,30 +103,50 @@ let union ts = flow @@ Markdown.(glue ~sep:(plain "|") (List.map protect ts)) let option t = atom @@ Markdown.(protect t @ code "?") -let field (a,t) = Markdown.( escaped a @ code ":" @ t.text ) +(* -------------------------------------------------------------------------- *) + +type tag = { + tag_name : string ; + tag_descr : Markdown.text ; +} + +let tags ?(title="Tag") (tgs : tag list) = + let open Markdown in + let header = [ + plain title, Left; + plain "Description", Left + ] in + let row tg = [ escaped tg.tag_name ; tg.tag_descr ] in + Markdown.Table { + caption = None ; header ; content = List.map row tgs ; + } + +(* -------------------------------------------------------------------------- *) + +let mfield (a,t) = Markdown.( escaped a @ code ":" @ t.text ) let record fds = let fields = if fds = [] then Markdown.plain "…" else - Markdown.(glue ~sep:(code ";") (List.map field fds)) + Markdown.(glue ~sep:(code ";") (List.map mfield fds)) in atom @@ Markdown.(code "{" @ fields @ code "}") type field = { - name : string ; - syntax : t ; - descr : Markdown.text ; + fd_name : string ; + fd_syntax : t ; + fd_descr : Markdown.text ; } -let fields ~title (fds : field list) = +let fields ?(title="Field") (fds : field list) = let open Markdown in let header = [ plain title, Left; plain "Format", Center; plain "Description", Left ] in - let column f = [ code f.name ; f.syntax.text ; f.descr ] in + let row f = [ code f.fd_name ; f.fd_syntax.text ; f.fd_descr ] in Markdown.Table { - caption = None ; header ; content = List.map column fds ; + caption = None ; header ; content = List.map row fds ; } (* -------------------------------------------------------------------------- *) diff --git a/src/plugins/server/syntax.mli b/src/plugins/server/syntax.mli index 1080751de02..b5f0367df2d 100644 --- a/src/plugins/server/syntax.mli +++ b/src/plugins/server/syntax.mli @@ -51,10 +51,18 @@ val option : t -> t val record : (string * t) list -> t val data : string -> Markdown.href -> t -type field = { name : string ; syntax : t ; descr : Markdown.text } +type tag = { tag_name : string ; tag_descr : Markdown.text } -(** Builds a table with fields column named with [~title] - (shall be capitalized) *) -val fields : title:string -> field list -> Markdown.element +(** Builds a table with tags description. + The [~title] is applied to the tag name column + (shall be capitalized, defaults to ["Tag"]). *) +val tags : ?title:string -> tag list -> Markdown.element + +type field = { fd_name : string ; fd_syntax : t ; fd_descr : Markdown.text } + +(** Builds a table with fields description. + The [~title] is applied to the field name column + (shall be capitalized, defaults to ["Field"]). *) +val fields : ?title:string -> field list -> Markdown.element (* -------------------------------------------------------------------------- *) -- GitLab