OSDN Git Service

Support assignment of DEBUGLEVEL at configure time.
[mingw/mingw-get.git] / src / pkgshow.cpp
1 /*
2  * pkgshow.cpp
3  *
4  * $Id$
5  *
6  * Written by Keith Marshall <keithmarshall@users.sourceforge.net>
7  * Copyright (C) 2009, 2010, MinGW Project
8  *
9  *
10  * Implementation of the classes and methods required to support the
11  * 'mingw-get list' and 'mingw-get show' commands.
12  *
13  *
14  * This is free software.  Permission is granted to copy, modify and
15  * redistribute this software, under the provisions of the GNU General
16  * Public License, Version 3, (or, at your option, any later version),
17  * as published by the Free Software Foundation; see the file COPYING
18  * for licensing details.
19  *
20  * Note, in particular, that this software is provided "as is", in the
21  * hope that it may prove useful, but WITHOUT WARRANTY OF ANY KIND; not
22  * even an implied WARRANTY OF MERCHANTABILITY, nor of FITNESS FOR ANY
23  * PARTICULAR PURPOSE.  Under no circumstances will the author, or the
24  * MinGW Project, accept liability for any damages, however caused,
25  * arising from the use of this software.
26  *
27  */
28 #include <ctype.h>
29 #include <stdio.h>
30 #include <stdarg.h>
31
32 #include "pkgbase.h"
33 #include "pkgkeys.h"
34
35 #include "dmh.h"
36
37 /* Translatable message elements, to support internationalisation.
38  * FIXME: These need to be abstracted into a global message header,
39  * where catalogue numbering sequences may be implemented, and more
40  * readily managed for future internationalisation.
41  */
42 #define PKGMSG_DIRECTORY_HEADER_FORMAT  "\nPackage: %*sSubsystem: %s\n"
43
44 EXTERN_C const char *pkgMsgUnknownPackage( void );
45
46 /* Utility classes and definitions for interpreting and formatting
47  * package descriptions, encoded as UTF-8.
48  */
49 typedef unsigned long utf32_t;
50
51 #define UTF32_MAX                       (utf32_t)(0x10FFFFUL)
52 #define UTF32_OVERFLOW                  (utf32_t)(UTF32_MAX + 1UL)
53
54 #define UTF32_INVALID                   (utf32_t)(0x0FFFDUL)
55
56 class pkgUTF8Parser
57 {
58   /* A class for parsing a UTF-8 string, decomposing it into
59    * non-whitespace substrings, separated by whitespace, which
60    * is interpreted as potential word-wrap points.
61    */
62   public:
63     pkgUTF8Parser( const char* );
64     ~pkgUTF8Parser(){ delete next; }
65
66   protected:
67     class pkgUTF8Parser *next;
68     union { char utf8; wchar_t utf16; utf32_t utf32; } codepoint;
69     const char *text;
70     int length;
71
72     const char *ScanBuffer( const char* );
73     wchar_t GetCodePoint( ... );
74 };
75
76 /* Constructor...
77  */
78 pkgUTF8Parser::pkgUTF8Parser( const char *input ):
79 text( NULL ), length( 0 ), next( NULL )
80 {
81   /* Recursively construct a parse list for the input string; the
82    * first node marks the location of the first non-whitespace UTF-8
83    * entity in the input, while each successive node marks locations
84    * of internal non-whitespace substrings, each separated from its
85    * predecessor by whitespace.
86    *
87    * FIXME: The scanner implemented here is naive WRT layout of text,
88    * insofar as it considers all whitespace to represent valid points
89    * at which word breaks may be inserted, and it assumes that all code
90    * points represent glyphs which occupy equal horizontal space, (thus
91    * ignoring the special spacing requirements of diacritical marks, or
92    * other non-spacing entities.  In the case of conventionally typeset
93    * English text, in a monospaced font, this should be acceptable, but
94    * it isn't universally so.  We may ultimately wish to progress to a
95    * solution based on ICU, or similar, to support other languages.
96    */
97   const char *mark;
98
99   /* First, step over any initial whitespace...
100    */
101   while( *input && iswspace( GetCodePoint( mark = ScanBuffer( input ))) )
102     input = mark;
103
104   /* If we didn't run out of input...
105    */
106   if( *input )
107   {
108     /* ...then we've found the start of a non-whitespace substring;
109      * mark it, and then...
110      */
111     text = input;
112     while( *input && ! iswspace( GetCodePoint( mark = ScanBuffer( input ))) )
113     {
114       /* ...scan ahead, until we again encounter whitespace, or
115        * we run out of input; as we go, accumulate the effective
116        * length of the current substring.
117        */
118       input = mark; ++length;
119     }
120     /* Continue to recursively add parse nodes, to mark the
121      * locations of any further non-whitespace substrings.
122      */
123     next = new pkgUTF8Parser( input );
124   }
125 }
126
127 const char *pkgUTF8Parser::ScanBuffer( const char *input )
128 {
129   /* Read one or more octets from a UTF-8 encoded string/stream,
130    * and identify the single code point value associated with the
131    * initial octet sequence.  Leave this for subsequent retrieval
132    * by GetCodePoint(); return a pointer to the next available
133    * octet sequence, (if any).
134    *
135    * We begin by loading the first available octet into the code
136    * point buffer, while zero filling the higher order octet space;
137    * if the resultant doesn't have the high order bit of the least
138    * significant octet set, then the octet represents a seven-bit
139    * ASCII code, and there is nothing more to be done...
140    */
141   if( (codepoint.utf32 = 0xFFUL & (utf32_t)(*input++)) > 0x7FUL )
142   {
143     /* ...otherwise we have what is potentially the first octet
144      * in a multibyte UTF-8 encoding sequence; if this is a well
145      * formed sequence, then the number of contiguous high order
146      * bits which are set, (between two and six), indicates the
147      * length of the sequence, so we count them...
148      */
149     char mask = 0x80; int contbytes = 0;
150     if( codepoint.utf32 == 0xFFUL )
151       /*
152        * This is a degenerate form; if it were valid, (which it
153        * isn't), it would represent a sequence of eight octets,
154        * (the current one, plus seven continuation octets), all
155        * of which (together) encode a single code point; however,
156        * we cannot detect this by locating the most significant
157        * bit which isn't set, because no such bit is present,
158        * so we simply set the continuation count directly.
159        */
160       contbytes = 7;
161
162     /* Any other octet value must have at least one bit which is
163      * not set; we may determine the appropriate sequence length
164      * by repeated testing of high-order bits, (using a mask with
165      * a progressively incrementing number of such bits set)...
166      */
167     else while( (codepoint.utf8 & (mask = 0x80 | (mask >> 1))) == mask )
168       /*
169        * ...incrementing the continuation count on each cycle,
170        * while we continue to match high order bits which are
171        * set; (the first mismatch indicates the location of the
172        * first high-order bit in the data, which is not set).
173        */
174       ++contbytes;
175
176     /* The original UTF-8 specification forbids any more than
177      * five continuation bytes...
178      */
179     if( contbytes > 5 )
180       /*
181        * ...so mark as an UTF-32 overflow...
182        */
183       codepoint.utf32 = UTF32_OVERFLOW;
184
185     else
186       /* At this point, our mask covers the range of bits in the
187        * input octet, which serve only to indicate the length of
188        * the ensuing encoding sequence; they do not represent any
189        * part of the encoded data, so discard them.
190        */
191       codepoint.utf8 &= ~mask;
192
193     /* Any valid UTF-8 encoding sequence MUST be the shortest
194      * possible, to represent the equivalent UTF-32 code point
195      * value; (arbitrarily longer sequences may decode to the
196      * same value, but are deemed invalid).  To permit us to
197      * detect over-long encoding sequences, we make a note of
198      * the smallest UTF-32 value which cannot be encoded with
199      * fewer than the currently determined sequence length.
200      */
201     utf32_t minval = (contbytes == 1) ? 0x80UL : 0x100UL << (5 * contbytes);
202
203     /* We now have the significant data portion of the first
204      * input octet, already loaded into the code point buffer,
205      * and we know how many additional bytes are required to
206      * complete the the sequence.  We also know that all valid
207      * continuation octets must conform to a "10xxxxxx" binary
208      * bit pattern; thus we may loop over the expected number
209      * of continuation octets, validating and accumulating...
210      */
211     do { if( (*input & 0xC0) != 0x80 )
212          {
213            /* Invalid UTF-8 sequence continuation byte;
214             * must bail out immediately, leaving the current
215             * input byte as a possible starting point for
216             * the next encoding sequence...
217             */
218            codepoint.utf32 = UTF32_INVALID;
219            return input;
220          }
221          /* If we get past the preceding validity check,
222           * the current octet is a valid UTF-8 continuation
223           * byte...
224           */
225          else if( codepoint.utf32 < UTF32_OVERFLOW )
226          {
227            /* ...provided we haven't already overflowed the
228             * UTF-32 accumulator, shift the accumulated code
229             * point value to the left, to accommodate the next
230             * sextet of data bits to be added, and update the
231             * accumulator by merging them in...
232             */
233            codepoint.utf32 <<= 6;
234            codepoint.utf8 |= *input++ & ~0xC0;
235          }
236          /* ...continuing until all anticipated continuation
237           * bytes have been processed.
238           */
239        } while( --contbytes > 0 );
240
241     /* Reject any other abnormal encoding sequences, such as...
242      *
243      *   - an overlong encoding; (could have been encoded with
244      *     a shorter octet sequence)...
245      */
246     if(  (codepoint.utf32 < minval)
247       /*
248        * - any value greater than the maximum specified by the
249        *   Unicode Standard...
250        */
251     ||   (codepoint.utf32 > UTF32_MAX)
252       /*
253        * - any value in the range 0xD800..0xDFFF; (represents
254        *   one element of a UTF-16 surrogate pair, and is thus
255        *   invalid as a free-standing UTF-32 code point)...
256        */
257     ||  ((codepoint.utf32 >= 0xD800UL) && (codepoint.utf32 <= 0xDFFFUL))  )
258       /*
259        * FIXME: there may be other invalid sequences; should we
260        * add tests to detect the ilk of non-characters or private
261        * use code points, and also flag them as invalid?  They
262        * are, after all still valid Unicode scalar values, even
263        * if they don't map to any specific grapheme.
264        */
265       codepoint.utf32 = UTF32_INVALID;
266   }
267   /* We always return a pointer to the next available octet in
268    * the input string/stream; this represents the point at which
269    * scanning should resume, to retrieve the code point associated
270    * with the next available character encoding entity.
271    */
272   return input;
273 }
274
275 wchar_t pkgUTF8Parser::GetCodePoint( ... )
276 {
277   /* Retrieve the code point value representing the last scanned
278    * UTF-8 single code point sequence, encoding it as UTF-16.
279    *
280    * NOTE:
281    * While we don't actually use any argument in this routine,
282    * we allow any to be arbitrarily specified; this is to support
283    * calls of the form:
284    *
285    *   value = GetCodePoint( input = ScanBuffer( input ) );
286    *
287    */
288   if( codepoint.utf32 > 0xFFFFUL )
289   {
290     /* This code point lies beyond the BMP; we must decompose it
291      * to form a surrogate pair, returning the leading surrogate
292      * immediately, while leaving the trailing surrogate behind,
293      * in the code point buffer, to be fetched by a subsequent
294      * call; (we place the onus on the caller to complete this
295      * call-back, to retrieve the trailing surrogate).
296      */
297     utf32_t retval = (codepoint.utf32 >> 10) + 0xD800UL - (0x10000UL >> 10);
298     codepoint.utf32 = 0xDC00UL | (codepoint.utf32 & 0x3FFUL);
299     return (wchar_t)(retval & 0xFFFFUL);
300   }
301   /* Except in the special case of returning a leading surrogate
302    * above, whether the code point value lies within the BMP, or
303    * it is the residual trailing surrogate from an immediately
304    * preceding decomposition, it is already representable as a
305    * UTF-16 value, so simply return it.
306    */
307   return codepoint.utf16;
308 }
309
310 class pkgNroffLayoutEngine : public pkgUTF8Parser
311 {
312   /* A class for laying out a UTF-8 encoded string as a rudimentary
313    * "nroff" style word-wrapped and justified paragraph.
314    */
315   public:
316     pkgNroffLayoutEngine( const char *input ):pkgUTF8Parser( input ){}
317     pkgNroffLayoutEngine *WriteLn( int = 0, int = 0, int = 65 );
318 };
319
320 pkgNroffLayoutEngine*
321 pkgNroffLayoutEngine::WriteLn( int style, int offset, int maxlen )
322 {
323   /* Method to lay out a single line of text, with a specified
324    * justification style, left margin offset and line length.
325    *
326    * Justification is achieved by insertion of extra padding
327    * space at natural word breaks, commencing at left and right
328    * ends of successive lines alternately; this alternation is
329    * controlled by the static "padmode" flag.
330    */
331   static int padmode = 1;
332   pkgNroffLayoutEngine *curr = this;
333
334   int count = 0; ++maxlen;
335   while( (curr != NULL) && (maxlen > curr->length) )
336   {
337     /* Determine the maximum number of "words" from the output
338      * stream which will fit on the current line, and discount
339      * "maxlen" by the space they will occupy, so establishing
340      * the number of padding spaces which will be required for
341      * justification in the specified style.
342      */
343     ++count;
344     maxlen -= 1 + curr->length;
345     curr = (pkgNroffLayoutEngine *)(curr->next);
346   }
347
348   while( offset-- > 0 )
349     /*
350      * Emit white space to fill the left margin.
351      */
352     putwchar( '\x20' );
353
354   if( style < 0 )
355     /*
356      * Justification style is "flush right"; insert all
357      * necessary padding space at the left margin.
358      */
359     while( maxlen-- > 0 )
360       putwchar( '\x20' );
361
362   else if( curr == NULL )
363   {
364     /* The output text stream has been exhausted; the
365      * current line will be the last in the "paragraph";
366      * we do not justify it.
367      */
368     style = 0;
369     /*
370      * Additionally, we reset "padmode" to ensure that
371      * each subsequent paragraph starts in the same phase
372      * of alternation of padding character insertion.
373      */
374     padmode = 1;
375   }
376
377   /* For justification, padding spaces will be distributed
378    * as evenly as possible over all word breaks within the
379    * line; where an exactly equal distribution cannot be
380    * achieved...
381    */
382   int lpad = 0;
383   if( (style > 0) && ((padmode = 1 - padmode) == 1) )
384   {
385     /* ...and for alternating lines on which wider spacing
386      * is inserted from the left, we calculate the number of
387      * leftmost word breaks in which one extra padding space
388      * is to be inserted.
389      */
390     lpad = maxlen % (count - 1);
391     maxlen -= lpad;
392   }
393
394   /* Back up to the first word on the current output line...
395    */
396   curr = this;
397   /*
398    * ...then for all words comprising this line...
399    */
400   while( count > 0 )
401   {
402     /* ...emit the glyphs comprising one word.
403      */
404     while( curr->length-- > 0 )
405       putwchar( curr->GetCodePoint( curr->text = curr->ScanBuffer( curr->text )) );
406
407     if( --count > 0 )
408     {
409       /* There are more words to be placed on this line...
410        */
411       if( style > 0 )
412       {
413         /* ...and justification is required...
414          */
415         if( lpad-- > 0 )
416           /*
417            * ...and this line has pending left-weighted
418            * extra padding, so this word break gets one
419            * extra space...
420            */
421           putwchar( '\x20' );
422
423         for( int pad = maxlen / count; pad > 0; --pad )
424         {
425           /* ...plus any additional equally distributed
426            * padding spaces; (note that this will stretch
427            * automatically towards the right end of the
428            * line, for right-weighted padding)...
429            */
430           --maxlen; putwchar( '\x20' );
431         }
432       }
433       /* Ensure there is always at least one space separating
434        * adjacent words; this has already been included in the
435        * layout computation, in addition to the extra padding
436        * spaces just inserted.
437        */
438       putwchar( '\x20' );
439     }
440     /* Move on to the next word, if any, in the accumulated
441      * output stream; (note that this may represent the first
442      * word to be placed on the next line).
443      */
444     curr = (pkgNroffLayoutEngine *)(curr->next);
445   }
446   /* Emit a line break at end of line.
447    */
448   putwchar( '\n' );
449
450   /* Return a pointer to the first word to be placed on the next
451    * line, or NULL at end of paragraph.
452    */
453   return curr;
454 }
455
456 class pkgDirectoryViewerEngine
457 {
458   /* A minimal abstract base class, through which directory
459    * traversal methods of the following "pkgDirectory" class
460    * gain access to an appropriate handler in a specialised
461    * directory viewer class, (or any other class which may
462    * provide directory traversal hooks).  All such helper
463    * classes should derive from this base class, providing
464    * a "Dispatch" method as the directory traversal hook.
465    */
466   public:
467     virtual void Dispatch( pkgXmlNode* ) = 0;
468 #if 0
469     pkgDirectoryViewerEngine()
470     {
471       for( int i = 1; i < 9; i++ ) printf( "%10d", i ); putchar( '\n' );
472       for( int i = 1; i < 9; i++ ) printf( "1234567890" ); putchar( '\n' );
473     }
474 #endif
475 };
476
477 class pkgDirectory
478 {
479   /* A locally defined class, used to manage a list of package
480    * or component package references in the form of an unbalanced
481    * binary tree, such that an in-order traversal will produce
482    * an alpha-numerically sorted package list.
483    */
484   public:
485     pkgDirectory( pkgXmlNode* );
486     pkgDirectory *Insert( const char*, pkgDirectory* );
487     void InOrder( pkgDirectoryViewerEngine* );
488     ~pkgDirectory();
489
490   protected:
491     pkgXmlNode *entry;
492     pkgDirectory *prev;
493     pkgDirectory *next;
494 };
495
496 /* Constructor...
497  */
498 pkgDirectory::pkgDirectory( pkgXmlNode *item ):
499 entry( item ), prev( NULL ), next( NULL ){}
500
501 pkgDirectory *pkgDirectory::Insert( const char *keytype, pkgDirectory *newentry )
502 {
503   /* Add a new package or component reference to a directory compilation,
504    * using an unbalanced binary tree representation to achieve a sorted
505    * listing, in ascending alpha-numeric collating order.
506    */
507   if( this && newentry )
508   {
509     /* We have an existing directory to augment, and a valid reference
510      * pointer for a new directory entry; we must locate the appropriate
511      * insertion point, to achieve correct sort order.
512      */
513     pkgDirectory *refpt, *pt = this;
514     const char *mt = "", *ref = newentry->entry->GetPropVal( keytype, mt );
515     do { refpt = pt;
516          if( pt->prev && strcmp( ref, pt->prev->entry->GetPropVal( keytype, mt )) < 0 )
517            /*
518             * Follow predecessor reference pointers, until we locate
519             * the last with an entry which must sort AFTER the new
520             * reference to be inserted...
521             */
522            pt = pt->prev;
523
524          if( pt->next && strcmp( ref, pt->next->entry->GetPropVal( keytype, mt )) > 0 )
525            /*
526             * ...and similarly for successor references, until we
527             * locate the last to sort BEFORE the new entry...
528             */
529            pt = pt->next;
530
531          /* ...repeating each of the above, until the insertion point
532           * has been located.
533           */
534        } while( refpt != pt );
535
536     /* We've found the insertion point...
537      */
538     if( strcmp( ref, pt->entry->GetPropVal( keytype, mt )) < 0 )
539     {
540       /* ...and the new entry must become its immediate predecessor
541        * in dictionary sort order; displace the current predecessor,
542        * if any, to make way for this insertion...
543        */
544       if( pt->prev && strcmp( ref, pt->prev->entry->GetPropVal( keytype, mt )) > 0 )
545         /*
546          * In this case, the original predecessor must also appear
547          * before the new entry, in dictionary sort order...
548          */
549         newentry->prev = pt->prev;
550       else
551         /* ...or in this alternative case, it must appear as a
552          * successor, in the sort order.
553          */
554         newentry->next = pt->prev;
555
556       /* Finally, attach the new entry at the appropriate insertion point.
557        */
558       pt->prev = newentry;
559     }
560     else
561     { /* Similarly, for the case where the new entry must appear as
562        * a successor to the insertion point entry, we displace the
563        * original successor entry, if any...
564        */
565       if( pt->next && strcmp( ref, pt->next->entry->GetPropVal( keytype, mt )) > 0 )
566         /*
567          * ...re-attaching it as predecessor...
568          */
569         newentry->prev = pt->next;
570       else
571         /* ...or as successor to the new entry, as appropriate...
572          */
573         newentry->next = pt->next;
574
575       /* ...again finishing by attaching the new entry at the insertion point.
576        */
577       pt->next = newentry;
578     }
579   }
580   /* Finally, we return the pointer to either the original root node in the
581    * directory tree, or if that had never been previously assigned, then the
582    * new entry, which will become the root of a new directory tree.
583    */
584   return this ? this : newentry;
585 }
586
587 void pkgDirectory::InOrder( pkgDirectoryViewerEngine *action )
588 {
589   /* Perform an in-order traversal of a package directory tree,
590    * invoking a specified processing action on each node in turn;
591    * note that this requires a pointer to a concrete instance of
592    * a "Viewer" class, derived from the abstract "ViewerEngine".
593    */
594   if( this )
595   {
596     /* Proceeding only when we have a valid directory object,
597      * recursively traverse the "left hand" (prev) sub-tree...
598      */
599     prev->InOrder( action );
600     /*
601      * ...processing the current node when no unprocessed
602      * "left hand" sub-tree nodes remain...
603      */
604     action->Dispatch( entry );
605     /*
606      * ...then finish off with a recursive traversal of the
607      * "right-hand" (next) sub-tree...
608      */
609     next->InOrder( action );
610   }
611 }
612
613 /* Destructor...
614  */
615 pkgDirectory::~pkgDirectory()
616 {
617   /* We need to recursively delete any "prev" and "next" references
618    * for the current directory node, as a prequisite to deleting the
619    * current node itself.
620    */
621   delete prev;
622   delete next;
623 }
624
625 class pkgDirectoryViewer : public pkgDirectoryViewerEngine
626 {
627   /* A concrete class, providing the directory traversal hooks
628    * for display of package directory content in a CLI console.
629    */
630   public:
631     pkgDirectoryViewer();
632     virtual void Dispatch( pkgXmlNode* );
633
634   protected:
635     int page_width;
636     virtual void EmitHeader( pkgXmlNode* );
637     virtual void EmitDescription( pkgXmlNode*, const char* = NULL );
638     int ct;
639 };
640
641 /* Constructor...
642  */
643 pkgDirectoryViewer::pkgDirectoryViewer()
644 {
645   /* Set up the display width, for word-wrapped output...
646    */
647   const char *cols;
648   if( (cols = getenv( "COLS" )) != NULL )
649   {
650     /* In cases where the COLS environment variable defines the
651      * terminal width, then use 90% of that...
652      *
653      * FIXME: consider querying the terminfo database, where one
654      * is available, (not likely on MS-Windows), to get a default
655      * value when COLS is not set...
656      */
657     page_width = atoi( cols );
658     page_width -= page_width / 10;
659   }
660   else
661     /* When no COLS, (or terminfo), setting is available, then
662      * assume a default 80-column console, and set 90% of that.
663      */
664     page_width = 72;
665
666   /* Set single entry output mode, for component package listings.
667    */
668   ct = -1;
669 }
670
671 void pkgDirectoryViewer::EmitHeader( pkgXmlNode *entry )
672 {
673   /* Method to display package identification information
674    * in a standard console (CLI) based package directory listing;
675    * package name is presented flush left, while the associated
676    * subsystem identification is presented flush right, both
677    * on a single output line, at nominated console width.
678    *
679    * NOTE:
680    * There is no provision to accommodate package or subsystem
681    * names containing other than ASCII characters; we assume that
682    * such names are restricted to the ASCII subset of UTF-8.
683    */
684   const char *fmt = PKGMSG_DIRECTORY_HEADER_FORMAT;
685   const char *pkg = entry->GetPropVal( name_key, value_unknown );
686   const char *sys = entry->GetContainerAttribute( subsystem_key, value_unknown );
687
688   /* Justification is achieved by adjustment of the field width
689    * used to format the package name, (in "%*s" format).  We use
690    * snprintf to determine the required width, initially relative
691    * to a default 80-column console, then adjust it to the actual
692    * nominated width; the additional two in the adjustment is to
693    * discount the leading and trailing '\n' codes, which
694    * surround the formatted output string.
695    */
696   int padding = snprintf( NULL, 0, fmt, 80, pkg, sys ) - (80 + page_width + 2);
697   printf( fmt, padding, pkg, sys );
698 }
699
700 static __inline__ __attribute__((__always_inline__))
701 void pkgNroffLayout( int style, int offset, int width, const char *text )
702 {
703   /* Wrapper to facilitate layout of a single paragraph of text,
704    * with specified justification style, left margin offset and
705    * page width, using an nroff style layout engine.
706    */
707   pkgNroffLayoutEngine layout( text ), *output = &layout;
708
709   /* Precede each paragraph by one line height vertical space...
710    */
711   putchar( '\n' );
712   while( output != NULL )
713     /*
714      * ...then write out the text, line by line, until done.
715      */
716     output = output->WriteLn( style, offset, width );
717 }
718
719 static int offset_printf( int offset, const char *fmt, ... )
720 {
721   /* Helper to emit regular printf() formatted output, with
722    * a preset first line indent.
723    */
724   va_list argv;
725   va_start( argv, fmt );
726
727   /* We need a temporary storage location, to capture the
728    * printf() return value.
729    */
730   int retval;
731
732   /* Fill the left margin with whitespace, to the first line
733    * indent width.
734    */
735   while( offset-- > 0 )
736     putchar( '\x20' );
737
738   /* Emit the printf() output...
739    */
740   retval = vprintf( fmt, argv );
741   va_end( argv );
742
743   /* ...and return its result.
744    */
745   return retval;
746 }
747
748 static void underline( int underline_glyph, int offset, int len )
749 {
750   /* Helper to underline a preceding line of text, of specified
751    * length, with a specified repeating glyph, beginning at a
752    * specified left margin offset.
753    */
754   while( offset-- > 0 )
755     /*
756      * Fill the left margin with whitespace...
757      */
758     putchar( '\x20' );
759   while( len-- > 0 )
760     /*
761      * ...followed by the specified number of repetitions
762      * of the specified underlining glyph.
763      */
764     putchar( underline_glyph );
765 }
766
767 void pkgDirectoryViewer::EmitDescription( pkgXmlNode *pkg, const char *title )
768 {
769   /* Method to print formatted package descriptions to the console
770    * stdout stream, using the nroff style formatting engine, with
771    * fixed (for the time being) zero left margin offset.
772    */
773   int offset = 0;
774
775   /* These XML element and attribute identification keys should
776    * perhaps be defined in the pkgkeys.h header; once more, for
777    * the time being we simply define them locally.
778    */
779   const char *title_key = "title";
780   const char *description_key = "description";
781   const char *paragraph_key = "paragraph";
782
783   /* The procedure is recursive, selecting description elements
784    * from inner levels of the XML document, progressing outward to
785    * the document root, then printing them in outer to inner order;
786    * proceed only if we have a valid starting package element,
787    * within which to search for description elements...
788    */
789   if( pkg != NULL )
790   {
791     /* ...in which case, we locate the first of any such
792      * elements at the current nesting level.
793      */
794     pkgXmlNode *desc = pkg->FindFirstAssociate( description_key );
795     pkgXmlNode *content = desc;
796     while( (title == NULL) && (desc != NULL) )
797     {
798       /* On first entry, the "title" argument should have been
799        * passed as NULL; we subsequently select the first title
800        * attribute we encounter, at the innermost level where
801        * one is available, to become the title for this
802        * package description.
803        */
804       if( (title = desc->GetPropVal( title_key, NULL )) != NULL )
805       {
806         /* This is the innermost definition we've encountered for a
807          * title line; print and underline it.  (The -1 discounts the
808          * the page offset padding spaces and trailing newline, which
809          * surround the title text itself in the initial printf(),
810          * when computing the required underline length).
811          */
812         putchar( '\n' );
813         underline( '-', offset, offset_printf( offset, "%s\n", title ) - 1 );
814         putchar( '\n' );
815       }
816       else
817         /* We haven't identified a title yet; check in any further
818          * description elements which appear at this nesting level.
819          */
820         desc = desc->FindNextAssociate( description_key );
821     }
822
823     /* Regardless of whether we've found a title yet, or not,
824      * if we haven't already reached the document root...
825      */
826     if( pkg != pkg->GetDocumentRoot() )
827       /*
828        * ...recurse, to inspect the next outward containing element.
829        */
830       EmitDescription( pkg->GetParent(), title );
831
832     /* Finally, unwind the recursion stack...
833      */
834     while( content != NULL )
835     {
836       /* ...selecting paragraph elements from within each
837        * description element...
838        */
839       pkgXmlNode *para = content->FindFirstAssociate( paragraph_key );
840       while( para != NULL )
841       {
842         /* ...and printing each in turn...
843          */
844         pkgNroffLayout( 1, offset, page_width - offset, para->GetText() );
845         /*
846          * ...repeating for the next paragraph, if any...
847          */
848         para = para->FindNextAssociate( paragraph_key );
849       }
850       /* ...and then proceeding to the next description element, if any,
851        * ultimately falling through to unwind the next level of recursion,
852        * if any, within which paragraphs from any description elements from
853        * inner levels of the document will be printed.
854        */
855       content = content->FindNextAssociate( description_key );
856     }
857   }
858 }
859
860 void pkgDirectoryViewer::Dispatch( pkgXmlNode *entry )
861 {
862   /* Method to dispatch requests to display information
863    * about a single package entity.
864    */
865   if( entry->IsElementOfType( package_key ) )
866   {
867     /* The selected entity is a full package;
868      * create an auxiliary directory...
869      */
870     pkgDirectory *dir = NULL;
871     pkgXmlNode *cpt = entry->FindFirstAssociate( component_key );
872     while( cpt != NULL )
873     {
874       /* ...in which we enumerate its components.
875        */
876       dir = dir->Insert( class_key, new pkgDirectory( cpt ) );
877       cpt = cpt->FindNextAssociate( component_key );
878     }
879
880     /* Signalling that a component list is to be included...
881      */
882     ct = 0;
883     /* ...print the standard form package name header...
884      */
885     EmitHeader( entry );
886     if( dir != NULL )
887     {
888       /* ...with included enumeration of the component names...
889        */
890       dir->InOrder( this ); putchar( '\n' );
891       /*
892        * ...before discarding the auxiliary directory.
893        */
894       delete dir;
895     }
896     /* For this category of information display, we don't include
897      * package version information; proceed immediately, to print
898      * the description, as it applies to all components...
899      */
900     EmitDescription( entry );
901     /*
902      * ...followed by one additional line space; (note that we don't
903      * include this within the EmitDescription method itself, because
904      * its recursive processing would result in a surfeit of embedded
905      * line spaces, within the description).
906      */
907     putchar( '\n' );
908
909     /* Finally, reset the component tracking enumeration flag to its
910      * normal (initial) state.
911      */
912     ct = -1;
913   }
914
915   else if( entry->IsElementOfType( component_key ) )
916   {
917     /* In this case, the selected entity is a component package...
918      */
919     if( ct < 0 )
920     {
921       /* ...and the component tracking state indicates that we are NOT
922        * simply enumerating components for the more inclusive style of
923        * package information printed in the preceding case.  Thus, we
924        * deduce that the user has explicitly requested information on
925        * just this specific component package; retrieve and print the
926        * standard header information for the containing package...
927        */
928       EmitHeader( entry->GetParent() );
929       /*
930        * ...followed by this specific component identification.
931        */
932       printf( "Component: %s\n", entry->GetPropVal( class_key, value_unknown ) );
933
934       /* In this case, we DO want to print package version information
935        * for the specific component package, indicating the installed
936        * version, if any, and the latest available in the repository.
937        * Thus, we initiate a local action request...
938        */
939       pkgActionItem avail;
940       pkgXmlNode *rel = entry->FindFirstAssociate( release_key );
941       while( rel != NULL )
942       {
943         /* ...to scan all associated release keys, and select the
944          * most recent recorded in the database as available...
945          */
946         avail.SelectIfMostRecentFit( rel );
947         if( rel->GetInstallationRecord( rel->GetPropVal( tarname_key, NULL )) != NULL )
948           /*
949            * ...also noting if any is marked as installed...
950            */
951           avail.SelectPackage( rel, to_remove );
952
953         /* ...until all release keys have been inspected...
954          */
955         rel = rel->FindNextAssociate( release_key );
956       }
957       /* ...and performing a final check on installation status.
958        */
959       avail.ConfirmInstallationStatus();
960
961       /* Now print the applicable version information, noting that
962        * "none" may be appropriate for the installed version.
963        */
964       pkgXmlNode *current = avail.Selection( to_remove );
965       const char *tarname = avail.Selection()->GetPropVal( tarname_key, NULL );
966       printf( "\nInstalled Version:  %s\nRepository Version: %s\n",
967           (current != NULL)
968           ? current->GetPropVal( tarname_key, value_unknown )
969           : value_none,
970           tarname
971         );
972
973       /* Finally, collate and print the package description for
974        * this component package, with following line space, just
975        * as we did in the preceding case.
976        */
977       EmitDescription( entry );
978       putchar( '\n' );
979     }
980
981     else
982       /* The component package is simply being enumerated, as part
983        * of the full package information data; we simply emit its
984        * component class name into the package information header.
985        */
986       printf( ct++ ? ", %s" : "Components: %s",
987           entry->GetPropVal( class_key, value_unknown )
988         );
989   }
990 }
991
992 void pkgXmlDocument::DisplayPackageInfo( int argc, char **argv )
993 {
994   /* Primary method for retrieval and display of package information
995    * on demand from the mingw-get command line interface.
996    */
997   pkgDirectory *dir = NULL;
998   pkgDirectoryViewer output;
999
1000   if( argc > 1 )
1001   {
1002     /* One or more possible package name arguments
1003      * were specified on the command line.
1004      */
1005     while( --argc > 0 )
1006     {
1007       /* Look up each one in the package catalogue...
1008        */
1009       pkgXmlNode *pkg;
1010       if( (pkg = FindPackageByName( *++argv )) != NULL )
1011       {
1012         /* ...and, when found, create a corresponding
1013          * print-out directory entry, inserting it in
1014          * its alphanumerically sorted position.
1015          */
1016         dir = dir->Insert( name_key, new pkgDirectory( pkg ) );
1017       }
1018       else
1019         /* We found no information on the requested package;
1020          * diagnose as a non-fatal error.
1021          */
1022         dmh_notify( DMH_ERROR, pkgMsgUnknownPackage(), *argv );
1023     }
1024   }
1025
1026   else
1027   {
1028     /* No arguments were specified; interpret this as a request
1029      * to display information pertaining to all known packages in
1030      * the current mingw-get repository's universe, thus...
1031      */
1032     pkgXmlNode *grp = GetRoot()->FindFirstAssociate( package_collection_key );
1033     while( grp != NULL )
1034     {
1035       /* ...for each known package collection...
1036        */
1037       pkgXmlNode *pkg = grp->FindFirstAssociate( package_key );
1038       while( pkg != NULL )
1039       {
1040         /* ...add an entry for each included package
1041          * into the print-out directory...
1042          */
1043         dir = dir->Insert( name_key, new pkgDirectory( pkg ) );
1044         /*
1045          * ...enumerating each and every package...
1046          */
1047         pkg = pkg->FindNextAssociate( package_key );
1048       }
1049       /* ...then repeat for the next package collection,
1050        * if any.
1051        */
1052       grp = grp->FindNextAssociate( package_collection_key );
1053     }
1054   }
1055
1056   /* Regardless of how the print-out directory was compiled,
1057    * we hand it off to the common viewer, for output.
1058    */
1059   dir->InOrder( &output );
1060
1061   /* Finally, we are done with the print-out directory,
1062    * so we may delete it.
1063    */
1064   delete dir;
1065 }
1066
1067 /* $RCSfile$: end of file */