diff options
-rw-r--r-- | Makefile.am | 3 | ||||
-rw-r--r-- | scripts/command_execution.bash | 2 | ||||
-rw-r--r-- | scripts/command_execution.bash.result | 2 | ||||
-rw-r--r-- | src/builtins/builtin_exceptions.h | 4 | ||||
-rw-r--r-- | src/builtins/echo_builtin.cpp | 38 | ||||
-rw-r--r-- | src/builtins/echo_builtin.h | 4 | ||||
-rw-r--r-- | src/builtins/printf_builtin.cpp | 63 | ||||
-rw-r--r-- | src/builtins/printf_builtin.h | 35 | ||||
-rw-r--r-- | src/builtins/tests/printf_tests.cpp | 67 | ||||
-rw-r--r-- | src/cppbash_builtin.cpp | 43 | ||||
-rw-r--r-- | src/cppbash_builtin.h | 4 |
11 files changed, 225 insertions, 40 deletions
diff --git a/Makefile.am b/Makefile.am index 826970a..a706c43 100644 --- a/Makefile.am +++ b/Makefile.am @@ -107,6 +107,7 @@ cppunittests_SOURCES = test/run_tests.cpp \ src/builtins/tests/source_tests.cpp \ src/builtins/tests/shopt_tests.cpp \ src/builtins/tests/return_tests.cpp \ + src/builtins/tests/printf_tests.cpp \ test/test.h \ test/test.cpp \ test/post_check.cpp \ @@ -193,6 +194,8 @@ libcppbash_la_SOURCES = src/common.h \ src/builtins/shopt_builtin.cpp \ src/builtins/return_builtin.h \ src/builtins/return_builtin.cpp \ + src/builtins/printf_builtin.h \ + src/builtins/printf_builtin.cpp \ src/builtins/let_builtin.h \ src/builtins/let_builtin.cpp \ src/builtins/inherit_builtin.h \ diff --git a/scripts/command_execution.bash b/scripts/command_execution.bash index 3db9f42..2df1b0d 100644 --- a/scripts/command_execution.bash +++ b/scripts/command_execution.bash @@ -53,3 +53,5 @@ eval "FOO009=10" eval "echo abc" "def" "xyz" shopt -s extglob shopt -p +printf "%s %s\n" abc def +printf "%s %s\n" $FOO001, def diff --git a/scripts/command_execution.bash.result b/scripts/command_execution.bash.result index f176a71..3165843 100644 --- a/scripts/command_execution.bash.result +++ b/scripts/command_execution.bash.result @@ -53,6 +53,8 @@ shopt -u restricted shopt -u shift_verbose shopt -u sourcepath shopt -u xpg_echo +abc def +hello, def DEFAULTED=yes FOO001=hello FOO002=Hello World diff --git a/src/builtins/builtin_exceptions.h b/src/builtins/builtin_exceptions.h index d6eded4..3da6de8 100644 --- a/src/builtins/builtin_exceptions.h +++ b/src/builtins/builtin_exceptions.h @@ -58,4 +58,8 @@ public: } } }; + +class suppress_output: public std::exception +{ +}; #endif diff --git a/src/builtins/echo_builtin.cpp b/src/builtins/echo_builtin.cpp index 8d703ff..8caecef 100644 --- a/src/builtins/echo_builtin.cpp +++ b/src/builtins/echo_builtin.cpp @@ -26,15 +26,12 @@ #include <boost/spirit/include/qi.hpp> #include <boost/spirit/include/phoenix.hpp> +#include "builtins/builtin_exceptions.h" namespace qi = boost::spirit::qi; namespace karma = boost::spirit::karma; namespace phoenix = boost::phoenix; -class suppress_output -{ -}; - int echo_builtin::exec(const std::vector<std::string>& bash_args) { bool suppress_nl = false; @@ -64,7 +61,7 @@ int echo_builtin::exec(const std::vector<std::string>& bash_args) { try { - transform_escapes(*i); + transform_escapes(*i, out_buffer()); } catch(suppress_output) { @@ -121,34 +118,3 @@ bool echo_builtin::determine_options(const std::string &string, bool &suppress_n return false; } } - -void echo_builtin::transform_escapes(const std::string &string) -{ - using phoenix::val; - using qi::lit; - - auto escape_parser = - +( - lit('\\') >> - ( - lit('a')[this->out_buffer() << val("\a")] | - lit('b')[this->out_buffer() << val("\b")] | - // \e is a GNU extension - lit('e')[this->out_buffer() << val("\033")] | - lit('f')[this->out_buffer() << val("\f")] | - lit('n')[this->out_buffer() << val("\n")] | - lit('r')[this->out_buffer() << val("\r")] | - lit('t')[this->out_buffer() << val("\t")] | - lit('v')[this->out_buffer() << val("\v")] | - lit('c')[phoenix::throw_(suppress_output())] | - lit('\\')[this->out_buffer() << val('\\')] | - lit("0") >> qi::uint_parser<unsigned, 8, 1, 3>()[ this->out_buffer() << phoenix::static_cast_<char>(qi::_1)] | - lit("x") >> qi::uint_parser<unsigned, 16, 1, 2>()[ this->out_buffer() << phoenix::static_cast_<char>(qi::_1)] - - ) | - qi::char_[this->out_buffer() << qi::_1] - ); - - auto begin = string.begin(); - qi::parse(begin, string.end(), escape_parser); -} diff --git a/src/builtins/echo_builtin.h b/src/builtins/echo_builtin.h index 6de6475..3f5da62 100644 --- a/src/builtins/echo_builtin.h +++ b/src/builtins/echo_builtin.h @@ -51,10 +51,6 @@ class echo_builtin: public virtual cppbash_builtin /// \param enable_escapes returns back whether to enable escapes /// \return false if all options have been processed bool determine_options(const std::string &string, bool &suppress_nl, bool &enable_escapes); - - /// \brief transforms escapes in echo input - /// \return false when further output should be suppressed - void transform_escapes(const std::string &string); }; #endif diff --git a/src/builtins/printf_builtin.cpp b/src/builtins/printf_builtin.cpp new file mode 100644 index 0000000..c365863 --- /dev/null +++ b/src/builtins/printf_builtin.cpp @@ -0,0 +1,63 @@ +/* + Please use git log for copyright holder and year information + + This file is part of libbash. + + libbash is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 2 of the License, or + (at your option) any later version. + + libbash is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with libbash. If not, see <http://www.gnu.org/licenses/>. +*/ +/// +/// \file printf_builtin.h +/// \brief implementation for the printf builtin +/// + +#include "builtins/printf_builtin.h" + +#include <boost/format.hpp> + +#include "core/interpreter.h" +#include "cppbash_builtin.h" + +int printf_builtin::exec(const std::vector<std::string>& bash_args) +{ + std::vector<std::string>::const_iterator begin; + if(!(bash_args[0] == "-v")) + begin = bash_args.begin(); + else if(bash_args.size() < 3) + throw interpreter_exception("printf: illegal number of arguments"); + else + begin = bash_args.begin() + 2; + + std::stringstream format_string; + transform_escapes(*begin, format_string); + boost::format formatter(format_string.str()); + for(auto iter = begin + 1; iter != bash_args.end(); ++iter) + formatter = formatter % *iter; + + if(!(bash_args[0][0] == '-')) + { + *_out_stream << formatter; + } + else if(bash_args[0] == "-v") + { + std::stringstream output; + output << formatter; + _walker.set_value(bash_args[1], output.str()); + } + else + { + throw interpreter_exception("printf: invalid option: " + bash_args[0]); + } + + return 0; +} diff --git a/src/builtins/printf_builtin.h b/src/builtins/printf_builtin.h new file mode 100644 index 0000000..07d5ac7 --- /dev/null +++ b/src/builtins/printf_builtin.h @@ -0,0 +1,35 @@ +/* + Please use git log for copyright holder and year information + + This file is part of libbash. + + libbash is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 2 of the License, or + (at your option) any later version. + + libbash is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with libbash. If not, see <http://www.gnu.org/licenses/>. +*/ +/// +/// \file printf_builtin.h +/// \brief implementation for the printf builtin +/// +#ifndef LIBBASH_BUILTINS_printf_BUILTIN_H_ +#define LIBBASH_BUILTINS_printf_BUILTIN_H_ + +#include "cppbash_builtin.h" + +class printf_builtin : public virtual cppbash_builtin +{ +public: + BUILTIN_CONSTRUCTOR(printf) + virtual int exec(const std::vector<std::string>& ); +}; + +#endif diff --git a/src/builtins/tests/printf_tests.cpp b/src/builtins/tests/printf_tests.cpp new file mode 100644 index 0000000..a0c463c --- /dev/null +++ b/src/builtins/tests/printf_tests.cpp @@ -0,0 +1,67 @@ +/* + Please use git log for copyright holder and year information + + This file is part of libbash. + + libbash is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 2 of the License, or + (at your option) any later version. + + libbash is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with libbash. If not, see <http://www.gnu.org/licenses/>. +*/ +/// +/// \file printf_tests.cpp +/// \brief series of unit tests for printf builtin +/// +#include <boost/lexical_cast.hpp> +#include <gtest/gtest.h> + +#include "builtins/builtin_exceptions.h" +#include "core/interpreter.h" +#include "cppbash_builtin.h" + +namespace +{ + void verify_error(const std::vector<std::string>& arguments, const std::string& expected, interpreter& walker) + { + try + { + cppbash_builtin::exec("printf", arguments, std::cout, std::cerr, std::cin, walker); + FAIL(); + } + catch(interpreter_exception& e) + { + EXPECT_STREQ(expected.c_str(), e.what()); + } + } + + void verify_output(const std::vector<std::string>& arguments, const std::string& expected, interpreter& walker) + { + std::stringstream output; + EXPECT_EQ(0, cppbash_builtin::exec("printf", arguments, output, std::cerr, std::cin, walker)); + EXPECT_STREQ(expected.c_str(), output.str().c_str()); + } +} + +TEST(printf_builtin_test, bad_argument) +{ + interpreter walker; + verify_error({"-v"}, "printf: illegal number of arguments", walker); + verify_error({"-p"}, "printf: invalid option: -p", walker); +} + +TEST(printf_builtin_test, normal) +{ + interpreter walker; + verify_output({"-v", "foo", "%s\n", "bar"}, "", walker); + EXPECT_STREQ(walker.resolve<std::string>("foo").c_str(), "bar\n"); + + verify_output({"%s %s\n", "foo", "bar"}, "foo bar\n", walker); +} diff --git a/src/cppbash_builtin.cpp b/src/cppbash_builtin.cpp index a0299a9..97ab789 100644 --- a/src/cppbash_builtin.cpp +++ b/src/cppbash_builtin.cpp @@ -23,7 +23,12 @@ #include "cppbash_builtin.h" +#include <boost/spirit/include/karma.hpp> +#include <boost/spirit/include/qi.hpp> +#include <boost/spirit/include/phoenix.hpp> + #include "builtins/boolean_builtins.h" +#include "builtins/builtin_exceptions.h" #include "builtins/continue_builtin.h" #include "builtins/declare_builtin.h" #include "builtins/echo_builtin.h" @@ -31,10 +36,15 @@ #include "builtins/inherit_builtin.h" #include "builtins/let_builtin.h" #include "builtins/return_builtin.h" +#include "builtins/printf_builtin.h" #include "builtins/shopt_builtin.h" #include "builtins/source_builtin.h" #include "builtins/unset_builtin.h" +namespace qi = boost::spirit::qi; +namespace karma = boost::spirit::karma; +namespace phoenix = boost::phoenix; + cppbash_builtin::cppbash_builtin(BUILTIN_ARGS): _out_stream(&out), _err_stream(&err), _inp_stream(&in), _walker(walker) { } @@ -52,8 +62,41 @@ cppbash_builtin::builtins_type& cppbash_builtin::builtins() { {"true", boost::factory<true_builtin*>()}, {"false", boost::factory<false_builtin*>()}, {"return", boost::factory<return_builtin*>()}, + {"printf", boost::factory<printf_builtin*>()}, {"let", boost::factory<let_builtin*>()}, {"unset", boost::factory<unset_builtin*>()}, }); return *p; } + +void cppbash_builtin::transform_escapes(const std::string &string, + std::ostream& output) const +{ + using phoenix::val; + using qi::lit; + + auto escape_parser = + +( + lit('\\') >> + ( + lit('a')[output << val("\a")] | + lit('b')[output << val("\b")] | + // \e is a GNU extension + lit('e')[output << val("\033")] | + lit('f')[output << val("\f")] | + lit('n')[output << val("\n")] | + lit('r')[output << val("\r")] | + lit('t')[output << val("\t")] | + lit('v')[output << val("\v")] | + lit('c')[phoenix::throw_(suppress_output())] | + lit('\\')[output << val('\\')] | + lit("0") >> qi::uint_parser<unsigned, 8, 1, 3>()[ output << phoenix::static_cast_<char>(qi::_1)] | + lit("x") >> qi::uint_parser<unsigned, 16, 1, 2>()[ output << phoenix::static_cast_<char>(qi::_1)] + + ) | + qi::char_[output << qi::_1] + ); + + auto begin = string.begin(); + qi::parse(begin, string.end(), escape_parser); +} diff --git a/src/cppbash_builtin.h b/src/cppbash_builtin.h index 96d80a0..c42ee1a 100644 --- a/src/cppbash_builtin.h +++ b/src/cppbash_builtin.h @@ -120,6 +120,10 @@ class cppbash_builtin: public boost::noncopyable typedef std::map<std::string, boost::function< cppbash_builtin*(BUILTIN_ARGS) >> builtins_type; static builtins_type& builtins(); + /// \brief transforms escapes in echo input + /// \param the target string + /// \param the place to write + void transform_escapes(const std::string &string, std::ostream& output) const; }; #define BUILTIN_CONSTRUCTOR(name) \ |