1 /*
2 ** Copyright (c) 2007 D. Richard Hipp
3 **
4 ** This program is free software; you can redistribute it and/or
5 ** modify it under the terms of the Simplified BSD License (also
6 ** known as the "2-Clause License" or "FreeBSD License".)
7 **
8 ** This program is distributed in the hope that it will be useful,
9 ** but without any warranty; without even the implied warranty of
10 ** merchantability or fitness for a particular purpose.
11 **
12 ** Author contact information:
13 **   drh@hwaci.com
14 **   http://www.hwaci.com/drh/
15 **
16 *******************************************************************************
17 **
18 ** This file contains code used to format and print comments or other
19 ** text on a TTY.
20 */
21 #include "config.h"
22 #include "comformat.h"
23 #include <assert.h>
24 
25 #if INTERFACE
26 #define COMMENT_PRINT_NONE       ((u32)0x00000000) /* No flags = non-legacy. */
27 #define COMMENT_PRINT_LEGACY     ((u32)0x00000001) /* Use legacy algorithm. */
28 #define COMMENT_PRINT_TRIM_CRLF  ((u32)0x00000002) /* Trim leading CR/LF. */
29 #define COMMENT_PRINT_TRIM_SPACE ((u32)0x00000004) /* Trim leading/trailing. */
30 #define COMMENT_PRINT_WORD_BREAK ((u32)0x00000008) /* Break lines on words. */
31 #define COMMENT_PRINT_ORIG_BREAK ((u32)0x00000010) /* Break before original. */
32 #define COMMENT_PRINT_DEFAULT    (COMMENT_PRINT_LEGACY) /* Defaults. */
33 #define COMMENT_PRINT_UNSET      (-1)              /* Not initialized. */
34 #endif
35 
36 /*
37 ** This is the previous value used by most external callers when they
38 ** needed to specify a default maximum line length to be used with the
39 ** comment_print() function.
40 */
41 #ifndef COMMENT_LEGACY_LINE_LENGTH
42 # define COMMENT_LEGACY_LINE_LENGTH    (78)
43 #endif
44 
45 /*
46 ** This is the number of spaces to print when a tab character is seen.
47 */
48 #ifndef COMMENT_TAB_WIDTH
49 # define COMMENT_TAB_WIDTH             (8)
50 #endif
51 
52 /*
53 ** This function sets the maximum number of characters to print per line
54 ** based on the detected terminal line width, if available; otherwise, it
55 ** uses the legacy default terminal line width minus the amount to indent.
56 **
57 ** Zero is returned to indicate any failure.  One is returned to indicate
58 ** the successful detection of the terminal line width.  Negative one is
59 ** returned to indicate the terminal line width is using the hard-coded
60 ** legacy default value.
61 */
comment_set_maxchars(int indent,int * pMaxChars)62 static int comment_set_maxchars(
63   int indent,
64   int *pMaxChars
65 ){
66   struct TerminalSize ts;
67   if ( !terminal_get_size(&ts) ){
68     return 0;
69   }
70 
71   if( ts.nColumns ){
72     *pMaxChars = ts.nColumns - indent;
73     return 1;
74   }else{
75     /*
76     ** Fallback to using more-or-less the "legacy semantics" of hard-coding
77     ** the maximum line length to a value reasonable for the vast majority
78     ** of supported systems.
79     */
80     *pMaxChars = COMMENT_LEGACY_LINE_LENGTH - indent;
81     return -1;
82   }
83 }
84 
85 /*
86 ** This function checks the current line being printed against the original
87 ** comment text.  Upon matching, it updates the provided character and line
88 ** counts, if applicable.  The caller needs to emit a new line, if desired.
89 */
comment_check_orig(const char * zOrigText,const char * zLine,int * pCharCnt,int * pLineCnt)90 static int comment_check_orig(
91   const char *zOrigText, /* [in] Original comment text ONLY, may be NULL. */
92   const char *zLine,     /* [in] The comment line to print. */
93   int *pCharCnt,         /* [in/out] Pointer to the line character count. */
94   int *pLineCnt          /* [in/out] Pointer to the total line count. */
95 ){
96   if( zOrigText && fossil_strcmp(zLine, zOrigText)==0 ){
97     if( pCharCnt ) *pCharCnt = 0;
98     if( pLineCnt ) (*pLineCnt)++;
99     return 1;
100   }
101   return 0;
102 }
103 
104 /*
105 ** This function scans the specified comment line starting just after the
106 ** initial index and returns the index of the next spacing character -OR-
107 ** zero if such a character cannot be found.  For the purposes of this
108 ** algorithm, the NUL character is treated the same as a spacing character.
109 */
comment_next_space(const char * zLine,int index,int * distUTF8)110 static int comment_next_space(
111   const char *zLine, /* [in] The comment line being printed. */
112   int index,         /* [in] The current character index being handled. */
113   int *distUTF8      /* [out] Distance to next space in UTF-8 sequences. */
114 ){
115   int nextIndex = index + 1;
116   int fNonASCII=0;
117   for(;;){
118     char c = zLine[nextIndex];
119     if( (c&0x80)==0x80 ) fNonASCII=1;
120     if( c==0 || fossil_isspace(c) ){
121       if( distUTF8 ){
122         if( fNonASCII!=0 ){
123           *distUTF8 = strlen_utf8(&zLine[index], nextIndex-index);
124         }else{
125           *distUTF8 = nextIndex-index;
126         }
127       }
128       return nextIndex;
129     }
130     nextIndex++;
131   }
132   return 0; /* NOT REACHED */
133 }
134 
135 /*
136 ** Count the number of UTF-8 sequences in a string. Incomplete, ill-formed and
137 ** overlong sequences are counted as one sequence. The invalid lead bytes 0xC0
138 ** to 0xC1 and 0xF5 to 0xF7 are allowed to initiate (ill-formed) 2- and 4-byte
139 ** sequences, respectively, the other invalid lead bytes 0xF8 to 0xFF are
140 ** treated as invalid 1-byte sequences (as lone trail bytes).
141 ** Combining characters and East Asian Wide and Fullwidth characters are counted
142 ** as one, so this function does not calculate the effective "display width".
143 */
strlen_utf8(const char * zString,int lengthBytes)144 int strlen_utf8(const char *zString, int lengthBytes){
145   int i;          /* Counted bytes. */
146   int lengthUTF8; /* Counted UTF-8 sequences. */
147 #if 0
148   assert( lengthBytes>=0 );
149 #endif
150   for(i=0, lengthUTF8=0; i<lengthBytes; i++, lengthUTF8++){
151     char c = zString[i];
152     int cchUTF8=1; /* Code units consumed. */
153     int maxUTF8=1; /* Expected sequence length. */
154     if( (c&0xe0)==0xc0 )maxUTF8=2;          /* UTF-8 lead byte 110vvvvv */
155     else if( (c&0xf0)==0xe0 )maxUTF8=3;     /* UTF-8 lead byte 1110vvvv */
156     else if( (c&0xf8)==0xf0 )maxUTF8=4;     /* UTF-8 lead byte 11110vvv */
157     while( cchUTF8<maxUTF8 &&
158             i<lengthBytes-1 &&
159             (zString[i+1]&0xc0)==0x80 ){    /* UTF-8 trail byte 10vvvvvv */
160       cchUTF8++;
161       i++;
162     }
163   }
164   return lengthUTF8;
165 }
166 
167 /*
168 ** This function is called when printing a logical comment line to calculate
169 ** the necessary indenting.  The caller needs to emit the indenting spaces.
170 */
comment_calc_indent(const char * zLine,int indent,int trimCrLf,int trimSpace,int * piIndex)171 static void comment_calc_indent(
172   const char *zLine, /* [in] The comment line being printed. */
173   int indent,        /* [in] Number of spaces to indent, zero for none. */
174   int trimCrLf,      /* [in] Non-zero to trim leading/trailing CR/LF. */
175   int trimSpace,     /* [in] Non-zero to trim leading/trailing spaces. */
176   int *piIndex       /* [in/out] Pointer to first non-space character. */
177 ){
178   if( zLine && piIndex ){
179     int index = *piIndex;
180     if( trimCrLf ){
181       while( zLine[index]=='\r' || zLine[index]=='\n' ){ index++; }
182     }
183     if( trimSpace ){
184       while( fossil_isspace(zLine[index]) ){ index++; }
185     }
186     *piIndex = index;
187   }
188 }
189 
190 /*
191 ** This function prints one logical line of a comment, stopping when it hits
192 ** a new line -OR- runs out of space on the logical line.
193 */
comment_print_line(const char * zOrigText,const char * zLine,int origIndent,int indent,int lineChars,int trimCrLf,int trimSpace,int wordBreak,int origBreak,int * pLineCnt,const char ** pzLine)194 static void comment_print_line(
195   const char *zOrigText, /* [in] Original comment text ONLY, may be NULL. */
196   const char *zLine,     /* [in] The comment line to print. */
197   int origIndent,        /* [in] Number of spaces to indent before the original
198                          **      comment. */
199   int indent,            /* [in] Number of spaces to indent, before the line
200                          **      to print. */
201   int lineChars,         /* [in] Maximum number of characters to print. */
202   int trimCrLf,          /* [in] Non-zero to trim leading/trailing CR/LF. */
203   int trimSpace,         /* [in] Non-zero to trim leading/trailing spaces. */
204   int wordBreak,         /* [in] Non-zero to try breaking on word boundaries. */
205   int origBreak,         /* [in] Non-zero to break before original comment. */
206   int *pLineCnt,         /* [in/out] Pointer to the total line count. */
207   const char **pzLine    /* [out] Pointer to the end of the logical line. */
208 ){
209   int index = 0, charCnt = 0, lineCnt = 0, maxChars, i;
210   char zBuf[400]; int iBuf=0; /* Output buffer and counter. */
211   int cchUTF8, maxUTF8;       /* Helper variables to count UTF-8 sequences. */
212   if( !zLine ) return;
213   if( lineChars<=0 ) return;
214 #if 0
215   assert( indent<sizeof(zBuf)-5 );       /* See following comments to explain */
216   assert( origIndent<sizeof(zBuf)-5 );   /* these limits. */
217 #endif
218   if( indent>sizeof(zBuf)-6 ){
219     /* Limit initial indent to fit output buffer. */
220     indent = sizeof(zBuf)-6;
221   }
222   comment_calc_indent(zLine, indent, trimCrLf, trimSpace, &index);
223   if( indent>0 ){
224     for(i=0; i<indent; i++){
225       zBuf[iBuf++] = ' ';
226     }
227   }
228   if( origIndent>sizeof(zBuf)-6 ){
229     /* Limit line indent to fit output buffer. */
230     origIndent = sizeof(zBuf)-6;
231   }
232   maxChars = lineChars;
233   for(;;){
234     int useChars = 1;
235     char c = zLine[index];
236     /* Flush the output buffer if there's no space left for at least one more
237     ** (potentially 4-byte) UTF-8 sequence, one level of indentation spaces,
238     ** a new line, and a terminating NULL. */
239     if( iBuf>sizeof(zBuf)-origIndent-6 ){
240       zBuf[iBuf]=0;
241       iBuf=0;
242       fossil_print("%s", zBuf);
243     }
244     if( c==0 ){
245       break;
246     }else{
247       if( origBreak && index>0 ){
248         const char *zCurrent = &zLine[index];
249         if( comment_check_orig(zOrigText, zCurrent, &charCnt, &lineCnt) ){
250           zBuf[iBuf++] = '\n';
251           comment_calc_indent(zLine, origIndent, trimCrLf, trimSpace, &index);
252           for( i=0; i<origIndent; i++ ){
253             zBuf[iBuf++] = ' ';
254           }
255           maxChars = lineChars;
256         }
257       }
258       index++;
259     }
260     if( c=='\n' ){
261       lineCnt++;
262       charCnt = 0;
263       useChars = 0;
264     }else if( c=='\t' ){
265       int distUTF8;
266       int nextIndex = comment_next_space(zLine, index, &distUTF8);
267       if( nextIndex<=0 || distUTF8>maxChars ){
268         break;
269       }
270       charCnt++;
271       useChars = COMMENT_TAB_WIDTH;
272       if( maxChars<useChars ){
273         zBuf[iBuf++] = ' ';
274         break;
275       }
276     }else if( wordBreak && fossil_isspace(c) ){
277       int distUTF8;
278       int nextIndex = comment_next_space(zLine, index, &distUTF8);
279       if( nextIndex<=0 || distUTF8>maxChars ){
280         break;
281       }
282       charCnt++;
283     }else{
284       charCnt++;
285     }
286     assert( c!='\n' || charCnt==0 );
287     zBuf[iBuf++] = c;
288     /* Skip over UTF-8 sequences, see comment on strlen_utf8() for details. */
289     cchUTF8=1; /* Code units consumed. */
290     maxUTF8=1; /* Expected sequence length. */
291     if( (c&0xe0)==0xc0 )maxUTF8=2;          /* UTF-8 lead byte 110vvvvv */
292     else if( (c&0xf0)==0xe0 )maxUTF8=3;     /* UTF-8 lead byte 1110vvvv */
293     else if( (c&0xf8)==0xf0 )maxUTF8=4;     /* UTF-8 lead byte 11110vvv */
294     while( cchUTF8<maxUTF8 &&
295             (zLine[index]&0xc0)==0x80 ){    /* UTF-8 trail byte 10vvvvvv */
296       cchUTF8++;
297       zBuf[iBuf++] = zLine[index++];
298     }
299     maxChars -= useChars;
300     if( maxChars<=0 ) break;
301     if( c=='\n' ) break;
302   }
303   if( charCnt>0 ){
304     zBuf[iBuf++] = '\n';
305     lineCnt++;
306   }
307   /* Flush the remaining output buffer. */
308   if( iBuf>0 ){
309     zBuf[iBuf]=0;
310     iBuf=0;
311     fossil_print("%s", zBuf);
312   }
313   if( pLineCnt ){
314     *pLineCnt += lineCnt;
315   }
316   if( pzLine ){
317     *pzLine = zLine + index;
318   }
319 }
320 
321 /*
322 ** This is the legacy comment printing algorithm.  It is being retained
323 ** for backward compatibility.
324 **
325 ** Given a comment string, format that string for printing on a TTY.
326 ** Assume that the output cursors is indent spaces from the left margin
327 ** and that a single line can contain no more than 'width' characters.
328 ** Indent all subsequent lines by 'indent'.
329 **
330 ** Returns the number of new lines emitted.
331 */
comment_print_legacy(const char * zText,int indent,int width)332 static int comment_print_legacy(
333   const char *zText, /* The comment text to be printed. */
334   int indent,        /* Number of spaces to indent each non-initial line. */
335   int width          /* Maximum number of characters per line. */
336 ){
337   int maxChars = width - indent;
338   int si, sk, i, k, kc;
339   int doIndent = 0;
340   char *zBuf;
341   char zBuffer[400];
342   int lineCnt = 0;
343   int cchUTF8, maxUTF8; /* Helper variables to count UTF-8 sequences. */
344 
345   if( width<0 ){
346     comment_set_maxchars(indent, &maxChars);
347   }
348   if( zText==0 ) zText = "(NULL)";
349   if( maxChars<=0 ){
350     maxChars = strlen(zText);
351   }
352   /* Ensure the buffer can hold the longest-possible UTF-8 sequences. */
353   if( maxChars >= (sizeof(zBuffer)/4-1) ){
354     zBuf = fossil_malloc(maxChars*4+1);
355   }else{
356     zBuf = zBuffer;
357   }
358   for(;;){
359     while( fossil_isspace(zText[0]) ){ zText++; }
360     if( zText[0]==0 ){
361       if( doIndent==0 ){
362         fossil_print("\n");
363         lineCnt = 1;
364       }
365       if( zBuf!=zBuffer) fossil_free(zBuf);
366       return lineCnt;
367     }
368     for(sk=si=i=k=kc=0; zText[i] && kc<maxChars; i++){
369       char c = zText[i];
370       kc++; /* Count complete UTF-8 sequences. */
371       /* Skip over UTF-8 sequences, see comment on strlen_utf8() for details. */
372       cchUTF8=1; /* Code units consumed. */
373       maxUTF8=1; /* Expected sequence length. */
374       if( (c&0xe0)==0xc0 )maxUTF8=2;        /* UTF-8 lead byte 110vvvvv */
375       else if( (c&0xf0)==0xe0 )maxUTF8=3;   /* UTF-8 lead byte 1110vvvv */
376       else if( (c&0xf8)==0xf0 )maxUTF8=4;   /* UTF-8 lead byte 11110vvv */
377       if( maxUTF8>1 ){
378         zBuf[k++] = c;
379         while( cchUTF8<maxUTF8 &&
380                 (zText[i+1]&0xc0)==0x80 ){  /* UTF-8 trail byte 10vvvvvv */
381           cchUTF8++;
382           zBuf[k++] = zText[++i];
383         }
384       }
385       else if( fossil_isspace(c) ){
386         si = i;
387         sk = k;
388         if( k==0 || zBuf[k-1]!=' ' ){
389           zBuf[k++] = ' ';
390         }
391       }else{
392         zBuf[k] = c;
393         if( c=='-' && k>0 && fossil_isalpha(zBuf[k-1]) ){
394           si = i+1;
395           sk = k+1;
396         }
397         k++;
398       }
399     }
400     if( doIndent ){
401       fossil_print("%*s", indent, "");
402     }
403     doIndent = 1;
404     if( sk>0 && zText[i] ){
405       zText += si;
406       zBuf[sk] = 0;
407     }else{
408       zText += i;
409       zBuf[k] = 0;
410     }
411     fossil_print("%s\n", zBuf);
412     lineCnt++;
413   }
414 }
415 
416 /*
417 ** This is the comment printing function.  The comment printing algorithm
418 ** contained within it attempts to preserve the formatting present within
419 ** the comment string itself while honoring line width limitations.  There
420 ** are several flags that modify the default behavior of this function:
421 **
422 **         COMMENT_PRINT_LEGACY: Forces use of the legacy comment printing
423 **                               algorithm.  For backward compatibility,
424 **                               this is the default.
425 **
426 **      COMMENT_PRINT_TRIM_CRLF: Trims leading and trailing carriage-returns
427 **                               and line-feeds where they do not materially
428 **                               impact pre-existing formatting (i.e. at the
429 **                               start of the comment string -AND- right
430 **                               before line indentation).  This flag does
431 **                               not apply to the legacy comment printing
432 **                               algorithm.  This flag may be combined with
433 **                               COMMENT_PRINT_TRIM_SPACE.
434 **
435 **     COMMENT_PRINT_TRIM_SPACE: Trims leading and trailing spaces where they
436 **                               do not materially impact the pre-existing
437 **                               formatting (i.e. at the start of the comment
438 **                               string -AND- right before line indentation).
439 **                               This flag does not apply to the legacy
440 **                               comment printing algorithm.  This flag may
441 **                               be combined with COMMENT_PRINT_TRIM_CRLF.
442 **
443 **     COMMENT_PRINT_WORD_BREAK: Attempts to break lines on word boundaries
444 **                               while honoring the logical line length.
445 **                               If this flag is not specified, honoring the
446 **                               logical line length may result in breaking
447 **                               lines in the middle of words.  This flag
448 **                               does not apply to the legacy comment
449 **                               printing algorithm.
450 **
451 **     COMMENT_PRINT_ORIG_BREAK: Looks for the original comment text within
452 **                               the text being printed.  Upon matching, a
453 **                               new line will be emitted, thus preserving
454 **                               more of the pre-existing formatting.
455 **
456 ** Given a comment string, format that string for printing on a TTY.
457 ** Assume that the output cursors is indent spaces from the left margin
458 ** and that a single line can contain no more than 'width' characters.
459 ** Indent all subsequent lines by 'indent'.
460 **
461 ** Returns the number of new lines emitted.
462 */
comment_print(const char * zText,const char * zOrigText,int indent,int width,int flags)463 int comment_print(
464   const char *zText,     /* The comment text to be printed. */
465   const char *zOrigText, /* Original comment text ONLY, may be NULL. */
466   int indent,            /* Spaces to indent each non-initial line. */
467   int width,             /* Maximum number of characters per line. */
468   int flags              /* Zero or more "COMMENT_PRINT_*" flags. */
469 ){
470   int maxChars = width - indent;
471   int legacy = flags & COMMENT_PRINT_LEGACY;
472   int trimCrLf = flags & COMMENT_PRINT_TRIM_CRLF;
473   int trimSpace = flags & COMMENT_PRINT_TRIM_SPACE;
474   int wordBreak = flags & COMMENT_PRINT_WORD_BREAK;
475   int origBreak = flags & COMMENT_PRINT_ORIG_BREAK;
476   int lineCnt = 0;
477   const char *zLine;
478 
479   if( legacy ){
480     return comment_print_legacy(zText, indent, width);
481   }
482   if( width<0 ){
483     comment_set_maxchars(indent, &maxChars);
484   }
485   if( zText==0 ) zText = "(NULL)";
486   if( maxChars<=0 ){
487     maxChars = strlen(zText);
488   }
489   if( trimSpace ){
490     while( fossil_isspace(zText[0]) ){ zText++; }
491   }
492   if( zText[0]==0 ){
493     fossil_print("\n");
494     lineCnt++;
495     return lineCnt;
496   }
497   zLine = zText;
498   for(;;){
499     comment_print_line(zOrigText, zLine, indent, zLine>zText ? indent : 0,
500                        maxChars, trimCrLf, trimSpace, wordBreak, origBreak,
501                        &lineCnt, &zLine);
502     if( !zLine || !zLine[0] ) break;
503   }
504   return lineCnt;
505 }
506 
507 /*
508 ** Return the "COMMENT_PRINT_*" flags specified by the following sources,
509 ** evaluated in the following cascading order:
510 **
511 **    1. The global --comfmtflags (alias --comment-format) command-line option.
512 **    2. The local (per-repository) "comment-format" setting.
513 **    3. The global (all-repositories) "comment-format" setting.
514 **    4. The default value COMMENT_PRINT_DEFAULT.
515 */
get_comment_format()516 int get_comment_format(){
517   int comFmtFlags;
518   /* The global command-line option is present, or the value has been cached. */
519   if( g.comFmtFlags!=COMMENT_PRINT_UNSET ){
520     comFmtFlags = g.comFmtFlags;
521     return comFmtFlags;
522   }
523   /* Load the local (per-repository) or global (all-repositories) value, and use
524   ** g.comFmtFlags as a cache. */
525   comFmtFlags = db_get_int("comment-format", COMMENT_PRINT_UNSET);
526   if( comFmtFlags!=COMMENT_PRINT_UNSET ){
527     g.comFmtFlags = comFmtFlags;
528     return comFmtFlags;
529   }
530   /* Fallback to the default value. */
531   comFmtFlags = COMMENT_PRINT_DEFAULT;
532   return comFmtFlags;
533 }
534 
535 /*
536 **
537 ** COMMAND: test-comment-format
538 **
539 ** Usage: %fossil test-comment-format ?OPTIONS? PREFIX TEXT ?ORIGTEXT?
540 **
541 ** Test comment formatting and printing.  Use for testing only.
542 **
543 ** Options:
544 **   --file           The comment text is really just a file name to
545 **                    read it from.
546 **   --decode         Decode the text using the same method used when
547 **                    handling the value of a C-card from a manifest.
548 **   --legacy         Use the legacy comment printing algorithm.
549 **   --trimcrlf       Enable trimming of leading/trailing CR/LF.
550 **   --trimspace      Enable trimming of leading/trailing spaces.
551 **   --wordbreak      Attempt to break lines on word boundaries.
552 **   --origbreak      Attempt to break when the original comment text
553 **                    is detected.
554 **   --indent         Number of spaces to indent (default (-1) is to
555 **                    auto-detect).  Zero means no indent.
556 **   -W|--width NUM   Width of lines (default (-1) is to auto-detect).
557 **                    Zero means no limit.
558 */
test_comment_format(void)559 void test_comment_format(void){
560   const char *zWidth;
561   const char *zIndent;
562   const char *zPrefix;
563   char *zText;
564   char *zOrigText;
565   int indent, width;
566   int fromFile = find_option("file", 0, 0)!=0;
567   int decode = find_option("decode", 0, 0)!=0;
568   int flags = COMMENT_PRINT_NONE;
569   if( find_option("legacy", 0, 0) ){
570     flags |= COMMENT_PRINT_LEGACY;
571   }
572   if( find_option("trimcrlf", 0, 0) ){
573     flags |= COMMENT_PRINT_TRIM_CRLF;
574   }
575   if( find_option("trimspace", 0, 0) ){
576     flags |= COMMENT_PRINT_TRIM_SPACE;
577   }
578   if( find_option("wordbreak", 0, 0) ){
579     flags |= COMMENT_PRINT_WORD_BREAK;
580   }
581   if( find_option("origbreak", 0, 0) ){
582     flags |= COMMENT_PRINT_ORIG_BREAK;
583   }
584   zWidth = find_option("width","W",1);
585   if( zWidth ){
586     width = atoi(zWidth);
587   }else{
588     width = -1; /* automatic */
589   }
590   zIndent = find_option("indent",0,1);
591   if( zIndent ){
592     indent = atoi(zIndent);
593   }else{
594     indent = -1; /* automatic */
595   }
596   if( g.argc!=4 && g.argc!=5 ){
597     usage("?OPTIONS? PREFIX TEXT ?ORIGTEXT?");
598   }
599   zPrefix = g.argv[2];
600   zText = g.argv[3];
601   if( g.argc==5 ){
602     zOrigText = g.argv[4];
603   }else{
604     zOrigText = 0;
605   }
606   if( fromFile ){
607     Blob fileData;
608     blob_read_from_file(&fileData, zText, ExtFILE);
609     zText = mprintf("%s", blob_str(&fileData));
610     blob_reset(&fileData);
611     if( zOrigText ){
612       blob_read_from_file(&fileData, zOrigText, ExtFILE);
613       zOrigText = mprintf("%s", blob_str(&fileData));
614       blob_reset(&fileData);
615     }
616   }
617   if( decode ){
618     zText = mprintf(fromFile?"%z":"%s" /*works-like:"%s"*/, zText);
619     defossilize(zText);
620     if( zOrigText ){
621       zOrigText = mprintf(fromFile?"%z":"%s" /*works-like:"%s"*/, zOrigText);
622       defossilize(zOrigText);
623     }
624   }
625   if( indent<0 ){
626     indent = strlen(zPrefix);
627   }
628   if( zPrefix && *zPrefix ){
629     fossil_print("%s", zPrefix);
630   }
631   fossil_print("(%d lines output)\n",
632                comment_print(zText, zOrigText, indent, width, flags));
633   if( zOrigText && zOrigText!=g.argv[4] ) fossil_free(zOrigText);
634   if( zText && zText!=g.argv[3] ) fossil_free(zText);
635 }
636