pkgman search: Add -D/--details

* Add a detailed listing mode (-D/--details), which prints a table with
  repository/installation location name, package name, package version,
  package architecture.
* Make the normal listing more compact. Now there's only one row per
  package. The "Installed" column has been replaced by "Status" which
  displays if/where the package is installed and whether it matches the
  repository version.
This commit is contained in:
Ingo Weinhold
2013-11-27 15:38:42 +01:00
parent 01adde8502
commit bf5786ebd6
4 changed files with 464 additions and 57 deletions
+1
View File
@@ -17,6 +17,7 @@ BinCommand pkgman :
JobStateListener.cpp
PackageManager.cpp
pkgman.cpp
TextTable.cpp
:
package be
$(TARGET_LIBSUPC++) $(TARGET_LIBSTDC++)
+274
View File
@@ -0,0 +1,274 @@
/*
* Copyright 2013, Ingo Weinhold, [email protected].
* Distributed under the terms of the MIT License.
*/
#include "TextTable.h"
#include <stdio.h>
#include <algorithm>
// #pragma mark - Column
struct TextTable::Column {
Column(const BString& title, enum alignment align, bool canTruncate)
:
fTitle(title),
fAlignment(align),
fCanBeTruncated(canTruncate),
fNeededWidth(0),
fWidth(0)
{
UpdateNeededWidth(fTitle);
fMinWidth = fNeededWidth;
}
const BString& Title() const
{
return fTitle;
}
enum alignment Alignment() const
{
return fAlignment;
}
bool CanBeTruncated() const
{
return fCanBeTruncated;
}
int32 NeededWidth() const
{
return fNeededWidth;
}
int32 MinWidth() const
{
return fMinWidth;
}
int32 Width() const
{
return fWidth;
}
void SetWidth(int32 width)
{
fWidth = width;
}
void UpdateNeededWidth(const BString& text)
{
// TODO: Full-width character support.
int32 textWidth = text.CountChars();
if (textWidth > fNeededWidth)
fNeededWidth = textWidth;
}
BString Format(const BString& text)
{
// TODO: Full-width character support.
int32 textWidth = text.CountChars();
if (textWidth == fWidth)
return text;
// truncate, if too long
if (textWidth > fWidth) {
BString result(text);
result.TruncateChars(fWidth);
return result;
}
// align, if too short
int32 missing = fWidth - textWidth;
switch (fAlignment) {
case B_ALIGN_LEFT:
default:
{
BString result(text);
result.Append(' ', missing);
return result;
}
case B_ALIGN_RIGHT:
{
BString result;
result.Append(' ', missing);
result.Append(text);
return result;
}
case B_ALIGN_CENTER:
{
BString result;
result.Append(' ', missing / 2);
result.Append(text);
result.Append(' ', missing - missing / 2);
return result;
}
}
}
private:
BString fTitle;
enum alignment fAlignment;
bool fCanBeTruncated;
int32 fNeededWidth;
int32 fMinWidth;
int32 fWidth;
};
// #pragma mark - TextTable
TextTable::TextTable()
:
fColumns(10, true),
fRows(100, true)
{
}
TextTable::~TextTable()
{
}
int32
TextTable::CountColumns() const
{
return fColumns.CountItems();
}
void
TextTable::AddColumn(const BString& title, enum alignment align,
bool canTruncate)
{
Column* column = new Column(title, align, canTruncate);
if (!fColumns.AddItem(column)) {
delete column;
throw std::bad_alloc();
}
}
int32
TextTable::CountRows() const
{
return fRows.CountItems();
}
BString
TextTable::TextAt(int32 rowIndex, int32 columnIndex) const
{
BStringList* row = fRows.ItemAt(rowIndex);
if (row == NULL)
return BString();
return row->StringAt(columnIndex);
}
void
TextTable::SetTextAt(int32 rowIndex, int32 columnIndex, const BString& text)
{
// If necessary append empty rows up to the specified row index.
while (rowIndex >= fRows.CountItems()) {
BStringList* row = new BStringList();
if (!fRows.AddItem(row)) {
delete row;
throw std::bad_alloc();
}
}
// If necessary append empty strings up to the specified column index.
BStringList* row = fRows.ItemAt(rowIndex);
while (columnIndex >= row->CountStrings()) {
if (!row->Add(BString()))
throw std::bad_alloc();
}
// set the text
if (!row->Replace(columnIndex, text))
throw std::bad_alloc();
}
void
TextTable::Print(int32 maxWidth)
{
int32 columnCount = fColumns.CountItems();
if (columnCount == 0)
return;
// determine the column widths
int32 rowCount = fRows.CountItems();
for (int32 rowIndex = 0; rowIndex < rowCount; rowIndex++) {
BStringList* row = fRows.ItemAt(rowIndex);
int32 rowColumnCount = std::min(row->CountStrings(), columnCount);
for (int32 columnIndex = 0; columnIndex < rowColumnCount;
columnIndex++) {
fColumns.ItemAt(columnIndex)->UpdateNeededWidth(
row->StringAt(columnIndex));
}
}
int32 neededWidth = (columnCount - 1) * 2;
// spacing
for (int32 i = 0; i < columnCount; i++)
neededWidth += fColumns.ItemAt(i)->NeededWidth();
int32 width = neededWidth;
int32 missingWidth = neededWidth - std::min(maxWidth, neededWidth);
for (int32 i = 0; i < columnCount; i++) {
Column* column = fColumns.ItemAt(i);
if (missingWidth > 0 && column->CanBeTruncated()) {
int32 truncateBy = std::min(missingWidth,
column->NeededWidth() - column->MinWidth());
column->SetWidth(column->NeededWidth() - truncateBy);
missingWidth -= truncateBy;
width -= truncateBy;
} else
column->SetWidth(column->NeededWidth());
}
// print the header
BString line;
for (int32 i = 0; i < columnCount; i++) {
if (i > 0)
line << " ";
Column* column = fColumns.ItemAt(i);
line << column->Format(column->Title());
}
line << '\n';
fputs(line.String(), stdout);
line.SetTo('-', width);
line << '\n';
fputs(line.String(), stdout);
// print the rows
for (int32 rowIndex = 0; rowIndex < rowCount; rowIndex++) {
line.Truncate(0);
BStringList* row = fRows.ItemAt(rowIndex);
for (int32 columnIndex = 0; columnIndex < columnCount; columnIndex++) {
if (columnIndex > 0)
line << " ";
line << fColumns.ItemAt(columnIndex)->Format(
row->StringAt(columnIndex));
}
line << '\n';
fputs(line.String(), stdout);
}
}
+42
View File
@@ -0,0 +1,42 @@
/*
* Copyright 2013, Ingo Weinhold, [email protected].
* Distributed under the terms of the MIT License.
*/
#ifndef TEXT_TABLE_H
#define TEXT_TABLE_H
#include <InterfaceDefs.h>
#include <ObjectList.h>
#include <StringList.h>
class TextTable {
public:
TextTable();
~TextTable();
int32 CountColumns() const;
void AddColumn(const BString& title,
enum alignment align = B_ALIGN_LEFT,
bool canTruncate = false);
int32 CountRows() const;
BString TextAt(int32 rowIndex, int32 columnIndex) const;
void SetTextAt(int32 rowIndex, int32 columnIndex,
const BString& text);
void Print(int32 maxWidth);
private:
struct Column;
typedef BObjectList<Column> ColumnList;
typedef BObjectList<BStringList> RowList;
private:
ColumnList fColumns;
RowList fRows;
};
#endif // TEXT_TABLE_H
+147 -57
View File
@@ -15,17 +15,18 @@
#include <unistd.h>
#include <algorithm>
#include <set>
#include <package/solver/SolverPackage.h>
#include "Command.h"
#include "PackageManager.h"
#include "pkgman.h"
#include "TextTable.h"
// TODO: internationalization!
// The printing code will need serious attention wrt. dealing with UTF-8 and,
// even worse, full-width characters.
// The table code doesn't support full-width characters yet.
using namespace BPackageKit;
@@ -42,10 +43,20 @@ static const char* const kLongUsage =
"Options:\n"
" -a, --all\n"
" List all packages. Specified instead of <search-string>.\n"
" -D, --details\n"
" Print more details. Matches in each installation location and each\n"
" repository will be listed individually with their version.\n"
" -i, --installed-only\n"
" Only find installed packages.\n"
" -u, --uninstalled-only\n"
" Only find not installed packages.\n"
"\n"
"Status flags in non-detailed listings:\n"
" S - installed in system with a matching version in a repository\n"
" s - installed in system without a matching version in a repository\n"
" H - installed in home with a matching version in a repository\n"
" h - installed in home without a matching version in a repository\n"
" v - multiple different versions available in repositories\n"
"\n";
@@ -65,16 +76,64 @@ get_terminal_width()
}
struct PackageComparator {
PackageComparator(const BSolverRepository* systemRepository,
const BSolverRepository* homeRepository)
:
fSystemRepository(systemRepository),
fHomeRepository(homeRepository)
{
}
int operator()(const BSolverPackage* a, const BSolverPackage* b) const
{
int cmp = a->Name().Compare(b->Name());
if (cmp != 0)
return cmp;
// Names are equal. Sort by installation location and then by repository
// name.
if (a->Repository() == b->Repository())
return 0;
if (a->Repository() == fSystemRepository)
return -1;
if (b->Repository() == fSystemRepository)
return 1;
if (a->Repository() == fHomeRepository)
return -1;
if (b->Repository() == fHomeRepository)
return 1;
return a->Repository()->Name().Compare(b->Repository()->Name());
}
private:
const BSolverRepository* fSystemRepository;
const BSolverRepository* fHomeRepository;
};
static int
compare_packages(const BSolverPackage* a, const BSolverPackage* b,
void* comparator)
{
return (*(PackageComparator*)comparator)(a, b);
}
int
SearchCommand::Execute(int argc, const char* const* argv)
{
bool installedOnly = false;
bool uninstalledOnly = false;
bool listAll = false;
bool details = false;
while (true) {
static struct option sLongOptions[] = {
{ "all", no_argument, 0, 'a' },
{ "details", no_argument, 0, 'D' },
{ "help", no_argument, 0, 'h' },
{ "installed-only", no_argument, 0, 'i' },
{ "uninstalled-only", no_argument, 0, 'u' },
@@ -82,7 +141,7 @@ SearchCommand::Execute(int argc, const char* const* argv)
};
opterr = 0; // don't print errors
int c = getopt_long(argc, (char**)argv, "ahiu", sLongOptions, NULL);
int c = getopt_long(argc, (char**)argv, "aDhiu", sLongOptions, NULL);
if (c == -1)
break;
@@ -91,6 +150,10 @@ SearchCommand::Execute(int argc, const char* const* argv)
listAll = true;
break;
case 'D':
details = true;
break;
case 'h':
PrintUsageAndExit(false);
break;
@@ -139,66 +202,93 @@ SearchCommand::Execute(int argc, const char* const* argv)
return 0;
}
// sort packages by name and installation location/repository
const BSolverRepository* systemRepository
= static_cast<const BSolverRepository*>(
packageManager.SystemRepository());
const BSolverRepository* homeRepository
= static_cast<const BSolverRepository*>(
packageManager.HomeRepository());
PackageComparator comparator(systemRepository, homeRepository);
packages.SortItems(&compare_packages, &comparator);
// print table
TextTable table;
// determine column widths
BString installedColumnTitle("Installed");
BString nameColumnTitle("Name");
BString descriptionColumnTitle("Description");
if (details) {
table.AddColumn("Repository");
table.AddColumn("Name");
table.AddColumn("Version");
table.AddColumn("Arch");
int installedColumnWidth = installedColumnTitle.Length();
int nameColumnWidth = nameColumnTitle.Length();
int descriptionColumnWidth = descriptionColumnTitle.Length();
int32 packageCount = packages.CountItems();
for (int32 i = 0; i < packageCount; i++) {
BSolverPackage* package = packages.ItemAt(i);
int32 packageCount = packages.CountItems();
for (int32 i = 0; i < packageCount; i++) {
BSolverPackage* package = packages.ItemAt(i);
nameColumnWidth = std::max(nameColumnWidth,
(int)package->Name().Length());
descriptionColumnWidth = std::max(descriptionColumnWidth,
(int)package->Info().Summary().Length());
BString repository = "";
if (package->Repository() == systemRepository)
repository = "<system>";
else if (package->Repository() == homeRepository)
repository = "<home>";
else
repository = package->Repository()->Name();
table.SetTextAt(i, 0, repository);
table.SetTextAt(i, 1, package->Name());
table.SetTextAt(i, 2, package->Version().ToString());
table.SetTextAt(i, 3, package->Info().ArchitectureName());
}
} else {
table.AddColumn("Status");
table.AddColumn("Name");
table.AddColumn("Description", B_ALIGN_LEFT, true);
int32 packageCount = packages.CountItems();
for (int32 i = 0; i < packageCount;) {
// find the next group of equally named packages
int32 groupStart = i;
std::set<BPackageVersion> versions;
BSolverPackage* systemPackage = NULL;
BSolverPackage* homePackage = NULL;
while (i < packageCount) {
BSolverPackage* package = packages.ItemAt(i);
if (i > groupStart
&& package->Name() != packages.ItemAt(groupStart)->Name()) {
break;
}
if (package->Repository() == systemRepository)
systemPackage = package;
else if (package->Repository() == homeRepository)
homePackage = package;
else
versions.insert(package->Version());
i++;
}
// add a table row for the group
BString status;
if (systemPackage != NULL) {
status << (versions.find(systemPackage->Version())
!= versions.end() ? 'S' : 's');
}
if (homePackage != NULL) {
status << (versions.find(homePackage->Version())
!= versions.end() ? 'H' : 'h');
}
if (versions.size() > 1)
status << 'v';
int32 rowIndex = table.CountRows();
BSolverPackage* package = packages.ItemAt(groupStart);
table.SetTextAt(rowIndex, 0, status);
table.SetTextAt(rowIndex, 1, package->Name());
table.SetTextAt(rowIndex, 2, package->Info().Summary());
}
}
// print header
BString header;
header.SetToFormat("%-*s %-*s %s",
installedColumnWidth, installedColumnTitle.String(),
nameColumnWidth, nameColumnTitle.String(),
descriptionColumnTitle.String());
printf("%s\n", header.String());
int minLineWidth = header.Length();
int lineWidth = minLineWidth + descriptionColumnWidth
- descriptionColumnTitle.Length();
int terminalWidth = get_terminal_width();
if (lineWidth > terminalWidth) {
// truncate description
int actualLineWidth = std::max(minLineWidth, terminalWidth);
descriptionColumnWidth -= lineWidth - actualLineWidth;
lineWidth = actualLineWidth;
}
header.SetTo('-', lineWidth);
printf("%s\n", header.String());
// print packages
for (int32 i = 0; i < packageCount; i++) {
BSolverPackage* package = packages.ItemAt(i);
const char* installed = "";
if (package->Repository() == static_cast<const BSolverRepository*>(
packageManager.SystemRepository()))
installed = "system";
else if (package->Repository() == static_cast<const BSolverRepository*>(
packageManager.HomeRepository()))
installed = "home";
printf("%-*s %-*s %-*.*s\n",
installedColumnWidth, installed,
nameColumnWidth, package->Name().String(),
descriptionColumnWidth, descriptionColumnWidth,
package->Info().Summary().String());
}
table.Print(get_terminal_width());
return 0;
}