init
This commit is contained in:
commit
807c3b0094
11 changed files with 533 additions and 0 deletions
99
src/config.cc
Normal file
99
src/config.cc
Normal file
|
|
@ -0,0 +1,99 @@
|
|||
#include "config.hh"
|
||||
|
||||
#include <cstdlib>
|
||||
#include <fstream>
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
namespace nix::shorturl {
|
||||
|
||||
std::filesystem::path Config::defaultConfigPath()
|
||||
{
|
||||
if (auto env = std::getenv("NIX_SHORTURL_CONFIG"))
|
||||
return env;
|
||||
|
||||
if (auto xdg = std::getenv("XDG_CONFIG_HOME"))
|
||||
return std::filesystem::path(xdg) / "nix" / "shorturl.json";
|
||||
|
||||
if (auto home = std::getenv("HOME"))
|
||||
return std::filesystem::path(home) / ".config" / "nix" / "shorturl.json";
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
std::optional<Config> Config::load(const std::filesystem::path & path)
|
||||
{
|
||||
if (path.empty() || !std::filesystem::exists(path))
|
||||
return std::nullopt;
|
||||
|
||||
std::ifstream file(path);
|
||||
if (!file.is_open())
|
||||
return std::nullopt;
|
||||
|
||||
nlohmann::json json;
|
||||
try {
|
||||
file >> json;
|
||||
} catch (const nlohmann::json::parse_error &) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
Config config;
|
||||
|
||||
if (!json.contains("schemes") || !json["schemes"].is_object())
|
||||
return std::nullopt;
|
||||
|
||||
for (auto & [name, value] : json["schemes"].items()) {
|
||||
if (!value.is_object() || !value.contains("template") || !value["template"].is_string())
|
||||
continue;
|
||||
config.schemes[name] = SchemeConfig{value["template"].get<std::string>()};
|
||||
}
|
||||
|
||||
return config;
|
||||
}
|
||||
|
||||
std::optional<Config> Config::load()
|
||||
{
|
||||
return load(defaultConfigPath());
|
||||
}
|
||||
|
||||
bool Config::hasScheme(const std::string & scheme) const
|
||||
{
|
||||
return schemes.count(scheme) > 0;
|
||||
}
|
||||
|
||||
std::optional<std::string> Config::expand(
|
||||
const std::string & scheme,
|
||||
const std::string & path,
|
||||
const std::map<std::string, std::string, std::less<>> & query,
|
||||
const std::string & fragment) const
|
||||
{
|
||||
auto it = schemes.find(scheme);
|
||||
if (it == schemes.end())
|
||||
return std::nullopt;
|
||||
|
||||
std::string result = it->second.templateStr;
|
||||
|
||||
// Replace {path} with the actual path
|
||||
std::string placeholder = "{path}";
|
||||
auto pos = result.find(placeholder);
|
||||
if (pos != std::string::npos)
|
||||
result.replace(pos, placeholder.size(), path);
|
||||
|
||||
// Append query parameters
|
||||
if (!query.empty()) {
|
||||
bool first = (result.find('?') == std::string::npos);
|
||||
for (auto & [k, v] : query) {
|
||||
result += first ? '?' : '&';
|
||||
result += k + "=" + v;
|
||||
first = false;
|
||||
}
|
||||
}
|
||||
|
||||
// Append fragment
|
||||
if (!fragment.empty()) {
|
||||
result += "#" + fragment;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
} // namespace nix::shorturl
|
||||
35
src/config.hh
Normal file
35
src/config.hh
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
#pragma once
|
||||
|
||||
#include <filesystem>
|
||||
#include <functional>
|
||||
#include <map>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
|
||||
namespace nix::shorturl {
|
||||
|
||||
struct SchemeConfig {
|
||||
std::string templateStr;
|
||||
};
|
||||
|
||||
struct Config {
|
||||
std::map<std::string, SchemeConfig> schemes;
|
||||
|
||||
static std::filesystem::path defaultConfigPath();
|
||||
static std::optional<Config> load(const std::filesystem::path & path);
|
||||
static std::optional<Config> load();
|
||||
|
||||
/**
|
||||
* Expand a short URL scheme + path into a full URL string.
|
||||
* Query parameters and fragment are appended to the expanded URL.
|
||||
*/
|
||||
std::optional<std::string> expand(
|
||||
const std::string & scheme,
|
||||
const std::string & path,
|
||||
const std::map<std::string, std::string, std::less<>> & query = {},
|
||||
const std::string & fragment = {}) const;
|
||||
|
||||
bool hasScheme(const std::string & scheme) const;
|
||||
};
|
||||
|
||||
} // namespace nix::shorturl
|
||||
8
src/plugin.cc
Normal file
8
src/plugin.cc
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
#include "shorturl-scheme.hh"
|
||||
|
||||
#include <nix/util/types.hh>
|
||||
|
||||
static auto reg = nix::OnStartup([] {
|
||||
nix::fetchers::registerInputScheme(
|
||||
std::make_shared<nix::shorturl::ShortUrlInputScheme>());
|
||||
});
|
||||
97
src/shorturl-scheme.cc
Normal file
97
src/shorturl-scheme.cc
Normal file
|
|
@ -0,0 +1,97 @@
|
|||
#include "shorturl-scheme.hh"
|
||||
|
||||
#include <nix/fetchers/attrs.hh>
|
||||
#include <nix/util/url.hh>
|
||||
|
||||
namespace nix::shorturl {
|
||||
|
||||
ShortUrlInputScheme::ShortUrlInputScheme()
|
||||
{
|
||||
if (auto cfg = Config::load())
|
||||
config = std::move(*cfg);
|
||||
}
|
||||
|
||||
std::string_view ShortUrlInputScheme::schemeName() const
|
||||
{
|
||||
return "shorturl";
|
||||
}
|
||||
|
||||
std::optional<nix::fetchers::Input>
|
||||
ShortUrlInputScheme::inputFromURL(
|
||||
const nix::fetchers::Settings & settings,
|
||||
const ParsedURL & url,
|
||||
bool requireTree) const
|
||||
{
|
||||
if (!config.hasScheme(url.scheme))
|
||||
return std::nullopt;
|
||||
|
||||
auto expanded = config.expand(url.scheme, url.path, url.query, url.fragment);
|
||||
if (!expanded)
|
||||
return std::nullopt;
|
||||
|
||||
// Parse expanded URL and check it doesn't resolve to another shorturl scheme
|
||||
auto parsedExpanded = nix::parseURL(*expanded);
|
||||
if (config.hasScheme(parsedExpanded.scheme))
|
||||
throw nix::Error("shorturl scheme '%s' expands to another shorturl scheme '%s' (circular reference)",
|
||||
url.scheme, parsedExpanded.scheme);
|
||||
|
||||
// Delegate to the real scheme via Input::fromURL
|
||||
return nix::fetchers::Input::fromURL(settings, *expanded, requireTree);
|
||||
}
|
||||
|
||||
std::optional<nix::fetchers::Input>
|
||||
ShortUrlInputScheme::inputFromAttrs(
|
||||
const nix::fetchers::Settings & /* settings */,
|
||||
const nix::fetchers::Attrs & /* attrs */) const
|
||||
{
|
||||
// Lock files store the real type (e.g., "github"), not "shorturl".
|
||||
// We never match from attrs — this ensures lock files work without the plugin.
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
StringSet ShortUrlInputScheme::allowedAttrs() const
|
||||
{
|
||||
return {};
|
||||
}
|
||||
|
||||
std::pair<nix::ref<SourceAccessor>, nix::fetchers::Input>
|
||||
ShortUrlInputScheme::getAccessor(nix::ref<Store> store, const nix::fetchers::Input & input) const
|
||||
{
|
||||
// Reconstruct the real Input from the stored attrs (which have the real type).
|
||||
// Call the real scheme's getAccessor directly to avoid double fingerprint assignment
|
||||
// in Input::getAccessorUnchecked.
|
||||
auto attrs = input.toAttrs();
|
||||
auto realInput = nix::fetchers::Input::fromAttrs(*input.settings, std::move(attrs));
|
||||
return realInput.scheme->getAccessor(store, realInput);
|
||||
}
|
||||
|
||||
bool ShortUrlInputScheme::isLocked(const nix::fetchers::Input & input) const
|
||||
{
|
||||
auto attrs = input.toAttrs();
|
||||
auto realInput = nix::fetchers::Input::fromAttrs(*input.settings, std::move(attrs));
|
||||
return realInput.isLocked();
|
||||
}
|
||||
|
||||
std::optional<std::string>
|
||||
ShortUrlInputScheme::getFingerprint(nix::ref<Store> store, const nix::fetchers::Input & input) const
|
||||
{
|
||||
auto attrs = input.toAttrs();
|
||||
auto realInput = nix::fetchers::Input::fromAttrs(*input.settings, std::move(attrs));
|
||||
return realInput.getFingerprint(store);
|
||||
}
|
||||
|
||||
ParsedURL ShortUrlInputScheme::toURL(const nix::fetchers::Input & input) const
|
||||
{
|
||||
auto attrs = input.toAttrs();
|
||||
auto realInput = nix::fetchers::Input::fromAttrs(*input.settings, std::move(attrs));
|
||||
return realInput.toURL();
|
||||
}
|
||||
|
||||
bool ShortUrlInputScheme::isDirect(const nix::fetchers::Input & input) const
|
||||
{
|
||||
auto attrs = input.toAttrs();
|
||||
auto realInput = nix::fetchers::Input::fromAttrs(*input.settings, std::move(attrs));
|
||||
return realInput.isDirect();
|
||||
}
|
||||
|
||||
} // namespace nix::shorturl
|
||||
43
src/shorturl-scheme.hh
Normal file
43
src/shorturl-scheme.hh
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
#pragma once
|
||||
|
||||
#include "config.hh"
|
||||
|
||||
#include <nix/fetchers/fetchers.hh>
|
||||
|
||||
namespace nix::shorturl {
|
||||
|
||||
struct ShortUrlInputScheme : nix::fetchers::InputScheme
|
||||
{
|
||||
Config config;
|
||||
|
||||
ShortUrlInputScheme();
|
||||
|
||||
std::string_view schemeName() const override;
|
||||
|
||||
std::optional<nix::fetchers::Input>
|
||||
inputFromURL(
|
||||
const nix::fetchers::Settings & settings,
|
||||
const ParsedURL & url,
|
||||
bool requireTree) const override;
|
||||
|
||||
std::optional<nix::fetchers::Input>
|
||||
inputFromAttrs(
|
||||
const nix::fetchers::Settings & settings,
|
||||
const nix::fetchers::Attrs & attrs) const override;
|
||||
|
||||
StringSet allowedAttrs() const override;
|
||||
|
||||
std::pair<nix::ref<SourceAccessor>, nix::fetchers::Input>
|
||||
getAccessor(nix::ref<Store> store, const nix::fetchers::Input & input) const override;
|
||||
|
||||
bool isLocked(const nix::fetchers::Input & input) const override;
|
||||
|
||||
std::optional<std::string>
|
||||
getFingerprint(nix::ref<Store> store, const nix::fetchers::Input & input) const override;
|
||||
|
||||
ParsedURL toURL(const nix::fetchers::Input & input) const override;
|
||||
|
||||
bool isDirect(const nix::fetchers::Input & input) const override;
|
||||
};
|
||||
|
||||
} // namespace nix::shorturl
|
||||
Loading…
Add table
Add a link
Reference in a new issue