Use a static table of tokens for agi::Path

The set of possible tokens is fixed, so using std::map is a bunch of
pointless overhead (that turns out to not even really simplify the
code).
This commit is contained in:
Thomas Goyne 2014-07-04 12:54:28 -07:00 committed by Thomas Goyne
parent 6fab17d860
commit 93522e30a8
3 changed files with 56 additions and 61 deletions

View file

@ -14,10 +14,6 @@
// //
// Aegisub Project http://www.aegisub.org/ // Aegisub Project http://www.aegisub.org/
/// @file path.cpp
/// @brief Platform-independent path code
/// @ingroup libaegisub
#include "libaegisub/path.h" #include "libaegisub/path.h"
#include "libaegisub/fs.h" #include "libaegisub/fs.h"
@ -26,44 +22,56 @@
#include <boost/range/distance.hpp> #include <boost/range/distance.hpp>
namespace { namespace {
template<class T, class U> static const char *tokens[] = {
typename T::const_iterator last_less_than(T const& cont, U const& value) { "?audio",
auto it = cont.upper_bound(value); "?data",
if (it != cont.begin()) "?dictionary",
--it; "?local",
return it; "?script",
"?temp",
"?user",
"?video"
};
int find_token(const char *str, size_t len) {
if (len < 5 || str[0] != '?') return -1;
int idx;
switch (str[1] + str[4]) {
case 'a' + 'i': idx = 0; break;
case 'd' + 'a': idx = 1; break;
case 'd' + 't': idx = 2; break;
case 'l' + 'a': idx = 3; break;
case 's' + 'i': idx = 4; break;
case 't' + 'p': idx = 5; break;
case 'u' + 'r': idx = 6; break;
case 'v' + 'e': idx = 7; break;
default: return -1;
} }
return strncmp(str, tokens[idx], strlen(tokens[idx])) == 0 ? idx : -1;
}
} }
namespace agi { namespace agi {
Path::Path() { Path::Path() {
tokens["?user"]; static_assert(sizeof(paths) / sizeof(paths[0]) == sizeof(tokens) / sizeof(tokens[0]),
tokens["?local"]; "Token and path arrays need to be the same size");
tokens["?data"];
tokens["?temp"];
tokens["?dictionary"];
FillPlatformSpecificPaths(); FillPlatformSpecificPaths();
tokens["?audio"];
tokens["?script"];
tokens["?video"];
} }
fs::path Path::Decode(std::string const& path) const { fs::path Path::Decode(std::string const& path) const {
const auto it = last_less_than(tokens, path); int idx = find_token(path.c_str(), path.size());
if (idx == -1 || paths[idx].empty())
if (!it->second.empty() && boost::starts_with(path, it->first)) return fs::path(path).make_preferred();
return (it->second/path.substr(it->first.size())).make_preferred(); return (paths[idx]/path.substr(strlen(tokens[idx]))).make_preferred();
return fs::path(path).make_preferred();
} }
fs::path Path::MakeRelative(fs::path const& path, std::string const& token) const { fs::path Path::MakeRelative(fs::path const& path, std::string const& token) const {
const auto it = tokens.find(token); int idx = find_token(token.c_str(), token.size());
if (it == tokens.end()) throw agi::InternalError("Bad token: " + token); if (idx == -1) throw agi::InternalError("Bad token: " + token);
return MakeRelative(path, it->second); return MakeRelative(path, paths[idx]);
} }
fs::path Path::MakeRelative(fs::path const& path, fs::path const& base) const { fs::path Path::MakeRelative(fs::path const& path, fs::path const& base) const {
@ -92,53 +100,47 @@ fs::path Path::MakeRelative(fs::path const& path, fs::path const& base) const {
fs::path Path::MakeAbsolute(fs::path path, std::string const& token) const { fs::path Path::MakeAbsolute(fs::path path, std::string const& token) const {
if (path.empty()) return path; if (path.empty()) return path;
const auto it = tokens.find(token); int idx = find_token(token.c_str(), token.size());
if (it == tokens.end()) throw agi::InternalError("Bad token: " + token); if (idx == -1) throw agi::InternalError("Bad token: " + token);
path.make_preferred(); path.make_preferred();
const auto str = path.string(); const auto str = path.string();
if (boost::starts_with(str, "?dummy") || boost::starts_with(str, "dummy-audio:")) if (boost::starts_with(str, "?dummy") || boost::starts_with(str, "dummy-audio:"))
return path; return path;
return (it->second.empty() || path.is_absolute()) ? path : it->second/path; return (paths[idx].empty() || path.is_absolute()) ? path : paths[idx]/path;
} }
std::string Path::Encode(fs::path const& path) const { std::string Path::Encode(fs::path const& path) const {
// Find the shortest encoding of path made relative to each token // Find the shortest encoding of path made relative to each token
std::string shortest = path.string(); std::string shortest = path.string();
size_t length = boost::distance(path); size_t length = boost::distance(path);
for (auto const& tok : tokens) { for (size_t i = 0; i < paths.size(); ++i) {
if (tok.second.empty()) continue; if (paths[i].empty()) continue;
const auto p = MakeRelative(path, tok.first); const auto p = MakeRelative(path, tokens[i]);
const size_t d = boost::distance(p); const size_t d = boost::distance(p);
if (d < length) { if (d < length) {
length = d; length = d;
shortest = (tok.first/p).string(); shortest = (tokens[i]/p).string();
} }
} }
return shortest; return shortest;
} }
void Path::SetToken(std::string const& token_name, fs::path const& token_value) { void Path::SetToken(const char *token_name, fs::path const& token_value) {
const auto it = tokens.find(token_name); int idx = find_token(token_name, strlen(token_name));
if (it == tokens.end()) throw agi::InternalError("Bad token: " + token_name); if (idx == -1) throw agi::InternalError("Bad token: " + std::string(token_name));
if (token_value.empty()) if (token_value.empty())
it->second = token_value; paths[idx] = token_value;
else if (!token_value.is_absolute()) else if (!token_value.is_absolute())
it->second.clear(); paths[idx].clear();
else { else {
it->second = token_value; paths[idx] = token_value;
it->second.make_preferred(); paths[idx].make_preferred();
if (fs::FileExists(it->second)) if (fs::FileExists(paths[idx]))
it->second = it->second.parent_path(); paths[idx] = paths[idx].parent_path();
}
paths.clear();
for (auto const& tok : tokens) {
if (!tok.second.empty())
paths[tok.second] = tok.first;
} }
} }

View file

@ -14,23 +14,16 @@
// //
// Aegisub Project http://www.aegisub.org/ // Aegisub Project http://www.aegisub.org/
/// @file path.h
/// @brief Common paths.
/// @ingroup libaegisub
#include <libaegisub/fs_fwd.h> #include <libaegisub/fs_fwd.h>
#include <array>
#include <boost/filesystem/path.hpp> #include <boost/filesystem/path.hpp>
#include <map>
namespace agi { namespace agi {
/// Class for handling everything path-related in Aegisub /// Class for handling everything path-related in Aegisub
class Path { class Path {
/// Token -> Path map /// Token -> Path map
std::map<std::string, fs::path> tokens; std::array<fs::path, 8> paths;
/// Path -> Token map
std::map<fs::path, std::string> paths;
/// Platform-specific code to fill in the default paths, called in the constructor /// Platform-specific code to fill in the default paths, called in the constructor
void FillPlatformSpecificPaths(); void FillPlatformSpecificPaths();
@ -74,7 +67,7 @@ public:
/// @param token_name A single word token beginning with '?' /// @param token_name A single word token beginning with '?'
/// @param token_value An absolute path to a directory or file /// @param token_value An absolute path to a directory or file
/// @throws InternalError if `token` is not a valid token name /// @throws InternalError if `token` is not a valid token name
void SetToken(std::string const& token_name, fs::path const& token_value); void SetToken(const char *token_name, fs::path const& token_value);
}; };
} }

View file

@ -40,7 +40,7 @@ agi::fs::path WinGetFolderPath(int folder) {
namespace agi { namespace agi {
void Path::FillPlatformSpecificPaths() { void Path::FillPlatformSpecificPaths() {
tokens["?temp"] = boost::filesystem::temp_directory_path(); SetToken("?temp", boost::filesystem::temp_directory_path());
SetToken("?user", WinGetFolderPath(CSIDL_APPDATA)/"Aegisub"); SetToken("?user", WinGetFolderPath(CSIDL_APPDATA)/"Aegisub");
SetToken("?local", WinGetFolderPath(CSIDL_LOCAL_APPDATA)/"Aegisub"); SetToken("?local", WinGetFolderPath(CSIDL_LOCAL_APPDATA)/"Aegisub");