Here is a custom directive heavily based on the explanations here.
Unfortunately due to the fact that the information you need is contained in a private member of the iterator, this only works with the really simple example that you posted. If you output anything else before everythings gets misaligned. You can work around this if you are willing to modify slightly the code in detail/output_iterator.hpp. You can either comment the "private:" in position_policy
or simply add a member function get_out_column
in the same vein as get_out_count
.
In order to use it you need to change your:
karma::int_ % karma::eol;
to:
custom_generator::align_list_to_current_position[karma::int_];
As you can see the custom directive requires a lot of boilerplate but big part of this code is common to every directive. In fact, besides changing the names, I have only needed to change three things:
Make sure that tracking
is in the set of required properties:
typedef typename boost::mpl::int_<
Subject::properties::value | karma::generator_properties::tracking
> properties;
Make the attribute of the directive be the same as the one a list(%) would have(by looking here):
template <typename Context, typename Iterator>
struct attribute
: boost::spirit::traits::build_std_vector<
typename boost::spirit::traits::attribute_of<Subject, Context, Iterator>::type
>
{};
And finally change the generate
function. In this function I simply build a list that has as its left member whatever you passed to the directive and as its right one the concatenation of karma::eol and as many spaces are as needed to be aligned.
#include <iostream>
#include <string>
#include <vector>
#include <boost/spirit/include/karma.hpp>
//START OF ALIGN_LIST_TO_CURRENT_POSITION.HPP
#include <boost/spirit/include/karma_generate.hpp>
///////////////////////////////////////////////////////////////////////////////
// definition the place holder
namespace custom_generator
{
BOOST_SPIRIT_TERMINAL(align_list_to_current_position);
}
///////////////////////////////////////////////////////////////////////////////
// implementation the enabler
namespace boost { namespace spirit
{
// We want custom_generator::align_list_to_current_position to be usable as a directive only,
// and only for generator expressions (karma::domain).
template <>
struct use_directive<karma::domain, custom_generator::tag::align_list_to_current_position>
: mpl::true_ {};
}}
///////////////////////////////////////////////////////////////////////////////
// implementation of the generator
namespace custom_generator
{
// That's the actual columns generator
template <typename Subject>
struct align_list_to_current_position_generator
: boost::spirit::karma::unary_generator<
align_list_to_current_position_generator<Subject> >
{
// Define required output iterator properties: take the properties needed by the subject and add tracking
typedef typename boost::mpl::int_<Subject::properties::value | boost::spirit::karma::generator_properties::tracking> properties;
// Define the attribute type exposed by this parser component
template <typename Context, typename Iterator>
struct attribute
: boost::spirit::traits::build_std_vector<
typename boost::spirit::traits::attribute_of<Subject, Context, Iterator>::type>
{};
align_list_to_current_position_generator(Subject const& s)
: subject(s)
{}
// This function is called during the actual output generation process.
// It dispatches to the embedded generator while supplying a new
// delimiter to use
template <typename OutputIterator, typename Context
, typename Delimiter, typename Attribute>
bool generate(OutputIterator& sink, Context& ctx
, Delimiter const& delimiter, Attribute const& attr) const
{
using boost::spirit::karma::repeat;
using boost::spirit::karma::char_;
using boost::spirit::karma::eol;
using boost::spirit::karma::domain;
std::size_t column = sink.get_out_count();
//This would only work if you comment "private:" in line 82 of boost/spirit/home/karma/detail/output_iterator.hpp
// std::size_t column = sink.track_position_data.get_column()-1;
return boost::spirit::compile<domain>(subject%(eol << repeat(column)[char_(" ")])).generate(sink, ctx, delimiter, attr);
}
// This function is called during error handling to create
// a human readable string for the error context.
template <typename Context>
boost::spirit::info what(Context& ctx) const
{
return boost::spirit::info("align_list_to_current_position", subject.what(ctx));
}
Subject subject;
};
}
///////////////////////////////////////////////////////////////////////////////
// instantiation of the generator
namespace boost { namespace spirit { namespace karma
{
// This is the factory function object invoked in order to create
// an instance of our align_list_to_current_position_generator.
template <typename Subject, typename Modifiers>
struct make_directive<custom_generator::tag::align_list_to_current_position, Subject, Modifiers>
{
typedef custom_generator::align_list_to_current_position_generator<Subject> result_type;
result_type operator()(unused_type, Subject const& s, unused_type) const
{
return result_type(s);
}
};
}}}
//END OF ALIGN_LIST_TO_CURRENT_POSITION.HPP
int main() {
std::vector<int> v { 0, 1, 2, 3 };
{
namespace karma = boost::spirit::karma;
using custom_generator::align_list_to_current_position;
karma::rule<std::ostream_iterator<char>, std::vector<int>() > myRule =
karma::lit("Some text: ") << align_list_to_current_position[karma::int_];
karma::generate(std::ostream_iterator<char>(std::cout), myRule, v);
std::cout << std::endl;
//This rule would work if you make the changes mentioned in align_list_to_current_position_generator::generate
karma::rule<std::ostream_iterator<char>, std::vector<int>() > myRuleThatFails =
karma::lit(":_(") << karma::eol << karma::lit("Some text: ") << align_list_to_current_position[karma::int_ << karma::int_];
karma::generate(std::ostream_iterator<char>(std::cout), myRuleThatFails, v);
}
return 0;
}