Commit 3d59455e authored by jan.koester's avatar jan.koester
Browse files

test

parent 517285a1
Loading
Loading
Loading
Loading
+178 −57
Changes for commands/text/tr.cpp: 178 added lines, 57 removed lines.
Original line number Diff line number Diff line
@@ -29,96 +29,217 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#include <string>
#include <vector>

#include <unistd.h>
#include <cerrno>
#include <cctype>
#include <cstdlib>
#include "shell.h"

namespace fresh { namespace cmd {

    // "tr SET1 SET2" (translate) or "tr -d SET1" (delete): reads stdin,
    // writes stdout. SETs support "a-z"-style ranges and "\n"/"\t"/"\\"
    // escapes; no [:class:]/[=equiv=] classes, no "-s" squeeze, no
    // "-c"/"-C" complement -- covers the common byte-translation/deletion
    // idiom (e.g. "tr A-Z a-z", "tr -d '\r'") without a full POSIX tr.
    // POSIX tr: tr [-c|-C] [-s] [-t] SET1 SET2, tr [-c] -d [-s] SET1 [SET2],
    // tr [-c] -s SET1. Sets take escapes (\\ \a \b \f \n \r \t \v \NNN),
    // ranges a-z, [:class:], [=c=], and in SET2 [c*n] / [c*] (fill).
    class TrCommand : public ShellCommand {
    public:
        TrCommand():ShellCommand("tr"){}

        static std::string expandSet(const std::string &spec){
            std::string out;
            for(size_t i=0;i<spec.size();++i){
                char c=spec[i];
                if(c=='\\' && i+1<spec.size()){
                    char e=spec[++i];
    private:
        // one set; fillAt: index of a "[c*]" element (SET2 only), -1 if none
        static bool expandSet(const std::string &spec,std::vector<unsigned char> &out,
                              long &fillAt,unsigned char &fillChar,std::string &err){
            fillAt=-1;
            size_t i=0,n=spec.size();
            auto readChar=[&](size_t &k)->int{
                if(k>=n) return -1;
                unsigned char c=(unsigned char)spec[k++];
                if(c!='\\' || k>=n) return c;
                unsigned char e=(unsigned char)spec[k++];
                switch(e){
                        case 'n': out+='\n'; break;
                        case 't': out+='\t'; break;
                        case 'r': out+='\r'; break;
                        case '\\': out+='\\'; break;
                        default: out+=e; break;
                    case 'a': return '\a';
                    case 'b': return '\b';
                    case 'f': return '\f';
                    case 'n': return '\n';
                    case 'r': return '\r';
                    case 't': return '\t';
                    case 'v': return '\v';
                    case '\\': return '\\';
                    default:
                        if(e>='0' && e<='7'){
                            int v=e-'0';
                            for(int d=0;d<2 && k<n && spec[k]>='0' && spec[k]<='7';++d) v=v*8+(spec[k++]-'0');
                            return v & 0xff;
                        }
                        return e;
                }
            };
            while(i<n){
                if(spec[i]=='[' && i+1<n && spec[i+1]==':'){
                    size_t e=spec.find(":]",i+2);
                    if(e!=std::string::npos){
                        std::string cls=spec.substr(i+2,e-i-2);
                        int (*fn)(int)=nullptr;
                        if(cls=="alpha") fn=isalpha; else if(cls=="digit") fn=isdigit;
                        else if(cls=="alnum") fn=isalnum; else if(cls=="space") fn=isspace;
                        else if(cls=="upper") fn=isupper; else if(cls=="lower") fn=islower;
                        else if(cls=="punct") fn=ispunct; else if(cls=="xdigit") fn=isxdigit;
                        else if(cls=="cntrl") fn=iscntrl; else if(cls=="print") fn=isprint;
                        else if(cls=="graph") fn=isgraph; else if(cls=="blank") fn=isblank;
                        else { err="invalid character class '"+cls+"'"; return false; }
                        for(int c=0;c<256;++c) if(fn(c)) out.push_back((unsigned char)c);
                        i=e+2;
                        continue;
                    }
                if(i+2<spec.size() && spec[i+1]=='-'){
                    unsigned char lo=(unsigned char)c,hi=(unsigned char)spec[i+2];
                    if(lo<=hi){
                        for(unsigned char v=lo; v<=hi; ++v){
                            out+=(char)v;
                            if(v==255) break;
                }
                        i+=2;
                if(spec[i]=='[' && i+1<n && spec[i+1]=='='){
                    size_t k=i+2;
                    int c=readChar(k);
                    if(c>=0 && k+1<n && spec[k]=='=' && spec[k+1]==']'){
                        out.push_back((unsigned char)c);
                        i=k+2;
                        continue;
                    }
                }
                out+=c;
                if(spec[i]=='['){
                    // [c*n] / [c*]
                    size_t k=i+1;
                    int c=readChar(k);
                    if(c>=0 && k<n && spec[k]=='*'){
                        size_t close=spec.find(']',k+1);
                        if(close!=std::string::npos){
                            std::string cnt=spec.substr(k+1,close-k-1);
                            bool digits=true;
                            for(char d: cnt) if(!isdigit((unsigned char)d)) digits=false;
                            if(digits){
                                long times=cnt.empty() ? 0 : strtol(cnt.c_str(),nullptr,cnt[0]=='0' ? 8 : 10);
                                if(times==0){ fillAt=(long)out.size(); fillChar=(unsigned char)c; }
                                else for(long t=0;t<times;++t) out.push_back((unsigned char)c);
                                i=close+1;
                                continue;
                            }
                        }
                    }
                }
            return out;
                size_t k=i;
                int lo=readChar(k);
                if(k<n && spec[k]=='-' && k+1<n){
                    size_t k2=k+1;
                    int hi=readChar(k2);
                    if(hi<lo){
                        err="range-endpoints are in reverse collating sequence order";
                        return false;
                    }
                    for(int c=lo;c<=hi;++c) out.push_back((unsigned char)c);
                    i=k2;
                    continue;
                }
                out.push_back((unsigned char)lo);
                i=k;
            }
            return true;
        }

        struct Output {
            std::string buf;
            void put(unsigned char c){ buf+=(char)c; if(buf.size()>=65536) flush(); }
            void flush(){
                size_t off=0;
                while(off<buf.size()){
                    ssize_t w=::write(1,buf.data()+off,buf.size()-off);
                    if(w<0){ if(errno==EINTR) continue; break; }
                    off+=(size_t)w;
                }
                buf.clear();
            }
        };

    public:
        int runCommand(int argc,const char **argv){
            bool del=false;
            bool complement=false,del=false,squeeze=false,truncate=false;
            std::vector<std::string> operands;
            bool endOfOpts=false;
            for(int i=1;i<argc;++i){
                std::string a=argv[i];
                if(a=="-d") del=true;
                else operands.push_back(a);
                if(endOfOpts || a.size()<2 || a[0]!='-'){ operands.push_back(a); continue; }
                if(a=="--"){ endOfOpts=true; continue; }
                if(a=="--complement"){ complement=true; continue; }
                if(a=="--delete"){ del=true; continue; }
                if(a=="--squeeze-repeats"){ squeeze=true; continue; }
                if(a=="--truncate-set1"){ truncate=true; continue; }
                for(size_t k=1;k<a.size();++k){
                    switch(a[k]){
                        case 'c': case 'C': complement=true; break;
                        case 'd': del=true; break;
                        case 's': squeeze=true; break;
                        case 't': truncate=true; break;
                        default:
                            std::cerr << "tr: invalid option -- '" << a[k] << "'" << std::endl;
                            return 1;
                    }
                }
            }
            size_t need=(del && !squeeze) || (squeeze && !del && operands.size()==1) ? 1 : 2;
            if(operands.size()<need || operands.size()>2 || (del && !squeeze && operands.size()!=1)){
                std::cerr << "tr: " << (operands.size()<need ? "missing operand" : "extra operand") << std::endl;
                return 1;
            }

            if(del){
                if(operands.empty()){
                    std::cerr << "tr: missing operand" << std::endl;
            std::string err;
            std::vector<unsigned char> set1,set2;
            long fill1,fill2;
            unsigned char fc1=0,fc2=0;
            if(!expandSet(operands[0],set1,fill1,fc1,err) ||
               (operands.size()>1 && !expandSet(operands[1],set2,fill2,fc2,err))){
                std::cerr << "tr: " << err << std::endl;
                return 1;
            }
                std::string set=expandSet(operands[0]);
                bool table[256]={false};
                for(unsigned char c: set) table[c]=true;
                char ch;
                while(std::cin.get(ch))
                    if(!table[(unsigned char)ch])
                        std::cout.put(ch);
                return 0;
            if(complement){
                bool in[256]={false};
                for(unsigned char c: set1) in[c]=true;
                set1.clear();
                for(int c=0;c<256;++c) if(!in[c]) set1.push_back((unsigned char)c);
            }

            if(operands.size()<2){
                std::cerr << "tr: usage: tr SET1 SET2" << std::endl;
                return 1;
            bool delSet[256]={false},sqSet[256]={false};
            unsigned char map[256];
            for(int c=0;c<256;++c) map[c]=(unsigned char)c;
            bool translate=!del && operands.size()==2;
            if(del) for(unsigned char c: set1) delSet[c]=true;
            if(translate){
                if(operands.size()>1 && fill2>=0){
                    // [c*]: pad SET2 to SET1's length at that position
                    long pad=(long)set1.size()-(long)set2.size();
                    if(pad>0) set2.insert(set2.begin()+fill2,(size_t)pad,fc2);
                }
            std::string set1=expandSet(operands[0]);
            std::string set2=expandSet(operands[1]);
            if(set2.empty()){
                std::cerr << "tr: SET2 must not be empty" << std::endl;
                if(truncate && set1.size()>set2.size()) set1.resize(set2.size());
                if(set2.empty() && !set1.empty()){
                    std::cerr << "tr: when not truncating set1, string2 must be non-empty" << std::endl;
                    return 1;
                }

            char map[256];
            for(int i=0;i<256;++i) map[i]=(char)i;
            for(size_t i=0;i<set1.size();++i){
                unsigned char from=(unsigned char)set1[i];
                unsigned char to=(unsigned char)set2[i<set2.size() ? i : set2.size()-1];
                map[from]=(char)to;
                for(size_t i=0;i<set1.size();++i)
                    map[set1[i]]=set2[i<set2.size() ? i : set2.size()-1];
            }
            if(squeeze){
                const std::vector<unsigned char> &sq=(operands.size()==2) ? set2 : set1;
                for(unsigned char c: sq) sqSet[c]=true;
            }

            char ch;
            while(std::cin.get(ch))
                std::cout.put(map[(unsigned char)ch]);
            Output out;
            unsigned char buf[65536];
            int last=-1;
            for(;;){
                ssize_t n=::read(0,buf,sizeof buf);
                if(n<0){ if(errno==EINTR) continue; break; }
                if(n==0) break;
                for(ssize_t k=0;k<n;++k){
                    unsigned char c=buf[k];
                    if(del && delSet[c]) continue;
                    c=map[c];
                    if(squeeze && sqSet[c] && last==(int)c) continue;
                    out.put(c);
                    last=c;
                }
            }
            out.flush();
            return 0;
        }
    };