]> git.donarmstrong.com Git - perltidy.git/blob - docs/eos_flag.md
New upstream version 20220613
[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 and needs to be fixed.
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 will the problem be fixed?
42
43 A fix is being 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).  An update such as the above can be made right now to facilitate a smooth transition to the new default.
64
65 In the second step, possibly later in 2022, the new **-eos** flag will become 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 is made.
119
120 If double encoding does appear to be occuring after the default change 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 will eventually become
126 the default setting.  Programs which use Perl::Tidy as a
127 filter can be tested right now with the new **-eos** flag to be sure that double
128 encoding is not possible when the default is changed.
129
130 ## Reference
131
132 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).
133
134 ## Appendix, a little closer look
135
136 A string of text (here, a Perl script) can be stored by Perl in one of two
137 internal storage formats.  For simplicity let's call them 'B' mode (for 'Byte')
138 mode and 'C' mode (for 'Character').  The 'B' mode can be used for text with
139 single-byte characters or for storing an encoded string of multi-byte
140 characters.  The 'C' mode is needed for actually working with multi-byte
141 characters.  Thinking of a Perl script as a single long string of text, we can
142 look at the mode of the text of a source script as it is processed by perltidy
143 at three points:
144
145     - when it enters as a source
146     - at the intermediate stage as it is processed
147     - when it is leaves to its destination
148
149 Since 'C' mode only has meaning within Perl scripts, a rule is that outside of
150 the realm of Perl the text must be stored in 'B' mode.
151
152 The source can only be in 'C' mode if it arrives by a call from another Perl
153 program, and the destination can only be in 'C' mode if the destination is a
154 Perl program.  Otherwise, if the destination is a file, or object with a print
155 method, then it will be assumed to be ending its existance as a Perl string and
156 will be placed in an end state which is 'B' mode.
157
158 Transition from a starting 'B' mode to 'C' mode is done by a decoding operation
159 according to the user settings. A transition from an intermediate 'C' mode to
160 an ending 'B' mode is done by an encoding operation.  It never makes sense to
161 transition from a starting 'C' mode to a 'B' mode, or from an intermediate 'B'
162 mode to an ending 'C' mode.
163
164 Let us make a list of all possible sets of string storage modes to be sure that
165 all cases are covered.  If each of the three stages list above (entry,
166 intermedite, and exit) could be in 'B' or 'C' mode then we would have a total
167 of 2 x 2 x 2 = 8 combinations of states.  Each end point may either be a file
168 or a string reference. Here is a list of them, with a note indicating which
169 ones are possible, and when:
170
171     #   modes    when
172     1 - B->B->B  always ok: (file or string )->(file or string)
173     2 - B->B->C  never (trailing B->C never done)
174     3 - B->C->B  ok if destination is a file or -eos is set     [NEW DEFAULT]
175     4 - B->C->C  ok if destination is a string and -neos is set [OLD DEFAULT]
176     5 - C->B->B  never (leading C->B never done)
177     6 - C->B->C  never (leading C->B and trailing B->C never done)
178     7 - C->C->B  only for string-to-file
179     8 - C->C->C  only for string-to-string
180
181 So three of these cases (2, 5, and 6) cannot occur and the other five can
182 occur.  Of these five possible cases, only four are possible when the
183 destination is a string:
184
185     1 - B->B->B  ok
186     3 - B->C->B  ok if -eos is set  [NEW DEFAULt]
187     4 - B->C->C  ok if -neos is set [OLD DEFAULT]
188     8 - C->C->C  string-to-string only
189
190 The first three of these may start at either a file or a string, and the last one only starts at a string.
191
192 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.
193
194 The last case in this table, the C->C->C route, corresponds to programs which
195 pass decoded strings to perltidy. This is a common usage pattern, and this
196 route is not influenced by the **-eos** flag setting, since it only applies to
197 strings that have been decoded by perltidy itself.
198
199 Incidentally, the full name of the flag, **--encode-output-strings**, is not
200 the best because it does not describe what happens in this case.  It was
201 difficult to find a concise name for this flag.  A more correct name would have
202 been **--encode-output-strings-that-you-decode**, but that is rather long.  A
203 more intuitive name for the flag might have been **--be-a-nice-filter**.
204
205 Finally, note that case 7 in the full table, the C->C->B route, is an unusual
206 but possible situation involving a source string being sent directly to a file.
207 It is the only situation in which perltidy does an encoding without having done
208 a corresponding previous decoding.