Добавлена новая команда diff для просмотра внесенных изменений на текущий момент.

Добавлена возможность установки комментария, имени автора, электронной почты при создании снимка.
Функции проверок через регулярные выражения перенесены в новый модуль lib.
This commit is contained in:
Alexander Zhirov 2025-05-25 18:34:52 +03:00
parent eff4fa2fe6
commit 4301c27ca9
Signed by: alexander
GPG key ID: C8D8BE544A27C511
8 changed files with 90 additions and 36 deletions

View file

@ -2,7 +2,7 @@
"git": "/tmp/testgit", "git": "/tmp/testgit",
"project": "/tmp/test", "project": "/tmp/test",
"email": "user@site.domain", "email": "user@site.domain",
"user": "snag", "author": "snag",
"presnag": [ "presnag": [
"/usr/bin/ls", "/usr/bin/ls",
"/usr/local/bin/script.sh" "/usr/local/bin/script.sh"

View file

@ -11,14 +11,37 @@ int main(string[] args)
auto argumets = new Program(programName, snagVersion) auto argumets = new Program(programName, snagVersion)
.add(new Command("init", "Initializing the repository for storing snapshots")) .add(new Command("init", "Initializing the repository for storing snapshots"))
.add(new Command("status", "Checking the status of tracked files")) .add(new Command("status", "Checking the status of tracked files"))
.add(new Command("create", "Create a new backup")) .add(new Command("diff", "Show changed data"))
.add(new Command("list", "List of backups") .add(new Command("create", "Create a new snapshot")
.add(new Option("c", "comment", "Specify comment")
.optional
.validateEachWith(
opt => opt.length > 0,
"cannot be empty"
)
)
.add(new Option("a", "author", "Specify author")
.optional
.validateEachWith(
opt => opt.length > 0,
"cannot be empty"
)
)
.add(new Option("e", "email", "Specify email")
.optional
.validateEachWith(
opt => opt.isValidEmail,
"must contain an email address"
)
)
)
.add(new Command("list", "List of snapshots")
.add(new Flag("c", "comment", "Show comment") .add(new Flag("c", "comment", "Show comment")
.name("comment") .name("comment")
.optional .optional
) )
.add(new Flag("u", "user", "Show user") .add(new Flag("a", "author", "Show author")
.name("user") .name("author")
.optional .optional
) )
.add(new Flag("e", "email", "Show email") .add(new Flag("e", "email", "Show email")
@ -33,7 +56,7 @@ int main(string[] args)
.optional .optional
.validateEachWith( .validateEachWith(
opt => opt.exists && opt.isFile, opt => opt.exists && opt.isFile,
"A JSON file path must be provided" "must specify the path to the JSON file"
) )
) )
.parse(args); .parse(args);
@ -56,16 +79,23 @@ int main(string[] args)
.on("init", init => .on("init", init =>
snag.initialize() snag.initialize()
) )
.on("diff", diff =>
snag.diff()
)
.on("status", status => .on("status", status =>
snag.status() snag.status()
) )
.on("create", create => .on("create", create =>
snag.create() snag.create(
create.option("comment", ""),
create.option("author", ""),
create.option("email", "")
)
) )
.on("list", list => .on("list", list =>
snag.list( snag.list(
list.flag("comment"), list.flag("comment"),
list.flag("user"), list.flag("author"),
list.flag("email") list.flag("email")
) )
) )

View file

@ -3,8 +3,8 @@ module snag.config.config;
import std.json; import std.json;
import std.file; import std.file;
import std.path; import std.path;
import std.regex;
import std.string; import std.string;
import snag.lib;
import snag.config.exception; import snag.config.exception;
@ -12,12 +12,7 @@ class SnagConfig {
private string _git; private string _git;
private string _project; private string _project;
private string _email; private string _email;
private string _user; private string _author;
private bool isValidEmail(string email) {
auto emailPattern = ctRegex!r"^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$";
return !matchFirst(email, emailPattern).empty;
}
this(string configFile) { this(string configFile) {
string jsonText; string jsonText;
@ -87,21 +82,21 @@ class SnagConfig {
~ _email ~ _email
); );
if ("user" !in jsonData) if ("author" !in jsonData)
throw new SnagConfigException( throw new SnagConfigException(
"The configuration file is missing the \"user\" parameter" "The configuration file is missing the \"author\" parameter"
); );
_user = jsonData["user"].str; _author = jsonData["author"].str;
if (!_user.length) if (!_author.length)
throw new SnagConfigException( throw new SnagConfigException(
"The \"user\" parameter must contain an user name" "The \"author\" parameter must contain an author name"
); );
} }
@property string git() const { return _git; } @property string git() const { return _git; }
@property string project() const { return _project; } @property string project() const { return _project; }
@property string email() const { return _email; } @property string email() const { return _email; }
@property string user() const { return _user; } @property string author() const { return _author; }
} }

View file

@ -8,8 +8,8 @@ import std.array;
import std.process; import std.process;
import std.algorithm; import std.algorithm;
import std.string; import std.string;
import std.regex;
import snag.lib;
import snag.core.exception; import snag.core.exception;
class Snag { class Snag {
@ -17,11 +17,6 @@ class Snag {
private SnagConfig _config; private SnagConfig _config;
private string _date; private string _date;
private bool isValidHash(string hash) {
auto hashPattern = ctRegex!r"^[a-fA-F0-9]{7}$";
return !matchFirst(hash, hashPattern).empty;
}
this(SnagConfig config) { this(SnagConfig config) {
_baseCommand = format( _baseCommand = format(
"git --git-dir=%s --work-tree=%s", "git --git-dir=%s --work-tree=%s",
@ -58,7 +53,7 @@ class Snag {
"A Git repository initialization error occurred" "A Git repository initialization error occurred"
); );
git( git(
["config", "user.name", _config.user], ["config", "user.name", _config.author],
"A Git repository initialization error occurred" "A Git repository initialization error occurred"
); );
writeln( writeln(
@ -84,7 +79,7 @@ class Snag {
}); });
} }
void create() { void create(string comment, string author, string email) {
auto result = git( auto result = git(
["status", "--porcelain"], ["status", "--porcelain"],
"An error occurred while checking the file tracking status" "An error occurred while checking the file tracking status"
@ -96,11 +91,16 @@ class Snag {
"Current file state doesn't need to be archived again" "Current file state doesn't need to be archived again"
); );
author.length && (environment["GIT_AUTHOR_NAME"] = author);
email.length && (environment["GIT_AUTHOR_EMAIL"] = email);
string message = comment.length ? comment : "Standard snapshot creation";
result = execute(_baseCommand ~ ["rev-parse", "--short", "HEAD"]); result = execute(_baseCommand ~ ["rev-parse", "--short", "HEAD"]);
if (result.status == 128) { if (result.status == 128) {
// Если это самый первый коммит после инициализации репозитория // Если это самый первый коммит после инициализации репозитория
git(["add", "."], "Failed to prepare files for archiving"); git(["add", "."], "Failed to prepare files for archiving");
git(["commit", "-m", "test"], "Failed to create a backup"); git(["commit", "-m"] ~ message, "Failed to create a backup");
writeln("Backup was created successfully"); writeln("Backup was created successfully");
return; return;
} else if (result.status != 0) } else if (result.status != 0)
@ -139,7 +139,7 @@ class Snag {
"Failed to prepare files for archiving" "Failed to prepare files for archiving"
); );
git( git(
["commit", "-m", "test"], ["commit", "-m"] ~ message,
"Failed to create a backup" "Failed to create a backup"
); );
newSnapshot = git( newSnapshot = git(
@ -153,7 +153,7 @@ class Snag {
"Failed to prepare files for archiving" "Failed to prepare files for archiving"
); );
git( git(
["commit", "-m", "test"], ["commit", "-m"] ~ message,
"Failed to create a backup" "Failed to create a backup"
); );
newSnapshot = git( newSnapshot = git(
@ -172,7 +172,7 @@ class Snag {
writeln("Backup was created successfully: ", newSnapshot); writeln("Backup was created successfully: ", newSnapshot);
} }
void list(bool comment, bool user, bool email) { void list(bool comment, bool author, bool email) {
string currentSnapshot = git( string currentSnapshot = git(
["rev-parse", "--short", "HEAD"], ["rev-parse", "--short", "HEAD"],
"Failed to retrieve current snapshot information" "Failed to retrieve current snapshot information"
@ -181,7 +181,7 @@ class Snag {
string format = "format:%h\t%ad"; string format = "format:%h\t%ad";
comment && (format ~= "\t%s"); comment && (format ~= "\t%s");
user && (format ~= "\t%an"); author && (format ~= "\t%an");
email && (format ~= "\t%ae"); email && (format ~= "\t%ae");
git( git(
@ -193,7 +193,7 @@ class Snag {
], ],
"Failed to retrieve the list of snapshots" "Failed to retrieve the list of snapshots"
).output.split('\n').map!(line => line.split('\t')).array.each!(e => ).output.split('\n').map!(line => line.split('\t')).array.each!(e =>
writefln("%s\t%s", writefln("%s\t%s",
currentSnapshot == e[0] ? " >" : "", currentSnapshot == e[0] ? " >" : "",
e.join("\t") e.join("\t")
) )
@ -227,4 +227,16 @@ class Snag {
); );
writeln("Backup was restored successfully: ", hash); writeln("Backup was restored successfully: ", hash);
} }
void diff() {
auto result = git(
["diff"],
"Failed to retrieve changes"
);
if (result.output.length) {
result.output.write;
return;
}
writeln("No changes at the moment");
}
} }

13
source/snag/lib/lib.d Normal file
View file

@ -0,0 +1,13 @@
module snag.lib.lib;
import std.regex;
bool isValidHash(string hash) {
auto hashPattern = ctRegex!r"^[a-fA-F0-9]{7}$";
return !matchFirst(hash, hashPattern).empty;
}
bool isValidEmail(string email) {
auto emailPattern = ctRegex!r"^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$";
return !matchFirst(email, emailPattern).empty;
}

View file

@ -0,0 +1,3 @@
module snag.lib;
public import snag.lib.lib;

View file

@ -1,5 +1,6 @@
module snag; module snag;
public import snag.version_; public import snag.version_;
public import snag.lib;
public import snag.config; public import snag.config;
public import snag.core; public import snag.core;

View file

@ -1,3 +1,3 @@
module snag.version_; module snag.version_;
enum snagVersion = "0.0.7"; enum snagVersion = "0.0.8";