Commit 42803035 authored by jan.koester's avatar jan.koester
Browse files

extend fresh

parent 78e7d779
Loading
Loading
Loading
Loading
+83 −45
Changes for commands/filesystem/cat.cpp: 83 added lines, 45 removed lines.
Original line number Diff line number Diff line
/*******************************************************************************
Copyright (c) 2022, Jan Koester jan.koester@gmx.net
Copyright (c) 2026, Jan Koester jan.koester@gmx.net
All rights reserved.

Redistribution and use in source and binary forms, with or without
@@ -25,74 +25,112 @@ ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*******************************************************************************/


#include <iostream>
#include <fstream>
#include <string>
#include <vector>
#include <cstring>
#include <cerrno>

#include <unistd.h>
#include <fcntl.h>

#include "shell.h"
#include "../text/textutil.h"

namespace fresh { namespace cmd {

    // cat [-A] [-b] [-e] [-E] [-n] [-s] [-t] [-T] [-u] [-v] [FILE...]
    class CatCommand : public ShellCommand {
    public:
        CatCommand():ShellCommand("cat"){}

        static int catStream(std::istream &in,bool numberLines){
            std::string line;
            long n=1;
            while(std::getline(in,line)){
                if(numberLines)
                    std::cout << n++ << "\t";
                std::cout << line << "\n";
            }
            return 0;
        }

        int runCommand(int argc,const char **argv){
            optind=0;
            opterr=0;

            bool numberLines=false;
            int opt;
            while((opt=getopt(argc,const_cast<char *const *>(argv),"n"))!=-1){
                switch(opt){
                    case 'n':
                        numberLines=true;
                        break;
                    default:
                        std::cerr << "cat: invalid option" << std::endl;
                        return 1;
            bool number=false,numberNonBlank=false,squeeze=false,showEnds=false,showTabs=false,showNonPrint=false;
            std::vector<std::string> files;
            bool endOfOpts=false;
            for(int i=1;i<argc;++i){
                std::string a=argv[i];
                if(endOfOpts || a.size()<2 || a[0]!='-'){ files.push_back(a); continue; }
                if(a=="--"){ endOfOpts=true; continue; }
                if(a=="--number") number=true;
                else if(a=="--number-nonblank") numberNonBlank=true;
                else if(a=="--squeeze-blank") squeeze=true;
                else if(a=="--show-ends") showEnds=true;
                else if(a=="--show-tabs") showTabs=true;
                else if(a=="--show-nonprinting") showNonPrint=true;
                else if(a=="--show-all"){ showNonPrint=showEnds=showTabs=true; }
                else if(a.compare(0,2,"--")==0){ std::cerr << "cat: unrecognized option '" << a << "'" << std::endl; return 1; }
                else for(size_t k=1;k<a.size();++k){
                    switch(a[k]){
                        case 'n': number=true; break;
                        case 'b': numberNonBlank=true; break;
                        case 's': squeeze=true; break;
                        case 'E': showEnds=true; break;
                        case 'T': showTabs=true; break;
                        case 'v': showNonPrint=true; break;
                        case 'A': showNonPrint=showEnds=showTabs=true; break;
                        case 'e': showNonPrint=showEnds=true; break;
                        case 't': showNonPrint=showTabs=true; break;
                        case 'u': break;
                        default: std::cerr << "cat: invalid option -- '" << a[k] << "'" << std::endl; return 1;
                    }
                }

            int failed=0;

            if(optind>=argc){
                catStream(std::cin,numberLines);
                return 0;
            }

            for(int i=optind; i<argc; ++i){
                std::string path=argv[i];
                if(path=="-"){
                    catStream(std::cin,numberLines);
            if(numberNonBlank) number=false;
            bool plain=!number && !numberNonBlank && !squeeze && !showEnds && !showTabs && !showNonPrint;
            if(files.empty()) files.push_back("-");

            int status=0;
            text::Out out;
            long long lineNo=0;
            bool atLineStart=true;
            int blankRun=0;
            for(auto &f: files){
                int fd=f=="-" ? 0 : open(f.c_str(),O_RDONLY);
                if(fd<0){
                    out.flush();
                    std::cerr << "cat: " << f << ": " << strerror(errno) << std::endl;
                    status=1;
                    continue;
                }

                std::ifstream in(path.c_str());
                if(!in.is_open()){
                    std::cerr << "cat: " << path << ": " << strerror(errno) << std::endl;
                    failed=1;
                char buf[65536];
                for(;;){
                    ssize_t n=::read(fd,buf,sizeof buf);
                    if(n<0){ if(errno==EINTR) continue; out.flush(); std::cerr << "cat: " << f << ": " << strerror(errno) << std::endl; status=1; break; }
                    if(n==0) break;
                    if(plain){ out.write(buf,(size_t)n); continue; }
                    for(ssize_t k=0;k<n;++k){
                        unsigned char c=(unsigned char)buf[k];
                        if(atLineStart){
                            bool blank=c=='\n';
                            if(blank){
                                if(squeeze && ++blankRun>1) continue;
                            }else blankRun=0;
                            if(number || (numberNonBlank && !blank)){
                                std::string num=std::to_string(++lineNo);
                                out << std::string(num.size()<6 ? 6-num.size() : 0,' ') << num << '\t';
                            }
                            atLineStart=false;
                        }
                        if(c=='\n'){
                            if(showEnds) out << '$';
                            out << '\n';
                            atLineStart=true;
                            continue;
                        }
                catStream(in,numberLines);
                        if(c=='\t' && showTabs){ out << "^I"; continue; }
                        if(showNonPrint && c!='\t'){
                            if(c>=128){ out << "M-"; c-=128; }
                            if(c<32){ out << '^' << (char)(c+64); continue; }
                            if(c==127){ out << "^?"; continue; }
                        }

            return failed;
                        out << (char)c;
                    }
                }
                if(fd!=0) close(fd);
            }
            return status;
        }
    };

+85 −44
Changes for commands/text/head.cpp: 85 added lines, 44 removed lines.
Original line number Diff line number Diff line
@@ -25,72 +25,113 @@ ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*******************************************************************************/


#include <iostream>
#include <fstream>
#include <string>
#include <vector>
#include <cstdlib>
#include <cctype>

#include "shell.h"
#include "textutil.h"

namespace fresh { namespace cmd {

    // "head [-n NUM] [FILE...]": first NUM lines (default 10) of each file,
    // or of stdin with no FILE. A ",NAME" header line precedes each file's
    // output only when more than one FILE is given, matching the real tool.
    // head [-n [-]N] [-c [-]N] [-q|-v] [FILE...]: the first N lines/bytes,
    // or with a leading "-" all but the last N; "head -5" is "-n 5".
    class HeadCommand : public ShellCommand {
    public:
        HeadCommand():ShellCommand("head"){}

        static void run(std::istream &in,long count){
            std::string line;
            long n=0;
            while(n<count && std::getline(in,line)){
                std::cout << line << "\n";
                ++n;
            }
        }

        int runCommand(int argc,const char **argv){
            long count=10;
            int i=1;
            for(; i<argc; ++i){
            long long count=10;
            bool bytes=false,allBut=false;
            int headers=0; // 0 auto, 1 always (-v), -1 never (-q)
            std::vector<std::string> files;
            bool endOfOpts=false;
            auto setCount=[&](std::string v,bool isBytes)->bool{
                bytes=isBytes;
                allBut=!v.empty() && v[0]=='-';
                if(allBut || (!v.empty() && v[0]=='+')) v=v.substr(1);
                if(!text::parseSize(v,count) || count<0){
                    std::cerr << "head: invalid number of " << (isBytes ? "bytes" : "lines") << ": '" << v << "'" << std::endl;
                    return false;
                }
                return true;
            };
            for(int i=1;i<argc;++i){
                std::string a=argv[i];
                if(a=="-n" && i+1<argc){
                    count=atol(argv[++i]);
                }else if(a.compare(0,2,"-n")==0 && a.size()>2){
                    count=atol(a.c_str()+2);
                }else if(!a.empty() && a[0]=='-' && a.size()>1 && isdigit((unsigned char)a[1])){
                    count=atol(a.c_str()+1);
                }else{
                if(endOfOpts || a.size()<2 || a[0]!='-'){ files.push_back(a); continue; }
                if(a=="--"){ endOfOpts=true; continue; }
                if(a.compare(0,8,"--lines=")==0){ if(!setCount(a.substr(8),false)) return 1; continue; }
                if(a.compare(0,8,"--bytes=")==0){ if(!setCount(a.substr(8),true)) return 1; continue; }
                if(a=="--lines" || a=="--bytes"){
                    if(i+1>=argc){ std::cerr << "head: option '" << a << "' requires an argument" << std::endl; return 1; }
                    if(!setCount(argv[++i],a=="--bytes")) return 1;
                    continue;
                }
                if(a=="--quiet" || a=="--silent"){ headers=-1; continue; }
                if(a=="--verbose"){ headers=1; continue; }
                if(isdigit((unsigned char)a[1])){ if(!setCount(a.substr(1),false)) return 1; continue; } // -N
                for(size_t k=1;k<a.size();++k){
                    char c=a[k];
                    if(c=='n' || c=='c'){
                        std::string v=a.substr(k+1);
                        if(v.empty()){
                            if(i+1>=argc){ std::cerr << "head: option requires an argument -- '" << c << "'" << std::endl; return 1; }
                            v=argv[++i];
                        }
                        if(!setCount(v,c=='c')) return 1;
                        break;
                    }
                    if(c=='q') headers=-1;
                    else if(c=='v') headers=1;
                    else{ std::cerr << "head: invalid option -- '" << c << "'" << std::endl; return 1; }
                }

            std::vector<std::string> files;
            for(; i<argc; ++i)
                files.push_back(argv[i]);

            if(files.empty()){
                run(std::cin,count);
                return 0;
            }
            if(files.empty()) files.push_back("-");
            bool showHeaders=headers==1 || (headers==0 && files.size()>1);

            bool multi=files.size()>1;
            bool ok=true;
            for(size_t k=0; k<files.size(); ++k){
                if(multi)
                    std::cout << (k>0 ? "\n" : "") << "==> " << files[k] << " <==" << std::endl;
                std::ifstream f(files[k].c_str());
                if(!f.is_open()){
                    std::cerr << "head: " << files[k] << ": No such file or directory" << std::endl;
                    ok=false;
            text::Out out;
            int status=0;
            bool first=true;
            for(auto &f: files){
                std::string data;
                if(!text::readInput(f,data)){
                    out.flush();
                    std::cerr << "head: cannot open '" << f << "' for reading: " << strerror(errno) << std::endl;
                    status=1;
                    continue;
                }
                run(f,count);
                if(showHeaders){
                    out << (first ? "" : "\n") << "==> " << (f=="-" ? "standard input" : f) << " <==\n";
                }
                first=false;
                size_t end;
                if(bytes){
                    size_t n=(size_t)count;
                    end=allBut ? (data.size()>n ? data.size()-n : 0) : std::min(n,data.size());
                }else if(!allBut){
                    end=0;
                    long long lines=0;
                    while(end<data.size() && lines<count){
                        size_t nl=data.find('\n',end);
                        end=nl==std::string::npos ? data.size() : nl+1;
                        ++lines;
                    }
                }else{
                    // all but the last N lines
                    std::vector<size_t> starts;
                    size_t p=0;
                    while(p<data.size()){
                        starts.push_back(p);
                        size_t nl=data.find('\n',p);
                        p=nl==std::string::npos ? data.size() : nl+1;
                    }
                    end=(long long)starts.size()>count ? starts[starts.size()-(size_t)count] : 0;
                    if(count==0) end=data.size();
                }
                out.write(data.data(),end);
            }
            return ok ? 0 : 1;
            return status;
        }
    };

+354 −101

File changed.

Preview size limit exceeded, changes collapsed.

+136 −46
Changes for commands/text/tail.cpp: 136 added lines, 46 removed lines.
Original line number Diff line number Diff line
@@ -25,77 +25,167 @@ ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*******************************************************************************/


#include <iostream>
#include <fstream>
#include <string>
#include <vector>
#include <deque>
#include <cstdlib>
#include <cctype>

#include <sys/stat.h>
#include <poll.h>
#include <signal.h>

#include "shell.h"
#include "textutil.h"

namespace fresh { namespace cmd {

    // "tail [-n NUM] [FILE...]": last NUM lines (default 10) of each file,
    // or of stdin with no FILE. No "-f" follow mode. Reads the whole input
    // and keeps only the trailing NUM lines in a bounded deque -- fine for
    // the log/short-file sizes this is actually used against.
    // tail [-n [+]N] [-c [+]N] [-f|-F] [-s SECS] [--pid=PID] [-q|-v] [FILE...]:
    // the last N lines/bytes, or from line/byte N on with "+N"; -f keeps
    // following the file(s) as they grow.
    class TailCommand : public ShellCommand {
    public:
        TailCommand():ShellCommand("tail"){}

        static void run(std::istream &in,long count){
            std::deque<std::string> buf;
            std::string line;
            while(std::getline(in,line)){
                buf.push_back(line);
                if((long)buf.size()>count)
                    buf.pop_front();
        static size_t startOf(const std::string &data,long long count,bool bytes,bool fromStart){
            if(bytes){
                if(fromStart) return count>0 ? std::min((size_t)(count-1),data.size()) : 0;
                return data.size()>(size_t)count ? data.size()-(size_t)count : 0;
            }
            if(fromStart){
                size_t p=0;
                for(long long l=1;l<count && p<data.size();++l){
                    size_t nl=data.find('\n',p);
                    p=nl==std::string::npos ? data.size() : nl+1;
                }
            for(size_t i=0;i<buf.size();++i)
                std::cout << buf[i] << "\n";
                return p;
            }
            if(count==0) return data.size();
            // back from the end: skip a final newline, then count N newlines
            size_t p=data.size();
            if(p && data[p-1]=='\n') --p;
            long long seen=0;
            while(p>0){
                if(data[p-1]=='\n' && ++seen==count) break;
                --p;
            }
            return p;
        }

        int runCommand(int argc,const char **argv){
            long count=10;
            int i=1;
            for(; i<argc; ++i){
            long long count=10;
            bool bytes=false,fromStart=false,follow=false;
            double sleepSecs=1.0;
            long pid=0;
            int headers=0;
            std::vector<std::string> files;
            bool endOfOpts=false;
            auto setCount=[&](std::string v,bool isBytes)->bool{
                bytes=isBytes;
                fromStart=!v.empty() && v[0]=='+';
                if(!v.empty() && (v[0]=='+' || v[0]=='-')) v=v.substr(1);
                if(!text::parseSize(v,count) || count<0){
                    std::cerr << "tail: invalid number of " << (isBytes ? "bytes" : "lines") << ": '" << v << "'" << std::endl;
                    return false;
                }
                return true;
            };
            for(int i=1;i<argc;++i){
                std::string a=argv[i];
                if(a=="-n" && i+1<argc){
                    count=atol(argv[++i]);
                }else if(a.compare(0,2,"-n")==0 && a.size()>2){
                    count=atol(a.c_str()+2);
                }else if(!a.empty() && a[0]=='-' && a.size()>1 && isdigit((unsigned char)a[1])){
                    count=atol(a.c_str()+1);
                }else{
                if(!endOfOpts && a.size()>1 && a[0]=='+' && isdigit((unsigned char)a[1]) && files.empty()){
                    if(!setCount(a,false)) return 1; // obsolete "+N"
                    continue;
                }
                if(endOfOpts || a.size()<2 || a[0]!='-'){ files.push_back(a); continue; }
                if(a=="--"){ endOfOpts=true; continue; }
                if(a.compare(0,2,"--")==0){
                    std::string n=a.substr(2),v;
                    size_t eq=n.find('=');
                    bool hasV=eq!=std::string::npos;
                    if(hasV){ v=n.substr(eq+1); n=n.substr(0,eq); }
                    auto need=[&]()->bool{
                        if(hasV) return true;
                        if(i+1>=argc){ std::cerr << "tail: option '--" << n << "' requires an argument" << std::endl; return false; }
                        v=argv[++i];
                        return true;
                    };
                    if(n=="lines" || n=="bytes"){ if(!need() || !setCount(v,n=="bytes")) return 1; }
                    else if(n=="follow" || n=="retry") follow=follow || n=="follow";
                    else if(n=="sleep-interval"){ if(!need()) return 1; sleepSecs=atof(v.c_str()); }
                    else if(n=="pid"){ if(!need()) return 1; pid=atol(v.c_str()); }
                    else if(n=="quiet" || n=="silent") headers=-1;
                    else if(n=="verbose") headers=1;
                    else{ std::cerr << "tail: unrecognized option '" << a << "'" << std::endl; return 1; }
                    continue;
                }
                if(isdigit((unsigned char)a[1])){ if(!setCount(a.substr(1),false)) return 1; continue; } // -N
                for(size_t k=1;k<a.size();++k){
                    char c=a[k];
                    if(c=='n' || c=='c' || c=='s'){
                        std::string v=a.substr(k+1);
                        if(v.empty()){
                            if(i+1>=argc){ std::cerr << "tail: option requires an argument -- '" << c << "'" << std::endl; return 1; }
                            v=argv[++i];
                        }
                        if(c=='s') sleepSecs=atof(v.c_str());
                        else if(!setCount(v,c=='c')) return 1;
                        break;
                    }
                    if(c=='f' || c=='F') follow=true;
                    else if(c=='q') headers=-1;
                    else if(c=='v') headers=1;
                    else{ std::cerr << "tail: invalid option -- '" << c << "'" << std::endl; return 1; }
                }

            std::vector<std::string> files;
            for(; i<argc; ++i)
                files.push_back(argv[i]);

            if(files.empty()){
                run(std::cin,count);
                return 0;
            }
            if(files.empty()) files.push_back("-");
            bool showHeaders=headers==1 || (headers==0 && files.size()>1);

            bool multi=files.size()>1;
            bool ok=true;
            for(size_t k=0; k<files.size(); ++k){
                if(multi)
                    std::cout << (k>0 ? "\n" : "") << "==> " << files[k] << " <==" << std::endl;
                std::ifstream f(files[k].c_str());
                if(!f.is_open()){
                    std::cerr << "tail: " << files[k] << ": No such file or directory" << std::endl;
                    ok=false;
            text::Out out;
            int status=0;
            bool first=true;
            std::vector<std::pair<std::string,off_t>> followed;
            for(auto &f: files){
                std::string data;
                if(!text::readInput(f,data)){
                    out.flush();
                    std::cerr << "tail: cannot open '" << f << "' for reading: " << strerror(errno) << std::endl;
                    status=1;
                    continue;
                }
                run(f,count);
                if(showHeaders) out << (first ? "" : "\n") << "==> " << (f=="-" ? "standard input" : f) << " <==\n";
                first=false;
                size_t s=startOf(data,count,bytes,fromStart);
                out.write(data.data()+s,data.size()-s);
                if(follow && f!="-") followed.push_back({f,(off_t)data.size()});
            }
            out.flush();
            if(!follow || followed.empty()) return status;

            std::string last=followed.back().first;
            for(;;){
                if(pid>0 && kill((pid_t)pid,0)!=0 && errno==ESRCH) return status;
                for(auto &fp: followed){
                    struct stat st;
                    if(stat(fp.first.c_str(),&st)!=0) continue;
                    if(st.st_size<fp.second){
                        std::cerr << "tail: " << fp.first << ": file truncated" << std::endl;
                        fp.second=0;
                    }
                    if(st.st_size>fp.second){
                        int fd=open(fp.first.c_str(),O_RDONLY);
                        if(fd<0) continue;
                        lseek(fd,fp.second,SEEK_SET);
                        std::string more;
                        text::readFd(fd,more);
                        close(fd);
                        if(followed.size()>1 && last!=fp.first) out << "\n==> " << fp.first << " <==\n";
                        last=fp.first;
                        out << more;
                        out.flush();
                        fp.second+=(off_t)more.size();
                    }
                }
                poll(nullptr,0,(int)(sleepSecs*1000));
            }
            return ok ? 0 : 1;
        }
    };

+131 −0

File added.

Preview size limit exceeded, changes collapsed.

Loading