diff --git a/TESTS_ENVIRONMENT.in b/TESTS_ENVIRONMENT.in index 61f7577d..b7385430 100644 --- a/TESTS_ENVIRONMENT.in +++ b/TESTS_ENVIRONMENT.in @@ -91,6 +91,17 @@ check_output() { test_num=`expr ${test_num} \+ 1` } +check_output_ws() { + diff -u - ${test_file_base}_${test_num}.tmp > ${test_file_base}_${test_num}.diff + if test $? -ne 0; then + echo $LAST_TEST + echo $1 + cat ${test_file_base}_${test_num}.diff + exit 1 + fi + test_num=`expr ${test_num} \+ 1` +} + test_err_filename() { echo ${test_file_base}_${test_num}.err } diff --git a/src/command_executor.cc b/src/command_executor.cc index e14243c8..ee00b41a 100644 --- a/src/command_executor.cc +++ b/src/command_executor.cc @@ -37,6 +37,7 @@ #include "pcrecpp.h" #include "lnav.hh" #include "log_format_loader.hh" +#include "shlex.hh" #include "command_executor.hh" @@ -328,35 +329,32 @@ string execute_file(const string &path_and_args, bool multiline) { map > scripts; map >::iterator iter; - static_root_mem wordmem; + vector split_args; string msg, retval; + shlex lexer(path_and_args); log_info("Executing file: %s", path_and_args.c_str()); - int exp_rc = wordexp(path_and_args.c_str(), - wordmem.inout(), - WRDE_NOCMD | WRDE_UNDEF); - - if (!wordexperr(exp_rc, msg)) { - retval = msg; + if (!lexer.split(split_args, lnav_data.ld_local_vars.top())) { + retval = "error: unable to parse path"; } - else if (wordmem->we_wordc == 0) { + else if (split_args.empty()) { retval = "error: no script specified"; } else { lnav_data.ld_local_vars.push(map()); - string script_name = wordmem->we_wordv[0]; + string script_name = split_args[0]; map &vars = lnav_data.ld_local_vars.top(); char env_arg_name[32]; string result, open_error = "file not found"; - snprintf(env_arg_name, sizeof(env_arg_name), "%d", (int) wordmem->we_wordc - 1); + snprintf(env_arg_name, sizeof(env_arg_name), "%d", (int) split_args.size() - 1); vars["#"] = env_arg_name; - for (unsigned int lpc = 0; lpc < wordmem->we_wordc; lpc++) { + for (unsigned int lpc = 0; lpc < split_args.size(); lpc++) { snprintf(env_arg_name, sizeof(env_arg_name), "%d", lpc); - vars[env_arg_name] = wordmem->we_wordv[lpc]; + vars[env_arg_name] = split_args[lpc]; } vector paths_to_exec; diff --git a/src/lnav.cc b/src/lnav.cc index 63507b10..bacde09a 100644 --- a/src/lnav.cc +++ b/src/lnav.cc @@ -1925,7 +1925,7 @@ static void looper(void) "to switch to the next/previous file")); } if (lnav_data.ld_view_stack.top() == &lnav_data.ld_views[LNV_TEXT] && - lnav_data.ld_text_source.text_line_count() == 0 && + lnav_data.ld_text_source.empty() && lnav_data.ld_log_source.text_line_count() > 0) { textview_curses *tc_log = &lnav_data.ld_views[LNV_LOG]; lnav_data.ld_view_stack.pop(); diff --git a/src/lnav_commands.cc b/src/lnav_commands.cc index 84dd2692..5b5cccb3 100644 --- a/src/lnav_commands.cc +++ b/src/lnav_commands.cc @@ -29,7 +29,6 @@ #include "config.h" -#include #include #include @@ -459,15 +458,13 @@ static string com_save_to(string cmdline, vector &args) fn = trim(remaining_args(cmdline, args)); - static_root_mem wordmem; + vector split_args; + shlex lexer(fn); - int rc = wordexp(fn.c_str(), wordmem.inout(), WRDE_NOCMD | WRDE_UNDEF); - - if (!wordexperr(rc, retval)) { - return retval; + if (!lexer.split(split_args, lnav_data.ld_local_vars.top())) { + return "error: unable to parse arguments"; } - - if (wordmem->we_wordc > 1) { + if (split_args.size() > 1) { return "error: more than one file name was matched"; } @@ -495,7 +492,7 @@ static string com_save_to(string cmdline, vector &args) } } - if (strcmp(wordmem->we_wordv[0], "-") == 0) { + if (split_args[0] == "-") { outfile = stdout; if (lnav_data.ld_flags & LNF_HEADLESS) { lnav_data.ld_stdout_used = true; @@ -515,8 +512,8 @@ static string com_save_to(string cmdline, vector &args) args[0].c_str()); } } - else if ((outfile = fopen(wordmem->we_wordv[0], mode)) == NULL) { - return "error: unable to open file -- " + string(wordmem->we_wordv[0]); + else if ((outfile = fopen(split_args[0].c_str(), mode)) == NULL) { + return "error: unable to open file -- " + split_args[0]; } if (args[0] == "write-csv-to") { @@ -1306,7 +1303,7 @@ static string com_open(string cmdline, vector &args) return retval; } - static_root_mem wordmem; + vector word_exp; list::iterator file_iter; size_t colon_index; int top = 0; @@ -1314,14 +1311,15 @@ static string com_open(string cmdline, vector &args) pat = trim(remaining_args(cmdline, args)); - int rc = wordexp(pat.c_str(), wordmem.inout(), WRDE_NOCMD | WRDE_UNDEF); + vector split_args; + shlex lexer(pat); - if (!wordexperr(rc, retval)) { - return retval; + if (!lexer.split(split_args, lnav_data.ld_local_vars.top())) { + return "error: unable to parse arguments"; } - for (size_t lpc = 0; lpc < wordmem->we_wordc; lpc++) { - string fn = wordmem->we_wordv[lpc]; + for (size_t lpc = 0; lpc < split_args.size(); lpc++) { + string fn = split_args[lpc]; if (startswith(fn, "pt:")) { lnav_data.ld_pt_search = fn; diff --git a/src/readline_highlighters.cc b/src/readline_highlighters.cc index 7ca4ba69..45865a50 100644 --- a/src/readline_highlighters.cc +++ b/src/readline_highlighters.cc @@ -516,6 +516,7 @@ void readline_shlex_highlighter(attr_line_t &al, int x) &view_curses::VC_STYLE, error_attrs)); break; + case ST_TILDE: case ST_ESCAPE: al.with_attr(string_attr( line_range(cap.c_begin, cap.c_end), @@ -556,6 +557,8 @@ void readline_shlex_highlighter(attr_line_t &al, int x) } break; } + case ST_WHITESPACE: + break; } } diff --git a/src/shlex.hh b/src/shlex.hh index d56f7ec4..c12a240e 100644 --- a/src/shlex.hh +++ b/src/shlex.hh @@ -38,6 +38,7 @@ enum shlex_token_t { ST_ERROR, + ST_WHITESPACE, ST_ESCAPE, ST_DOUBLE_QUOTE_START, ST_DOUBLE_QUOTE_END, @@ -45,6 +46,7 @@ enum shlex_token_t { ST_SINGLE_QUOTE_END, ST_VARIABLE_REF, ST_QUOTED_VARIABLE_REF, + ST_TILDE, }; class shlex { @@ -126,6 +128,33 @@ public: break; } break; + case '~': + switch (this->s_state) { + case STATE_NORMAL: + cap_out.c_begin = this->s_index; + this->s_index += 1; + cap_out.c_end = this->s_index; + token_out = ST_TILDE; + return true; + default: + break; + } + break; + case ' ': + case '\t': + switch (this->s_state) { + case STATE_NORMAL: + cap_out.c_begin = this->s_index; + while (isspace(this->s_str[this->s_index])) { + this->s_index += 1; + } + cap_out.c_end = this->s_index; + token_out = ST_WHITESPACE; + return true; + default: + break; + } + break; default: break; } @@ -151,6 +180,9 @@ public: case ST_ESCAPE: result.append(1, this->s_str[cap.c_begin + 1]); break; + case ST_WHITESPACE: + result.append(&this->s_str[cap.c_begin], cap.length()); + break; case ST_VARIABLE_REF: case ST_QUOTED_VARIABLE_REF: { int extra = token == ST_VARIABLE_REF ? 0 : 1; @@ -166,6 +198,17 @@ public: } break; } + case ST_TILDE: { + const char *home_dir = getenv("HOME"); + + if (home_dir != NULL) { + result.append(home_dir); + } + else { + result.append("~"); + } + break; + } default: break; } @@ -177,6 +220,74 @@ public: return true; }; + bool split(std::vector &result, const std::map &vars) { + result.clear(); + + pcre_context::capture_t cap; + shlex_token_t token; + int last_index = 0; + bool start_new = true; + + while (isspace(this->s_str[this->s_index])) { + this->s_index += 1; + } + while (this->tokenize(cap, token)) { + if (start_new) { + result.push_back(""); + start_new = false; + } + result.back().append(&this->s_str[last_index], cap.c_begin - last_index); + switch (token) { + case ST_ERROR: + return false; + case ST_ESCAPE: + result.back().append(1, this->s_str[cap.c_begin + 1]); + break; + case ST_WHITESPACE: + start_new = true; + break; + case ST_VARIABLE_REF: + case ST_QUOTED_VARIABLE_REF: { + int extra = token == ST_VARIABLE_REF ? 0 : 1; + std::string var_name(&this->s_str[cap.c_begin + 1 + extra], cap.length() - 1 - extra * 2); + std::map::const_iterator local_var; + const char *var_value = getenv(var_name.c_str()); + + if ((local_var = vars.find(var_name)) != vars.end()) { + result.back().append(local_var->second); + } + else if (var_value != NULL) { + result.back().append(var_value); + } + break; + } + case ST_TILDE: { + const char *home_dir = getenv("HOME"); + + if (home_dir != NULL) { + result.back().append(home_dir); + } + else { + result.back().append("~"); + } + break; + } + default: + break; + } + last_index = cap.c_end; + } + + if (last_index < this->s_len) { + if (start_new || result.empty()) { + result.push_back(""); + } + result.back().append(&this->s_str[last_index], this->s_len - last_index); + } + + return true; + } + void reset() { this->s_index = 0; this->s_state = STATE_NORMAL; diff --git a/test/drive_shlexer.cc b/test/drive_shlexer.cc index db6078f4..2454c548 100644 --- a/test/drive_shlexer.cc +++ b/test/drive_shlexer.cc @@ -37,6 +37,7 @@ using namespace std; const char *ST_TOKEN_NAMES[] = { "err", + "wsp", "esc", "dst", "den", @@ -44,6 +45,7 @@ const char *ST_TOKEN_NAMES[] = { "sen", "ref", "qrf", + "til", }; int main(int argc, char *argv[]) @@ -84,6 +86,14 @@ int main(int argc, char *argv[]) if (lexer.eval(result, map())) { printf("eval -- %s\n", result.c_str()); } + lexer.reset(); + std::vector sresult; + if (lexer.split(sresult, map())) { + printf("split:\n"); + for (int lpc = 0; lpc < sresult.size(); lpc++) { + printf(" %d -- %s\n", lpc, sresult[lpc].c_str()); + } + } return EXIT_SUCCESS; } diff --git a/test/test_cmds.sh b/test/test_cmds.sh index 7ae45724..345cb038 100644 --- a/test/test_cmds.sh +++ b/test/test_cmds.sh @@ -1,7 +1,7 @@ #! /bin/bash run_test ${lnav_test} -n \ - -c "|${test_dir}/toplevel.lnav 123 456" \ + -c "|${test_dir}/toplevel.lnav 123 456 789" \ ${test_dir}/logfile_access_log.0 check_error_output "include toplevel.lnav" <