Skip to content

Commit

Permalink
template index is now stored locally and there is now a latest hash a…
Browse files Browse the repository at this point in the history
…ttribute + some cleanup
DeaSTL committed Jan 20, 2024
1 parent 84c45c0 commit cfabca8
Showing 29 changed files with 368 additions and 461 deletions.
53 changes: 53 additions & 0 deletions include/Frate/Command/Exceptions.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
#pragma once
#include <Frate/Frate.hpp>

namespace Frate::Command {

class CommandException : public FrateException {
public:
CommandException(const std::string &message) : FrateException(message) {}
};

class MissingArgumentException : public CommandException {
public:
MissingArgumentException(const std::string &message)
: CommandException(message) {}
};

class InvalidArgumentValueException : public CommandException {
public:
InvalidArgumentValueException(const std::string &message)
: CommandException(message) {}
};

class InvalidArgumentTypeException : public CommandException {
public:
InvalidArgumentTypeException(const std::string &message)
: CommandException(message) {}
};

class InvalidCommandException : public CommandException {
public:
InvalidCommandException(const std::string &message)
: CommandException(message) {}
};

class InvalidSubcommandException : public CommandException {
public:
InvalidSubcommandException(const std::string &message)
: CommandException(message) {}
};

class ActionNotImplementedException : public CommandException {
public:
ActionNotImplementedException(const std::string &message)
: CommandException(message) {}
};

class HandlerNotImplementedException : public CommandException {
public:
HandlerNotImplementedException(const std::string &message)
: CommandException(message) {}
};

} // namespace Frate::Command
48 changes: 48 additions & 0 deletions include/Frate/CommandHandler.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
#pragma once
#include "Frate/Command/Exceptions.hpp"
#include <memory>
#include <string>
#include <vector>

namespace Frate::Command {
class Interface;

class CommandHandler {
private:
std::vector<std::string> aliases;
std::vector<std::string> flags{};
std::vector<CommandHandler> subcommands{};
std::vector<std::string> positional_args{};
bool implemented{true};
bool requires_project{true};
bool unlimited_args{false};
std::string docs;
std::shared_ptr<Interface> inter;

public:
CommandHandler(std::shared_ptr<Interface> inter) : inter(inter) {}

virtual ~CommandHandler() = default;

virtual void run() = 0;
virtual void registerOptions() = 0;
virtual void checkInput() = 0;

// Checkers
bool isImplemented() { return implemented; }
bool requiresProject() { return requires_project; }
bool hasUnlimitedArgs() { return unlimited_args; }

// Getters
std::vector<std::string> &getAliases() { return aliases; }
std::vector<std::string> &getFlags() { return flags; }
std::vector<CommandHandler> &getSubcommands() {
return subcommands;
}

std::vector<std::string> &getPosArgs() { return positional_args; }
std::string &getDocs() { return docs; }
std::shared_ptr<Interface> &getInterface() { return inter; }
};

} // namespace Frate::Command
66 changes: 66 additions & 0 deletions include/Frate/CommandTargetHandler.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
#pragma once
#include <Frate/Command/Exceptions.hpp>
#include <Frate/CommandHandler.hpp>

namespace Frate::Command {
class TargetHandler {
public:

virtual void actionAdd() {}

virtual void actionRemove() {}

virtual void actionList() {}

virtual void actionUpdate() {}

virtual void actionSet() {}

virtual void actionGet() {}

virtual void actionSearch() {}

};

class TargetPackageHandler : public TargetHandler, public CommandHandler {
public:
TargetPackageHandler(std::shared_ptr<Interface> inter)
: CommandHandler(inter) {}

~TargetPackageHandler() override = default;

void run() override {

}
void registerOptions() override {

}
void checkInput() override {

}
void actionAdd() override {}
void actionRemove() override {}
void actionList() override {}
void actionUpdate() override {}
};

class TargetPackageIndexHandler : public TargetHandler, public CommandHandler {
public:
TargetPackageIndexHandler(std::shared_ptr<Interface> inter)
: CommandHandler(inter) {}

~TargetPackageIndexHandler() override = default;

void run() override {

}
void registerOptions() override {

}
void checkInput() override {

}
void actionUpdate() override {}
};

} // namespace Frate::Command
1 change: 1 addition & 0 deletions include/Frate/Generators.hpp
Original file line number Diff line number Diff line change
@@ -9,6 +9,7 @@
namespace Frate::Generators {
namespace Project {
using ::Frate::Project::Config;
using ::Frate::Project::TemplateIndexEntry;
using ::Frate::Project::TemplateMeta;
using inja::Environment;
using nlohmann::json;
27 changes: 0 additions & 27 deletions include/Frate/Interface.hpp
Original file line number Diff line number Diff line change
@@ -10,33 +10,6 @@
namespace Frate::Command {
class Interface;
using Handler = struct Handler_s;
/* =============================
* Future interface for commands
* =============================
*/
class CommandHandler;

class CommandHandler {
public:
virtual ~CommandHandler() = default;
std::vector<std::string> aliases;
std::vector<std::string> flags{};
std::vector<CommandHandler> subcommands{};
std::vector<std::string> positional_args{};
bool implemented{true};
bool requires_project{true};
bool unlimited_args{false};
std::string docs;
std::function<bool(std::shared_ptr<Interface>)> callback{
[](std::shared_ptr<Interface> inter) -> bool {
(void)inter;
Utils::error << "This command has not been implemented yet"
<< std::endl;
return false;
}};
virtual bool run(std::shared_ptr<Interface> inter) = 0;
virtual bool options(std::shared_ptr<Interface> inter) = 0;
};

using Handler = struct Handler_s {
std::vector<std::string> aliases;
2 changes: 1 addition & 1 deletion include/Frate/Project/Config.hpp
Original file line number Diff line number Diff line change
@@ -2,7 +2,7 @@
#include "Frate/Dependency.hpp"
#include "Frate/Project/Mode.hpp"
#include "Frate/Project/ProjectPrompt.hpp"
#include "Frate/Project/Template.hpp"
#include "Frate/Project/TemplateConfig.hpp"
#include "Frate/Project/TemplateMeta.hpp"
#include <Frate/Package.hpp>
#include <Frate/RemoteServer.hpp>
6 changes: 6 additions & 0 deletions include/Frate/Project/Exceptions.hpp
Original file line number Diff line number Diff line change
@@ -20,6 +20,12 @@ namespace Frate::Project {
: TemplateException(message) {}
};

class TemplateIndexFailedToLoad : public TemplateException {
public:
TemplateIndexFailedToLoad(const std::string &message)
: TemplateException(message) {}
};

class TemplateConfigNotFound : public TemplateException {
public:
TemplateConfigNotFound(const std::string &message)
69 changes: 0 additions & 69 deletions include/Frate/Project/InstalledTemplate.hpp

This file was deleted.

File renamed without changes.
26 changes: 26 additions & 0 deletions include/Frate/Project/TemplateIndexEntry.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
#pragma once
#include <nlohmann/json.hpp>
#include <string>

namespace Frate::Project {

class TemplateIndexEntry {
private:
std::string name;
std::string description;
std::string latest_hash;
std::string git;

public:
friend void from_json(const nlohmann::json &j, TemplateIndexEntry &t);
friend void to_json(nlohmann::json &j, const TemplateIndexEntry &t);

std::string &getName() { return name; }

std::string &getDescription() { return description; }

std::string &getLatestHash() { return latest_hash; }

std::string &getGit() { return git; }
};
} // namespace Frate::Project
11 changes: 8 additions & 3 deletions include/Frate/Project/TemplateManager.hpp
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
#include "Frate/Project/TemplateMeta.hpp"
#include <Frate/Project/Config.hpp>
#include <Frate/Project/InstalledTemplate.hpp>
#include <vector>

namespace Frate::Project {
@@ -14,8 +13,6 @@ namespace Frate::Project {
std::unordered_map<std::string, TemplateMeta>>
installed;

std::unordered_map<std::string, std::string> latest_hashes;

bool index_loaded = false;
/*
* Loads the index from the github repo
@@ -48,6 +45,12 @@ namespace Frate::Project {
std::filesystem::path &template_path,
std::string &hash);

/*
* From the template index it will find the latest hash of the template name
* provided
*/
std::string get_latest_hash(const std::string &name);

public:
TemplateManager() = default;
~TemplateManager() = default;
@@ -66,6 +69,8 @@ namespace Frate::Project {
*/
void update(const std::string &name);

void updateIndex();

/*
* Creates a tempalte in a tmp path and then returns the path that rendering
* is supposed to happen in
4 changes: 3 additions & 1 deletion include/Frate/Project/TemplateMeta.hpp
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
#pragma once
#include "Frate/Constants.hpp"
#include "Frate/Project/TemplateIndexEntry.hpp"
#include <nlohmann/json.hpp>
#include <string>
#include <unordered_map>
@@ -23,7 +24,7 @@ namespace Frate::Project {
* Contains the mapping of the template and the relative path to the project
* path
*/
std::unordered_map<std::filesystem::path, std::filesystem::path> file_map;
std::unordered_map<std::filesystem::path, std::filesystem::path> file_map{};

std::vector<std::filesystem::path> non_template_files;

@@ -43,6 +44,7 @@ namespace Frate::Project {
public:
TemplateMeta();
TemplateMeta(const nlohmann::json &json_obj);
TemplateMeta(TemplateIndexEntry &entry);
friend void from_json(const nlohmann::json &json_obj, TemplateMeta &temp);
friend void to_json(nlohmann::json &json_obj, const TemplateMeta &temp);
friend std::ostream &operator<<(std::ostream &os_stream,
28 changes: 0 additions & 28 deletions include/Frate/Template/InstalledTemplate.hpp

This file was deleted.

6 changes: 0 additions & 6 deletions include/Frate/Template/New.hpp

This file was deleted.

44 changes: 0 additions & 44 deletions include/Frate/Template/Template.hpp

This file was deleted.

75 changes: 0 additions & 75 deletions include/Frate/Template/TemplateManager.hpp

This file was deleted.

19 changes: 0 additions & 19 deletions include/Frate/Template/TemplateMeta.hpp

This file was deleted.

9 changes: 9 additions & 0 deletions src/Command/Helpers/Interface.cpp
Original file line number Diff line number Diff line change
@@ -20,6 +20,7 @@
#include <Frate/Interface.hpp>
#include <memory>
#include <nlohmann/json_fwd.hpp>
#include <Frate/CommandTargetHandler.hpp>

namespace Frate::Command {

@@ -95,6 +96,14 @@ namespace Frate::Command {

bool execute(std::shared_ptr<Interface> inter) {


std::vector<std::unique_ptr<CommandHandler>> root_commands;

root_commands.push_back(std::make_unique<TargetPackageIndexHandler>(inter));
root_commands.push_back(std::make_unique<TargetPackageHandler>(inter));

TargetPackageHandler add_handler(inter);

OptionsInit::Main(inter);
inter->parse();
if (inter->args->count("version")) {
2 changes: 1 addition & 1 deletion src/Command/Helpers/Project.cpp
Original file line number Diff line number Diff line change
@@ -2,7 +2,7 @@
#include <Frate/Generators.hpp>
#include <Frate/Project/Config.hpp>
#include <Frate/Project/ProjectPrompt.hpp>
#include <Frate/Project/Template.hpp>
#include <Frate/Project/TemplateConfig.hpp>
#include <Frate/Utils/Macros.hpp>
#include <fstream>
#include <nlohmann/json_fwd.hpp>
2 changes: 1 addition & 1 deletion src/Command/Helpers/Template.cpp
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
#include <Frate/Project/Config.hpp>
#include <Frate/Project/Template.hpp>
#include <Frate/Project/TemplateConfig.hpp>
#include <Frate/Utils/Logging.hpp>
#include <Frate/Utils/Macros.hpp>

29 changes: 14 additions & 15 deletions src/Command/Helpers/TemplateMeta.cpp
Original file line number Diff line number Diff line change
@@ -13,22 +13,24 @@
namespace Frate::Project {
using std::filesystem::directory_entry;

TemplateMeta::TemplateMeta(const nlohmann::json &json_obj) {
TemplateMeta::TemplateMeta(const nlohmann::json &json_obj): install_path(Constants::INSTALLED_TEMPLATE_PATH / this->name / this->hash) {
Utils::verbose << "Creating template meta from json with contents: "
<< json_obj << std::endl;

from_json(json_obj, *this);
this->install_path =
Constants::INSTALLED_TEMPLATE_PATH / this->name / this->hash;
file_map = {};
}

TemplateMeta::TemplateMeta() {
this->install_path =
Constants::INSTALLED_TEMPLATE_PATH / this->name / this->hash;
TemplateMeta::TemplateMeta(): install_path(Constants::INSTALLED_TEMPLATE_PATH / this->name / this->hash) {
file_map = {};
}

TemplateMeta::TemplateMeta(TemplateIndexEntry &entry)
: name(entry.getName()), description(entry.getDescription()),
hash(entry.getLatestHash()), git(entry.getGit()),
install_path(Constants::INSTALLED_TEMPLATE_PATH / this->name /
this->hash) {}

void from_json(const nlohmann::json &json_obj, TemplateMeta &temp) {
FROM_JSON_FIELD(temp, name);
FROM_JSON_FIELD(temp, description);
@@ -107,23 +109,23 @@ namespace Frate::Project {
void TemplateMeta::refresh(std::shared_ptr<Config> config) {

std::filesystem::path override_path = config->path / "override";

Utils::FileFilter template_filter(install_path);
template_filter.addDirs({"scripts", "__init__", "__post__", "cmake_includes"});
template_filter.addDirs(
{"scripts", "__init__", "__post__", "cmake_includes"});
template_filter.addFiles({"CMakeLists.txt"});


auto install_path_files = template_filter.filterIn();


for (auto &file : install_path_files) {
std::filesystem::path relative_path =
std::filesystem::relative(file, install_path);
file_map[relative_path.string()] = file;
}

Utils::FileFilter override_filter(override_path);
override_filter.addDirs({"scripts", "__init__", "__post__", "cmake_includes"});
override_filter.addDirs(
{"scripts", "__init__", "__post__", "cmake_includes"});
override_filter.addFiles({"CMakeLists.txt"});

auto override_path_files = override_filter.filterIn();
@@ -207,7 +209,7 @@ namespace Frate::Project {

std::ofstream cpm_file;
try {
if (!std::filesystem::exists(config->path / "cmake")){
if (!std::filesystem::exists(config->path / "cmake")) {
std::filesystem::create_directories(config->path / "cmake");
}
cpm_file.open(config->path / "cmake/CPM.cmake");
@@ -222,7 +224,6 @@ namespace Frate::Project {
void TemplateMeta::render(std::shared_ptr<Config> config) {
Utils::info << "Rendering template" << std::endl;
this->env = std::make_shared<Lua::TemplateEnvironment>(config);


if (!scripts_loaded) {

@@ -234,7 +235,6 @@ namespace Frate::Project {
}
}


// Generate a list of all files that are not templates
std::vector<std::filesystem::path> all_files;

@@ -249,7 +249,6 @@ namespace Frate::Project {
{"scripts", "__init__", "__post__", "cmake_includes"});
non_template_filter.addFiles({"template.json"});


all_files = non_template_filter.filterOut();

for (auto &file : all_files) {
8 changes: 0 additions & 8 deletions src/Command/Template/New.cpp

This file was deleted.

2 changes: 1 addition & 1 deletion src/Generators/ProjectGenerator/LoadTemplateConfig.cpp
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
#include <Frate/Generators.hpp>
#include <Frate/Project/Config.hpp>
#include <Frate/Project/Template.hpp>
#include <Frate/Project/TemplateConfig.hpp>
#include <fstream>
#include <inja.hpp>

9 changes: 6 additions & 3 deletions src/Generators/ProjectGenerator/ProjectGenerator.cpp
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
#include "Frate/Project/TemplateIndexEntry.hpp"
#include "Frate/System/Build.hpp"
#include "Frate/Utils/General.hpp"
#include <Frate/Constants.hpp>
#include <Frate/Generators.hpp>
#include <Frate/Project/Config.hpp>
#include <Frate/Project/Exceptions.hpp>
#include <Frate/Project/TemplateMeta.hpp>
#include <filesystem>

namespace Frate::Generators::Project {
using Project::TemplateMeta;

json getTemplateIndex() {
std::cout << "Getting Template Index" << std::endl;
@@ -25,7 +26,7 @@ namespace Frate::Generators::Project {

Project::TemplateMeta current_template;

for (TemplateMeta templ : index) {
for (TemplateIndexEntry templ : index) {
if (inter->pro->type == templ.getName()) {
has_template = true;
current_template = templ;
@@ -50,7 +51,9 @@ namespace Frate::Generators::Project {
inter->templates->install(current_template.getName());

Utils::verbose << "Installed template: "
<< inter->pro->current_template.getName() << " with hash of: " << inter->pro->current_template.getHash() << std::endl;
<< inter->pro->current_template.getName()
<< " with hash of: "
<< inter->pro->current_template.getHash() << std::endl;

inter->pro->current_template.build(inter->pro);

2 changes: 1 addition & 1 deletion src/Generators/ProjectGenerator/TemplatePrompts.cpp
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
#include "Frate/Project/Template.hpp"
#include "Frate/Project/TemplateConfig.hpp"
#include <Frate/Generators.hpp>
#include <Frate/Project/Config.hpp>
#include <Frate/Project/TemplateMeta.hpp>
129 changes: 0 additions & 129 deletions src/Project/InstalledTemplate.cpp

This file was deleted.

18 changes: 18 additions & 0 deletions src/Project/TemplateIndexEntry.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
#include <Frate/Project/TemplateIndexEntry.hpp>
#include <Frate/Utils/Logging.hpp>
#include <Frate/Utils/Macros.hpp>

namespace Frate::Project {
void from_json(const nlohmann::json &json_obj, TemplateIndexEntry &template_obj){
FROM_JSON_FIELD(template_obj, name);
FROM_JSON_FIELD(template_obj, description);
FROM_JSON_FIELD(template_obj, latest_hash);
FROM_JSON_FIELD(template_obj, git);
}
void to_json(nlohmann::json &json_obj, const TemplateIndexEntry &template_obj){
TO_JSON_FIELD(template_obj, name);
TO_JSON_FIELD(template_obj, description);
TO_JSON_FIELD(template_obj, latest_hash);
TO_JSON_FIELD(template_obj, git);
}
}
131 changes: 103 additions & 28 deletions src/Project/TemplateManager.cpp
Original file line number Diff line number Diff line change
@@ -14,13 +14,63 @@
namespace Frate::Project {

void TemplateManager::load_index() {

if (this->index_loaded) {
return;
}

Utils::verbose << "Attempting to load template index file" << std::endl;

if (!std::filesystem::exists(Constants::TEMPLATE_INDEX_PATH)) {
Utils::verbose << "Template index file doesn't exist" << std::endl;
}
else {
std::ifstream file(Constants::TEMPLATE_INDEX_PATH);
if (!file.is_open()) {
Utils::error << "Failed to open template index file" << std::endl;
throw TemplateIndexNotLoaded("Failed to open template index file");
}

nlohmann::json json_obj;

try {
file >> json_obj;
} catch (std::exception &e) {
throw TemplateIndexFailedToLoad("Failed to parse template index file");
}

this->index = json_obj;
this->index_loaded = true;
Utils::verbose << "Loaded template index file" << std::endl;

return;
}

Utils::verbose << "Attempting to load template index upstream" << std::endl;
try {
nlohmann::json index_json =
Utils::fetchJson(Constants::TEMPLATE_INDEX_URL);
for (TemplateMeta template_json : index_json) {
index.push_back(template_json);
}
index_loaded = true;

try {
std::ofstream file(Constants::TEMPLATE_INDEX_PATH);
if (!file.is_open()) {
Utils::error
<< "Failed to open template index file to save upstream index"
<< std::endl;
}
else {
file << index_json.dump(2);
file.close();
}
} catch (std::exception &e) {
Utils::error << "Failed to save template upstream index file"
<< std::endl;
}

} catch (std::exception &e) {
Utils::error << "Failed to load template index" << std::endl;
Utils::error << e.what() << std::endl;
@@ -66,6 +116,22 @@ namespace Frate::Project {
return installed[name][hash];
}

std::string TemplateManager::get_latest_hash(const std::string &name){
//TODO: implement
//BLOCKED: need index template class
// Utils::verbose << "Getting latest hash for template: " << name << std::endl;
//
// TemplateManager templ;
// templ.load_index();
//
// for (TemplateMeta template_info : templ.getIndex()) {
// if (template_info.getName() == name) {
// return template_info.getHash();
// }
// }
// throw std::runtime_error("Template not found");

};
void TemplateManager::template_to_installed_path(
std::filesystem::path &tmp_path, std::filesystem::path &new_template_path,
std::string &hash) {
@@ -91,7 +157,7 @@ namespace Frate::Project {
Utils::verbose << "Copying: " << tmp_filtered_path << " to "
<< new_file_path << std::endl;

if(!std::filesystem::exists(new_file_path.parent_path())){
if (!std::filesystem::exists(new_file_path.parent_path())) {
std::filesystem::create_directories(new_file_path.parent_path());
}

@@ -108,14 +174,7 @@ namespace Frate::Project {
}

std::vector<Project::TemplateMeta> &TemplateManager::getIndex() {
if (index.empty() && !index_loaded) {
try {
load_index();
} catch (std::exception &e) {
Utils::error << e.what() << std::endl;
throw std::runtime_error("Failed to load template index");
}
}
load_index();
return index;
}

@@ -125,16 +184,45 @@ namespace Frate::Project {
return installed;
}

void updateIndex() {
Utils::verbose << "Updating template index" << std::endl;

if (!std::filesystem::exists(Constants::TEMPLATE_INDEX_PATH)) {
Utils::verbose << "Template index file doesn't exist" << std::endl;

std::ofstream file(Constants::TEMPLATE_INDEX_PATH);

if (!file.is_open()) {
Utils::error << "Failed to open template index file" << std::endl;
throw std::runtime_error("Failed to open template index file");
}

file.close();
}

try {
auto upstream_index = Utils::fetchJson(Constants::TEMPLATE_INDEX_URL);
std::ofstream file(Constants::TEMPLATE_INDEX_PATH);
if (!file.is_open()) {
Utils::error
<< "Failed to open template index file to save upstream index"
<< std::endl;
}
else {
file << upstream_index.dump(2);
file.close();
}
} catch (std::exception &e) {
Utils::error << "Failed to save template upstream index file"
<< std::endl;
}
}

TemplateMeta TemplateManager::install(const std::string &name,
std::string hash) {
Utils::info << "Installing template: " << name << std::endl;

try {
load_index();
} catch (TemplateIndexNotLoaded &e) {
Utils::error << e.what() << std::endl;
throw std::runtime_error("Failed to load template index");
}
load_index();

std::filesystem::path tmp_path =
Utils::randomTmpPath("frate-template-download-");
@@ -228,19 +316,6 @@ namespace Frate::Project {

installed[name][latest_commit.hash] = template_info;

// InstalledTemplate installed_template;
// installed_template.getName() = name;
// installed_template.getGit() = template_info.git;
// // We only want to add the latest commit
// installed_template.getCommits().push_back(latest_commit);
//
// Utils::info << "Putting template in config: " <<
// installed_template
// << std::endl;
//
// // Installing the template in the config
// installed.push_back(installed_template);

// Cleanup

std::filesystem::remove_all(tmp_path);
3 changes: 2 additions & 1 deletion src/main.cpp
Original file line number Diff line number Diff line change
@@ -9,6 +9,7 @@
int main(int argc, char **argv) {
// std::shared_ptr<Command::Context> ctx =
// std::make_shared<Command::Context>();


#ifdef TEST
Catch::Session session;
@@ -29,7 +30,7 @@ int main(int argc, char **argv) {
// TODO: Get some path checking in this bitch
using namespace Frate::Command;

Interface inter_ptr = Interface(argc, argv);
Interface inter_ptr(argc, argv);
auto inter = std::make_shared<Interface>(inter_ptr);
if (execute(inter)) {
return 0;

0 comments on commit cfabca8

Please sign in to comment.