#include "Entity.hpp"
#include "Log.hpp"
#include <algorithm>
#include <cassert>
#include <ostream>
#include <string_view>
#include <unordered_set>
#include <utility>
Entity::Entity() :
start_line(0),
end_line(0),
lines_included(0),
lines_covered(0),
type(EntityType::ROOT),
visible(false),
hide(false) {
//
}
void Entity::add(Entity&& o) {
if (o.type == EntityType::NAMESPACE && !o.ingroup.empty()) {
warn("file " << o.path << " line " << o.ingroup.get_line_number() <<
"namespace cannot have @ingroup, ignoring");
o.ingroup.clear();
}
if (!o.ingroup.empty()) {
if (addToGroup(std::move(o))) {
return;
} else {
/* keep track of warnings and don't repeat them */
static std::unordered_set<std::string> warned;
if (warned.insert(o.ingroup.to_string()).second) {
warn("file " << o.path << " line " << o.ingroup.get_line_number() <<
": unrecognized group '" << o.ingroup.view() <<
"', groups must be defined in config file, ignoring @ingroup");
}
}
}
addToThis(std::move(o));
}
bool Entity::addToGroup(Entity&& o) {
/* search for immediate child of given name */
auto iter = std::find_if(groups.begin(), groups.end(),
[&o](const Entity& g) {
return g.name == o.ingroup;
});
if (iter != groups.end()) {
iter->addToThis(std::move(o));
return true;
}
/* search recursively for descendent of given name */
for (auto& g : groups) {
if (g.addToGroup(std::move(o))) {
return true;
}
}
return false;
}
void Entity::addToThis(Entity&& o) {
if (o.type == EntityType::NAMESPACE) {
auto iter = std::find_if(namespaces.begin(), namespaces.end(),
[&o](const Entity& ns) {
return ns.name == o.name;
});
if (iter == namespaces.end()) {
namespaces.push_back(std::move(o));
} else {
iter->merge(std::move(o));
}
} else if (o.type == EntityType::GROUP) {
groups.push_back(std::move(o));
} else if (o.type == EntityType::TYPE) {
types.push_back(std::move(o));
} else if (o.type == EntityType::TYPEDEF) {
typedefs.push_back(std::move(o));
} else if (o.type == EntityType::CONCEPT) {
concepts.push_back(std::move(o));
} else if (o.type == EntityType::VARIABLE) {
variables.push_back(std::move(o));
} else if (o.type == EntityType::FUNCTION) {
functions.push_back(std::move(o));
} else if (o.type == EntityType::OPERATOR) {
operators.push_back(std::move(o));
} else if (o.type == EntityType::ENUMERATOR) {
enums.push_back(std::move(o));
} else if (o.type == EntityType::MACRO) {
macros.push_back(std::move(o));
} else if (o.type == EntityType::TEMPLATE) {
// ignore, likely a parse error within the template declaration
} else if (o.type == EntityType::FILE || o.type == EntityType::DIR) {
/* maintain directory structure */
std::filesystem::path path = o.path, subdir;
path = path.parent_path();
Entity* e = this;
e->lines_included += o.lines_included;
e->lines_covered += o.lines_covered;
for (auto iter = path.begin(); iter != path.end(); ++iter) {
auto single = iter->string();
auto found = std::find_if(e->dirs.begin(), e->dirs.end(),
[&single](auto& s) { return s.name == single; });
subdir /= single;
if (found == e->dirs.end()) {
/* add subdirectory */
e = &e->dirs.emplace_back();
e->type = EntityType::DIR;
e->visible = true;
e->name = single;
e->path = subdir.string();
} else {
e = &(*found);
}
e->lines_included += o.lines_included;
e->lines_covered += o.lines_covered;
}
e->files.push_back(std::move(o));
} else {
warn("unrecognized entity type, ignoring");
}
visible = visible || o.visible;
}
void Entity::merge(Entity&& o) {
/* add namespaces via the merging logic of addToThis() */
std::for_each(o.namespaces.begin(), o.namespaces.end(),
[this](Entity& o) {
this->addToThis(std::move(o));
});
groups.splice(groups.end(), std::move(o.groups));
types.splice(types.end(), std::move(o.types));
typedefs.splice(types.end(), std::move(o.typedefs));
concepts.splice(concepts.end(), std::move(o.concepts));
variables.splice(variables.end(), std::move(o.variables));
functions.splice(functions.end(), std::move(o.functions));
operators.splice(operators.end(), std::move(o.operators));
macros.splice(macros.end(), std::move(o.macros));
enums.splice(enums.end(), std::move(o.enums));
dirs.splice(dirs.end(), std::move(o.dirs));
files.splice(files.end(), std::move(o.files));
/* merge line counts, recalling that -1 indicates excluded lines, so cannot
* simply add the two vectors */
end_line = uint32_t(std::max(line_counts.size(), o.line_counts.size()));
line_counts.resize(end_line, -1);
lines_included = 0;
lines_covered = 0;
for (uint32_t i = 0; i < end_line; ++i) {
int count = (i < o.line_counts.size()) ? o.line_counts[i] : -1;
if (line_counts[i] >= 0 || count >= 0) {
line_counts[i] = std::max(line_counts[i], 0) + std::max(count, 0);
++lines_included;
if (line_counts[i] > 0) {
++lines_covered;
}
}
}
if (ingroup.empty()) {
ingroup = std::move(o.ingroup);
}
name = o.name;
if (type == EntityType::TEMPLATE) {
decl += " ";
decl += std::move(o.decl);
}
docs += std::move(o.docs);
brief += std::move(o.brief);
type = o.type;
hide = hide || o.hide;
visible = visible || o.visible;
}
bool Entity::exists(std::filesystem::path& path) const {
auto parent_path = path.parent_path();
auto e = this;
for (auto iter = parent_path.begin(); iter != parent_path.end(); ++iter) {
auto single = iter->string();
auto found = std::find_if(e->dirs.begin(), e->dirs.end(),
[&single](auto& s) { return s.name == single; });
if (found == e->dirs.end()) {
return false;
} else {
e = &(*found);
}
}
auto file = path.filename().string();
auto found = std::find_if(e->files.begin(), e->files.end(),
[&file](auto& s) { return s.name == file; });
return found != e->files.end();
}
std::list<Entity*> Entity::get(std::filesystem::path& path) {
auto parent_path = path.parent_path();
std::list<Entity*> r;
auto e = this;
r.push_back(e);
for (auto iter = parent_path.begin(); iter != parent_path.end(); ++iter) {
auto single = iter->string();
auto found = std::find_if(e->dirs.begin(), e->dirs.end(),
[&single](auto& s) { return s.name == single; });
assert(found != e->dirs.end());
e = &(*found);
r.push_back(e);
}
auto file = path.filename().string();
auto found = std::find_if(e->files.begin(), e->files.end(),
[&file](auto& s) { return s.name == file; });
assert(found != e->files.end());
e = &(*found);
r.push_back(e);
return r;
}
void Entity::clear() {
*this = Entity();
}
void Entity::delete_by_predicate(std::function<bool(const Entity&)> p){
std::erase_if(namespaces, p);
std::erase_if(groups, p);
std::erase_if(types, p);
std::erase_if(concepts, p);
std::erase_if(variables, p);
std::erase_if(functions, p);
std::erase_if(operators, p);
std::erase_if(enums, p);
std::erase_if(macros, p);
std::erase_if(dirs, p);
std::erase_if(files, p);
auto r = [p](Entity& e){ e.delete_by_predicate(p); };
std::for_each(namespaces.begin(), namespaces.end(), r);
std::for_each(groups.begin(), groups.end(), r);
std::for_each(types.begin(), types.end(), r);
std::for_each(concepts.begin(), concepts.end(), r);
std::for_each(variables.begin(), variables.end(), r);
std::for_each(functions.begin(), functions.end(), r);
std::for_each(operators.begin(), operators.end(), r);
std::for_each(enums.begin(), enums.end(), r);
std::for_each(macros.begin(), macros.end(), r);
std::for_each(dirs.begin(), dirs.end(), r);
std::for_each(files.begin(), files.end(), r);
}