6 * Written by Keith Marshall <keithmarshall@users.sourceforge.net>
7 * Copyright (C) 2009, 2010, MinGW Project
10 * Implementation of the classes and methods required to support the
11 * 'mingw-get list' and 'mingw-get show' commands.
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.
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.
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.
42 #define PKGMSG_DIRECTORY_HEADER_FORMAT "\nPackage: %*sSubsystem: %s\n"
44 EXTERN_C const char *pkgMsgUnknownPackage( void );
46 /* Utility classes and definitions for interpreting and formatting
47 * package descriptions, encoded as UTF-8.
49 typedef unsigned long utf32_t;
51 #define UTF32_MAX (utf32_t)(0x10FFFFUL)
52 #define UTF32_OVERFLOW (utf32_t)(UTF32_MAX + 1UL)
54 #define UTF32_INVALID (utf32_t)(0x0FFFDUL)
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.
63 pkgUTF8Parser( const char* );
64 ~pkgUTF8Parser(){ delete next; }
67 class pkgUTF8Parser *next;
68 union { char utf8; wchar_t utf16; utf32_t utf32; } codepoint;
72 const char *ScanBuffer( const char* );
73 wchar_t GetCodePoint( ... );
78 pkgUTF8Parser::pkgUTF8Parser( const char *input ):
79 text( NULL ), length( 0 ), next( NULL )
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.
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.
99 /* First, step over any initial whitespace...
101 while( *input && iswspace( GetCodePoint( mark = ScanBuffer( input ))) )
104 /* If we didn't run out of input...
108 /* ...then we've found the start of a non-whitespace substring;
109 * mark it, and then...
112 while( *input && ! iswspace( GetCodePoint( mark = ScanBuffer( input ))) )
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.
118 input = mark; ++length;
120 /* Continue to recursively add parse nodes, to mark the
121 * locations of any further non-whitespace substrings.
123 next = new pkgUTF8Parser( input );
127 const char *pkgUTF8Parser::ScanBuffer( const char *input )
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).
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...
141 if( (codepoint.utf32 = 0xFFUL & (utf32_t)(*input++)) > 0x7FUL )
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...
149 char mask = 0x80; int contbytes = 0;
150 if( codepoint.utf32 == 0xFFUL )
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.
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)...
167 else while( (codepoint.utf8 & (mask = 0x80 | (mask >> 1))) == mask )
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).
176 /* The original UTF-8 specification forbids any more than
177 * five continuation bytes...
181 * ...so mark as an UTF-32 overflow...
183 codepoint.utf32 = UTF32_OVERFLOW;
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.
191 codepoint.utf8 &= ~mask;
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.
201 utf32_t minval = (contbytes == 1) ? 0x80UL : 0x100UL << (5 * contbytes);
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...
211 do { if( (*input & 0xC0) != 0x80 )
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...
218 codepoint.utf32 = UTF32_INVALID;
221 /* If we get past the preceding validity check,
222 * the current octet is a valid UTF-8 continuation
225 else if( codepoint.utf32 < UTF32_OVERFLOW )
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...
233 codepoint.utf32 <<= 6;
234 codepoint.utf8 |= *input++ & ~0xC0;
236 /* ...continuing until all anticipated continuation
237 * bytes have been processed.
239 } while( --contbytes > 0 );
241 /* Reject any other abnormal encoding sequences, such as...
243 * - an overlong encoding; (could have been encoded with
244 * a shorter octet sequence)...
246 if( (codepoint.utf32 < minval)
248 * - any value greater than the maximum specified by the
249 * Unicode Standard...
251 || (codepoint.utf32 > UTF32_MAX)
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)...
257 || ((codepoint.utf32 >= 0xD800UL) && (codepoint.utf32 <= 0xDFFFUL)) )
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.
265 codepoint.utf32 = UTF32_INVALID;
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.
275 wchar_t pkgUTF8Parser::GetCodePoint( ... )
277 /* Retrieve the code point value representing the last scanned
278 * UTF-8 single code point sequence, encoding it as UTF-16.
281 * While we don't actually use any argument in this routine,
282 * we allow any to be arbitrarily specified; this is to support
285 * value = GetCodePoint( input = ScanBuffer( input ) );
288 if( codepoint.utf32 > 0xFFFFUL )
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).
297 utf32_t retval = (codepoint.utf32 >> 10) + 0xD800UL - (0x10000UL >> 10);
298 codepoint.utf32 = 0xDC00UL | (codepoint.utf32 & 0x3FFUL);
299 return (wchar_t)(retval & 0xFFFFUL);
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.
307 return codepoint.utf16;
310 class pkgNroffLayoutEngine : public pkgUTF8Parser
312 /* A class for laying out a UTF-8 encoded string as a rudimentary
313 * "nroff" style word-wrapped and justified paragraph.
316 pkgNroffLayoutEngine( const char *input ):pkgUTF8Parser( input ){}
317 pkgNroffLayoutEngine *WriteLn( int = 0, int = 0, int = 65 );
320 pkgNroffLayoutEngine*
321 pkgNroffLayoutEngine::WriteLn( int style, int offset, int maxlen )
323 /* Method to lay out a single line of text, with a specified
324 * justification style, left margin offset and line length.
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.
331 static int padmode = 1;
332 pkgNroffLayoutEngine *curr = this;
334 int count = 0; ++maxlen;
335 while( (curr != NULL) && (maxlen > curr->length) )
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.
344 maxlen -= 1 + curr->length;
345 curr = (pkgNroffLayoutEngine *)(curr->next);
348 while( offset-- > 0 )
350 * Emit white space to fill the left margin.
356 * Justification style is "flush right"; insert all
357 * necessary padding space at the left margin.
359 while( maxlen-- > 0 )
362 else if( curr == NULL )
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.
370 * Additionally, we reset "padmode" to ensure that
371 * each subsequent paragraph starts in the same phase
372 * of alternation of padding character insertion.
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
383 if( (style > 0) && ((padmode = 1 - padmode) == 1) )
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
390 lpad = maxlen % (count - 1);
394 /* Back up to the first word on the current output line...
398 * ...then for all words comprising this line...
402 /* ...emit the glyphs comprising one word.
404 while( curr->length-- > 0 )
405 putwchar( curr->GetCodePoint( curr->text = curr->ScanBuffer( curr->text )) );
409 /* There are more words to be placed on this line...
413 /* ...and justification is required...
417 * ...and this line has pending left-weighted
418 * extra padding, so this word break gets one
423 for( int pad = maxlen / count; pad > 0; --pad )
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)...
430 --maxlen; putwchar( '\x20' );
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.
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).
444 curr = (pkgNroffLayoutEngine *)(curr->next);
446 /* Emit a line break at end of line.
450 /* Return a pointer to the first word to be placed on the next
451 * line, or NULL at end of paragraph.
456 class pkgDirectoryViewerEngine
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.
467 virtual void Dispatch( pkgXmlNode* ) = 0;
469 pkgDirectoryViewerEngine()
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' );
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.
485 pkgDirectory( pkgXmlNode* );
486 pkgDirectory *Insert( const char*, pkgDirectory* );
487 void InOrder( pkgDirectoryViewerEngine* );
498 pkgDirectory::pkgDirectory( pkgXmlNode *item ):
499 entry( item ), prev( NULL ), next( NULL ){}
501 pkgDirectory *pkgDirectory::Insert( const char *keytype, pkgDirectory *newentry )
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.
507 if( this && newentry )
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.
513 pkgDirectory *refpt, *pt = this;
514 const char *mt = "", *ref = newentry->entry->GetPropVal( keytype, mt );
516 if( pt->prev && strcmp( ref, pt->prev->entry->GetPropVal( keytype, mt )) < 0 )
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...
524 if( pt->next && strcmp( ref, pt->next->entry->GetPropVal( keytype, mt )) > 0 )
526 * ...and similarly for successor references, until we
527 * locate the last to sort BEFORE the new entry...
531 /* ...repeating each of the above, until the insertion point
534 } while( refpt != pt );
536 /* We've found the insertion point...
538 if( strcmp( ref, pt->entry->GetPropVal( keytype, mt )) < 0 )
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...
544 if( pt->prev && strcmp( ref, pt->prev->entry->GetPropVal( keytype, mt )) > 0 )
546 * In this case, the original predecessor must also appear
547 * before the new entry, in dictionary sort order...
549 newentry->prev = pt->prev;
551 /* ...or in this alternative case, it must appear as a
552 * successor, in the sort order.
554 newentry->next = pt->prev;
556 /* Finally, attach the new entry at the appropriate insertion point.
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...
565 if( pt->next && strcmp( ref, pt->next->entry->GetPropVal( keytype, mt )) > 0 )
567 * ...re-attaching it as predecessor...
569 newentry->prev = pt->next;
571 /* ...or as successor to the new entry, as appropriate...
573 newentry->next = pt->next;
575 /* ...again finishing by attaching the new entry at the insertion point.
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.
584 return this ? this : newentry;
587 void pkgDirectory::InOrder( pkgDirectoryViewerEngine *action )
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".
596 /* Proceeding only when we have a valid directory object,
597 * recursively traverse the "left hand" (prev) sub-tree...
599 prev->InOrder( action );
601 * ...processing the current node when no unprocessed
602 * "left hand" sub-tree nodes remain...
604 action->Dispatch( entry );
606 * ...then finish off with a recursive traversal of the
607 * "right-hand" (next) sub-tree...
609 next->InOrder( action );
615 pkgDirectory::~pkgDirectory()
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.
625 class pkgDirectoryViewer : public pkgDirectoryViewerEngine
627 /* A concrete class, providing the directory traversal hooks
628 * for display of package directory content in a CLI console.
631 pkgDirectoryViewer();
632 virtual void Dispatch( pkgXmlNode* );
636 virtual void EmitHeader( pkgXmlNode* );
637 virtual void EmitDescription( pkgXmlNode*, const char* = NULL );
643 pkgDirectoryViewer::pkgDirectoryViewer()
645 /* Set up the display width, for word-wrapped output...
648 if( (cols = getenv( "COLS" )) != NULL )
650 /* In cases where the COLS environment variable defines the
651 * terminal width, then use 90% of that...
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...
657 page_width = atoi( cols );
658 page_width -= page_width / 10;
661 /* When no COLS, (or terminfo), setting is available, then
662 * assume a default 80-column console, and set 90% of that.
666 /* Set single entry output mode, for component package listings.
671 void pkgDirectoryViewer::EmitHeader( pkgXmlNode *entry )
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.
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.
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 );
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.
696 int padding = snprintf( NULL, 0, fmt, 80, pkg, sys ) - (80 + page_width + 2);
697 printf( fmt, padding, pkg, sys );
700 static __inline__ __attribute__((__always_inline__))
701 void pkgNroffLayout( int style, int offset, int width, const char *text )
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.
707 pkgNroffLayoutEngine layout( text ), *output = &layout;
709 /* Precede each paragraph by one line height vertical space...
712 while( output != NULL )
714 * ...then write out the text, line by line, until done.
716 output = output->WriteLn( style, offset, width );
719 static int offset_printf( int offset, const char *fmt, ... )
721 /* Helper to emit regular printf() formatted output, with
722 * a preset first line indent.
725 va_start( argv, fmt );
727 /* We need a temporary storage location, to capture the
728 * printf() return value.
732 /* Fill the left margin with whitespace, to the first line
735 while( offset-- > 0 )
738 /* Emit the printf() output...
740 retval = vprintf( fmt, argv );
743 /* ...and return its result.
748 static void underline( int underline_glyph, int offset, int len )
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.
754 while( offset-- > 0 )
756 * Fill the left margin with whitespace...
761 * ...followed by the specified number of repetitions
762 * of the specified underlining glyph.
764 putchar( underline_glyph );
767 void pkgDirectoryViewer::EmitDescription( pkgXmlNode *pkg, const char *title )
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.
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.
779 const char *title_key = "title";
780 const char *description_key = "description";
781 const char *paragraph_key = "paragraph";
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...
791 /* ...in which case, we locate the first of any such
792 * elements at the current nesting level.
794 pkgXmlNode *desc = pkg->FindFirstAssociate( description_key );
795 pkgXmlNode *content = desc;
796 while( (title == NULL) && (desc != NULL) )
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.
804 if( (title = desc->GetPropVal( title_key, NULL )) != NULL )
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).
813 underline( '-', offset, offset_printf( offset, "%s\n", title ) - 1 );
817 /* We haven't identified a title yet; check in any further
818 * description elements which appear at this nesting level.
820 desc = desc->FindNextAssociate( description_key );
823 /* Regardless of whether we've found a title yet, or not,
824 * if we haven't already reached the document root...
826 if( pkg != pkg->GetDocumentRoot() )
828 * ...recurse, to inspect the next outward containing element.
830 EmitDescription( pkg->GetParent(), title );
832 /* Finally, unwind the recursion stack...
834 while( content != NULL )
836 /* ...selecting paragraph elements from within each
837 * description element...
839 pkgXmlNode *para = content->FindFirstAssociate( paragraph_key );
840 while( para != NULL )
842 /* ...and printing each in turn...
844 pkgNroffLayout( 1, offset, page_width - offset, para->GetText() );
846 * ...repeating for the next paragraph, if any...
848 para = para->FindNextAssociate( paragraph_key );
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.
855 content = content->FindNextAssociate( description_key );
860 void pkgDirectoryViewer::Dispatch( pkgXmlNode *entry )
862 /* Method to dispatch requests to display information
863 * about a single package entity.
865 if( entry->IsElementOfType( package_key ) )
867 /* The selected entity is a full package;
868 * create an auxiliary directory...
870 pkgDirectory *dir = NULL;
871 pkgXmlNode *cpt = entry->FindFirstAssociate( component_key );
874 /* ...in which we enumerate its components.
876 dir = dir->Insert( class_key, new pkgDirectory( cpt ) );
877 cpt = cpt->FindNextAssociate( component_key );
880 /* Signalling that a component list is to be included...
883 /* ...print the standard form package name header...
888 /* ...with included enumeration of the component names...
890 dir->InOrder( this ); putchar( '\n' );
892 * ...before discarding the auxiliary directory.
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...
900 EmitDescription( entry );
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).
909 /* Finally, reset the component tracking enumeration flag to its
910 * normal (initial) state.
915 else if( entry->IsElementOfType( component_key ) )
917 /* In this case, the selected entity is a component package...
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...
928 EmitHeader( entry->GetParent() );
930 * ...followed by this specific component identification.
932 printf( "Component: %s\n", entry->GetPropVal( class_key, value_unknown ) );
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...
940 pkgXmlNode *rel = entry->FindFirstAssociate( release_key );
943 /* ...to scan all associated release keys, and select the
944 * most recent recorded in the database as available...
946 avail.SelectIfMostRecentFit( rel );
947 if( rel->GetInstallationRecord( rel->GetPropVal( tarname_key, NULL )) != NULL )
949 * ...also noting if any is marked as installed...
951 avail.SelectPackage( rel, to_remove );
953 /* ...until all release keys have been inspected...
955 rel = rel->FindNextAssociate( release_key );
957 /* ...and performing a final check on installation status.
959 avail.ConfirmInstallationStatus();
961 /* Now print the applicable version information, noting that
962 * "none" may be appropriate for the installed version.
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",
968 ? current->GetPropVal( tarname_key, value_unknown )
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.
977 EmitDescription( entry );
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.
986 printf( ct++ ? ", %s" : "Components: %s",
987 entry->GetPropVal( class_key, value_unknown )
992 void pkgXmlDocument::DisplayPackageInfo( int argc, char **argv )
994 /* Primary method for retrieval and display of package information
995 * on demand from the mingw-get command line interface.
997 pkgDirectory *dir = NULL;
998 pkgDirectoryViewer output;
1002 /* One or more possible package name arguments
1003 * were specified on the command line.
1007 /* Look up each one in the package catalogue...
1010 if( (pkg = FindPackageByName( *++argv )) != NULL )
1012 /* ...and, when found, create a corresponding
1013 * print-out directory entry, inserting it in
1014 * its alphanumerically sorted position.
1016 dir = dir->Insert( name_key, new pkgDirectory( pkg ) );
1019 /* We found no information on the requested package;
1020 * diagnose as a non-fatal error.
1022 dmh_notify( DMH_ERROR, pkgMsgUnknownPackage(), *argv );
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...
1032 pkgXmlNode *grp = GetRoot()->FindFirstAssociate( package_collection_key );
1033 while( grp != NULL )
1035 /* ...for each known package collection...
1037 pkgXmlNode *pkg = grp->FindFirstAssociate( package_key );
1038 while( pkg != NULL )
1040 /* ...add an entry for each included package
1041 * into the print-out directory...
1043 dir = dir->Insert( name_key, new pkgDirectory( pkg ) );
1045 * ...enumerating each and every package...
1047 pkg = pkg->FindNextAssociate( package_key );
1049 /* ...then repeat for the next package collection,
1052 grp = grp->FindNextAssociate( package_collection_key );
1056 /* Regardless of how the print-out directory was compiled,
1057 * we hand it off to the common viewer, for output.
1059 dir->InOrder( &output );
1061 /* Finally, we are done with the print-out directory,
1062 * so we may delete it.
1067 /* $RCSfile$: end of file */