Compare commits

..

5 commits

Author SHA1 Message Date
016455d8f2
0.0.11 2025-05-28 00:23:27 +03:00
c65b18a915
Создание правил при инициализации репозитория 2025-05-28 00:20:55 +03:00
c388d01a2f
Информативный вывод при вызове status 2025-05-28 00:20:27 +03:00
aa4260cdb9
Добавлено чтение из конфигурационного файла набора правил для отслеживания файлов 2025-05-28 00:18:55 +03:00
5797e83f07
Добавлен новый модуль rules для управления правилами отслеживания файлов в виде записей для gitignore
- create: создание правил из конфигурационного файла:
    - tracking - отслеживаемые файлы/пути
    - ignore - правила gitignore
- update: обновление существующих правил из конфигурационного файла
- reset: сброс изменений в правилах - откат на момент до внесения изменений в конфигурационный файл
- clear: очистить файл с правилами
- show: просмотр правил
- save: сохранить правила без возможности сброса (reset)
2025-05-28 00:17:42 +03:00
7 changed files with 267 additions and 5 deletions

View file

@ -10,5 +10,15 @@
"postsnag": [
"/usr/bin/ls",
"/usr/local/bin/script.sh"
]
],
"rules": {
"tracking": [
"/etc/*.conf"
],
"ignore": [
"/usr/exit/.gitignore",
"/usr/exit/dd",
"/file1"
]
}
}

View file

@ -21,6 +21,23 @@ int main(string[] args)
.optional
)
)
.add(new Command("rules", "Tracking rules")
.add(new Command("update", "Update rules")
.add(new Flag("r", "remove", "Removing from tracking the found ignored files")
.name("remove")
.optional
)
)
.add(new Command("reset", "Reset rules (restores rules to pre-change state)"))
.add(new Command("clear", "Clear rules"))
.add(new Command("save", "Save rules"))
.add(new Command("show", "Show rules")
.add(new Flag("c", "config", "Show rules from the configuration file")
.name("config")
.optional
)
)
)
.add(new Command("status", "Checking the status of tracked files"))
.add(new Command("diff", "Show changed data"))
.add(new Command("import", "Import snapshot from a tar.gz archive")
@ -160,6 +177,26 @@ int main(string[] args)
i.option("author", ""),
i.option("email", "")
)
)
.on("rules", (r) {
auto rules = new SnagRules(config);
r
.on("update", update =>
rules.update(update.flag("remove"))
)
.on("reset", reset =>
rules.reset()
)
.on("clear", clear =>
rules.clear()
)
.on("save", clear =>
rules.save()
)
.on("show", show =>
rules.show(show.flag("config"))
);
}
);
} catch (SnagException e) {
e.print();

View file

@ -5,6 +5,8 @@ import std.file;
import std.path;
import std.string;
import snag.lib;
import std.algorithm;
import std.array;
import snag.config.exception;
@ -13,6 +15,8 @@ class SnagConfig {
private string _project;
private string _email;
private string _author;
private string[] _tracking;
private string[] _ignore;
this(string configFile) {
string jsonText;
@ -93,10 +97,34 @@ class SnagConfig {
throw new SnagConfigException(
"The \"author\" parameter must contain an author name"
);
if ("rules" in jsonData) {
if (jsonData["rules"].type != JSONType.object)
throw new SnagConfigException(
"The \"rules\" parameter must be an object"
);
auto rules = jsonData["rules"];
if ("tracking" in rules) {
if (rules["tracking"].type != JSONType.array)
throw new SnagConfigException(
"The \"tracking\" parameter must be an array containing a set of paths to tracked files"
);
_tracking = rules["tracking"].array.map!(item => item.str).array;
}
if ("ignore" in rules) {
if (rules["ignore"].type != JSONType.array)
throw new SnagConfigException(
"The \"ignore\" parameter must contain a gitignore rule"
);
_ignore = rules["ignore"].array.map!(item => item.str).array;
}
}
}
@property string git() const { return _git; }
@property string project() const { return _project; }
@property string email() const { return _email; }
@property string author() const { return _author; }
@property const(string[]) tracking() const { return _tracking; }
@property const(string[]) ignore() const { return _ignore; }
}

View file

@ -13,6 +13,7 @@ import std.path;
import snag.lib;
import snag.core.exception;
import snag.core.rules;
class Snag {
private string[] _baseCommand;
@ -44,6 +45,22 @@ class Snag {
return result;
}
private string gitStatus(string shortStatus, bool formatted = false) {
immutable string[string] statusMap = [
"??": "Untracked",
"A": "Added",
"M": "Modified",
"D": "Deleted",
"R": "Renamed",
"C": "Copied",
"U": "Unmerged",
"T": "Type changed",
"!": "Ignored"
];
string fullStatus = statusMap.get(shortStatus, shortStatus);
return formatted && fullStatus.length < 8 ? fullStatus ~ "\t" : fullStatus;
}
void initialize(bool force) {
auto result = execute(_baseCommand ~ ["rev-parse", "--git-dir"]);
!force && !result.status &&
@ -68,6 +85,9 @@ class Snag {
["config", "user.name", _config.author],
"A Git repository initialization error occurred"
);
(new SnagRules(_config)).create();
writeln(
"The Git repository has been initialized successfully: ",
_config.git
@ -84,9 +104,11 @@ class Snag {
return;
}
writeln("The following list of files requires backup:");
result.output.split('\n').filter!(e => !e.strip.empty).each!((e) {
writefln("\t/%s", e.strip.split[1]);
});
result.output.split('\n')[0..$-1].map!(e =>
e.strip.split
).each!(e =>
writefln("\t%s\t/%s", gitStatus(e[0], true), e[1])
);
}
void create(string comment, string author, string email) {

View file

@ -2,3 +2,4 @@ module snag.core;
public import snag.core.core;
public import snag.core.exception;
public import snag.core.rules;

164
source/snag/core/rules.d Normal file
View file

@ -0,0 +1,164 @@
module snag.core.rules;
import snag.config;
import snag.core.exception;
import std.algorithm;
import std.array;
import std.path;
import std.stdio;
import std.string;
import std.conv;
import std.container;
import std.process;
import std.file;
class SnagRules {
private string[] _rules;
private SnagConfig _config;
private string[] _baseCommand;
private string _gitignore;
private string _gitignoreBak;
private auto git(string[] command, string message, string separator = ":\n\t") {
auto result = execute(_baseCommand ~ command);
if (result.status)
throw new SnagException(
message ~ separator ~ result.output.split('\n')[0]
);
return result;
}
private string[] generateGitignoreRules(string finalPath) {
string[] rules;
string[] parts = finalPath.split("/").filter!(p => !p.empty).array;
if (parts.length == 0) return rules;
rules ~= "/*";
rules ~= "!/" ~ parts[0];
rules ~= "/" ~ parts[0] ~ "/*";
if (parts.length > 1) {
string currentPath = "/" ~ parts[0];
foreach (i; 1 .. parts.length) {
currentPath ~= "/" ~ parts[i];
rules ~= "!" ~ currentPath;
if (i < parts.length.to!int - 1) {
rules ~= currentPath ~ "/*";
}
}
}
return rules;
}
private void generate() {
string[] rules;
auto tempRules = new RedBlackTree!string;
_config.tracking.each!(
track => rules ~= generateGitignoreRules(track)
);
rules.each!((rule) {
if (rule in tempRules) return;
tempRules.insert(rule);
_rules ~= rule;
});
_rules ~= _config.ignore;
}
this(SnagConfig config) {
_config = config;
_gitignore = config.git.buildPath("info/exclude");
_gitignoreBak = _gitignore ~ ".bak";
_baseCommand = format(
"git --git-dir=%s --work-tree=%s",
config.git, config.project
).split();
generate();
}
private void restoreUntracked() {
git(
["diff", "--cached", "--name-only"],
"Failed to retrieve the list of files removed from tracking"
).output.split('\n')[0..$-1].each!(
file => git(
["add", "-f", file],
"Failed to restore the file to tracking"
)
);
}
private void removeUntracked() {
git(
["ls-files", "-i", "-c", "--exclude-standard"],
"Failed to get the list of files to remove from tracking"
).output.split('\n')[0..$-1].each!(
file => git(
["rm", "--cached", file],
"Failed to remove file from tracking"
)
);
}
void create() {
auto file = File(_gitignore, "w");
_rules.each!(rule => file.writeln(rule));
file.close();
}
void update(bool remove) {
if (!_gitignoreBak.exists) {
readText(_gitignore).splitLines().equal(_rules) &&
throw new SnagException(
"Rule configuration is up-to-date and doesn't require updating"
);
copy(_gitignore, _gitignoreBak);
}
restoreUntracked();
create();
remove && removeUntracked();
}
void reset() {
!_gitignoreBak.exists &&
throw new SnagException(
"No rule changes to reset"
);
restoreUntracked();
rename(_gitignoreBak, _gitignore);
}
void clear() {
!readText(_gitignore).splitLines().length &&
throw new SnagException(
"The configuration has no rules"
);
!_gitignoreBak.exists && copy(_gitignore, _gitignoreBak);
restoreUntracked();
File(_gitignore, "w").close();
}
void show(bool config) {
if (config)
_rules.join('\n').writeln;
else
readText(_gitignore).write;
}
void save() {
if (!_gitignoreBak.exists) {
writeln("The rules are up to date");
return;
}
remove(_gitignoreBak);
writeln("The rules have been saved");
}
}

View file

@ -1,3 +1,3 @@
module snag.version_;
enum snagVersion = "0.0.10";
enum snagVersion = "0.0.11";