Commit 6715f8fd authored by jan.koester's avatar jan.koester
Browse files

test

parent c020a6eb
Loading
Loading
Loading
Loading
+571 −123

File changed.

Preview size limit exceeded, changes collapsed.

+163 −68
Changes for commands/text/xargs.cpp: 163 added lines, 68 removed lines.
Original line number Diff line number Diff line
@@ -138,59 +138,134 @@ namespace fresh { namespace cmd {
            }
        }

        // splits input at a single delimiter character (-0, -d)
        static std::vector<std::string> splitAt(const std::string &text,char delim){
            if(delim=='\0') return splitNul(text);
            std::vector<std::string> tokens;
            size_t start=0;
            for(size_t i=0;i<=text.size();++i){
                if(i==text.size() || text[i]==delim){
                    if(i>start || (i<text.size() && i>start)) tokens.push_back(text.substr(start,i-start));
                    start=i+1;
                }
            }
            return tokens;
        }

        // "-d '\n'" & co.: C escapes or a single character
        static bool parseDelim(const std::string &d,char &out){
            if(d.size()==1){ out=d[0]; return true; }
            if(d.size()==2 && d[0]=='\\'){
                switch(d[1]){
                    case 'n': out='\n'; return true;
                    case 't': out='\t'; return true;
                    case '0': out='\0'; return true;
                    case '\\': out='\\'; return true;
                    default: return false;
                }
            }
            if(d.size()>2 && d[0]=='\\' && (d[1]=='x' || d[1]=='X')){
                out=(char)strtol(d.c_str()+2,nullptr,16);
                return true;
            }
            if(d.size()>1 && d[0]=='\\'){
                out=(char)strtol(d.c_str()+1,nullptr,8);
                return true;
            }
            return false;
        }

        // one invocation; GNU xargs' status rules: 255 stops everything
        // (124), 1-125 make the overall status 123, 126/127 are passed on
        static bool invoke(const std::vector<std::string> &tokens,bool trace,int &overall){
            if(trace){
                for(size_t k=0;k<tokens.size();++k) std::cerr << (k ? " " : "") << tokens[k];
                std::cerr << std::endl;
            }
            int st=Shell::instance().runCommandTokens(tokens);
            if(st==255){ overall=124; return false; }
            if(st==126 || st==127){ overall=st; return false; }
            if(st!=0 && overall==0) overall=123;
            return true;
        }

        int runCommand(int argc,const char **argv){
            bool noRunIfEmpty=false,nulSeparated=false;
            long maxArgs=-1;
            std::string replaceStr;
            bool noRunIfEmpty=false,trace=false,haveDelim=false;
            char delim='\n';
            long maxArgs=-1,maxLines=-1;
            std::string replaceStr,eofStr;
            std::string inputFile;
            std::vector<std::string> baseArgv;

            // option letters that take a value, attached ("-n32") or
            // separate ("-n 32"); others may be combined ("-0r")
            auto needsValue=[](char c){ return strchr("adEeIiLlnPs",c)!=nullptr; };
            auto setOpt=[&](char c,const std::string &v)->bool{
                switch(c){
                    case '0': haveDelim=true; delim='\0'; return true;
                    case 'r': noRunIfEmpty=true; return true;
                    case 't': trace=true; return true;
                    case 'x': case 'p': return true; // limits/prompting: not needed here
                    case 'd':
                        if(!parseDelim(v,delim)){ std::cerr << "xargs: invalid delimiter '" << v << "'" << std::endl; return false; }
                        haveDelim=true; return true;
                    case 'I': case 'i': replaceStr=v.empty() ? "{}" : v; return true;
                    case 'n': maxArgs=atol(v.c_str()); return true;
                    case 'L': case 'l': maxLines=v.empty() ? 1 : atol(v.c_str()); return true;
                    case 'a': inputFile=v; return true;
                    case 'E': case 'e': eofStr=v; return true;
                    case 'P': case 's': return true; // parallelism/size: run sequentially
                    default:
                        std::cerr << "xargs: invalid option -- '" << c << "'" << std::endl;
                        return false;
                }
            };

            int i=1;
            for(; i<argc; ++i){
                std::string a=argv[i];
                if(a=="-r" || a=="--no-run-if-empty"){
                    noRunIfEmpty=true;
                }else if(a=="-0" || a=="--null"){
                    nulSeparated=true;
                }else if(a=="-I"){
                    if(i+1>=argc){
                        std::cerr << "xargs: -I: option requires an argument" << std::endl;
                        return 2;
                    }
                    replaceStr=argv[++i];
                }else if(a.compare(0,2,"-I")==0 && a.size()>2){
                    replaceStr=a.substr(2);
                }else if(a=="-n"){
                    if(i+1>=argc){
                        std::cerr << "xargs: -n: option requires an argument" << std::endl;
                        return 2;
                    }
                    maxArgs=atol(argv[++i]);
                }else if(a.compare(0,2,"-n")==0 && a.size()>2){
                    maxArgs=atol(a.c_str()+2);
                }else if(a.compare(0,11,"--max-args=")==0){
                    maxArgs=atol(a.c_str()+11);
                }else if(a=="-a"){
                    if(i+1>=argc){
                        std::cerr << "xargs: -a: option requires an argument" << std::endl;
                        return 2;
                    }
                    inputFile=argv[++i];
                }else if(a.compare(0,2,"-a")==0 && a.size()>2){
                    inputFile=a.substr(2);
                }else if(a=="--"){
                    ++i;
                    break;
                }else if(!a.empty() && a[0]=='-' && a.size()>1){
                    // Unrecognized option: real xargs would error; this
                    // build never passes one this applet doesn't already
                    // understand, so treat it (conservatively) as already
                    // being the command name rather than silently eating
                    // an argument the caller meant for COMMAND.
                    break;
                if(a=="--"){ ++i; break; }
                if(a.compare(0,2,"--")==0){
                    std::string name=a.substr(2),val;
                    size_t eq=name.find('=');
                    if(eq!=std::string::npos){ val=name.substr(eq+1); name=name.substr(0,eq); }
                    if(name=="null"){ haveDelim=true; delim='\0'; }
                    else if(name=="no-run-if-empty") noRunIfEmpty=true;
                    else if(name=="verbose") trace=true;
                    else if(name=="exit" || name=="interactive") {}
                    else if(name=="max-args" || name=="max-lines" || name=="max-procs" || name=="max-chars" ||
                            name=="delimiter" || name=="replace" || name=="arg-file" || name=="eof"){
                        if(eq==std::string::npos && name!="replace" && name!="eof"){
                            if(i+1>=argc){ std::cerr << "xargs: --" << name << " requires an argument" << std::endl; return 1; }
                            val=argv[++i];
                        }
                        static const char *const longs[]={"max-args","max-lines","max-procs","max-chars","delimiter","replace","arg-file","eof"};
                        static const char shorts[]="nLPsdIaE";
                        for(int k=0;k<8;++k) if(name==longs[k] && !setOpt(shorts[k],val)) return 1;
                    }else{
                        std::cerr << "xargs: unrecognized option '" << a << "'" << std::endl;
                        return 1;
                    }
                    continue;
                }
                if(a.size()<2 || a[0]!='-') break;
                for(size_t k=1;k<a.size();++k){
                    char c=a[k];
                    if(needsValue(c)){
                        std::string v=a.substr(k+1);
                        // -I/-i/-L/-l/-e/-E may stand alone; the rest need a value
                        if(v.empty() && !strchr("iIlLeE",c)){
                            if(i+1>=argc){ std::cerr << "xargs: option requires an argument -- '" << c << "'" << std::endl; return 1; }
                            v=argv[++i];
                        }else if(v.empty() && c=='I'){
                            if(i+1>=argc){ std::cerr << "xargs: option requires an argument -- 'I'" << std::endl; return 1; }
                            v=argv[++i];
                        }
                        if(!setOpt(c,v)) return 1;
                        break;
                    }
                    if(!setOpt(c,"")) return 1;
                }
            }

            for(; i<argc; ++i)
@@ -203,7 +278,7 @@ namespace fresh { namespace cmd {
                std::ifstream f(inputFile.c_str());
                if(!f.is_open()){
                    std::cerr << "xargs: " << inputFile << ": " << strerror(errno) << std::endl;
                    return 2;
                    return 1;
                }
                std::ostringstream ss;
                ss << f.rdbuf();
@@ -214,43 +289,63 @@ namespace fresh { namespace cmd {
                input=ss.str();
            }

            int overall=0;

            // -I: one invocation per input line (or delimited item)
            if(!replaceStr.empty()){
                // "-I replstr": one invocation per LINE of input (never
                // further word-split), substituting every occurrence of
                // replstr in the base argv with that line's own text --
                // matches real xargs' own "-I" semantics.
                std::istringstream lines(input);
                std::string line;
                std::vector<std::string> items=splitAt(input,haveDelim ? delim : '\n');
                bool any=false;
                int status=0;
                while(std::getline(lines,line)){
                    if(line.empty()) continue;
                for(auto &item: items){
                    if(item.empty()) continue;
                    if(!eofStr.empty() && item==eofStr) break;
                    any=true;
                    std::vector<std::string> tokens=baseArgv;
                    for(size_t k=0;k<tokens.size();++k)
                        replaceAll(tokens[k],replaceStr,line);
                    status=Shell::instance().runCommandTokens(tokens);
                        replaceAll(tokens[k],replaceStr,item);
                    if(!invoke(tokens,trace,overall)) return overall;
                }
                if(!any && !noRunIfEmpty)
                    return Shell::instance().runCommandTokens(baseArgv);
                return status;
                (void)any;
                return overall;
            }

            std::vector<std::string> items=nulSeparated ? splitNul(input) : tokenize(input);

            if(items.empty())
                return noRunIfEmpty ? 0 : Shell::instance().runCommandTokens(baseArgv);

            std::vector<std::vector<std::string>> batches;
            if(maxLines>0 && !haveDelim){
                // -L N: N non-blank input lines per invocation
                std::istringstream lines(input);
                std::string line;
                std::vector<std::string> cur;
                long n=0;
                while(std::getline(lines,line)){
                    std::vector<std::string> t=tokenize(line);
                    if(t.empty()) continue;
                    cur.insert(cur.end(),t.begin(),t.end());
                    if(++n==maxLines){ batches.push_back(cur); cur.clear(); n=0; }
                }
                if(!cur.empty()) batches.push_back(cur);
            }else{
                std::vector<std::string> items=haveDelim ? splitAt(input,delim) : tokenize(input);
                if(!eofStr.empty()){
                    auto it=std::find(items.begin(),items.end(),eofStr);
                    items.erase(it,items.end());
                }
                size_t batchLimit=(maxArgs>0) ? (size_t)maxArgs : MAX_BATCH_ARGS;
            int status=0;
                for(size_t start=0; start<items.size(); start+=batchLimit){
                    size_t end=std::min(start+batchLimit,items.size());
                    batches.emplace_back(items.begin()+start,items.begin()+end);
                }
            }

            if(batches.empty()){
                if(noRunIfEmpty) return 0;
                invoke(baseArgv,trace,overall);
                return overall;
            }
            for(auto &b: batches){
                std::vector<std::string> tokens=baseArgv;
                for(size_t k=start;k<end;++k)
                    tokens.push_back(items[k]);
                status=Shell::instance().runCommandTokens(tokens);
                tokens.insert(tokens.end(),b.begin(),b.end());
                if(!invoke(tokens,trace,overall)) return overall;
            }
            return status;
            return overall;
        }
    };