7

In the code below, made using the "AI" Claude, there is too much copying and pasting of lines like the ones shown below. What is the best way to refactor the full code?

 archivefile/.style={ before typesetting nodes={ content/.wrap value={ \textcolor{orange!80}{\faFileArchive}\ ##1 } } } 

Here is the full code.

\documentclass{article} \usepackage{forest} \usepackage{fontawesome5} \definecolor{foldercolor}{RGB}{124,166,198} \definecolor{filecolor}{RGB}{88,88,88} \forestset{ dir tree/.style={ for tree={ font = \sffamily, grow' = 0, child anchor = west, parent anchor= south, anchor = west, calign = first, inner ysep = 1pt, inner xsep = 1.75pt, text depth = 0pt, edge path = { \noexpand \path[draw, \forestoption{edge}] (!u.south west) +(7.5pt,0) |- (.child anchor)\forestoption{edge label}; }, before typesetting nodes={ if n=1 {insert before={[,phantom]}} {} }, fit = band, before computing xy = {l = 15pt}, } }, open/.style={ before typesetting nodes={ content/.wrap value={ \textcolor{foldercolor}{\faFolderOpen}\ ##1 } } }, closed/.style={ before typesetting nodes={ content/.wrap value={ \textcolor{foldercolor}{\faFolder}\ ##1 } } }, file/.style={ before typesetting nodes={ content/.wrap value={ \textcolor{filecolor}{\faFile}\ ##1 } } }, codefile/.style={ before typesetting nodes={ content/.wrap value={ \textcolor{filecolor}{\faFileCode}\ ##1 } } }, pdffile/.style={ before typesetting nodes={ content/.wrap value={ \textcolor{red!70}{\faFilePdf}\ ##1 } } }, textfile/.style={ before typesetting nodes={ content/.wrap value={ \textcolor{filecolor}{\faFile*}\ ##1 } } }, imagefile/.style={ before typesetting nodes={ content/.wrap value={ \textcolor{blue!70}{\faFileImage}\ ##1 } } }, archivefile/.style={ before typesetting nodes={ content/.wrap value={ \textcolor{orange!80}{\faFileArchive}\ ##1 } } }, } \begin{document} \subsection*{Exemple d'arborescence de fichiers} \begin{forest} dir tree [system, open [config, closed [settings.conf, textfile] [database.yml, codefile] ] [doc, open [manual.pdf, pdffile] [README.md, textfile] [images, closed [logo.png, imagefile] [banner.jpg, imagefile] ] ] [lib, closed [utils.py, codefile] [helpers.js, codefile] [archive.zip, archivefile] ] [test, open [test\_unit.py, codefile] [test\_integration.py, codefile] [fixtures, closed [data.json, file] ] ] ] \end{forest} \subsection*{Exemple simple} \begin{forest} dir tree [system, open [config, closed] [Dog, closed] [lib, closed] [test, closed] ] \end{forest} \end{document} 

enter image description here

12
  • 1
    what do you mean by 'factorise'? I know how to do this for integers ... I guess it has a different meaning here. (this is not a criticism. I just do not know lots of the terminology people use here.) you might mean 'parametise`, but I think you'd say that ... Commented Nov 22 at 22:06
  • 1
    your code would be less verbose if you used features from forest 2.1 rather than only those from older versions. Commented Nov 22 at 22:08
  • 1
    should this code have an attribution? it's hard to believe you came up with this by reading a current copy of the manual, so I'm assuming it was written 8+ years ago, possibly pre forest 2.0, but certainly pre 2.1.5. Commented Nov 22 at 23:08
  • 1
    so please link. Commented Nov 23 at 0:00
  • 2
    @cfr, in the book Martin Fowler gives most of the credit for the idea of refactoring to Kent Beck, I am not sure if either of them knows about embroidery, but they do seem to know a lot about the value of keeping your code simple and tidy, and I think they might be very happy with disentangling. Commented Nov 23 at 16:33

2 Answers 2

15

I do not know what is meant by 'factorise' here1.

Strategies:

  1. Use features of Forest 2.1, published 8 years ago, rather than relying only on those available in older versions.
  2. Use standard TikZ methods to simplify the code and reduce duplication e.g. styles.
  3. Optionally, use handlers to make the code more succinct.

(1) greatly simplifies the code. (2) greatly reduces duplication. (3) has costs as well as benefits, but an implementation is now included below.

\documentclass[border=10pt,tikz]{standalone} \usepackage[edges]{forest} \usepackage{fontawesome5} \definecolor{foldercolor}{RGB}{124,166,198} \definecolor{filecolor}{RGB}{88,88,88} % ateb: https://tex.stackexchange.com/a/755230/ % cwestiwn projetmbc: https://tex.stackexchange.com/q/755225/ % but the original origin isn't identified - looks like Medina's? \forestset{ fa dir tree/.style={ for tree={ font = \sffamily, folder, grow' = 0, inner ysep = 1pt, inner xsep = 1.75pt, }, }, fa/.style 2 args={ before typesetting nodes={ content/.process={Ow{content}{ \textcolor{#1}{#2}\ ##1 }% }, }, }, open/.style={ fa={foldercolor}{\faFolderOpen}, }, closed/.style={ fa={foldercolor}{\faFolder}, }, file/.style={ fa={filecolor}{\faFile}, }, codefile/.style={ fa={filecolor}{\faFile}, }, pdffile/.style={ fa={red!70}{\faFilePdf}, }, textfile/.style={ fa={filecolor}{\faFile*}, }, imagefile/.style={ fa={blue!70}{\faFileImage}, }, archivefile/.style={ fa={orange!80}{\faFileArchive}, }, } \begin{document} \begin{forest} fa dir tree [system, open [config, closed [settings.conf, textfile] [database.yml, codefile] ] [doc, open [manual.pdf, pdffile] [README.md, textfile] [images, closed [logo.png, imagefile] [banner.jpg, imagefile] ] ] [lib, closed [utils.py, codefile] [helpers.js, codefile] [archive.zip, archivefile] ] [test, open [test\_unit.py, codefile] [test\_integration.py, codefile] [fixtures, closed [data.json, file] ] ] ] \end{forest} \begin{forest} fa dir tree [system, open [config, closed] [Dog, closed] [lib, closed] [test, closed] ] \end{forest} \end{document} 

more complex and simpler pair of trees

Edit

I would not recommend using a loop here for reasons explained in the comments.

If anything, I would use a handler, as mentioned above.

The reason I don't necessarily recommend this is that you do not really gain much in this case in terms of concision or flexibility. The number of file/directory types is small and the above definitions are very short. Moreover, they are very clear: anybody who knows only a little pgf/tikz will see immediately how to define an additional style, if required.

I like custom handlers, but they obviously are that much less efficient2, and they can make code more obscure. It's not clear to me that the code above is better rewritten this way:

\documentclass[border=10pt,tikz]{standalone} \usepackage[edges]{forest} \usepackage{fontawesome5} \definecolor{foldercolor}{RGB}{124,166,198} \definecolor{filecolor}{RGB}{88,88,88} % ateb: https://tex.stackexchange.com/a/755230/ % cwestiwn projetmbc: https://tex.stackexchange.com/q/755225/ % but the original origin isn't identified - looks like Medina's? \forestset{ fa dir tree/.style={ for tree={ font = \sffamily, folder, grow' = 0, inner ysep = 1pt, inner xsep = 1.75pt, }, }, fa/.style 2 args={ before typesetting nodes={ content/.process={Ow{content}{ \textcolor{#1}{#2}\ ##1 }% }, }, }, /handlers/.fa/.code 2 args={% \edef\projetmbctemptpath{\pgfkeyscurrentpath}% \pgfkeys{% \pgfkeyscurrentpath/.style={% fa={#1}{#2}, }, }% }, open/.fa={foldercolor}{\faFolderOpen}, closed/.fa={foldercolor}{\faFolder}, file/.fa={filecolor}{\faFile}, codefile/.fa={filecolor}{\faFile}, pdffile/.fa={red!70}{\faFilePdf}, textfile/.fa={filecolor}{\faFile*}, imagefile/.fa={blue!70}{\faFileImage}, archivefile/.fa={orange!80}{\faFileArchive}, } \begin{document} \begin{forest} fa dir tree [system, open [config, closed [settings.conf, textfile] [database.yml, codefile] ] [doc, open [manual.pdf, pdffile] [README.md, textfile] [images, closed [logo.png, imagefile] [banner.jpg, imagefile] ] ] [lib, closed [utils.py, codefile] [helpers.js, codefile] [archive.zip, archivefile] ] [test, open [test\_unit.py, codefile] [test\_integration.py, codefile] [fixtures, closed [data.json, file] ] ] ] \end{forest} \begin{forest} fa dir tree [system, open [config, closed] [Dog, closed] [lib, closed] [test, closed] ] \end{forest} \end{document} 

This produces exactly the same output and the style definitions are more concise, but it is also rather less obvious what they do. Compare e.g.

 /handlers/.fa/.code 2 args={% \edef\projetmbctemptpath{\pgfkeyscurrentpath}% \pgfkeys{% \pgfkeyscurrentpath/.style={% fa={#1}{#2}, }, }% }, open/.fa={foldercolor}{\faFolderOpen}, 

with

 open/.style={ fa={foldercolor}{\faFolderOpen}, }, 

In both cases, you'd have to lookup the fa style, but in the first, you also have to figure out what .fa does and that means looking up what \pgfkeyscurrentpath refers to, unless you happen to know already3.

However, if you have dozens of these, just annotate the code with an explanation and use the handler.

1I think this is something you do with numbers - paradigmatically, integers. 2But probably not a priority since we are using forest already. 3In which case, you probably wouldn't be asking this question :-).

9
  • I talk about code refactoring : en.wikipedia.org/wiki/Code_refactoring Commented Nov 22 at 23:21
  • Could we also use a kind of loop for all the textfile/.style={fa={filecolor}{\faFile*},} like lines? Commented Nov 22 at 23:25
  • @projetmbc you could, but I do not think it would be worth the trouble for code this short and simple. your can't use a \foreach type loop because it forms a group. you cannot use .list because you have 3 arguments. you could use 2 levels of split. i.e. the first split sends the arguments to a style which splits each argument into three and passes those to code which creates the style. but you need a lot of more complex code to avoid duplicating a small amount of simple, easily understood code. I would not consider that an improvement. a handler would be better, IMHO. Commented Nov 22 at 23:52
  • 1
    @projetmbc see edit above. I would rather this than a loop, though split could be made to work here, if required. Commented Nov 23 at 3:02
  • 1
    @projetmbc sorry, but explaining the argument processor is not something I can do in a comment. have a look at that bit of the manual and then ask a question if something doesn't make sense. O is option. w does not capture anything. the interpretation here is governed by the handler. Commented Nov 24 at 1:31
3

Just for fun, here is an l3draw solution which might need some adjustments and improvements ...

\documentclass{article} \usepackage{l3draw} \usepackage{fontawesome5} \ExplSyntaxOn \int_new:N \g_projetmbc_dirtree_step_int \int_new:N \l_projetmbc_dirtree_level_int \bool_new:N \l_projetmbc_dirtree_first_item_bool \coffin_new:N \l_projetmbc_dirtree_icon_coffin \coffin_new:N \l_projetmbc_dirtree_name_coffin \NewDocumentCommand { \drawpath } { m } { \group_begin: \nullfont \int_gzero:N \g_projetmbc_dirtree_step_int \draw_begin: #1 \draw_end: \group_end: } \color_set:nnn { foldercolor } { RGB } { 124 , 166 , 198 } \color_set:nnn { filecolor } { RGB } { 88 , 88 , 88 } \NewDocumentCommand { \pathitem } { O { filecolor } m m m } { \hcoffin_set:Nn \l_projetmbc_dirtree_icon_coffin { \color_select:n {#1} #2 } \hcoffin_set:Nn \l_projetmbc_dirtree_name_coffin { \normalfont #3 } \draw_coffin_use:Nnnn \l_projetmbc_dirtree_icon_coffin { hc } { vc } { \l_projetmbc_dirtree_level_int * 0.5cm + 0.2cm , \g_projetmbc_dirtree_step_int * \baselineskip } \draw_coffin_use:Nnnn \l_projetmbc_dirtree_name_coffin { l } { vc } { \l_projetmbc_dirtree_level_int * 0.5cm + 0.5 cm , \g_projetmbc_dirtree_step_int * \baselineskip } \int_compare:nNnT { \l_projetmbc_dirtree_level_int } > { 0 } { \int_step_inline:nn { \l_projetmbc_dirtree_level_int - 1 } { \draw_path_moveto:n { ##1 * 0.5cm - 0.33cm, ( \g_projetmbc_dirtree_step_int + 1 ) * \baselineskip } \draw_path_lineto:n { ##1 * 0.5cm - 0.33cm , \g_projetmbc_dirtree_step_int * \baselineskip } } \bool_if:NTF \l_projetmbc_dirtree_first_item_bool { \draw_path_moveto:n { \l_projetmbc_dirtree_level_int * 0.5cm - 0.33cm , ( \g_projetmbc_dirtree_step_int + 0.5 ) * \baselineskip } } { \draw_path_moveto:n { \l_projetmbc_dirtree_level_int * 0.5cm - 0.33cm , ( \g_projetmbc_dirtree_step_int + 1 ) * \baselineskip } } \draw_path_lineto:n { \l_projetmbc_dirtree_level_int * 0.5cm - 0.33cm , \g_projetmbc_dirtree_step_int * \baselineskip } \draw_path_lineto:n { \l_projetmbc_dirtree_level_int * 0.5cm - 0.1cm , \g_projetmbc_dirtree_step_int * \baselineskip } \draw_path_use_clear:n { stroke } } \int_gdecr:N \g_projetmbc_dirtree_step_int \bool_set_false:N \l_projetmbc_dirtree_first_item_bool \group_begin: \bool_set_true:N \l_projetmbc_dirtree_first_item_bool \int_incr:N \l_projetmbc_dirtree_level_int #4 \group_end: } \ExplSyntaxOff \begin{document} \drawpath{ \pathitem[foldercolor]{\faFolderOpen}{system}{ \pathitem[foldercolor]{\faFolderOpen}{foo}{ \pathitem[foldercolor]{\faFolderOpen}{bar}{ \pathitem{\faFile}{baz1}{} \pathitem{\faFile}{baz2}{} } \pathitem{\faFile}{baz3}{} } \pathitem[red!70]{\faFilePdf}{file.pdf}{} } } \end{document} 

output of above code


Edit to make it possible to uncontinue lines earlier by adding a star to the command (I have no better idea at the moment):

\documentclass{article} \usepackage{l3draw} \usepackage{fontawesome5} \ExplSyntaxOn \int_new:N \g_projetmbc_dirtree_step_int \int_new:N \l_projetmbc_dirtree_level_int \clist_new:N \l_projetmbc_dirtree_skip_level_clist \bool_new:N \l_projetmbc_dirtree_first_item_bool \coffin_new:N \l_projetmbc_dirtree_icon_coffin \coffin_new:N \l_projetmbc_dirtree_name_coffin \NewDocumentCommand { \drawpath } { m } { \group_begin: \clist_clear:N \l_projetmbc_dirtree_skip_level_clist \int_gzero:N \g_projetmbc_dirtree_step_int \nullfont \draw_begin: #1 \draw_end: \group_end: } \color_set:nnn { foldercolor } { RGB } { 124 , 166 , 198 } \color_set:nnn { filecolor } { RGB } { 88 , 88 , 88 } \cs_new_protected:Npn \projetmbc_dirtree_item:nnnnn #1#2#3#4#5 { \bool_if:nT {#1} { \clist_put_right:NV \l_projetmbc_dirtree_skip_level_clist \l_projetmbc_dirtree_level_int } \hcoffin_set:Nn \l_projetmbc_dirtree_icon_coffin { \color_select:n {#2} #3 } \hcoffin_set:Nn \l_projetmbc_dirtree_name_coffin { \normalfont #4 } \draw_coffin_use:Nnnn \l_projetmbc_dirtree_icon_coffin { hc } { vc } { \l_projetmbc_dirtree_level_int * 0.5cm + 0.2cm , \g_projetmbc_dirtree_step_int * \baselineskip } \draw_coffin_use:Nnnn \l_projetmbc_dirtree_name_coffin { l } { vc } { \l_projetmbc_dirtree_level_int * 0.5cm + 0.5 cm , \g_projetmbc_dirtree_step_int * \baselineskip } \int_compare:nNnT { \l_projetmbc_dirtree_level_int } > { 0 } { \int_step_inline:nn { \l_projetmbc_dirtree_level_int - 1 } { \exp_args:NNe \clist_if_in:NnF \l_projetmbc_dirtree_skip_level_clist { \int_eval:n { ##1 - 1 } } { \draw_path_moveto:n { ##1 * 0.5cm - 0.33cm, ( \g_projetmbc_dirtree_step_int + 1 ) * \baselineskip } \draw_path_lineto:n { ##1 * 0.5cm - 0.33cm , \g_projetmbc_dirtree_step_int * \baselineskip } } } \bool_if:NTF \l_projetmbc_dirtree_first_item_bool { \draw_path_moveto:n { \l_projetmbc_dirtree_level_int * 0.5cm - 0.33cm , ( \g_projetmbc_dirtree_step_int + 0.5 ) * \baselineskip } } { \draw_path_moveto:n { \l_projetmbc_dirtree_level_int * 0.5cm - 0.33cm , ( \g_projetmbc_dirtree_step_int + 1 ) * \baselineskip } } \draw_path_lineto:n { \l_projetmbc_dirtree_level_int * 0.5cm - 0.33cm , \g_projetmbc_dirtree_step_int * \baselineskip } \draw_path_lineto:n { \l_projetmbc_dirtree_level_int * 0.5cm - 0.1cm , \g_projetmbc_dirtree_step_int * \baselineskip } \draw_path_use_clear:n { stroke } } \int_gdecr:N \g_projetmbc_dirtree_step_int \bool_set_false:N \l_projetmbc_dirtree_first_item_bool \group_begin: \bool_set_true:N \l_projetmbc_dirtree_first_item_bool \int_incr:N \l_projetmbc_dirtree_level_int #5 \group_end: } \NewDocumentCommand { \drawpathfile } { O { filecolor } m m } { \projetmbc_dirtree_item:nnnnn { \c_false_bool } { #1 } { #2 } { #3 } { } } \NewDocumentCommand { \drawpathdir } { s O { foldercolor } m m m } { \projetmbc_dirtree_item:nnnnn { #1 } { #2 } { #3 } { #4 } { #5 } } \ExplSyntaxOff \begin{document} \drawpath{ \drawpathdir{\faFolderOpen}{system}{ \drawpathdir*{\faFolderOpen}{foo}{ \drawpathdir{\faFolderOpen}{bar}{ \drawpathfile{\faFile}{baz1} \drawpathfile{\faFile}{baz2} } } \drawpathfile[red!70]{\faFilePdf}{file.pdf} } } \end{document} 

output of above code


Or even like this without the need to manually state that the item is the last:

\documentclass{article} \usepackage{l3draw} \usepackage{fontawesome5} \ExplSyntaxOn \int_new:N \g_projetmbc_dirtree_step_int \int_new:N \l_projetmbc_dirtree_level_int \int_new:N \l_projetmbc_dirtree_item_count_int \int_new:N \l_projetmbc_dirtree_item_current_int \clist_new:N \l_projetmbc_dirtree_skip_level_clist \coffin_new:N \l_projetmbc_dirtree_icon_coffin \coffin_new:N \l_projetmbc_dirtree_name_coffin \NewDocumentCommand { \drawpath } { m } { \group_begin: \clist_clear:N \l_projetmbc_dirtree_skip_level_clist \int_gzero:N \g_projetmbc_dirtree_step_int \int_zero:N \l_projetmbc_dirtree_item_current_int \nullfont \draw_begin: #1 \draw_end: \group_end: } \color_set:nnn { foldercolor } { RGB } { 124 , 166 , 198 } \color_set:nnn { filecolor } { RGB } { 88 , 88 , 88 } \cs_new_protected:Npn \__projetmbc_dirtree_items_count:n #1 { \int_zero:N \l_projetmbc_dirtree_item_count_int \tl_map_inline:nn {#1} { \tl_analysis_log:n {##1} \tl_if_head_eq_meaning:nNT {##1} \drawpathfile { \int_incr:N \l_projetmbc_dirtree_item_count_int } \tl_if_head_eq_meaning:nNT {##1} \drawpathdir { \int_incr:N \l_projetmbc_dirtree_item_count_int } } } \cs_new_protected:Npn \projetmbc_dirtree_item:nnnn #1#2#3#4 { \int_incr:N \l_projetmbc_dirtree_item_current_int \__projetmbc_dirtree_items_count:n {#4} \int_compare:nNnT { \l_projetmbc_dirtree_item_current_int } = { \l_projetmbc_dirtree_item_count_int } { \clist_put_right:NV \l_projetmbc_dirtree_skip_level_clist \l_projetmbc_dirtree_level_int } \hcoffin_set:Nn \l_projetmbc_dirtree_icon_coffin { \color_select:n {#1} #2 } \hcoffin_set:Nn \l_projetmbc_dirtree_name_coffin { \normalfont #3 } \draw_coffin_use:Nnnn \l_projetmbc_dirtree_icon_coffin { hc } { vc } { \l_projetmbc_dirtree_level_int * 0.5cm + 0.2cm , \g_projetmbc_dirtree_step_int * \baselineskip } \draw_coffin_use:Nnnn \l_projetmbc_dirtree_name_coffin { l } { vc } { \l_projetmbc_dirtree_level_int * 0.5cm + 0.5 cm , \g_projetmbc_dirtree_step_int * \baselineskip } \int_compare:nNnT { \l_projetmbc_dirtree_level_int } > { 0 } { \int_step_inline:nn { \l_projetmbc_dirtree_level_int - 1 } { \exp_args:NNe \clist_if_in:NnF \l_projetmbc_dirtree_skip_level_clist { \int_eval:n { ##1 - 1 } } { \draw_path_moveto:n { ##1 * 0.5cm - 0.33cm, ( \g_projetmbc_dirtree_step_int + 1 ) * \baselineskip } \draw_path_lineto:n { ##1 * 0.5cm - 0.33cm , \g_projetmbc_dirtree_step_int * \baselineskip } } } \int_compare:nNnTF { \l_projetmbc_dirtree_item_current_int } = { 1 } { \draw_path_moveto:n { \l_projetmbc_dirtree_level_int * 0.5cm - 0.33cm , ( \g_projetmbc_dirtree_step_int + 0.5 ) * \baselineskip } } { \draw_path_moveto:n { \l_projetmbc_dirtree_level_int * 0.5cm - 0.33cm , ( \g_projetmbc_dirtree_step_int + 1 ) * \baselineskip } } \draw_path_lineto:n { \l_projetmbc_dirtree_level_int * 0.5cm - 0.33cm , \g_projetmbc_dirtree_step_int * \baselineskip } \draw_path_lineto:n { \l_projetmbc_dirtree_level_int * 0.5cm - 0.1cm , \g_projetmbc_dirtree_step_int * \baselineskip } \draw_path_use_clear:n { stroke } } \int_gdecr:N \g_projetmbc_dirtree_step_int \group_begin: \int_incr:N \l_projetmbc_dirtree_level_int \int_zero:N \l_projetmbc_dirtree_item_current_int #4 \group_end: } \NewDocumentCommand { \drawpathfile } { O { filecolor } m m } { \projetmbc_dirtree_item:nnnn { #1 } { #2 } { #3 } { } } \NewDocumentCommand { \drawpathdir } { O { foldercolor } m m m } { \projetmbc_dirtree_item:nnnn { #1 } { #2 } { #3 } { #4 } } \ExplSyntaxOff \begin{document} \drawpath{ \drawpathdir{\faFolderOpen}{system}{ \drawpathdir{\faFolderOpen}{foo}{ \drawpathdir{\faFolderOpen}{bar}{ \drawpathfile{\faFile}{baz1} \drawpathfile{\faFile}{baz2} } } \drawpathfile[red!70]{\faFilePdf}{file.pdf} } } \end{document} 
6
  • Hm, commenting out \pathitem{\faFile}{baz3}{} yields a bad result ... =/ Commented Nov 23 at 17:03
  • Cool. I wanted to start a challenge on this topic, but my proposal was closed.... Commented Nov 23 at 17:14
  • I like the code's conciseness. It's not for everyone, but it gets straight to the point. Commented Nov 23 at 17:15
  • 1
    @projetmbc It is probably not the best syntax ... I only stitched together something quickly, so there is surely some room for improvement ... One could make different commands for files and folders, for example ... Commented Nov 23 at 17:27
  • 1
    @projetmbc I changed my last edit, now there are two commands for files and directories (which use the same expl command internally). Commented Nov 23 at 17:33

You must log in to answer this question.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.