]> git.donarmstrong.com Git - perltidy.git/blob - docs/eos_flag.md
New upstream version 20230309
[perltidy.git] / docs / eos_flag.md
1 # The --encode-output-strings Flag
2
3 ## What's this about?
4
5 This is about making perltidy work better as a filter when called from other
6 Perl scripts.  For example, in the following example a reference to a
7 string `$source` is passed to perltidy and it stores the formatted result in
8 a string named `$output`:
9
10 ```
11     my $output;
12     my $err = Perl::Tidy::perltidy(
13         source      => \$source,
14         destination => \$output,
15         argv        => $argv,
16     );
17 ```
18
19 For a filtering operation we expect to be able to directly compare the source and output strings, like this:
20
21 ```
22     if ($output eq $source) {print "Your formatting is unchanged\n";}
23 ```
24
25 Or we might want to optionally skip the filtering step and pass the source
26 directly on to the next stage of processing.  This requires that the source
27 and output strings be in the same storage mode.
28
29 The problem is that in versions of perltidy prior to 2022 there was a use case
30 where this was not possible.  That case was when perltidy received an encoded
31 source and decoded it from a utf8 but did not re-encode it before storing it in
32 the output string.  So the source string was in a different storage mode than
33 the output string, and a direct comparison was not meaningful.
34
35 This problem is an unintentional result of the historical evolution of perltidy.
36
37 The same problem occurs if the destination is an array rather than a string,
38 so for simplicity we can limit this discussion to string destinations, which
39 are more common.
40
41 ## How has the problem been fixed?
42
43 A fix was phased in over a couple of steps. The first step was to
44 introduce a new flag in in version 20220217.  The new flag is
45 **--encode-output-strings**, or **-eos**.  When this is set, perltidy will fix
46 the specific problem mentioned above by doing an encoding before returning.
47 So perltidy will behave well as a filter when **-eos** is set.
48
49 To illustrate using this flag in the above example, we could write
50
51 ```
52     my $output;
53
54     # Make perltidy versions after 2022 behave well as a filter
55     $argv .= " -eos" if ($Perl::Tidy::VERSION > 20220101);
56     my $err = Perl::Tidy::perltidy(
57         source      => \$source,
58         destination => \$output,
59         argv        => $argv,
60     );
61 ```
62
63 With this modification we can make a meaningful direct comparison of `$source` and `$output`. The test on `$VERSION` allows this to work with older versions of perltidy (which would not recognize the flag -eos).
64
65 In the second step, introduced in version 20220613, the new **-eos** flag became the default.
66
67 ## What can go wrong?
68
69 The first step is safe because the default behavior is unchanged.  But the programmer has to set **-eos** for the corrected behavior to go into effect.
70
71 The second step, in which **-eos** becomes the default, will have no effect on programs which do not require perltidy to decode strings, and it will make some programs start processing encoded strings correctly.  But there is also the possibility of  **double encoding** of the output, or in other words data corruption, in some cases.  This could happen if an existing program already has already worked around this issue by encoding the output that it receives back from perltidy.  It is important to check for this.
72
73 To see how common this problem might be, all programs on CPAN which use Perl::Tidy as a filter were examined.  Of a total of 45 programs located, one was identified for which the change in default would definitely cause double encoding, and in one program it was difficult to determine.  It looked like the rest of the programs would either not be affected or would start working correctly when processing encoded files.  Here is a slightly revised version of the code for the program which would have a problem with double encoding with the new default:
74
75 ```
76     my $output;
77     Perl::Tidy::perltidy(
78         source      => \$self->{data},
79         destination => \$output,
80         stderr      => \$perltidy_err,
81         errorfile   => \$perltidy_err,
82         logfile     => \$perltidy_log,
83         argv        => $perltidy_argv,
84     );
85
86     # convert source back to raw
87     encode_utf8 $output;
88 ```
89
90 The problem is in the last line where encoding is done after the call to perltidy.  This
91 encoding operation was added by the module author to compensate for the lack of an
92 encoding step with the old default behavior.  But if we run this code with
93 **-eos**, which is the planned new default, encoding will also be done by perltidy before
94 it returns, with the result that `$output` gets double encoding.  This must be avoided. Here
95 is one way to modify the above code to avoid double encoding:
96
97 ```
98     my $has_eos_flag = $Perl::Tidy::VERSION > 20220101;
99     $perltidy_argv .= ' -eos' if $has_eos_flag;
100
101     Perl::Tidy::perltidy(
102         source      => \$self->{data},
103         destination => \$output,
104         stderr      => \$perltidy_err,
105         errorfile   => \$perltidy_err,
106         logfile     => \$perltidy_log,
107         argv        => $perltidy_argv,
108     );
109
110     # convert source back to raw if perltidy did not do it
111     encode_utf8($output) if ( !$has_eos_flag );
112 ```
113
114 A related problem is if an update of Perl::Tidy is made without also updating
115 a corrected version of a module such as the above.  To help reduce the chance
116 that this will occur the Change Log for perltidy will contain a warning to be
117 alert for the double encoding problem, and how to reset the default if
118 necessary.  This is also the reason for waiting some time before the second step was made.
119
120 If double encoding does appear to be occuring with the change in the default for some program which calls Perl::Tidy, then a quick emergency fix can be made by the program user by setting **-neos** to revert to the old default.  A better fix can eventually be made by the program author by removing the second encoding using a technique such as illustrated above.
121
122 ## Summary
123
124 A new flag, **-eos**, has been added to cause Perl::Tidy to behave better as a
125 filter when called from other Perl scripts.  This flag is the default setting
126 in the current release.
127
128 ## Reference
129
130 This flag was originally introduced to fix a problem with the widely-used **tidyall** program (see https://github.com/houseabsolute/perl-code-tidyall/issues/84).
131
132 ## Appendix, a little closer look
133
134 A string of text (here, a Perl script) can be stored by Perl in one of two
135 internal storage formats.  For simplicity let's call them 'B' mode (for 'Byte')
136 mode and 'C' mode (for 'Character').  The 'B' mode can be used for text with
137 single-byte characters or for storing an encoded string of multi-byte
138 characters.  The 'C' mode is needed for actually working with multi-byte
139 characters.  Thinking of a Perl script as a single long string of text, we can
140 look at the mode of the text of a source script as it is processed by perltidy
141 at three points:
142
143     - when it enters as a source
144     - at the intermediate stage as it is processed
145     - when it is leaves to its destination
146
147 Since 'C' mode only has meaning within Perl scripts, a rule is that outside of
148 the realm of Perl the text must be stored in 'B' mode.
149
150 The source can only be in 'C' mode if it arrives by a call from another Perl
151 program, and the destination can only be in 'C' mode if the destination is a
152 Perl program.  Otherwise, if the destination is a file, or object with a print
153 method, then it will be assumed to be ending its existance as a Perl string and
154 will be placed in an end state which is 'B' mode.
155
156 Transition from a starting 'B' mode to 'C' mode is done by a decoding operation
157 according to the user settings. A transition from an intermediate 'C' mode to
158 an ending 'B' mode is done by an encoding operation.  It never makes sense to
159 transition from a starting 'C' mode to a 'B' mode, or from an intermediate 'B'
160 mode to an ending 'C' mode.
161
162 Let us make a list of all possible sets of string storage modes to be sure that
163 all cases are covered.  If each of the three stages list above (entry,
164 intermedite, and exit) could be in 'B' or 'C' mode then we would have a total
165 of 2 x 2 x 2 = 8 combinations of states.  Each end point may either be a file
166 or a string reference. Here is a list of them, with a note indicating which
167 ones are possible, and when:
168
169     #   modes    when
170     1 - B->B->B  always ok: (file or string )->(file or string)
171     2 - B->B->C  never (trailing B->C never done)
172     3 - B->C->B  ok if destination is a file or -eos is set     [NEW DEFAULT]
173     4 - B->C->C  ok if destination is a string and -neos is set [OLD DEFAULT]
174     5 - C->B->B  never (leading C->B never done)
175     6 - C->B->C  never (leading C->B and trailing B->C never done)
176     7 - C->C->B  only for string-to-file
177     8 - C->C->C  only for string-to-string
178
179 So three of these cases (2, 5, and 6) cannot occur and the other five can
180 occur.  Of these five possible cases, only four are possible when the
181 destination is a string:
182
183     1 - B->B->B  ok
184     3 - B->C->B  ok if -eos is set  [NEW DEFAULt]
185     4 - B->C->C  ok if -neos is set [OLD DEFAULT]
186     8 - C->C->C  string-to-string only
187
188 The first three of these may start at either a file or a string, and the last one only starts at a string.
189
190 From this we can see that, if **-eos** is set, then only cases 1, 3, and 8 can occur.  In that case the starting and ending states have the same storage mode for all routes through perltidy which end at a string.  This verifies that perltidy will work well as a filter in all cases when the **-eos** flag is set, which is the goal here.
191
192 The last case in this table, the C->C->C route, corresponds to programs which
193 pass decoded strings to perltidy. This is a common usage pattern, and this
194 route is not influenced by the **-eos** flag setting, since it only applies to
195 strings that have been decoded by perltidy itself.
196
197 Incidentally, the full name of the flag, **--encode-output-strings**, is not
198 the best because it does not describe what happens in this case.  It was
199 difficult to find a concise name for this flag.  A more correct name would have
200 been **--encode-output-strings-that-you-decode**, but that is rather long.  A
201 more intuitive name for the flag might have been **--be-a-nice-filter**.
202
203 Finally, note that case 7 in the full table, the C->C->B route, is an unusual
204 but possible situation involving a source string being sent directly to a file.
205 It is the only situation in which perltidy does an encoding without having done
206 a corresponding previous decoding.