Reputation: 4762
---- Question ----
I'm attempting to convert the file name "/path/to/Module.cpp" into a const char*
holding the value "Module" compile-time. This helps me print ergonomic logging in a microcontroller. I use GCC 8.3.
There are lots of fine examples on how to strip only the path component, e.g. an adaptation of this answer
constexpr const char* filename(std::string_view path) {
return path.substr(path.find_last_of('/') + 1).data();
}
static const char* TAG = filename(__FILE__);
This results in TAG
holding "Module.cpp". That's a good start, but I'd like to strip the ".cpp" as well. Compile-time, naturally. Any ideas?
---- Answer ----
Thanks to @KamilCuk I was able to come up with the following header file which, when included by a cpp file, creates a nice tag for ESP IDF logging macros:
#ifndef _LOG_HPP_
#define _LOG_HPP_
#include "esp_log.h"
#include <string_view>
// Note: path processing naïvely assumes a valid Unix file path containing
// directories and an extension.
/**
* Find the length of stem in a file name
* @param path A file name with '/' as path separator and '.' as extension separator
* @return Number of characters in file stem excluding terminating zero
*/
constexpr size_t stemNameLen(const std::string_view& path) {
return path.find_last_of('.') - path.find_last_of('/') - 1;
}
// Rudimentary unit test
static_assert(stemNameLen(std::string_view("../foo/bar/MyModule.cpp")) == 8);
/**
* Get the stem in a file name
* @param path A file name with '/' as path separator and '.' as extension separator
* @return A string_view holding the stem of the input file name
*/
constexpr std::string_view stemName(const std::string_view& path) {
return path.substr(path.find_last_of('/') + 1, stemNameLen(path));
}
// Rudimentary unit test
static_assert(stemName(std::string_view("../foo/bar/MyModule.cpp")) == "MyModule");
/// Helper class for creating a C-style zero-terminated string from a string_view
template <size_t N>
class TerminatedString {
public:
constexpr TerminatedString(const std::string_view& path) {
size_t i = 0;
for (auto it = path.cbegin(); i + 1 < sizeof(_str) && it != path.cend(); i++, it++) {
_str[i] = *it;
}
}
constexpr const char *str() const {
return _str;
}
private:
char _str[N] {'\0', };
};
/// Module name from the file which includes this header
static constexpr std::string_view moduleName = stemName(__BASE_FILE__);
/// A zero-terminated log prefix from module name, initialized compile-time
static constexpr TerminatedString<moduleName.length() + 1> logPrefix{moduleName};
// Sanity check, assumes all file stems in project are less than 100 chars
static_assert(moduleName.length() < 100);
#define err(args...) ESP_LOGE(logPrefix.str(), args)
#define warn(args...) ESP_LOGW(logPrefix.str(), args)
#define info(args...) ESP_LOGI(logPrefix.str(), args)
#define debug(args...) ESP_LOGD(logPrefix.str(), args)
#define trace(args...) ESP_LOGV(logPrefix.str(), args)
#endif // _LOG_HPP_
Upvotes: 1
Views: 547
Reputation: 140880
The following "works", but it's not perfectly clean. Cleaning it up is left as an exercise. Anyway, it may show you how to do it:
#include <cstdio>
constexpr unsigned long filename_we_size(const char *path) {
// I know - some C pointer stuff. I don't know which C++ functions are
// constexpr which are not, and I am too lazy to check, so I used C pointers.
// Preferably rewrite it in more C++-ish style.
auto i = path;
while (*i) ++i;
auto end = i;
while (*i != '.' && i != path) --i;
const auto ext_len = end - i;
while (*i != '/' && i != path) --i;
const auto filename_len = end - i;
return filename_len - ext_len;
}
constexpr const char *filename_we(const char *path, char *out) {
auto i = path;
while (*i) ++i;
while (*i != '/' && i != path) --i;
if (*i) ++i;
auto r = out;
while (*i != '.' && *i) *r++ = *i++;
*r = 0;
return r;
}
// A structure. *Something* has to store the memory.
template <size_t N>
struct Tag {
char mem[N]{};
constexpr Tag(const char *path) {
filename_we(path, mem);
}
constexpr const char *str() const {
return mem;
}
constexpr operator const char *() const{
return mem;
}
constexpr char operator[](size_t i) const {
return mem[i];
}
};
static constexpr Tag<filename_we_size(__FILE__)> TAG{__FILE__};
int main() {
printf("%s\n", TAG.str());
}
Upvotes: 3