Extend options

+ Add global -E option
+ Add %include -e option
~ Move -C option to %include and %resolve
+ Improve comments and messages
This commit is contained in:
zawz 2020-08-28 14:46:42 +02:00
parent ca41c27246
commit af1de6d8fb
11 changed files with 218 additions and 62 deletions

1
.gitignore vendored
View file

@ -3,3 +3,4 @@
/run-tests.sh
/Zmakefile
/TODO
/lxsh

View file

@ -6,6 +6,8 @@
extern ztd::option_set options;
extern bool opt_minimize;
extern bool piped; // for cd in substitutions
ztd::option_set gen_options();
void print_help(const char* arg0);

View file

@ -11,13 +11,16 @@
/*
structure:
list_t : condlist[]
block:
- group
- brace: condlist[]
- subsh: condlist[]
- brace: list_t
- subsh: list_t
- cmd: arglist[]
- if
- pair<condlist[]>[]
- case
- arg (input)
- pair<arg,list_t>[] (cases)
condlist:
pipeline[]
@ -30,14 +33,11 @@ arglist:
arg:
- raw
- subarg[]
- subarg[] split into subarguments in case of subshells
subarg:
- raw
- variable
- block: subshell
- block: subshell (substitution)
*/
#define AND_OP false
@ -49,6 +49,7 @@ class pipeline;
class arg;
class subarg;
// type pack of condlist
typedef std::vector<condlist> list_t;
block make_cmd(std::vector<std::string> args);
@ -63,11 +64,14 @@ public:
void setstring(std::string const& str);
// has to be set manually
std::string raw;
std::vector<subarg> sa;
// return if is a string and only one subarg
std::string string();
std::string generate(int ind);
};
@ -99,12 +103,17 @@ public:
class block
{
public:
// type
enum blocktype { none, subshell, brace, main, cmd, function, case_block, for_block, if_block, while_block};
blocktype type;
// ctor
block() { type=none; }
block(blocktype in) { type=in; }
blocktype type;
// subshell/brace/main
list_t cls;
// cmd
arglist args;
@ -143,12 +152,13 @@ public:
condlist() { parallel=false; }
condlist(block const& pl) { parallel=false; this->add(pl);}
condlist(pipeline const& pl) { parallel=false; this->add(pl);}
void add(pipeline const& pl, bool or_op=false);
bool parallel;
// don't push_back here
std::vector<bool> or_ops;
bool parallel; // & at the end
void add(pipeline const& pl, bool or_op=false);
// don't push_back here, use add() instead
std::vector<pipeline> pls;
std::vector<bool> or_ops; // size of 1 less than pls, defines separator between pipelines
std::string generate(int ind);
};
@ -157,12 +167,15 @@ public:
class subarg
{
public:
// type
enum argtype { string, subshell };
argtype type;
// ctor
subarg(argtype in) { this->type=in; }
subarg(std::string const& in="") { type=string; val=in; }
subarg(block const& in) { type=subshell; sbsh=in; }
argtype type;
// raw string
std::string val;
// subshell

View file

@ -21,7 +21,8 @@ template<typename ... Args>
std::string strf( const std::string& format, Args ... args )
{
size_t size = snprintf( nullptr, 0, format.c_str(), args ... ) + 1; // Extra space for '\0'
if( size <= 0 ){ throw std::runtime_error( "Error during formatting." ); }
if( size <= 0 )
throw std::runtime_error( "Error during formatting." );
std::unique_ptr<char[]> buf( new char[ size ] );
snprintf( buf.get(), size, format.c_str(), args ... );
return std::string( buf.get(), buf.get() + size - 1 ); // We don't want the '\0' inside
@ -29,4 +30,10 @@ std::string strf( const std::string& format, Args ... args )
std::string delete_brackets(std::string const& in);
std::string pwd();
void _exec(std::string const& bin, std::vector<std::string> const& args);
std::string stringReplace(std::string subject, const std::string& search, const std::string& replace);
#endif //UTIL_HPP

BIN
lxsh

Binary file not shown.

View file

@ -6,6 +6,8 @@
#include "options.hpp"
#include "parse.hpp"
#include <unistd.h>
std::vector<std::string> included;
bool is_sub_special_cmd(std::string in)
@ -99,10 +101,24 @@ std::string concatargs(std::vector<std::string> args)
std::string generate_resolve(std::vector<std::string> args, int ind)
{
std::string ret;
auto opts=create_resolve_opts();
auto rargs = opts.process(args, false, true, false);
std::string cmd=concatargs(rargs);
std::string dir;
if(!opts['C'] && !piped)
{
dir=pwd();
std::string cddir=ztd::exec("dirname", g_origin).first;
cddir.pop_back();
chdir(cddir.c_str());
}
// exec call
auto p=ztd::shp("exec "+cmd);
if(!opts['f'] && p.second!=0)
@ -114,17 +130,29 @@ std::string generate_resolve(std::vector<std::string> args, int ind)
if(opts['p'])
{
block bl = parse(p.first);
std::string ret = bl.generate(ind, false);
block bl;
try
{
bl = parse(p.first);
}
catch(ztd::format_error& e)
{
throw ztd::format_error(e.what(), "command `"+cmd+'`', e.data(), e.where());
}
ret = bl.generate(ind, false);
std::string tmpind=INDENT;
ret = ret.substr(tmpind.size());
ret.pop_back(); // remove \n
return ret;
}
else
{
return p.first;
ret = p.first;
}
if(!opts['C'] && !piped)
chdir(dir.c_str());
return ret;
}
std::string generate_include(std::vector<std::string> args, int ind)
@ -140,6 +168,17 @@ std::string generate_include(std::vector<std::string> args, int ind)
else if(opts['d'])
quote = '"';
std::string curfile=g_origin;
std::string dir;
if(!opts['C'] && !piped)
{
dir=pwd();
std::string cddir=ztd::exec("dirname", curfile).first;
cddir.pop_back();
chdir(cddir.c_str());
}
// do shell resolution
std::string command="for I in ";
for(auto it: rargs)
@ -149,6 +188,7 @@ std::string generate_include(std::vector<std::string> args, int ind)
auto v = split(inc, '\n');
std::string file;
block bl;
bool indent_remove=true;
@ -159,10 +199,13 @@ std::string generate_include(std::vector<std::string> args, int ind)
add_include(it) ) // not already included
{
file=import_file(it);
if(opts['e'])
file = stringReplace(file, "\"", "\\\"");
if(opts['r'])
ret += file;
else
{
g_origin=it;
try
{
bl = parse(quote + file + quote);
@ -182,6 +225,9 @@ std::string generate_include(std::vector<std::string> args, int ind)
}
}
}
if(!opts['C'] && !piped)
chdir(dir.c_str());
g_origin=curfile;
if(!opts['r'])
ret.pop_back();
@ -215,13 +261,17 @@ std::string block::generate_case(int ind)
ind++;
for(auto cs: this->cases)
{
// case definition : foo)
if(!opt_minimize) ret += INDENT;
ret += cs.first.generate(ind) + ')';
if(!opt_minimize) ret += '\n';
// commands
for(auto it: cs.second)
ret += it.generate(ind+1);
// end of case: ;;
if(opt_minimize)
{
// ;; can be right after command
if(ret[ret.size()-1] == '\n')
ret.pop_back();
}
@ -233,6 +283,7 @@ std::string block::generate_case(int ind)
}
ret += ";;\n";
}
// close case
ind--;
if(!opt_minimize) ret += INDENT;
ret += "esac";
@ -251,25 +302,30 @@ std::string block::generate(int ind, bool print_shebang)
{
if(type==function)
{
// function definition
ret += shebang + "()";
if(!opt_minimize) ret += '\n' + INDENT;
ret += "{\n";
ret += "{\n";
// commands
for(auto it: cls)
ret += it.generate(ind+1);
if(!opt_minimize)
ret += INDENT;
if(!opt_minimize) ret += INDENT;
// end function
ret += '}';
}
else if(type==subshell)
{
// open subshell
ret += '(';
if(!opt_minimize) ret += '\n';
// commands
for(auto it: cls)
ret += it.generate(ind+1);
if(opt_minimize)
ret.pop_back();
ret.pop_back(); // ) can be right after command
else
ret += INDENT;
// close subshell
ret += ')';
}
else if(type==brace)

View file

@ -7,18 +7,19 @@
#include <unistd.h>
#include "util.hpp"
#include "struc.hpp"
#include "parse.hpp"
#include "options.hpp"
int main(int argc, char* argv[])
{
auto args=options.process(argc, argv);
auto args=options.process(argc, argv, false, true);
if(options['m'])
opt_minimize=true;
bool piped=false;
piped=false;
if(options['h'])
{
@ -58,34 +59,48 @@ int main(int argc, char* argv[])
}
}
if(!piped && !options['C'])
{
std::string dir=ztd::exec("dirname", file).first;
dir.pop_back();
file=ztd::exec("basename", file).first;
file.pop_back();
chdir(dir.c_str());
}
g_origin=file;
add_include(file);
if(args.size()>0)
try
{
try
block sh(parse(import_file(file)));
if(options['E'])
{
std::string data=sh.generate();
// generate path
std::string tmpdir = (getenv("TMPDIR") != NULL) ? getenv("TMPDIR") : "/tmp" ;
std::string filepath = tmpdir + "/lxsh_exec_" + ztd::sh("tr -dc '[:alnum:]' < /dev/urandom | head -c10");
// create stream
std::ofstream stream(filepath);
if(!stream)
throw std::runtime_error("Failed to write to file '"+filepath+'\'');
// output
stream << data;
stream.close();
auto p = ztd::exec("chmod", "+x", filepath);
if(p.second != 0)
return p.second;
args.erase(args.begin());
_exec(filepath, args);
}
else
{
block sh(parse(import_file(file)));
std::cout << sh.generate();
}
catch(ztd::format_error& e)
{
printFormatException(e);
}
catch(std::exception& e)
{
std::cerr << e.what() << std::endl;
}
}
catch(ztd::format_error& e)
{
printFormatException(e);
}
catch(std::exception& e)
{
std::cerr << e.what() << std::endl;
}
return 0;
}

View file

@ -2,13 +2,15 @@
ztd::option_set options = gen_options();
bool opt_minimize;
bool piped=false;
ztd::option_set gen_options()
{
ztd::option_set ret;
ret.add(ztd::option('h', "help", false, "Display this help message"));
ret.add(ztd::option('m', "minimize", false, "Minimize code"));
ret.add(ztd::option('C', "no-cd", false, "Don't change directories"));
ret.add(ztd::option('E', "exec", false, "Directly exec instead of outputting"));
ret.add(ztd::option("help-commands", false, "Print help for linker commands"));
return ret;
}
@ -19,8 +21,9 @@ ztd::option_set create_include_opts()
opts.add(
ztd::option('s', false, "Single quote contents"),
ztd::option('d', false, "Double quote contents"),
// ztd::option('e', false, "Escape for double quotes"),
ztd::option('e', false, "Escape double quotes"),
ztd::option('r', false, "Include raw contents, don't parse"),
ztd::option('C', false, "Don't cd to folder the file is in"),
ztd::option('f', false, "Force include even if already included. Don't count as included")
);
return opts;
@ -30,8 +33,8 @@ ztd::option_set create_resolve_opts()
{
ztd::option_set opts;
opts.add(
// ztd::option('e', false, "Escape for double quotes"),
ztd::option('p', false, "Parse contents as shell code"),
ztd::option('C', false, "Don't cd to folder this file is in"),
ztd::option('f', false, "Ignore non-zero return values")
);
return opts;
@ -39,7 +42,7 @@ ztd::option_set create_resolve_opts()
void print_help(const char* arg0)
{
printf("%s [options] [file]\n", arg0);
printf("%s [options] <file> [arg...]\n", arg0);
printf("Link extended shell, allows file including and command resolving\n");
printf("See --help-commands for help on linker commands\n");
printf("\n");
@ -50,7 +53,7 @@ void print_help(const char* arg0)
void print_include_help()
{
printf("%%include [options] <file...>\n");
printf("Include the targeted files. Paths are relative to folder of current file\n");
printf("Include the targeted files, from folder of current file\n");
printf(" - Regular shell processing applies to the file arguments\n");
printf(" - Only includes not already included files\n");
printf(" - `%%include` in command substitutions replaces the substitution\n");
@ -64,7 +67,7 @@ void print_include_help()
void print_resolve_help()
{
printf("%%resolve [options] <command...>\n");
printf("Execute shell command and substitute output. Paths is from folder of current file\n");
printf("Execute shell command and substitute output, from folder of current file\n");
printf(" - Fails if return value is not 0. Can be ignored with -f\n");
printf(" - `%%resolve` in command substitutions replaces the substitution\n");
printf(" =>`%%resolve_s` can be used inside a substitution to prevent this\n");

View file

@ -241,7 +241,9 @@ std::pair<condlist, uint32_t> parse_condlist(const char* in, uint32_t size, uint
return std::make_pair(ret, i);
}
// parse condlists until )
// parse a subshell
// must start right after the opening (
// ends at ) and nothing else
std::pair<block, uint32_t> parse_subshell(const char* in, uint32_t size, uint32_t start)
{
uint32_t i = skip_unread(in, size, start);
@ -258,7 +260,9 @@ std::pair<block, uint32_t> parse_subshell(const char* in, uint32_t size, uint32_
return std::make_pair(ret,i);
}
// parse condlists until }
// parse a brace block
// must start right after the opening {
// ends at } and nothing else
std::pair<block, uint32_t> parse_brace(const char* in, uint32_t size, uint32_t start)
{
uint32_t i = skip_unread(in, size, start);
@ -279,6 +283,9 @@ std::pair<block, uint32_t> parse_brace(const char* in, uint32_t size, uint32_t s
return std::make_pair(ret,i);
}
// parse a functions
// must start right after the ()
// then parses a brace block
std::pair<block, uint32_t> parse_function(const char* in, uint32_t size, uint32_t start)
{
block ret(block::function);
@ -301,16 +308,18 @@ std::pair<block, uint32_t> parse_cmd(const char* in, uint32_t size, uint32_t sta
block ret(block::cmd);
uint32_t i=start;
// parse first arg and keep it
auto tp=parse_arg(in, size, i);
i=skip_unread(in, size, tp.second);
if(word_eq("()", in, size, i))
if(word_eq("()", in, size, i)) // is a function
{
i += 2;
auto pp = parse_function(in, size, i);
// first arg is function name
pp.first.shebang = tp.first.raw;
return pp;
}
else
else // is a command
{
auto pp=parse_arglist(in, size, start);
ret.args = pp.first;
@ -320,16 +329,20 @@ std::pair<block, uint32_t> parse_cmd(const char* in, uint32_t size, uint32_t sta
return std::make_pair(ret, i);
}
// parse a case block
// must start right after the case
// ends at } and nothing else
std::pair<block, uint32_t> parse_case(const char* in, uint32_t size, uint32_t start)
{
block ret(block::case_block);
uint32_t i=start;
uint32_t i=skip_unread(in, size, start);;
auto pa = parse_arg(in, size, i); // case arg
// get the treated argument
auto pa = parse_arg(in, size, i);
ret.carg = pa.first;
i=skip_unread(in, size, pa.second);
// must be an 'in'
if(!word_eq("in", in, size, i))
{
auto pp=parse_arg(in, size, i);
@ -338,6 +351,7 @@ std::pair<block, uint32_t> parse_case(const char* in, uint32_t size, uint32_t st
i=skip_unread(in, size, i+2);
// parse all cases
while(i<size && !word_eq("esac", in, size, i, " \t\n;()&") )
{
// toto)
@ -361,18 +375,21 @@ std::pair<block, uint32_t> parse_case(const char* in, uint32_t size, uint32_t st
if(in[i] == ')')
throw ztd::format_error( strf("Unexpected token '%c', expecting ';;'", in[i]), g_origin, in, i );
// end of case: on same line
if(in[i-1] == ';' && in[i] == ';')
{
i++;
break;
}
// end of case: on new line
i=skip_unread(in, size, i);
if(word_eq(";;", in, size, i))
{
i+=2;
break;
}
// end of block: ignore missing ;;
if(word_eq("esac", in, size, i))
break;
@ -381,6 +398,7 @@ std::pair<block, uint32_t> parse_case(const char* in, uint32_t size, uint32_t st
ret.cases.push_back(cc);
}
// ended before finding esac
if(i>=size)
throw ztd::format_error("Expecting 'esac'", g_origin, in, i);
i+=4;
@ -405,8 +423,7 @@ std::pair<block, uint32_t> parse_block(const char* in, uint32_t size, uint32_t s
}
else if(word_eq("case", in, size, i))
{
i = skip_unread(in, size, i+4);
ret = parse_case(in, size, i);
ret = parse_case(in, size, i+4);
}
else // command
{
@ -424,15 +441,16 @@ std::pair<block, uint32_t> parse_block(const char* in, uint32_t size, uint32_t s
// parse main
block parse(const char* in, uint32_t size)
{
block ret(block::main);
uint32_t i=0;
// get shebang
if(word_eq("#!", in, size, 0))
{
i=skip_until(in, size, 0, "\n");
ret.shebang=std::string(in, i);
}
i = skip_unread(in, size, i);
// parse all commands
while(i<size)
{
auto pp=parse_condlist(in, size, i);
@ -442,6 +460,7 @@ block parse(const char* in, uint32_t size)
return ret;
}
// import a file's contents into a string
std::string import_file(std::string const& path)
{
std::ifstream st(path);

View file

@ -2,6 +2,8 @@
#include "util.hpp"
#include <unistd.h>
block make_cmd(std::vector<std::string> args)
{
block cmd(block::cmd);
@ -24,6 +26,7 @@ void arg::setstring(std::string const& str)
{
sa.resize(0);
sa.push_back(subarg(str));
raw = str;
}
void condlist::add(pipeline const& pl, bool or_op)

View file

@ -1,5 +1,9 @@
#include "util.hpp"
#include <unistd.h>
#include <ztd/shell.hpp>
std::string indenting_string="\t";
std::string indent(int n)
@ -52,3 +56,36 @@ std::string delete_brackets(std::string const& in)
}
return ret;
}
std::string pwd()
{
char buf[2048];
if(getcwd(buf, 2048) != NULL)
{
std::string ret=ztd::exec("pwd").first; // getcwd failed: call pwd
ret.pop_back();
return ret;
}
return std::string(buf);
}
void _exec(std::string const& bin, std::vector<std::string> const& args)
{
std::vector<char*> rargs;
rargs.push_back((char*) bin.c_str());
for(auto it=args.begin(); it!=args.end(); it++)
rargs.push_back((char*) it->c_str());
rargs.push_back(NULL);
execvp(bin.c_str(), rargs.data());
}
std::string stringReplace(std::string subject, const std::string& search, const std::string& replace)
{
size_t pos = 0;
while ((pos = subject.find(search, pos)) != std::string::npos)
{
subject.replace(pos, search.length(), replace);
pos += replace.length();
}
return subject;
}