diff --git a/README.md b/README.md index 9637f63..680b5db 100644 --- a/README.md +++ b/README.md @@ -2,26 +2,6 @@ Symlinking CLI tool for dotfiles (or any other configuration files organized in a directory). -## Install - -| Operating system | Link | -|---|---| -| Ubuntu 22.04 (Jammy Jellyfish) | [:arrow_upper_right:](https://github.com/kucaahbe/myrc.d/releases/latest) | -| Ubuntu 20.04 (Focal Fossa) | [:arrow_upper_right:](https://github.com/kucaahbe/myrc.d/releases/latest) | -| macOS | [:arrow_upper_right:](https://github.com/kucaahbe/myrc.d/releases/latest) | - -### Build - -Install [D compiler](https://dlang.org/download.html), `cd` into directory and run `dub`: - -```console -dub build --build=release -``` - -#### Build issues - -[documented here](build_issues.md) - ## Usage In the directory from where the app is started, the `install.sdl` manifest file is required: there should be specified @@ -30,6 +10,15 @@ folder installation details: ``` # the "install" section is mandatory install { + # executes specified commands, unless requirements below met + exec "git submodule -q update --init" { + # requirements: + // file should be created in $PWD + creates:file `LS_COLORS/.git` + creates:file `zsh-completions/.git` + creates:file `zsh-syntax-highlighting/.git` + } + ln "bashrc", "~/.bashrc" ln "bash_profile", "~/.bash_profile" } @@ -50,3 +39,23 @@ me@host:~/doftiles$ myrc /home/me/dotfiles/bashrc -> /home/me/.bashrc /home/me/dotfiles/bash_profile -> /home/me/.bash_profile ``` + +## Install + +| Operating system | Link | +|---|---| +| Ubuntu 22.04 (Jammy Jellyfish) | [:arrow_upper_right:](https://github.com/kucaahbe/myrc.d/releases/latest) | +| Ubuntu 20.04 (Focal Fossa) | [:arrow_upper_right:](https://github.com/kucaahbe/myrc.d/releases/latest) | +| macOS | [:arrow_upper_right:](https://github.com/kucaahbe/myrc.d/releases/latest) | + +### Build + +Install [D compiler](https://dlang.org/download.html), `cd` into directory and run `dub`: + +```console +dub build --build=release +``` + +#### Build issues + +[documented here](build_issues.md) diff --git a/source/cli.d b/source/cli.d index f46180d..003472b 100644 --- a/source/cli.d +++ b/source/cli.d @@ -1,7 +1,8 @@ import std.file: getcwd; import std.format: format; +import std.array: split, join; import std.algorithm.iteration: map, filter; -import std.algorithm.searching: maxElement; +import std.algorithm.searching: all, maxElement; import std.stdio; import core.stdc.stdlib; import std.conv: to; @@ -38,6 +39,16 @@ void cli_install(ref Config app_config) { string output = getcwd ~ " install:\n"; + foreach (command ; app_config.commands) { + if (command.outcomes.length > 0 && command.outcomes.all!(o => o.ok)) { + output ~= "+ `"~command.inspect~"`\n"; + } else { + output ~= "exec `"~command.inspect~"...`\n"; + command.invoke(); + output ~= "# `"~command.output.split('\n').join("\n# ")~"`\n"; + } + } + foreach (symlink ; app_config.symlinks) { if (!symlink.source.exists) { output ~= "# " ~ symlink.source.absolute ~ ": no such file or directory\n"; @@ -119,6 +130,19 @@ private void printStatus1(ref Config app_config) string output = getcwd ~ ":\n"; + foreach (command ; app_config.commands) { + if (command.outcomes.all!(o => o.ok)) { + output ~= "+ `"~command.inspect~"`\n"; + } else { + output ~= "- `"~command.inspect~"`\n"; + foreach (outcome ; command.outcomes) { + if (!outcome.ok) { + output ~= " # " ~ outcome.path.absolute ~ " does not exist\n"; + } + } + } + } + foreach (symlink ; app_config.symlinks) { output ~= symlink.ok ? "+ " : "- "; output ~= symlink.destination.absolute; diff --git a/source/command.d b/source/command.d index b8e7d83..1ee2134 100644 --- a/source/command.d +++ b/source/command.d @@ -1,3 +1,4 @@ +import std.array: join; import std.process; import path; @@ -22,6 +23,18 @@ class Command { this.args = args; } + /** returns string representation of the command + (command name and arguments) */ + string inspect() { + return name ~ ' ' ~ args.join(' '); + } + + /// string representation of [Command] + unittest { + auto cmd = new Command("echo", ["12", "3"]); + assert(cmd.inspect == "echo 12 3"); + } + /** invokes the command */ void invoke() { scope(exit) run = true; @@ -119,4 +132,53 @@ class CommandOutcome this.type = type; this.path = path; } + + /** test if outcome criteria is satisfied + * Returns: true if the outcome exists */ + bool ok() { + switch (type) { + case File: + return path.isFile; + case Directory: + return path.isDir; + case Symlink: + return path.isSymlink; + default: + return false; + } + } + + /// returns true if the outcome exists + unittest { + import std.file; + import std.process; + import std.conv; + import test_file; + import test_dir; + + auto testDir = setupTestDir(__FILE__, __LINE__); + scope(exit) removeTestDir(testDir); + + auto file = testDir ~ "/file"; + auto dir = testDir ~ "/dir"; + auto symlink = testDir ~ "/symlink"; + + auto path = Path(file); + auto outcome = new CommandOutcome(CommandOutcome.File, path); + assert(!outcome.ok); + TestFile(file, "content").create(); + assert(outcome.ok); + + path = Path(dir); + outcome = new CommandOutcome(CommandOutcome.Directory, path); + assert(!outcome.ok); + dir.mkdir; + assert(outcome.ok); + + path = Path(symlink); + outcome = new CommandOutcome(CommandOutcome.Symlink, path); + assert(!outcome.ok); + file.symlink(symlink); + assert(outcome.ok); + } } diff --git a/source/path.d b/source/path.d index aa506f1..add4565 100644 --- a/source/path.d +++ b/source/path.d @@ -1,6 +1,6 @@ import std.array; import std.path: expandTilde, absolutePath, asNormalizedPath, dirName; -import std.file: exists, isDir, isSymlink; +import std.file: exists, isFile, isDir, isSymlink; /** represent path in a file system */ struct Path @@ -88,6 +88,52 @@ struct Path assert(path.parent.absolute == cwd ~ '/' ~ ".test/app"); } + /** Returns: true if [Path] is a regular file in a file system */ + bool isFile() inout + { + return exists && absolute.isFile; + } + + /// returns true if [absolute] exists and is a file + unittest + { + import std.file; + import std.process; + import test_file; + import test_dir; + + auto testDir = setupTestDir(__FILE__, __LINE__); + scope(exit) removeTestDir(testDir); + + immutable auto file_path = testDir ~ "/file"; + + const auto path = Path(file_path); + assert(!path.isFile); + TestFile(file_path, "content").create; + assert(path.isFile); + } + + /// returns true if [absolute] exists and is a file + unittest + { + import std.file; + import std.process; + import test_file; + import test_dir; + + auto testDir = setupTestDir(__FILE__, __LINE__); + scope(exit) removeTestDir(testDir); + + immutable auto file_path = testDir ~ "/file"; + + const auto path = Path(file_path); + assert(!path.isFile); + + TestFile(file_path, "content").create; + + assert(path.isFile); + } + /** Returns: true if [Path] is a directory in a file system */ bool isDir() inout {