10

I'm not worried about supporting sections, although a bounty would be given for doing so :)

Here's what I have so far, but it produces strange output for #2:

\begin{filecontents}{test}
\Property this property = some value
\Property k = v
\end{filecontents}
\documentclass{article}
\usepackage{expl3}
\ExplSyntaxOn

\cs_new:Npn \Property #1=#2^^M
  {
    1:#1\par
    2:#2\par
  }

\ExplSyntaxOff
\begin{document}
\input{test}
\end{document}

Ideally, I would like to have a syntax like

\begin{filecontents*}{test.conf}
this property = some value
k = v

# bounty
[section]
property=value
\end{filecontents*}
\documentclass{article}
\usepackage{xparse}
\ExplSyntaxOn

\begin{document}
\conf_load:Nn \l_tmpa_prop { test.conf }

% outputs {some value} (brace groups irrelevant)
\conf_get:Nn \l_tmpa_prop { this ~ property }

% bounty; outputs {value} (brace groups irrelevant)
\conf_get:Nnn \l_tmpa_prop { section } { property }

\end{document}

How can this be accomplished? I should note that the details of the interface / naming is just a suggestion.

Ideas

(a work-in-progress list)

  • Reading in the file and mapping a macro over each line; doesn't support sections
  • Making necessary characters active for the read and practicing dark magic
  • One way would be to use the datatool package. Do you have a preference for using expl3? Also, it would be helpful if your provided an actual list of <property name>, <property value> pairs. Specifically do you need to be able to handle any of the special LaTeX characters ? – Peter Grill Sep 29 '14 at 18:15
  • @PeterGrill Unfortunately, every character is fair game. I do have a preference for expl3, but if a wrapper can be created, I'm fine with that. :) – Sean Allred Sep 29 '14 at 18:17
10
+100

Is this what you have in mind?

I define a toplevel property list and a section one; add the plists you need.

\begin{filecontents*}{test.conf}
this property = some value
k = v

# bounty
[section]
property=value
\end{filecontents*}
\documentclass{article}
\usepackage{xparse}
\ExplSyntaxOn
\NewDocumentCommand{\confload}{m}
 {
  \allred_conf_load:n { #1 }
 }

\prop_new:N \g_allred_conf_toplevel_prop
\prop_new:N \g_allred_conf_section_prop
\tl_new:N \l__allred_level_tl
\ior_new:N \g_allred_read_conf_stream

\cs_new_protected:Npn \allred_conf_load:n #1
 {
  \group_begin:
  \tex_endlinechar:D \c_minus_one % Ugly! Complain with the team!
  \char_set_catcode_comment:n { `\# }
  \ior_open:Nn \g_allred_read_conf_stream { #1 }
  \tl_set:Nn \l__allred_level_tl { toplevel }
  \ior_map_inline:Nn \g_allread_read_conf_stream
   {
    \tl_if_blank:nF { ##1 }
     {
      \__allred_process_line:x { \tl_trim_spaces:n { ##1 } }
     }
   }
  \ior_close:N \g_allred_read_conf_stream { #1 }
  \group_end:
 }

\cs_new_protected:Npn \__allred_process_line:n #1
 {
  \str_case_x:nnF { \tl_item:nn { #1 } { 1 } }
   {
    { [ } { \__allred_process_newlevel:n { #1 } }
   }
   {
    \__allred_process_property:n { #1 }
   }
 }
\cs_generate_variant:Nn \__allred_process_line:n { x }

\cs_new_protected:Npn \__allred_process_newlevel:n #1
 {
  \__allred_process_newlevel:w #1
 }
\cs_new_protected:Npn \__allred_process_newlevel:w [ #1 ]
 {
  \tl_set:Nn \l__allred_level_tl { #1 }
 }
\cs_new_protected:Npn \__allred_process_property:n #1
 {
  \__allred_process_property_aux:www #1 ==\q_stop
 }
\cs_new_protected:Npn \__allred_process_property_aux:www #1 = #2 = #3 \q_stop
 {
  \prop_gput:cxx { g__allred_conf_ \l__allred_level_tl _prop }
   { \tl_trim_spaces:n { #1 } }
   { \tl_trim_spaces:n { #2 } }
 }
\cs_generate_variant:Nn \prop_gput:cnn { cxx }

\ExplSyntaxOff


\begin{document}
\confload{test.conf}

\ExplSyntaxOn
\prop_show:N \g__allred_conf_toplevel_prop
\prop_show:N \g__allred_conf_section_prop
\ExplSyntaxOff


\end{document}

This is the output on the terminal

The property list \g__allred_conf_toplevel_prop contains the pairs (without
outer braces):
>  {this property}  =>  {some value}
>  {k}  =>  {v}.
<recently read> }

l.78 \prop_show:N \g__allred_conf_toplevel_prop

? 
The property list \g__allred_conf_section_prop contains the pairs (without
outer braces):
>  {property}  =>  {value}.
<recently read> }

l.79 \prop_show:N \g__allred_conf_section_prop
  • This is fantastic! A tick just doesn't seem to be enough, thank you! I'll add the bounty as promised when I am able to within the restrictions of StackExchange. – Sean Allred Sep 29 '14 at 18:31
  • On \endlinechar: the reason there's not an interface is that it's not that clear what one should be. It's better, long term, to add things only when we are reasonably clear what's the 'correct' approach rather than add half-tested ideas then have to revise them. With \endlinechar, use cases are rather rare and the team haven't needed it beyond places where a :D use is OK, at least to date. – Joseph Wright Sep 29 '14 at 19:43
  • @JosephWright A place where setting \endlinechar to –1 can be useful is certainly \ior_map_inline:Nn; maybe \ior_map_inline_no_end_line:Nn that does some trickery to resurrect the current value at the end? Sooner or later something like \parameter_set_endlinechar:n will be needed (and the same for all other internal parameters). – egreg Sep 29 '14 at 19:46
  • @egreg Isn't that normally covered by the string mapping case? – Joseph Wright Sep 29 '14 at 19:48
  • @JosephWright That's a very different thing, as the tokens are read in with category code 12, which may not be desired. – egreg Sep 29 '14 at 19:49
10

My solution uses no expl3, no special latex macros, only TeX primitives. Just for comparison. The coding of this task begins from line 11 (first ten lines are copied as universal macros from OPmac).

\bgroup \catcode`!=3 \catcode`?=3 % \replacestrings, \addto, \sxdef from OPmac
\gdef\replacestrings#1#2{%
   \long\def\tmp##1#1##2!{\ifx!##2!\addto\tmpb{##1}\else\addto\tmpb{##1#2}\tmp##2!\fi}%
   \edef\tmpb{\expandafter}\expandafter\tmp\tmpb?#1!%
   \def\tmp##1?{\def\tmpb{##1}}\expandafter\tmp\tmpb
}
\egroup
\long\def\addto#1#2{\expandafter\def\expandafter#1\expandafter{#1#2}}
\def\sxdef#1{\expandafter\xdef\csname#1\endcsname}

% \readconf config_file 
\newread\infile
\def\readconf #1 {\bgroup \catcode`\#=14 \endlinechar=-1
   \def\sectionconf{global}\openin\infile=#1 \readconfA
}
\def\readconfA{\ifeof\infile \egroup \else
   \read\infile to\tmp 
   \expandafter\readconfB\tmp\par
   \expandafter \readconfA\fi
}
\def\readconfB#1\par{\ifx\par#1\par \else \readconfC#1\par \fi}
\def\readconfC#1#2\par{\ifx[#1\expandafter\readconfD \else\expandafter\readconfE\fi#1#2\par}
\def\readconfD[#1]#2\par{\def\sectionconf{#1}}
\def\readconfE#1\par{\def\tmpb{#1}\replacestrings{= }{=}\replacestrings{ =}{=}%
   \expandafter\readconfF\tmpb\par}
\def\readconfF#1=#2\par{\setkeyval{#1}{#2}}

\def\setkeyval#1#2{\expandafter\ifx\csname conf:\sectionconf\endcsname\relax
   \sxdef{conf:\sectionconf}{}\fi
   \sxdef{conf:\sectionconf}{\csname conf:\sectionconf\endcsname{#1}}%
   \sxdef{key:\sectionconf:#1}{#2}%
}

% \showconf[section]
\def\showconf[#1]{\def\sectionconf{#1}\message{SECTION [#1]:}%
   \expandafter\expandafter\expandafter \showconfA \csname conf:#1\endcsname \relax
}
\def\showconfA#1{\ifx\relax#1\else \showconfB{#1}\expandafter\showconfA\fi}
\def\showconfB#1{\message{{#1} => {\csname key:\sectionconf:#1\endcsname}}}

\readconf test.conf

\showconf [global]  % SECTION [global]: {this property} => {some value} {k} => {v}
\showconf [section] % SECTION [section]: {property} => {value}

\end
  • +1 Wow! Very, very impressive! :) – Paulo Cereda Sep 30 '14 at 10:48

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.