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:
@@ -17,6 +17,7 @@ BinCommand pkgman :
|
||||
JobStateListener.cpp
|
||||
PackageManager.cpp
|
||||
pkgman.cpp
|
||||
TextTable.cpp
|
||||
:
|
||||
package be
|
||||
$(TARGET_LIBSUPC++) $(TARGET_LIBSTDC++)
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user