boost::program_options config file option with multiple tokens - c++

I can not seem to be able to read from config file multitoken options like I can from command line. What is the syntax for the config file?
This is how the option description is added:
//parser.cpp
- - -
po::options_description* generic;
generic=new po::options_description("Generic options");
generic->add_options()
("coordinate",po::value<std::vector<double> >()->multitoken(),"Coordinates (x,y)");
After which I parse command and config-files.
On command line '--coordinate 1 2' works. However, when I try in config file:
coordinate = 1,2
or
coordinate= 1 2
It fails giving a invalid_option_value exception. So what exactly is the syntax for config files in case of multitoken options?

In your configuration file, put each element of your vector on a different line.
coordinate=1
coordinate=2

You can achieve the behavior you seek by writing a custom validator. This custom validator accepts :
./progname --coordinate 1 2
./progname --coordinate "1 2"
#In config file:
coordinate= 1 2
Here is the code:
struct coordinate {
double x,y;
};
void validate(boost::any& v,
const vector<string>& values,
coordinate*, int) {
coordinate c;
vector<double> dvalues;
for(vector<string>::const_iterator it = values.begin();
it != values.end();
++it) {
stringstream ss(*it);
copy(istream_iterator<double>(ss), istream_iterator<double>(),
back_inserter(dvalues));
if(!ss.eof()) {
throw po::validation_error("Invalid coordinate specification");
}
}
if(dvalues.size() != 2) {
throw po::validation_error("Invalid coordinate specification");
}
c.x = dvalues[0];
c.y = dvalues[1];
v = c;
}
...
po::options_description config("Configuration");
config.add_options()
("coordinate",po::value<coordinate>()->multitoken(),"Coordinates (x,y)")
;
References:
http://www.boost.org/doc/libs/1_46_1/doc/html/program_options/howto.html#id2219998
https://stackoverflow.com/tags/boost-program-options/hot
Handle complex options with Boost's program_options

During finding myself confronted with a similar problem, I took the code above from Rob's answer (from May 4th, 2011), but had to change a few things due to changes in the boost architecture and C++11. I only cite the parts that I changed (or would have changed). The rest that is not within the validate function stays the same. For conformity reasons, I added the necessary std:: prefixes.
namespace po = boost::program_options;
void validate(boost::any& v,
const std::vector<std::string>& values,
coordinate*, int) {
coordinate c;
std::vector<double> dvalues;
for(const auto& val : values) {
std::stringstream ss(val);
std::copy(std::istream_iterator<double>(ss), std::istream_iterator<double>(),
std::back_inserter(dvalues));
if(!ss.eof()) {
throw po::invalid_option_value("Invalid coordinate specification");
}
}
if(dvalues.size() != 2) {
throw po::invalid_option_value("Invalid coordinate specification");
}
c.x = dvalues[0];
c.y = dvalues[1];
v = c;
}
The shift from po::validation_error to po::invalid_option_value was hinted in https://stackoverflow.com/a/12186109/4579106

Related

How to wrap several boolean flags into struct to pass them to a function with a convenient syntax

In some testing code there's a helper function like this:
auto make_condiment(bool salt, bool pepper, bool oil, bool garlic) {
// assumes that first bool is salt, second is pepper,
// and so on...
//
// Make up something according to flags
return something;
};
which essentially builds up something based on some boolean flags.
What concerns me is that the meaning of each bool is hardcoded in the name of the parameters, which is bad because at the call site it's hard to remember which parameter means what (yeah, the IDE can likely eliminate the problem entirely by showing those names when tab completing, but still...):
// at the call site:
auto obj = make_condiment(false, false, true, true); // what ingredients am I using and what not?
Therefore, I'd like to pass a single object describing the settings. Furthermore, just aggregating them in an object, e.g. std::array<bool,4>.
I would like, instead, to enable a syntax like this:
auto obj = make_smart_condiment(oil + garlic);
which would generate the same obj as the previous call to make_condiment.
This new function would be:
auto make_smart_condiment(Ingredients ingredients) {
// retrieve the individual flags from the input
bool salt = ingredients.hasSalt();
bool pepper = ingredients.hasPepper();
bool oil = ingredients.hasOil();
bool garlic = ingredients.hasGarlic();
// same body as make_condiment, or simply:
return make_condiment(salt, pepper, oil, garlic);
}
Here's my attempt:
struct Ingredients {
public:
enum class INGREDIENTS { Salt = 1, Pepper = 2, Oil = 4, Garlic = 8 };
explicit Ingredients() : flags{0} {};
explicit Ingredients(INGREDIENTS const& f) : flags{static_cast<int>(f)} {};
private:
explicit Ingredients(int fs) : flags{fs} {}
int flags; // values 0-15
public:
bool hasSalt() const {
return flags % 2;
}
bool hasPepper() const {
return (flags / 2) % 2;
}
bool hasOil() const {
return (flags / 4) % 2;
}
bool hasGarlic() const {
return (flags / 8) % 2;
}
Ingredients operator+(Ingredients const& f) {
return Ingredients(flags + f.flags);
}
}
salt{Ingredients::INGREDIENTS::Salt},
pepper{Ingredients::INGREDIENTS::Pepper},
oil{Ingredients::INGREDIENTS::Oil},
garlic{Ingredients::INGREDIENTS::Garlic};
However, I have the feeling that I am reinventing the wheel.
Is there any better, or standard, way of accomplishing the above?
Is there maybe a design pattern that I could/should use?
I think you can remove some of the boilerplate by using a std::bitset. Here is what I came up with:
#include <bitset>
#include <cstdint>
#include <iostream>
class Ingredients {
public:
enum Option : uint8_t {
Salt = 0,
Pepper = 1,
Oil = 2,
Max = 3
};
bool has(Option o) const { return value_[o]; }
Ingredients(std::initializer_list<Option> opts) {
for (const Option& opt : opts)
value_.set(opt);
}
private:
std::bitset<Max> value_ {0};
};
int main() {
Ingredients ingredients{Ingredients::Salt, Ingredients::Pepper};
// prints "10"
std::cout << ingredients.has(Ingredients::Salt)
<< ingredients.has(Ingredients::Oil) << "\n";
}
You don't get the + type syntax, but it's pretty close. It's unfortunate that you have to keep an Option::Max, but not too bad. Also I decided to not use an enum class so that it can be accessed as Ingredients::Salt and implicitly converted to an int. You could explicitly access and cast if you wanted to use enum class.
If you want to use enum as flags, the usual way is merge them with operator | and check them with operator &
#include <iostream>
enum Ingredients{ Salt = 1, Pepper = 2, Oil = 4, Garlic = 8 };
// If you want to use operator +
Ingredients operator + (Ingredients a,Ingredients b) {
return Ingredients(a | b);
}
int main()
{
using std::cout;
cout << bool( Salt & Ingredients::Salt ); // has salt
cout << bool( Salt & Ingredients::Pepper ); // doesn't has pepper
auto sp = Ingredients::Salt + Ingredients::Pepper;
cout << bool( sp & Ingredients::Salt ); // has salt
cout << bool( sp & Ingredients::Garlic ); // doesn't has garlic
}
note: the current code (with only the operator +) would not work if you mix | and + like (Salt|Salt)+Salt.
You can also use enum class, just need to define the operators
I would look at a strong typing library like:
https://github.com/joboccara/NamedType
For a really good video talking about this:
https://www.youtube.com/watch?v=fWcnp7Bulc8
When I first saw this, I was a little dismissive, but because the advice came from people I respected, I gave it a chance. The video convinced me.
If you look at CPP Best Practices and dig deeply enough, you'll see the general advice to avoid boolean parameters, especially strings of them. And Jonathan Boccara gives good reasons why your code will be stronger if you don't directly use the raw types, for the very reason that you've already identified.

Z3 Optimizer Unsatisfiability with Real Constraints Using C++ API

I'm running into a problem when trying to use the Z3 optimizer to solve graph partitioning problems. Specifically, the code bellow will fail to produce a satisfying model:
namespace z3 {
expr ite(context& con, expr cond, expr then_, expr else_) {
return to_expr(con, Z3_mk_ite(con, cond, then_, else_));;
}
}
bool smtPart(void) {
// Graph setup
vector<int32_t> nodes = {{ 4, 2, 1, 1 }};
vector<tuple<node_pos_t, node_pos_t, int32_t>> edges;
GraphType graph(nodes, edges);
// Z3 setup
z3::context con;
z3::optimize opt(con);
string n_str = "n", sub_p_str = "_p";
// Re-usable constants
z3::expr zero = con.int_val(0);
// Create the sort representing the different partitions.
const char* part_sort_names[2] = { "P0", "P1" };
z3::func_decl_vector part_consts(con), part_preds(con);
z3::sort part_sort =
con.enumeration_sort("PartID",
2,
part_sort_names,
part_consts,
part_preds);
// Create the constants that represent partition choices.
vector<z3::expr> part_vars;
part_vars.reserve(graph.numNodes());
z3::expr p0_acc = zero,
p1_acc = zero;
typename GraphType::NodeData total_weight = typename GraphType::NodeData();
for (const auto& node : graph.nodes()) {
total_weight += node.data;
ostringstream name;
name << n_str << node.id << sub_p_str;
z3::expr nchoice = con.constant(name.str().c_str(), part_sort);
part_vars.push_back(nchoice);
p0_acc = p0_acc + z3::ite(con,
nchoice == part_consts[0](),
con.int_val(node.data),
zero);
p1_acc = p1_acc + z3::ite(con,
nchoice == part_consts[1](),
con.int_val(node.data),
zero);
}
z3::expr imbalance = con.int_const("imbalance");
opt.add(imbalance ==
z3::ite(con,
p0_acc > p1_acc,
p0_acc - p1_acc,
p1_acc - p0_acc));
z3::expr imbalance_limit = con.real_val(total_weight, 100);
opt.add(imbalance <= imbalance_limit);
z3::expr edge_cut = zero;
for(const auto& edge : graph.edges()) {
edge_cut = edge_cut +
z3::ite(con,
(part_vars[edge.node0().pos()] ==
part_vars[edge.node1().pos()]),
zero,
con.int_val(edge.data));
}
opt.minimize(edge_cut);
opt.minimize(imbalance);
z3::check_result opt_result = opt.check();
if (opt_result == z3::check_result::sat) {
auto mod = opt.get_model();
size_t node_id = 0;
for (z3::expr& npv : part_vars) {
cout << "Node " << node_id++ << ": " << mod.eval(npv) << endl;
}
return true;
} else if (opt_result == z3::check_result::unsat) {
cerr << "Constraints are unsatisfiable." << endl;
return false;
} else {
cerr << "Result is unknown." << endl;
return false;
}
}
If I remove the minimize commands and use a solver instead of an optimize it will find a satisfying model with 0 imbalance. I can also get an optimize to find a satisfying model if I either:
Remove the constraint imbalance <= imbalance_limit or
Make the imbalance limit reducible to an integer. In this example the total weight is 8. If the imbalance limit is set to 8/1, 8/2, 8/4, or 8/8 the optimizer will find satisfying models.
I have tried to_real(imbalance) <= imbalance_limit to no avail. I also considered the possibility that Z3 is using the wrong logic (one that doesn't include theories for real numbers) but I haven't found a way to set that using the C/C++ API.
If anyone could tell me why the optimizer fails in the presence of the real valued constraint or could suggest improvements to my encoding it would be much appreciated. Thanks in advance.
Could you reproduce the result by using opt.to_string() to dump the state (just before the check())? This would create a string formatted in SMT-LIB2 with optimization commands. It is then easier to exchange benchmarks. You should see that it reports unsat with the optimization commands and sat if you comment out the optimization commands.
If you are able to produce a bug, then post an issue on GitHub.com/z3prover/z3.git with a repro.
If not, you can use Z3_open_log before you create the z3 context and record a rerunnable log file. It is possible (but not as easy) to dig into unsoundness bugs that way.
It turns out that this was a bug in Z3. I created an Issue on GitHub and they have since responded with a patch. I'm compiling and testing the fix now, but I expect it to work.
Edit: Yup, that patch fixed the issue for the command line tool and the C++ API.

Import CSV into Vertica using Rfc4180CsvParser and exclude header row

Is there a way to exclude the header row when importing data via the Rfc4180CsvParser? The COPY command has a SKIP option but the option doesn't seem to work when using the CSV parsers provided in the Vertica SDK.
Background
As background, the COPY command does not read CSV files by itself. For simple CSV files, one can say COPY schema.table FROM '/data/myfile.csv' DELIMITER ',' ENCLOSED BY '"'; but this will fail with data files which have string values with embedded quotes.
Adding ESCAPE AS '"' will generate an error ERROR 3169: ENCLOSED BY and ESCAPE AS can not be the same value . This is a problem as CSV values are enclosed and escaped by ".
Vertica SDK CsvParser extensions to the rescue
Vertica provides an SDK under /opt/vertica/sdk/examples with C++ programs that can be compiled into extensions. One of these is /opt/vertica/sdk/examples/ParserFunctions/Rfc4180CsvParser.cpp.
This works great as follows:
cd /opt/vertica/sdk/examples
make clean
vsql
==> CREATE LIBRARY Rfc4180CsvParserLib AS '/opt/vertica/sdk/examples/build/Rfc4180CsvParser.so';
==> COPY myschema.mytable FROM '/data/myfile.csv' WITH PARSER Rfc4180CsvParser();
Problem
The above works great except that it imports the first row of the data file as a row. The COPY command has a SKIP 1 option but this does not work with the parser.
Question
Is it possble to edit Rfc4180CsvParser.cpp to skip the first row, or better yet, take some parameter to specify number of rows to skip?
The program is just 135 lines but I don't see where/how to make this incision. Hints?
Copying the entire program below as I don't see a public repo to link to...
Rfc4180CsvParser.cpp
/* Copyright (c) 2005 - 2012 Vertica, an HP company -*- C++ -*- */
#include "Vertica.h"
#include "StringParsers.h"
#include "csv.h"
using namespace Vertica;
// Note, the class template is mostly for demonstration purposes,
// so that the same class can use each of two string-parsers.
// Custom parsers can also just pick a string-parser to use.
/**
* A parser that parses something approximating the "official" CSV format
* as defined in IETF RFC-4180: <http://tools.ietf.org/html/rfc4180>
* Oddly enough, many "CSV" files don't actually conform to this standard
* for one reason or another. But for sources that do, this parser should
* be able to handle the data.
* Note that the CSV format does not specify how to handle different
* data types; it is entirely a string-based format.
* So we just use standard parsers based on the corresponding column type.
*/
template <class StringParsersImpl>
class LibCSVParser : public UDParser {
public:
LibCSVParser() : colNum(0) {}
// Keep a copy of the information about each column.
// Note that Vertica doesn't let us safely keep a reference to
// the internal copy of this data structure that it shows us.
// But keeping a copy is fine.
SizedColumnTypes colInfo;
// An instance of the class containing the methods that we're
// using to parse strings to the various relevant data types
StringParsersImpl sp;
/// Current column index
size_t colNum;
/// Parsing state for libcsv
struct csv_parser parser;
// Format strings
std::vector<std::string> formatStrings;
/**
* Given a field in string form (a pointer to the first character and
* a length), submit that field to Vertica.
* `colNum` is the column number from the input file; how many fields
* it is into the current record.
*/
bool handleField(size_t colNum, char* start, size_t len) {
if (colNum >= colInfo.getColumnCount()) {
// Ignore column overflow
return false;
}
// Empty colums are null.
if (len==0) {
writer->setNull(colNum);
return true;
} else {
return parseStringToType(start, len, colNum, colInfo.getColumnType(c
olNum), writer, sp);
}
}
static void handle_record(void *data, size_t len, void *p) {
static_cast<LibCSVParser*>(p)->handleField(static_cast<LibCSVParser*>(p)
->colNum++, (char*)data, len);
}
static void handle_end_of_row(int c, void *p) {
// Ignore 'c' (the terminating character); trust that it's correct
static_cast<LibCSVParser*>(p)->colNum = 0;
static_cast<LibCSVParser*>(p)->writer->next();
}
virtual StreamState process(ServerInterface &srvInterface, DataBuffer &input
, InputState input_state) {
size_t processed;
while ((processed = csv_parse(&parser, input.buf + input.offset, input.s
ize - input.offset,
handle_record, handle_end_of_row, this)) > 0) {
input.offset += processed;
}
if (input_state == END_OF_FILE && input.size == input.offset) {
csv_fini(&parser, handle_record, handle_end_of_row, this);
return DONE;
}
return INPUT_NEEDED;
}
virtual void setup(ServerInterface &srvInterface, SizedColumnTypes &returnTy
pe);
virtual void destroy(ServerInterface &srvInterface, SizedColumnTypes &return
Type) {
csv_free(&parser);
}
};
template <class StringParsersImpl>
void LibCSVParser<StringParsersImpl>::setup(ServerInterface &srvInterface, Sized
ColumnTypes &returnType) {
csv_init(&parser, CSV_APPEND_NULL);
colInfo = returnType;
}
template <>
void LibCSVParser<FormattedStringParsers>::setup(ServerInterface &srvInterface,
SizedColumnTypes &returnType) {
csv_init(&parser, CSV_APPEND_NULL);
colInfo = returnType;
if (formatStrings.size() != returnType.getColumnCount()) {
formatStrings.resize(returnType.getColumnCount(), "");
}
sp.setFormats(formatStrings);
}
template <class StringParsersImpl>
class LibCSVParserFactoryTmpl : public ParserFactory {
public:
virtual void plan(ServerInterface &srvInterface,
PerColumnParamReader &perColumnParamReader,
PlanContext &planCtxt) {}
virtual UDParser* prepare(ServerInterface &srvInterface,
PerColumnParamReader &perColumnParamReader,
PlanContext &planCtxt,
const SizedColumnTypes &returnType)
{
return vt_createFuncObj(srvInterface.allocator,
LibCSVParser<StringParsersImpl>);
}
};
typedef LibCSVParserFactoryTmpl<StringParsers> LibCSVParserFactory;
RegisterFactory(LibCSVParserFactory);
typedef LibCSVParserFactoryTmpl<FormattedStringParsers> FormattedLibCSVParserFac
tory;
RegisterFactory(FormattedLibCSVParserFactory);
The quick and dirty way would be to just hardcode it. It's using a callback to handle_end_of_row. Track the row number and just don't process the first row . Something like:
static void handle_end_of_row(int c, void *ptr) {
// Ignore 'c' (the terminating character); trust that it's correct
LibCSVParser *p = static_cast<LibCSVParser*>(ptr);
p->colNum = 0;
if (rowcnt <= 0) {
p->bad_field = "";
rowcnt++;
} else if (p->bad_field.empty()) {
p->writer->next();
} else {
// libcsv doesn't give us the whole row to reject.
// So just write to the log.
// TODO: Come up with something more clever.
if (p->currSrvInterface) {
p->currSrvInterface->log("Invalid CSV field value: '%s' Row skipped.",
p->bad_field.c_str());
}
p->bad_field = "";
}
}
Also, best to initialize rownum = 0 in process since I think it will call this for each file in your COPY statement. There might be more clever ways of doing this. Basically, this will just process the record and then discard it.
As for supporting SKIP generically... look at TraditionalCSVParser for how to handle parameter passing. You'd have to add it to the parser factor prepare and send in the value to the LibCSVParser class and override getParameterType. Then in LibCSVParser you need to accept the parameter in the constructor, and modify process to skip the first skip rows. Then use that value instead of the hardcoded 0 above.

Is there an elegant way to cascade-merge two JSON trees using jsoncpp?

I am using jsoncpp to read settings from a JSON file.
I would like to have two cascading settings file, say MasterSettings.json and LocalSettings.json where LocalSettings is a subset of MasterSettings. I would like to load MasterSettings first and then LocalSettings. Where LocalSettings has a value that differs from MasterSettings, that value would overwrite the one from MasterSettings. Much like the cascade in CSS.
Is there any elegant way to do this with jsoncpp?
I'm going to assume your settings files are JSON objects.
As seen here, when JSONCpp parses a file, it clears the contents of the root node. This mean that trying to parse a new file on top of the old one won't preserve the old data. However, if you parse both files into separate Json::Value nodes, it's straight forward to recursively copy the values yourself by iterating over the keys in the second object using getMemberNames.
// Recursively copy the values of b into a. Both a and b must be objects.
void update(Json::Value& a, Json::Value& b) {
if (!a.isObject() || !b.isObject()) return;
for (const auto& key : b.getMemberNames()) {
if (a[key].isObject()) {
update(a[key], b[key]);
} else {
a[key] = b[key];
}
}
}
I know it has been a while. but...
In addition to the correct answer and the commentary, here is a code version for those who use a older g++ version:
void jsonMerge(Json::Value &a, Json::Value &b) {
if (!a.isObject() || !b.isObject()) return;
vector<string> member_name = b.getMemberNames();
string key = "";
for (unsigned i = 0, len = member_name.size(); i < len; i++) {
key = member_name[i];
if (!a[key].isNull() && a[key].type() == Json::objectValue && b[key].type() == Json::objectValue) {
jsonMerge(a[key], b[key]);
} else {
a[key] = b[key];
}
}
member_name.clear();
}

Get relative path from two absolute paths

I have two absolute filesystem paths (A and B), and I want to generate a third filesystem path that represents "A relative from B".
Use case:
Media player managing a playlist.
User adds file to playlist.
New file path added to playlist relative to playlist path.
In the future, entire music directory (including playlist) moved elsewhere.
All paths still valid because they are relative to the playlist.
boost::filesystem appears to have complete to resolve relative ~ relative => absolute, but nothing to do this in reverse (absolute ~ absolute => relative).
I want to do it with Boost paths.
With C++17 and its std::filesystem::relative, which evolved from boost, this is a no-brainer:
#include <filesystem>
#include <iostream>
namespace fs = std::filesystem;
int main()
{
const fs::path base("/is/the/speed/of/light/absolute");
const fs::path p("/is/the/speed/of/light/absolute/or/is/it/relative/to/the/observer");
const fs::path p2("/little/light/races/in/orbit/of/a/rogue/planet");
std::cout << "Base is base: " << fs::relative(p, base).generic_string() << '\n'
<< "Base is deeper: " << fs::relative(base, p).generic_string() << '\n'
<< "Base is orthogonal: " << fs::relative(p2, base).generic_string();
// Omitting exception handling/error code usage for simplicity.
}
Output (second parameter is base)
Base is base: or/is/it/relative/to/the/observer
Base is deeper: ../../../../../../..
Base is orthogonal: ../../../../../../little/light/races/in/orbit/of/a/rogue/planet
It uses std::filesystem::path::lexically_relative for comparison.
The difference to the pure lexical function is, that std::filesystem::relative resolves symlinks and normalizes both paths using
std::filesystem::weakly_canonical (which was introduced for relative) before comparison.
As of version 1.60.0 boost.filesystem does support this. You're looking for the member function path lexically_relative(const path& p) const.
Original, pre-1.60.0 answer below.
Boost doesn't support this; it's an open issue — #1976 (Inverse function for complete) — that nevertheless doesn't seem to be getting much traction.
Here's a vaguely naive workaround that seems to do the trick (not sure whether it can be improved):
#include <boost/filesystem/path.hpp>
#include <boost/filesystem/operations.hpp>
#include <boost/filesystem/fstream.hpp>
#include <stdexcept>
/**
* https://svn.boost.org/trac/boost/ticket/1976#comment:2
*
* "The idea: uncomplete(/foo/new, /foo/bar) => ../new
* The use case for this is any time you get a full path (from an open dialog, perhaps)
* and want to store a relative path so that the group of files can be moved to a different
* directory without breaking the paths. An IDE would be a simple example, so that the
* project file could be safely checked out of subversion."
*
* ALGORITHM:
* iterate path and base
* compare all elements so far of path and base
* whilst they are the same, no write to output
* when they change, or one runs out:
* write to output, ../ times the number of remaining elements in base
* write to output, the remaining elements in path
*/
boost::filesystem::path
naive_uncomplete(boost::filesystem::path const p, boost::filesystem::path const base) {
using boost::filesystem::path;
using boost::filesystem::dot;
using boost::filesystem::slash;
if (p == base)
return "./";
/*!! this breaks stuff if path is a filename rather than a directory,
which it most likely is... but then base shouldn't be a filename so... */
boost::filesystem::path from_path, from_base, output;
boost::filesystem::path::iterator path_it = p.begin(), path_end = p.end();
boost::filesystem::path::iterator base_it = base.begin(), base_end = base.end();
// check for emptiness
if ((path_it == path_end) || (base_it == base_end))
throw std::runtime_error("path or base was empty; couldn't generate relative path");
#ifdef WIN32
// drive letters are different; don't generate a relative path
if (*path_it != *base_it)
return p;
// now advance past drive letters; relative paths should only go up
// to the root of the drive and not past it
++path_it, ++base_it;
#endif
// Cache system-dependent dot, double-dot and slash strings
const std::string _dot = std::string(1, dot<path>::value);
const std::string _dots = std::string(2, dot<path>::value);
const std::string _sep = std::string(1, slash<path>::value);
// iterate over path and base
while (true) {
// compare all elements so far of path and base to find greatest common root;
// when elements of path and base differ, or run out:
if ((path_it == path_end) || (base_it == base_end) || (*path_it != *base_it)) {
// write to output, ../ times the number of remaining elements in base;
// this is how far we've had to come down the tree from base to get to the common root
for (; base_it != base_end; ++base_it) {
if (*base_it == _dot)
continue;
else if (*base_it == _sep)
continue;
output /= "../";
}
// write to output, the remaining elements in path;
// this is the path relative from the common root
boost::filesystem::path::iterator path_it_start = path_it;
for (; path_it != path_end; ++path_it) {
if (path_it != path_it_start)
output /= "/";
if (*path_it == _dot)
continue;
if (*path_it == _sep)
continue;
output /= *path_it;
}
break;
}
// add directory level to both paths and continue iteration
from_path /= path(*path_it);
from_base /= path(*base_it);
++path_it, ++base_it;
}
return output;
}
I just wrote code that can translate an absolute path to a relative path. It works in all my use cases, but I can not guarantee it is flawless.
I have abreviated boost::filesystem to 'fs' for readability. In the function definition, you can use fs::path::current_path() as a default value for 'relative_to'.
fs::path relativePath( const fs::path &path, const fs::path &relative_to )
{
// create absolute paths
fs::path p = fs::absolute(path);
fs::path r = fs::absolute(relative_to);
// if root paths are different, return absolute path
if( p.root_path() != r.root_path() )
return p;
// initialize relative path
fs::path result;
// find out where the two paths diverge
fs::path::const_iterator itr_path = p.begin();
fs::path::const_iterator itr_relative_to = r.begin();
while( itr_path != p.end() && itr_relative_to != r.end() && *itr_path == *itr_relative_to ) {
++itr_path;
++itr_relative_to;
}
// add "../" for each remaining token in relative_to
if( itr_relative_to != r.end() ) {
++itr_relative_to;
while( itr_relative_to != r.end() ) {
result /= "..";
++itr_relative_to;
}
}
// add remaining path
while( itr_path != p.end() ) {
result /= *itr_path;
++itr_path;
}
return result;
}
I was just thinking about using boost::filesystem for the same task, but - since my application uses both Qt and Boost libraries, I decided to use Qt which does this task with one simple method QString QDir::relativeFilePath( const QString & fileName ):
QDir dir("/home/bob");
QString s;
s = dir.relativeFilePath("images/file.jpg"); // s is "images/file.jpg"
s = dir.relativeFilePath("/home/mary/file.txt"); // s is "../mary/file.txt"
It works like a charm and saved me a few hours of my life.
Here's how I do it in the library I build on top of boost filesystem:
Step 1: Determine "deepest common root". Basically, its like the greatest common denominator for 2 paths. For example, if you're 2 paths are "C:\a\b\c\d" and "C:\a\b\c\l.txt" then the common root they both share is "C:\a\b\c\".
To get this, convert both paths into absolute- NOT canonical- form (you'll want to be able to do this for speculative paths & symlinks).
Step 2: To go from A to B, you suffix A with enough copies of "../" to shift up the directory tree to the common root, then add the string for B to travel down the tree to it. On windows you can have 2 paths with no common root, so going from any A to any B is not always possible.
namespace fs = boost::filesystem;
bool GetCommonRoot(const fs::path& path1,
const fs::path& path2,
fs::path& routeFrom1To2,
std::vector<fs::path>& commonDirsInOrder)
{
fs::path pathA( fs::absolute( path1));
fs::path pathB( fs::absolute( path2));
// Parse both paths into vectors of tokens. I call them "dir" because they'll
// be the common directories unless both paths are the exact same file.
// I also Remove the "." and ".." paths as part of the loops
fs::path::iterator iter;
std::vector<fs::path> dirsA;
std::vector<fs::path> dirsB;
for(iter = pathA.begin(); iter != pathA.end(); ++iter) {
std::string token = (*iter).string();
if(token.compare("..") == 0) { // Go up 1 level => Pop vector
dirsA.pop_back();
}
else if(token.compare(".") != 0) { // "." means "this dir" => ignore it
dirsA.push_back( *iter);
}
}
for(iter = pathB.begin(); iter != pathB.end(); ++iter) {
std::string token = (*iter).string();
if(token.compare("..") == 0) { // Go up 1 level => Pop vector
dirsB.pop_back();
}
else if(token.compare(".") != 0) { // "." means "this dir" => ignore it
dirsB.push_back( *iter);
}
}
// Determine how far to check in each directory set
size_t commonDepth = std::min<int>( dirsA.size(), dirsB.size());
if(!commonDepth) {
// They don't even share a common root- no way from A to B
return false;
}
// Match entries in the 2 vectors until we see a divergence
commonDirsInOrder.clear();
for(size_t i=0; i<commonDepth; ++i) {
if(dirsA[i].string().compare( dirsB[i].string()) != 0) { // Diverged
break;
}
commonDirsInOrder.push_back( dirsA[i]); // I could use dirsB too.
}
// Now determine route: start with A
routeFrom1To2.clear();
for(size_t i=0; i<commonDepth; ++i) {
routeFrom1To2 /= dirsA[i];
}
size_t backupSteps = dirsA.size() - commonDepth; // # of "up dir" moves we need
for(size_t i=0; i<backupSteps; ++i) {
routeFrom1To2 /= "../";
}
// Append B's path to go down to it from the common root
for(size_t i=commonDepth; i<dirsB.size(); ++i) {
routeFrom1To2 /= dirsB[i]; // ensures absolutely correct subdirs
}
return true;
}
This will do what you want- you go up from A until you hit the common folder it and B are both descendants of, then go down to B. You probably don't need the "commonDirsInOrder" return that I have, but the "routeFrom1To2" return IS the one you're asking for.
If you plan to actually change the working directory to "B" you can use "routeFrom1To2" directly. Be aware that this function will produce an absolute path despite all the ".." parts, but that shouldn't be a problem.
I have write down one simple solution for this trick.
There's no usage on boost libraries, only STL's std::string, std::vector.
The Win32 platform has been tested.
Just calling:
strAlgExeFile = helper.GetRelativePath(PathA, PathB);
And, it would return relative path from PathA to PathB.
Example:
strAlgExeFile = helper.GetRelativePath((helper.GetCurrentDir()).c_str(), strAlgExeFile.c_str());
#ifdef _WIN32
#define STR_TOKEN "\\"
#define LAST_FOLDER "..\\"
#define FOLDER_SEP "\\"
#define LINE_BREAK "\r\n"
#else
#define STR_TOKEN "/"
#define LAST_FOLDER "../"
#define FOLDER_SEP "/"
#define LINE_BREAK "\n"
#endif // _WIN32
void CHelper::SplitStr2Vec(const char* pszPath, vector<string>& vecString)
{
char * pch;
pch = strtok (const_cast < char*> (pszPath), STR_TOKEN );
while (pch != NULL)
{
vecString.push_back( pch );
pch = strtok (NULL, STR_TOKEN );
}
}
string& CHelper::GetRelativePath(const char* pszPath1,const char* pszPath2)
{
vector<string> vecPath1, vecPath2;
vecPath1.clear();
vecPath2.clear();
SplitStr2Vec(pszPath1, vecPath1);
SplitStr2Vec(pszPath2, vecPath2);
size_t iSize = ( vecPath1.size() < vecPath2.size() )? vecPath1.size(): vecPath2.size();
unsigned int iSameSize(0);
for (unsigned int i=0; i<iSize; ++i)
{
if ( vecPath1[i] != vecPath2[i])
{
iSameSize = i;
break;
}
}
m_strRelativePath = "";
for (unsigned int i=0 ; i< (vecPath1.size()-iSameSize) ; ++i)
m_strRelativePath += const_cast<char *> (LAST_FOLDER);
for (unsigned int i=iSameSize ; i<vecPath2.size() ; ++i)
{
m_strRelativePath += vecPath2[i];
if( i < (vecPath2.size()-1) )
m_strRelativePath += const_cast<char *> (FOLDER_SEP);
}
return m_strRelativePath;
}
I needed to do this without Boost and the other std based solution didn't do it for me so I reimplemented it. As I was working on this I realized that I'd done it before too...
Anyway, it's not as complete as some of the others but might be useful to people. It's Windows-specific; changes to make it POSIX involve directory separator and case sensitivity in the string compare.
Shortly after I got this implemented and working I had to transfer the surrounding functionality to Python so all of this just boiled down to os.path.relpath(to, from).
static inline bool StringsEqual_i(const std::string& lhs, const std::string& rhs)
{
return _stricmp(lhs.c_str(), rhs.c_str()) == 0;
}
static void SplitPath(const std::string& in_path, std::vector<std::string>& split_path)
{
size_t start = 0;
size_t dirsep;
do
{
dirsep = in_path.find_first_of("\\/", start);
if (dirsep == std::string::npos)
split_path.push_back(std::string(&in_path[start]));
else
split_path.push_back(std::string(&in_path[start], &in_path[dirsep]));
start = dirsep + 1;
} while (dirsep != std::string::npos);
}
/**
* Get the relative path from a base location to a target location.
*
* \param to The target location.
* \param from The base location. Must be a directory.
* \returns The resulting relative path.
*/
static std::string GetRelativePath(const std::string& to, const std::string& from)
{
std::vector<std::string> to_dirs;
std::vector<std::string> from_dirs;
SplitPath(to, to_dirs);
SplitPath(from, from_dirs);
std::string output;
output.reserve(to.size());
std::vector<std::string>::const_iterator to_it = to_dirs.begin(),
to_end = to_dirs.end(),
from_it = from_dirs.begin(),
from_end = from_dirs.end();
while ((to_it != to_end) && (from_it != from_end) && StringsEqual_i(*to_it, *from_it))
{
++to_it;
++from_it;
}
while (from_it != from_end)
{
output += "..\\";
++from_it;
}
while (to_it != to_end)
{
output += *to_it;
++to_it;
if (to_it != to_end)
output += "\\";
}
return output;
}