<div dir="ltr">Updated lenses, now with test lens:<br><br>(* ConfigObj_Typed lens:<br>   Pro:<br>    * validates value types and provides child node "type" of record<br>    * strips surrounding quotes from value_qstring<br>   Con:<br>    * child nodes of record are order-dependent: "commented" -> "type" -> ".comment"<br>    * child nodes of record are not automatically derived. Nodes "type" and "commented" must be provided<br>*)<br><br>module ConfigObj_Typed =<br>    let spaces              = del /[ \t]*/ " "<br>    let spaces_none         = del /[ \t]*/ ""<br>    let newline             = del /\n/ "\n"<br>    let option              = key /[a-z]([a-z_]*[a-z])?/<br>    let option_pad          = option . spaces<br>    let value_none          = [ label "type" . value "none" ]<br>    let value_boolean       = store /(true|false)/ . [ label "type" . value "boolean" ]<br>    let value_10_integer    = store /(0|[+-]?[1-9][0-9]*)/ . [ label "type" . value "integer base 10" ]<br>    let value_10_decimal    = store (/(0|[+-]?[0-9]*)\.[0-9]+/ - /[+-]0\.0+/) . [ label "type" . value "decimal base 10" ]<br>    let value_16_integer    = store /[+-]?0x[0-9a-fA-F]+/ . [ label "type" . value "integer base 16" ]<br>    let value_any_simple    = value_boolean | value_10_integer | value_10_decimal | value_16_integer<br>    let value_qstring       = (del "\"" "\"") . (store /([^\n\"]*([\].)*)*/) . (del "\"" "\"") . [ label "type" . value "string quoted" ]<br>    let value_ustring       = store (/[^ \t\n"#][^ \t\n#]*/ - lens_ctype value_any_simple) . [ label "type" . value "string unquoted" ]<br>    let value_any           = value_any_simple | value_qstring | value_ustring<br>    let value_any_pad       = value_none | ( spaces . value_any )<br>    let sep_assign          = del /=/ "="<br>    let sep_comment         = del /#/ "#"<br>    let comment_label       = label ".comment"<br>    let record_commented    = [ sep_comment . spaces . label "commented" . value "true" ] | [ label "commented" . value "false" ]<br>    let record_comment      = [ comment_label . sep_comment . store /.*/ ]<br>    let record_comment_pad  = spaces . record_comment<br>    let record_simple       = option_pad . sep_assign . value_any_pad . (spaces_none | record_comment_pad)<br>    let record              = [ record_commented . record_simple ]<br>    let comment             = [ comment_label . sep_comment . store (/.*/ - (/[ \t]*/ . lens_ctype record_simple)) ]<br>    let entry               = spaces_none . ( record | comment ) . newline<br>    let empty               = [ spaces_none . newline ]<br>    let line                = empty | entry<br>    let lns                 = line *<br><br><br>(* ConfigObj_Simple lens:<br>   Pro:<br>    * removes extraneous nodes "type" and "commented = false"<br>    * fast<br>   Con:<br>    * returns raw values - stripping surrounding quotes from value_qstring creates overlapping lenses in union.put<br>    * does not validate value types internally<br>    * child nodes of record are still order-dependent: "commented" before ".comment"<br>*)<br><br>module ConfigObj_Simple =<br>    let spaces              = del /[ \t]*/ " "<br>    let spaces_none         = del /[ \t]*/ ""<br>    let newline             = del /\n/ "\n"<br>    let option              = key /[a-z]([a-z_]*[a-z])?/<br>    let value_all           = store (/"([^\n\"]*([\].)*)*"/ | /[^ \t\n"#][^ \t\n#]*/)<br>    let sep_assign          = del /=/ "="<br>    let sep_comment         = del /#/ "#"<br>    let comment_label       = label ".comment"<br>    let record_commented    = [ sep_comment . spaces . label "commented" ]<br>    let record_comment      = [ comment_label . sep_comment . store /.*/ ]<br>    let record_simple       = option . spaces . sep_assign . (spaces . value_all)? . (spaces_none | (spaces . record_comment))<br>    let record              = [ record_commented? . record_simple ]<br>    let comment             = [ comment_label . sep_comment . store (/.*/ - (/[ \t]*/ . lens_ctype record_simple)) ]<br>    let entry               = spaces_none . ( record | comment ) . newline<br>    let empty               = [ spaces_none . newline ]<br>    let line                = empty | entry<br>    let lns                 = line *<br><br><br>module Test_ConfigObj =<br>    let lstrip (s1:string) =<br>        let l1 = [ label "a" . del /\n*/ "" ]? . [ label "b" . store(/(.|\n)*/) ] in<br>        let t1 = get l1 s1 in<br>        let t2 = rm "/a" t1 in<br>        put l1 t2 s1<br><br>    let conf =<br>"<br># comment<br># key_a = \"123\"<br>key_b = \"value 123\"<br><br>         <br>  <br># key_c = \"0.3\" # 1st inline comment #<br>key_d = \"true\" # inline comment with \"quoted\" word<br><br># this is a comment, with indented example<br>#   $ command<br>#   result1<br>#   result2<br># # key_e = \"Nested comment. Looks like a record, but still a comment.\"<br><br>key_f = <br>key_f_a = # spaces<br># key_g =<br># key_g_a =#no-spaces<br><br>#<br>    #<br>    #  <br><br>key_h=\"12\" #<br>    key_i= \"12\"  #<br>    key_h= \"12\"  #   <br><br>key_j = \"-0x01\"<br><br>#      key_k        =           \"word \\"in\\" quotes\"<br>##     key_l        =            \"a comment\" # still a comment<br><br>key_m = true    <br>key_n = 10#no-spaces<br>key_o =-0.001  <br>key_p =+0xfe0#no-spaces<br>key_q =unquoted\"string<br>"<br><br>    let conf_put = lstrip<br>"<br>####################<br># \"put\" test follows<br>####################<br># key_q = false ##### inline comment #####<br>key_r = #null value with comment<br>key_s = -100<br>key_t = +1.1119<br>key_u = 0x22<br># key_v = <><br>key_w = unquoted\"word<br>key_w = |<br>key_x = \"John \\"Doc\\" Cavendish\"<br>key_y =<br>"<br><br>    let tree_simple =<br>        {  }<br>        { ".comment" = " comment" }<br>        { "key_a" = "\"123\""<br>          { "commented" }<br>        }<br>        { "key_b" = "\"value 123\"" }<br>        {  }<br>        {  }<br>        {  }<br>        { "key_c" = "\"0.3\""<br>          { "commented" }<br>          { ".comment" = " 1st inline comment #" }<br>        }<br>        { "key_d" = "\"true\""<br>          { ".comment" = " inline comment with \"quoted\" word" }<br>        }<br>        {  }<br>        { ".comment" = " this is a comment, with indented example" }<br>        { ".comment" = "   $ command" }<br>        { ".comment" = "   result1" }<br>        { ".comment" = "   result2" }<br>        { ".comment" = " # key_e = \"Nested comment. Looks like a record, but still a comment.\"" }<br>        {  }<br>        { "key_f" }<br>        { "key_f_a"<br>          { ".comment" = " spaces" }<br>        }<br>        { "key_g"<br>          { "commented" }<br>        }<br>        { "key_g_a"<br>          { "commented" }<br>          { ".comment" = "no-spaces" }<br>        }<br>        {  }<br>        { ".comment" = "" }<br>        { ".comment" = "" }<br>        { ".comment" = "  " }<br>        {  }<br>        { "key_h" = "\"12\""<br>          { ".comment" = "" }<br>        }<br>        { "key_i" = "\"12\""<br>          { ".comment" = "" }<br>        }<br>        { "key_h" = "\"12\""<br>          { ".comment" = "   " }<br>        }<br>        {  }<br>        { "key_j" = "\"-0x01\"" }<br>        {  }<br>        { "key_k" = "\"word \\"in\\" quotes\""<br>          { "commented" }<br>        }<br>        { ".comment" = "#     key_l        =            \"a comment\" # still a comment" }<br>        {  }<br>        { "key_m" = "true" }<br>        { "key_n" = "10"<br>            { ".comment" = "no-spaces" }<br>        }<br>        { "key_o" = "-0.001" }<br>        { "key_p" = "+0xfe0"<br>          { ".comment" = "no-spaces" }<br>        }<br>        { "key_q" = "unquoted\"string" }<br><br>    let tree_typed = <br>        {  }<br>        { ".comment" = " comment" }<br>        { "key_a" = "123"<br>          { "commented" = "true" }<br>          { "type" = "string quoted" }<br>        }<br>        { "key_b" = "value 123"<br>          { "commented" = "false" }<br>          { "type" = "string quoted" }<br>        }<br>        {  }<br>        {  }<br>        {  }<br>        { "key_c" = "0.3"<br>          { "commented" = "true" }<br>          { "type" = "string quoted" }<br>          { ".comment" = " 1st inline comment #" }<br>        }<br>        { "key_d" = "true"<br>          { "commented" = "false" }<br>          { "type" = "string quoted" }<br>          { ".comment" = " inline comment with \"quoted\" word" }<br>        }<br>        {  }<br>        { ".comment" = " this is a comment, with indented example" }<br>        { ".comment" = "   $ command" }<br>        { ".comment" = "   result1" }<br>        { ".comment" = "   result2" }<br>        { ".comment" = " # key_e = \"Nested comment. Looks like a record, but still a comment.\"" }<br>        {  }<br>        { "key_f"<br>          { "commented" = "false" }<br>          { "type" = "none" }<br>        }<br>        { "key_f_a"<br>          { "commented" = "false" }<br>          { "type" = "none" }<br>          { ".comment" = " spaces" }<br>        }<br>        { "key_g"<br>          { "commented" = "true" }<br>          { "type" = "none" }<br>        }<br>        { "key_g_a"<br>          { "commented" = "true" }<br>          { "type" = "none" }<br>          { ".comment" = "no-spaces" }<br>        }<br>        {  }<br>        { ".comment" = "" }<br>        { ".comment" = "" }<br>        { ".comment" = "  " }<br>        {  }<br>        { "key_h" = "12"<br>          { "commented" = "false" }<br>          { "type" = "string quoted" }<br>          { ".comment" = "" }<br>        }<br>        { "key_i" = "12"<br>          { "commented" = "false" }<br>          { "type" = "string quoted" }<br>          { ".comment" = "" }<br>        }<br>        { "key_h" = "12"<br>          { "commented" = "false" }<br>          { "type" = "string quoted" }<br>          { ".comment" = "   " }<br>        }<br>        {  }<br>        { "key_j" = "-0x01"<br>          { "commented" = "false" }<br>          { "type" = "string quoted" }<br>        }<br>        {  }<br>        { "key_k" = "word \\"in\\" quotes"<br>          { "commented" = "true" }<br>          { "type" = "string quoted" }<br>        }<br>        { ".comment" = "#     key_l        =            \"a comment\" # still a comment" }<br>        {  }<br>        { "key_m" = "true"<br>          { "commented" = "false" }<br>          { "type" = "boolean" }<br>        }<br>        { "key_n" = "10"<br>          { "commented" = "false" }<br>          { "type" = "integer base 10" }<br>          { ".comment" = "no-spaces" }<br>        }<br>        { "key_o" = "-0.001"<br>          { "commented" = "false" }<br>          { "type" = "decimal base 10" }<br>        }<br>        { "key_p" = "+0xfe0"<br>          { "commented" = "false" }<br>          { "type" = "integer base 16" }<br>          { ".comment" = "no-spaces" }<br>        }<br>        { "key_q" = "unquoted\"string"<br>          { "commented" = "false" }<br>          { "type" = "string unquoted" }<br>        }<br><br><br>    test ConfigObj_Simple.lns get conf = tree_simple<br>    test ConfigObj_Typed.lns get conf = tree_typed<br><br>    test ConfigObj_Simple.lns put conf after<br>        set ".comment[last()+1]" "###################";<br>        set ".comment[last()+1]" " \"put\" test follows";<br>        set ".comment[last()+1]" "###################";<br>        set "/key_q[last()+1]" "false";<br>        set "/key_q[last()+0]/commented" "";<br>        set "/key_q[last()+0]/.comment" "#### inline comment #####";<br>        set "/key_r/.comment" "null value with comment";<br>        set "/key_s" "-100";<br>        set "/key_t" "+1.1119";<br>        set "/key_u" "0x22";<br>        set "/key_v" "<>";<br>        set "/key_v/commented" "";<br>        set "/key_w" "unquoted\"word";<br>        set "/key_w[last()+1]" "|";<br>        set "/key_x" "\"John \\"Doc\\" Cavendish\"";<br>        clear "/key_y"<br>    = (conf . conf_put)<br><br>    test ConfigObj_Typed.lns put conf after<br>        set ".comment[last()+1]" "###################";<br>        set ".comment[last()+1]" " \"put\" test follows";<br>        set ".comment[last()+1]" "###################";<br><br>        set "/key_q[last()+1]" "false";<br>        set "/key_q[last()+0]/commented" "true";<br>        set "/key_q[last()+0]/type" "boolean";<br>        set "/key_q[last()+0]/.comment" "#### inline comment #####";<br><br>        set "/key_r/commented" "false";<br>        set "/key_r/type" "none";<br>        set "/key_r/.comment" "null value with comment";<br><br>        set "/key_s" "-100";<br>        set "/key_s/commented" "false";<br>        set "/key_s/type" "integer base 10";<br><br>        set "/key_t" "+1.1119";<br>        set "/key_t/commented" "false";<br>        set "/key_t/type" "decimal base 10";<br><br>        set "/key_u" "0x22";<br>        set "/key_u/commented" "false";<br>        set "/key_u/type" "integer base 16";<br><br>        set "/key_v" "<>";<br>        set "/key_v/commented" "true";<br>        set "/key_v/type" "string unquoted";<br><br>        set "/key_w" "unquoted\"word";<br>        set "/key_w/commented" "false";<br>        set "/key_w/type" "string unquoted";<br><br>        set "/key_w[last()+1]" "|";<br>        set "/key_w[last()+0]/commented" "false";<br>        set "/key_w[last()+0]/type" "string unquoted";<br><br>        set "/key_x" "John \\"Doc\\" Cavendish";<br>        set "/key_x/commented" "false";<br>        set "/key_x/type" "string quoted";<br><br>        clear "/key_y";<br>        set "/key_y/commented" "false";<br>        set "/key_y/type" "none"<br>    = (conf . conf_put)<br></div>