//-------------------------------------------------------------------
// Projekt: NDir, Nice Directory
// Datei: dir.cpp -- Main program file
//------------------------------------------------------------------
// Autor: Michael Weers
// Letzte Änderung: 2000-06-17
// Version: 0.8.3
// Rechner: i386+, GNU Linux
// Compiler: GNU CC 2.91.66
//-------------------------------------------------------------------
#include
#include
#include
#include
#include
#include
#include
#include
#ifdef __ultrix__
#include "Ultrix-Compatibility.hpp"
#endif
#include "DirectoryList.hpp"
#include "StringUtils.hpp" // for icompare()
#include "ColorSetup.hpp"
#include "text/format.h"
using namespace std;
struct Options {
bool show_info;
bool show_locale_info;
bool show_fileinfo;
enum FileFilter_t { NON_HIDDEN, ALMOST_ALL, ALL };
FileFilter_t filter; // show hidden files?
bool format_long; // long output format
bool more_compact; // *Allow* view to do a more compact output than by default
bool show_groups;
enum ShowTime_t { STATUS_CHANGE, ACCESS, MODIFICATION };
ShowTime_t show_time;
bool dirs_as_files; // list directories as files instead of their contents
bool recursive;
// bool numeric_IDs= false;
CompareFunction_t compareFunction;
bool reverse;
enum Colorization_t { NEVER, IF_TERMINAL, ALWAYS };
Colorization_t colorization;
bool utc;
Options(): show_info( false),
show_locale_info( false),
show_fileinfo( false),
filter( NON_HIDDEN),
format_long( false),
more_compact( false),
show_groups( false),
show_time( MODIFICATION),
dirs_as_files( false),
recursive( false),
compareFunction ( &NamePrecedence ),
reverse( false),
colorization( IF_TERMINAL),
utc( false)
{}
};
#include
#include
class Dir_view {
/** The stream the output is written to*/
ostream& out;
/** The DirectoryList to be printed */
DirectoryList* DL;
/** The options, mainly for output. Those options not affecting
the output are ignored of course */
const Options& options;
/** The color setup */
const ColorSetup& colors;
int width; // The terminal width in characters (0 if unspecified)
bool is_terminal;
public:
Dir_view( DirectoryList* DL, const Options&, const ColorSetup&);
int getWidth() { return width; }
bool isTerminal() { return is_terminal; }
void write();
protected:
int getLineWidth();
void writeFileInfoList();
void writeFileInfo( const UnixFile& );
void writeListLong();
void writeListWide();
string longFormat( UnixFile& file);
public:
static string StatisticsLine( long int, long int files, long long int size);
};
Dir_view::Dir_view( DirectoryList* DL, const Options& o, const ColorSetup& s)
: out( cout), options( o), colors(s) {
this->DL= DL;
width= getLineWidth();
is_terminal= (isatty( STDOUT_FILENO) != 0);
}
void Dir_view::write() {
if ( !DL->directoryExists())
out << "\t" << text::clean( DL->getDirectory().getCanonicalPath() )
<< ": No such file or directory." << endl;
else if ( options.show_fileinfo){
writeFileInfoList();
}
else {
out << "\t" << "Directory: " + text::clean( DL->getDirectory().getCanonicalPath()) << "\n";
if ( !DL->isReadable())
out << "\t" << "Unable to read directory." << endl;
else if ( DL->isEmpty() ) {
out << "\t" << "No matching file found." << endl;
}
else {
if (options.format_long) writeListLong();
else writeListWide();
out << "\t";
out << StatisticsLine( DL->getAllFilesCount(),
DL->getRegFilesCount(),
DL->getRegFilesSize() ) << endl;
}
}
}
int Dir_view::getLineWidth() {
// Attempt 1: Determine Terminal line width from terminal itself.
// Code taken from GNU ls
#ifdef TIOCGWINSZ
{
struct winsize ws;
if (ioctl (STDOUT_FILENO, TIOCGWINSZ, &ws) != -1 && ws.ws_col != 0)
return ws.ws_col;
}
#endif
// Attempt 2: Determine line width from Enviromnent variable
char* value= getenv( "COLUMNS");
if (value != NULL) {
int i;
int r= sscanf( value, "%d", &i);
if (r == 1 && i > 0)
return i;
}
// Attempt 3: default value
return 80;
}
void Dir_view::writeFileInfoList() {
out << endl;
for ( vector::iterator pos= DL->file_list.begin();
pos != DL->file_list.end();
++ pos )
{
writeFileInfo( UnixFile( (**pos).getFullName(), UnixFile::AS_IS, true ) );
}
}
void Dir_view::writeFileInfo( const UnixFile& file) {
out << "Name: " << text::clean( file.getName()) << endl
<< "Type: " ;
switch (file.getType()) {
case UnixFile::REGFILE: out << "Regular file"; break;
case UnixFile::DIRECTORY: out << "Directory"; break;
case UnixFile::SYMLINK: out << "Symbolic link";
if ( file.isNavigable()) out << " to a directory";
if ( file.isBrokenLink()) out << " (broken)";
break;
case UnixFile::BLOCK_DEVICE: out << "Block Device"; break;
case UnixFile::CHAR_DEVICE: out << "Character Device"; break;
case UnixFile::PIPE: out << "Named Pipe"; break;
case UnixFile::SOCKET: out << "Socket";
}
out << endl
<< "Path: " << text::clean( file.getFullName()) << endl;
if (file.isFile()) {
out << "Size: "
#ifdef NO_THOUSANDS_GROUPING
<< text::format("%lld",file.getSize())
#else
<< text::format("%'lld",file.getSize())
#endif
<< " bytes" << endl ;
}
else if (file.isLink()) {
string target;
string link=file.getLinkName();
if (link.size()>0 && link[0]=='/') target= link;
else target=
UnixFile::getCanonicalPath( UnixFile::getAbsolutePath(
file.getParent() + UnixFile::separator + link ) );
out << "Link target: " << text::clean(target) << endl;
}
out << "Type and permissions: " << file.getAttributeString() << endl
<< "Owner: " << text::clean( file.getOwner( true) )
<< " (" << text::clean(file.getOwnerName()) << ")"
<< " Group: " << text::clean(file.getGroup( true)) << endl
<< "Last modified: " << file.getLastModified().toString( options.utc) << endl
<< "Last accessed: " << file.getLastAccessed().toString( options.utc) << endl
<< "Last Status change: " << file.getLastStatusChanged().toString( options.utc) << endl
<< endl;
}
void Dir_view::writeListWide() {
vector::iterator pos;
int name_length= 0; // find out longest file name
for (pos= DL->file_list.begin(); pos != DL->file_list.end(); ++pos) {
int l= (*pos)->getName().size();
if (name_length < l) name_length= l;
}
const int spacing= 3;
int lineWidth= getLineWidth(); // determie terminal width...
int colWidth= name_length + spacing;
int cols= lineWidth / colWidth;
if (cols < 1) cols= 1;
int col= 0; // # of current column (startig from 0)
for (pos= DL->file_list.begin(); pos != DL->file_list.end(); ++pos) {
UnixFile* f= (*pos);
if (f != NULL) {
string::size_type entry_length= f->getName().size();
out << colors.formatFilename( *f);
if (f->isNavigable()) {
out << '/';
entry_length++;
}
if ( col >= cols-1) { // letzte Spalte
out << endl;
col= 0;
}
else {
string fill( colWidth - entry_length, ' ');
out << fill;
col++;
}
}
} // for
if (col != 0) out << endl;
}
void Dir_view::writeListLong() {
vector::iterator pos;
for (pos= DL->file_list.begin(); pos != DL->file_list.end(); ++pos) {
UnixFile* f= (*pos);
if (f != NULL) {
out << longFormat( *f) << "\n";
}
}
}
string Dir_view::longFormat( UnixFile& file) {
string s;
s.reserve( 160);
s.append( file.getAttributeString()).append( options.more_compact ? " " : " ");
s += align( text::clean( file.getOwner( true)) , 9, LEFT);
if (options.show_groups) {
s += align( text::clean( file.getGroup( true)) , 9, LEFT);
}
Date date( 0);
switch ( options.show_time) {
case Options::STATUS_CHANGE: date= file.getLastStatusChanged(); break;
case Options::ACCESS: date= file.getLastAccessed(); break;
default: date= file.getLastModified();
}
s.append( date.toString( "%Y-%m-%d %H:%M", options.utc) );
if ( file.isFile() ) {
// format string: number: precision, ll: use long long int,
// d: print integers as decimal numbers, ': use thousands separator
#ifdef NO_THOUSANDS_GROUPING
s += text::format( "%16lld", file.getSize() ) + " ";
#else
s += text::format( "%'16lld", file.getSize() ) + " ";
#endif
}
else if ( file.isNavigable() )
if (file.isLink()) // symlink to a directory
s += align( "[-> Dir]", 18, CENTER);
else
s += align( "[Dir]", 18, CENTER);
else
s.append( 18, ' ');
// determine name string length
string::size_type name_length= file.getName().size();
if (file.isLink()) name_length += 4 + file.getLinkName().size(); // 4 chars " -> "
// now: if we use a terminal and the name string is too long, put it
// in the next line, right justified.
if (isTerminal() && s.size() + name_length > getWidth()) {
s += "\n";
// compiler warns about signed-unsigned-comp. but this is safe.
// add space
if (getWidth() > name_length) s.append( getWidth() - name_length, ' ');
}
s += colors.formatFilename( file);
if (file.isLink()) s.append(" -> ").append( file.getLinkName());
return s;
}
string Dir_view::StatisticsLine( long int all_files, long int files, long long int size) {
#ifdef NO_THOUSANDS_GROUPING
string s = text::format( "%ld", all_files) + " entries, "
+ text::format( "%ld", files ) + " regular files with "
+ text::format( "%lld", size ) + " bytes total size";
#else
string s = text::format( "%'ld", all_files) + " entries, "
+ text::format( "%'ld", files ) + " regular files with "
+ text::format( "%'lld", size ) + " bytes total size";
#endif
return s;
}
class Dir_main {
string progname;
int argc;
char** argv;
vector file_args;
vector L; // List of directories
Options options;
ColorSetup colorsetup;
// APrecedence* precedence;
long long int summary_filesize;
long int summary_filecount, summary_entrycount, summary_dirs_listed;
public:
int returncode;
Dir_main( int argc, char* argv[] );
~Dir_main();
protected:
void handle_file_arg( const string& arg);
/** Output a directory, eventually recurring into subdirectories */
void handle_directory( DirectoryList& DL);
void handle_argument( const string& arg);
public:
void run();
}; // class Dir_main
Dir_main::Dir_main( int argc, char* argv[] ): returncode( 0) {
progname= argv[0];
this->argc= argc;
this->argv= argv;
summary_entrycount=0; summary_filecount=0; summary_filesize=0; summary_dirs_listed=0;
}
Dir_main::~Dir_main() {
for( vector::size_type i= 0; i < L.size(); ++i) {
delete L[i]; L[i]= NULL;
}
}
void Dir_main::handle_file_arg( const string& arg) {
bool handeled= false; // is f handeled anywhere?
UnixFile* f= new UnixFile( arg);
if ( !f->exists() || ( f->isNavigable() && !options.dirs_as_files )) {
// create new DirectoryList and list it.
// ändern: nichtexistente Dateien schon
// hier fehlerbehandeln
DirectoryList* DL= new DirectoryList( f );
L.push_back( DL);
handeled= true;
return;
}
else {
// f is an ordinary file (maybe a directory
// if option 'dirs_as_files' is in effect);
// search DirectoryList where it belongs to...
vector::iterator dir;
for (dir= L.begin(); dir != L.end(); ++dir) {
DirectoryList* DL = (*dir);
UnixFile parent( f->getParent());
if ( (*dir)->getDirectory().equals( parent) ) {
// ...found!
DL->insert_unique( f);
handeled= true;
return;
}
}
if (!handeled) { // ... not found, make new DirectoryList
// appropriate to the file
DirectoryList* DL= new DirectoryList( new UnixFile( f->getParent()), false );
DL->insert( f);
L.push_back( DL);
handeled= true;
}
} // if-else
}
/** Output a directory, eventually recurring into subdirectories */
void Dir_main::handle_directory( DirectoryList& DL) {
DL.list( ! (options.filter == Options::NON_HIDDEN), options.filter == Options::ALL );
DL.syncStatistics();
DL.setCompareFunction( options.compareFunction);
DL.reversed_order= options.reverse;
DL.ReOrder(); // sort directory
if (&DL != L.front()) cout << endl; // A separating line between directories
Dir_view v( &DL, options, colorsetup);
v.write();
// now for the overall statistics:
if (DL.isReadable()) {
summary_entrycount += DL.getAllFilesCount();
summary_filecount += DL.getRegFilesCount();
summary_filesize += DL.getRegFilesSize();
summary_dirs_listed++;
}
if (options.recursive) {
vector::iterator f;
for ( f= DL.file_list.begin(); f != DL.file_list.end(); ++f) {
if ( (*f)->isSubNavigable() ) { // prevents recurring into links that
// point to parent directories.
DirectoryList DL_sub( new UnixFile( (**f).getPath()));
DL_sub.setCompareFunction( options.compareFunction);
handle_directory( DL_sub);
}
}
}
}
void Dir_main::handle_argument( const string& arg) {
if (arg.size() > 0 && arg[0]=='-') { // option
if (arg=="-l") options.format_long= true;
else if (arg=="-a") options.filter= Options::ALL;
else if (arg=="-A") options.filter= Options::ALMOST_ALL;
else if (arg=="-?" || arg=="--help") options.show_info= true;
else if (arg=="-?l") options.show_locale_info= true;
else if (arg=="-d") options.dirs_as_files= true;
else if (arg=="-g") options.show_groups= options.more_compact= true;
else if (arg=="-R") options.recursive= true;
else if (arg=="-t") options.compareFunction= ModDatePrecedence;
else if (arg=="-u") options.show_time= Options::ACCESS;
else if (arg=="-c") options.show_time= Options::STATUS_CHANGE;
else if (arg=="-S") options.compareFunction= FileSizePrecedence;
else if (arg=="-U") options.compareFunction= 0;
else if (arg=="-X") options.compareFunction= ExtensionPrecedence;
else if (arg=="-r") options.reverse= true;
else if (arg=="-cn") options.colorization= Options::NEVER;
else if (arg=="-ca") options.colorization= Options::ALWAYS;
else if (arg=="--info") options.show_fileinfo= true;
else if (arg=="--utc") options.utc= true;
// new options here ...
else {
cerr << "NDir: Unrecognized option '" << arg << "'." << endl
<< "Aborting." << endl;
returncode= 1;
return;
}
}
else
file_args.push_back( arg);
}
void Dir_main::run() {
// parsing arguments and set options
// if ( progname.size() >= 2 && progname.compare( progname.size()-2, 2, "lv")==0 )
if (progname.substr( 0, 2)=="lv") {
options.format_long= true;
}
// char* options_from_env= getenv("NDIR_OPTIONS");
// if (options_from_env!=NULL) {
// StringTokenizer st( options_from_env, " \t");
// while (st.hasMoreTokens()) handle_option( st.getNextToken());
// }
for ( int i=1; i < argc; ++i) {
handle_argument( argv[i]);
if (returncode!=0) break;
}
// now "normalize" some options...
if (options.compareFunction==ModDatePrecedence) { // if -t was given,
if (options.show_time==Options::ACCESS) // and -s, then sort according to atime.
options.compareFunction= AccessDatePrecedence;
if (options.show_time==Options::STATUS_CHANGE)
options.compareFunction= StatusChangeDatePrecedence;
}
if (options.show_fileinfo) {
options.dirs_as_files= true;
}
switch (options.colorization) {
case Options::NEVER: colorsetup.do_colorization= false; break;
case Options::IF_TERMINAL: colorsetup.do_colorization= (isatty( STDOUT_FILENO) != 0); break;
case Options::ALWAYS: colorsetup.do_colorization= true; break;
}
for (vector::iterator i= file_args.begin(); i != file_args.end(); ++i)
handle_file_arg( *i);
// Now the program's main functions
if (returncode!=0) { // nothing...
}
else if (options.show_info) {
cout << endl
<< " NDir -- Nice Directory. Version 0.8.7" << endl
<< " Copyright (c) 1997-2003 Michael Weers." << endl
<< endl
<< " Displays contents of directories." << endl
<< endl
<< " Usage: ndir / lw / lv " << endl
<< " lw and lv are alternative names for NDir. Use lv for a detailed listing." << endl
<< " See the manual page (man ndir) for details." << endl
<< endl;
}
else if (options.show_locale_info) {
char* lc_collate= setlocale( LC_COLLATE, NULL);
if (lc_collate != NULL)
cout << " NDir uses string comparison conventions for locale: "
<< lc_collate << endl
<< " Latin characters are compared case-insensitive: "
<< ((icompare( "ax", "Ay") < 0) ? "Yes" : "No") << endl;
char* lc_numeric= setlocale( LC_NUMERIC, NULL);
if (lc_numeric != NULL)
cout << " NDir uses number display conventions for locale: "
<< lc_numeric << endl;
cout << endl;
}
else {
// now the usual -- print the directories
if (L.empty()) { // Special case: no DirectoryList created yet.
// create one with current directory
handle_file_arg( ".");
}
// generate output.
// note: recursive walk through directory tree is done within
// handle_directroy()
vector::iterator d;
for (d= L.begin(); d != L.end(); ++d) {
handle_directory( **d );
}
if ( !options.show_fileinfo && (L.size() > 1 || options.recursive) ) {
cout << endl
<< colorsetup.format("Summary for "+text::format( "%ld", summary_dirs_listed)+" directories:", "01") << endl
<< "\t" << Dir_view::StatisticsLine( summary_entrycount, summary_filecount, summary_filesize) << endl;
}
}
} // run()
int main( int argc, char* argv[]) {
setlocale( LC_ALL, ""); // i18n settings
Dir_main application( argc, argv);
application.run();
return application.returncode;
}
Generated by: mw on nemea on Thu Dec 4 21:20:44 2003, using kdoc 2.0a54. |