diff --git a/README.md b/README.md index 398262c..ef5d590 100644 --- a/README.md +++ b/README.md @@ -129,6 +129,7 @@ Commonly used functions in many scripts * `_progressBar_` Prints a progress bar within a for/while loop * `_rootAvailable_` Validate we have superuser access as root (via sudo if requested) * `_runAsRoot_` Run the requested command as root (via sudo if requested) + * `_safeExit_` Cleans up temporary files before exiting a script * `_seekConfirmation_` Seek user input for yes/no question * `_setPATH_` Add directories to $PATH so script can find executables @@ -150,11 +151,9 @@ Common utilities for working with files. * `_listFiles_` Find files in a directory. Use either glob or regex. * `_backupFile_` Creates a backup of a specified file with .bak extension or optionally to a specified directory. - * `_cleanFilename_` Cleans a filename of all non-alphanumeric (or user specified) characters and overwrites original - * `_parseFilename_` Break a filename into its component parts which and place them into prefixed variables (dir, basename, extension, full path, etc.) + * `_parseFilename_` Break a filename into its component parts which and place them into prefixed variables for use in your script (dir, basename, extension, path, etc.) * `_decryptFile_` Decrypts a file with `openssl` * `_encryptFile_` Encrypts a file with `openssl` - * `_ext_` Extract the extension from a filename * `_extract_` Extract a compressed file * `_json2yaml_` Convert JSON to YAML uses python * `_makeSymlink_` Creates a symlink and backs up a file which may be overwritten by the new symlink. If the exact same symlink already exists, nothing is done. diff --git a/test/files.bats b/test/files.bats index b539ab2..2fd2d8b 100755 --- a/test/files.bats +++ b/test/files.bats @@ -52,6 +52,8 @@ setup() { VERBOSE=false FORCE=false DRYRUN=false + PASS=123 + } teardown() { @@ -62,8 +64,6 @@ teardown() { ######## FIXTURES ######## YAML1="${BATS_TEST_DIRNAME}/fixtures/yaml1.yaml" YAML1parse="${BATS_TEST_DIRNAME}/fixtures/yaml1.yaml.txt" -YAML2="${BATS_TEST_DIRNAME}/fixtures/yaml2.yaml" -JSON="${BATS_TEST_DIRNAME}/fixtures/json.json" unencrypted="${BATS_TEST_DIRNAME}/fixtures/test.md" encrypted="${BATS_TEST_DIRNAME}/fixtures/test.md.enc" @@ -75,6 +75,24 @@ encrypted="${BATS_TEST_DIRNAME}/fixtures/test.md.enc" assert_output "" } +@test "_decryptFile_" { + run _decryptFile_ "${encrypted}" "test-decrypted.md" + assert_success + assert_file_exist "test-decrypted.md" + run cat "test-decrypted.md" + assert_success + assert_line --index 0 "# About" + assert_line --index 1 "This repository contains everything needed to bootstrap and configure new Mac computer. Included here are:" +} + +@test "_encryptFile_" { + run _encryptFile_ "${unencrypted}" "test-encrypted.md.enc" + assert_success + assert_file_exist "test-encrypted.md.enc" + run cat "test-encrypted.md.enc" + assert_line --index 0 --partial "Salted__" +} + _testBackupFile_() { @test "_backupFile_: no source" { @@ -152,19 +170,223 @@ _testListFiles_() { refute_output --partial "yestest1.txt" assert_output --partial "notest1.txt" } + + @test "_listFiles: fail no args" { + run _listFiles_ + assert_failure + } + + @test "_listFiles: fail one arg" { + run _listFiles_ "g" + assert_failure + } } +_testParseFilename_() { + @test "_parseFilename_: fail with no file" { + run _parseFilename_ "somenonexistantfile" + assert_failure + assert_output --partial "Can't locate a file to parse" + } + @test "_parseFilename_: file with one extension" { + touch "testfile.txt" + VERBOSE=true + run _parseFilename_ "testfile.txt" + assert_success + assert_line --index 0 --regexp "\[ debug\].*{PARSE_FULL}: /.*testfile\.txt$" + assert_line --index 1 --regexp "\[ debug\].*${PARSE_BASE}: testfile\.txt$" + assert_line --index 2 --regexp "\[ debug\].*${PARSE_PATH}: /.*" + assert_line --index 3 --regexp "\[ debug\].*${PARSE_EXT}: txt$" + assert_line --index 4 --regexp "\[ debug\].*${PARSE_BASENOEXT}: testfile$" + } + @test "_parseFilename_: file with dots in name" { + touch "testfile.for.testing.txt" + VERBOSE=true + run _parseFilename_ "testfile.for.testing.txt" + assert_success + assert_line --index 0 --regexp "\[ debug\].*{PARSE_FULL}: /.*testfile\.for\.testing\.txt$" + assert_line --index 1 --regexp "\[ debug\].*${PARSE_BASE}: testfile\.for\.testing\.txt$" + assert_line --index 2 --regexp "\[ debug\].*${PARSE_PATH}: /.*" + assert_line --index 3 --regexp "\[ debug\].*${PARSE_EXT}: txt$" + assert_line --index 4 --regexp "\[ debug\].*${PARSE_BASENOEXT}: testfile\.for\.testing$" + } + @test "_parseFilename_: file with no extension" { + touch "testfile" + VERBOSE=true + run _parseFilename_ "testfile" + assert_success + assert_line --index 0 --regexp "\[ debug\].*{PARSE_FULL}: /.*testfile$" + assert_line --index 1 --regexp "\[ debug\].*${PARSE_BASE}: testfile$" + assert_line --index 2 --regexp "\[ debug\].*${PARSE_PATH}: /.*" + assert_line --index 3 --regexp "\[ debug\].*${PARSE_EXT}: $" + assert_line --index 4 --regexp "\[ debug\].*${PARSE_BASENOEXT}: testfile$" + } + @test "_parseFilename_: file with tar.gz" { + touch "testfile.tar.gz" + VERBOSE=true + run _parseFilename_ "testfile.tar.gz" + assert_success + assert_line --index 0 --regexp "\[ debug\].*{PARSE_FULL}: /.*testfile\.tar\.gz$" + assert_line --index 1 --regexp "\[ debug\].*${PARSE_BASE}: testfile\.tar\.gz$" + assert_line --index 2 --regexp "\[ debug\].*${PARSE_PATH}: /.*" + assert_line --index 3 --regexp "\[ debug\].*${PARSE_EXT}: tar\.gz$" + assert_line --index 4 --regexp "\[ debug\].*${PARSE_BASENOEXT}: testfile$" + } + @test "_parseFilename_: file with three extensions" { + touch "testfile.tar.gzip.bzip" + VERBOSE=true + run _parseFilename_ -n3 "testfile.tar.gzip.bzip" + assert_success + assert_line --index 0 --regexp "\[ debug\].*{PARSE_FULL}: /.*testfile\.tar\.gzip\.bzip$" + assert_line --index 1 --regexp "\[ debug\].*${PARSE_BASE}: testfile\.tar\.gzip\.bzip$" + assert_line --index 2 --regexp "\[ debug\].*${PARSE_PATH}: /.*" + assert_line --index 3 --regexp "\[ debug\].*${PARSE_EXT}: tar\.gzip\.bzip$" + assert_line --index 4 --regexp "\[ debug\].*${PARSE_BASENOEXT}: testfile$" + } + + # _parseFilename_ "test.tar.gz" + # _parseFilename_ "test.tar.gzip" +} + +_testMakeSymlink_() { + + @test "_makeSymlink_: Fail with no source fire" { + run _makeSymlink_ "sourceFile" "destFile" + + assert_failure + } + + @test "_makeSymlink_: fail with no specified destination" { + touch "test.txt" + run _makeSymlink_ "test.txt" + + assert_failure + } + + @test "_makeSymlink_: make link" { + touch "test.txt" + touch "test2.txt" + run _makeSymlink_ "${TESTDIR}/test.txt" "${TESTDIR}/test2.txt" + + assert_success + assert_output --regexp "\[ info\] symlink /.*/test\.txt → /.*/test2\.txt" + assert_link_exist "test2.txt" + assert_file_exist "test2.txt.bak" + } + + @test "_makeSymlink_: Ignore already existing links" { + touch "test.txt" + ln -s "$(realpath test.txt)" "${TESTDIR}/test2.txt" + run _makeSymlink_ "$(realpath test.txt)" "${TESTDIR}/test2.txt" + + assert_success + assert_link_exist "test2.txt" + assert_output --regexp "\[ info\] Symlink already exists: /.*/test\.txt → /.*/test2\.txt" + } + + @test "_makeSymlink_: Don't make backup" { + touch "test.txt" + touch "test2.txt" + run _makeSymlink_ -n "${TESTDIR}/test.txt" "${TESTDIR}/test2.txt" + + assert_success + assert_output --regexp "\[ info\] symlink /.*/test\.txt → /.*/test2\.txt" + assert_link_exist "test2.txt" + assert_file_not_exist "test2.txt.bak" + } + +} + +_testParseYAML_() { + + @test "_parseYAML: success" { + run _parseYAML_ "$YAML1" + assert_success + assert_output "$( cat "$YAML1parse")" + } + + @test "_parseYAML_: empty file" { + touch empty.yaml + run _parseYAML_ "empty.yaml" + assert_failure + } + + @test "_parseYAML_: no file" { + run _parseYAML_ "empty.yaml" + assert_failure + } +} + +@test "_readFile_: Failure" { + run _readFile_ "testfile.txt" + assert_failure +} + +@test "_readFile_: Reads files line by line" { + echo -e "line 1\nline 2\nline 3" > testfile.txt + + run _readFile_ "testfile.txt" + assert_line --index 0 'line 1' + assert_line --index 2 'line 3' +} + +@test "_sourceFile_ failure" { + run _sourceFile_ "someNonExistantFile" + + assert_failure + assert_output --partial "[ fatal] Attempted to source 'someNonExistantFile'. Not found" +} + +@test "_sourceFile_ success" { + echo "echo 'hello world'" > "testSourceFile.txt" + run _sourceFile_ "testSourceFile.txt" + + assert_success + assert_output "hello world" +} + +@test "_uniqueFileName_: Count to 3" { + touch "test.txt" + touch "test.txt.1" + touch "test.txt.2" + + run _uniqueFileName_ "test.txt" + assert_output --regexp ".*/test\.txt\.3$" +} + +@test "_uniqueFileName_: Don't confuse existing numbers" { + touch "test-2.txt" + + run _uniqueFileName_ "test-2.txt" + assert_output --regexp ".*/test-2\.txt\.1$" +} + +@test "_uniqueFileName_: User specified separator" { + touch "test.txt" + + run _uniqueFileName_ "test.txt" " " + assert_output --regexp ".*/test\.txt 1$" +} + +@test "_uniqueFileName_: failure" { + run _uniqueFileName_ + + assert_failure +} _testBackupFile_ _testListFiles_ +_testParseFilename_ +_testMakeSymlink_ +_testParseYAML_ diff --git a/test/fixtures/test.md b/test/fixtures/test.md new file mode 100644 index 0000000..bb1be36 --- /dev/null +++ b/test/fixtures/test.md @@ -0,0 +1,79 @@ +# About +This repository contains everything needed to bootstrap and configure new Mac computer. Included here are: + + * dotfiles + * ~/bin/ scripts + * Configuration files + * Scripting templates and utilities + * `install.sh`, a script to put everything where it needs to go + +**Disclaimer:** *I am not a professional programmer and I bear no responsibility whatsoever if any of these scripts wipes your computer, destroys your data, crashes your car, or otherwise causes mayhem and destruction. USE AT YOUR OWN RISK.* + +## install.sh +This script runs through a series of tasks to configure a new computer. There are three distinct areas of `install.sh` which are executed in order. These are: + + 1. **Bootstrapping** - Installing base components such as Command Line Tools, Homebrew, Node, RVM, etc. + 2. **Installation** - Symlinking dotfiles and installing executables such as NPM Packages, Homebrew Casks, etc. + 3. **Configuration** - Configures installed packages and apps. + +The files are organized into three subdirectories. + +``` +dotfiles +├── bin/ +├── config/ +│   ├── bash/ +│   └── shell/ +├── install.sh +├── install-config.yaml +├── lib/ +│ ├── bootstrap/ +│ └── configure/ +└── scripting/ +``` + + * **bin** - Symlinked to `~/bin` and is added to your `$PATH`. + * **config** - Contains the elements needed to configure your environment and specific apps. + * config/**bash** - Files in this directory are *sourced* by `.bash_profile`. + * config/**shell** - Files here are symlinked to your local environment. Ahem, dotfiles. + * **lib** - Contains the scripts and configuration for `install.sh` + * lib/**bootstrap** - Scripts here are executed by `install.sh` first. + * lib/**configure** - Scripts here are exectuted by `install.sh` after packages have been installed + * **config-install.yaml** - This YAML file contains the list of symlinks to be created, as well as the packages to be installed. + * **scripting** - This directory contains bash scripting utilities and templates which I re-use often. + +**IMPORTANT:** Unless you want to use my defaults, make sure you do the following: + + * Edit `config-install.yaml` to reflect your preferred packages + * Review the files in `config/` to configure your own aliases, preferences, etc. + +#### Private Files + +Sometimes there are files which contain private information. These might be API keys, local directory structures, or anything else you want to keep hidden. + +Private files are held in a separate folder named `private`. This repository is added as a git-submodule and files within it are symlinked to `$HOME` or sourced to the Bash terminal. + +Since you're not me, you should **fork this repository and replace the `private` directory with a git submodule of your own.** + +Within the private directory you can write your own install script to configure and install your own files. This script should be named: `privateInstall.sh` + +If `private/privateInstall.sh` exists, `install.sh` will invoke it. + +## Cloning this repo to a new computer +The first step needed to use these dotfiles is to clone this repo into the $HOME directory. To make this easy, I created [a gist](https://gist.github.com/natelandau/b6ec165862277f3a7a4beff76da53a9c) which can easily be run with the following command: + +``` +curl -SL https://gist.githubusercontent.com/natelandau/b3e1dfba7491137f0a0f5e25721fffc2/raw/d98763695a0ddef1de9db2383f43149005423f20/bootstrapNewMac | bash +``` + +This gist creates a script `~/bootstrap.sh` in your home directory which completes the following tasks + + 1. Creates a new public SSH key if needed + 2. Copies your public key to your clipboard + 3. Opens Github to allow you to add this public key to your 'known keys' + 4. Clones this dotfiles repo to your home directory + +See. Easy. Now you're ready to run `~/dotfiles/install.sh` and get your new computer working. + +### A Note on Code Reuse +Many of the scripts, configuration files, and other information herein were created by me over many years without ever having the intention to make them public. As a novice programmer, I have Googled, GitHubbed, and StackExchanged a path to solve my own scripting needs. Quite often I would lift a function whole-cloth from a GitHub repo and not keep track of it's original location. I have done my best within these files to recreate my footsteps and give credit to the original creators of the code when possible. Unfortunately, I fear that I missed as many as I found. My goal of making this repository public is not to take credit for the wonderful code written by others. If you recognize or wrote something here that I didn't credit, please let me know. \ No newline at end of file diff --git a/test/fixtures/test.md.enc b/test/fixtures/test.md.enc new file mode 100644 index 0000000..15ba29d Binary files /dev/null and b/test/fixtures/test.md.enc differ diff --git a/test/fixtures/yaml1.yaml b/test/fixtures/yaml1.yaml new file mode 100644 index 0000000..b1245c3 --- /dev/null +++ b/test/fixtures/yaml1.yaml @@ -0,0 +1,29 @@ +# A list of tasty fruits +fruits: + - Apple + - Orange + - Strawberry + - Mango + +# An single record +employee: + name: Jimmy veloper + job: Developer + skill: Elite + +# Multiple records +employees: + martin: + name: Martin D'vloper + job: Developer + skills: + - python + - perl + - pascal + tabitha: + name: Tabitha Bitumen + job: Developer + skills: + - lisp + - fortran + - erlang \ No newline at end of file diff --git a/test/fixtures/yaml1.yaml.txt b/test/fixtures/yaml1.yaml.txt new file mode 100644 index 0000000..49534fe --- /dev/null +++ b/test/fixtures/yaml1.yaml.txt @@ -0,0 +1,17 @@ +fruits+=("Apple") +fruits+=("Orange") +fruits+=("Strawberry") +fruits+=("Mango") +employee_name=("Jimmy veloper") +employee_job=("Developer") +employee_skill=("Elite") +employees_martin_name=("Martin D'vloper") +employees_martin_job=("Developer") +employees_martin_skills+=("python") +employees_martin_skills+=("perl") +employees_martin_skills+=("pascal") +employees_tabitha_name=("Tabitha Bitumen") +employees_tabitha_job=("Developer") +employees_tabitha_skills+=("lisp") +employees_tabitha_skills+=("fortran") +employees_tabitha_skills+=("erlang") \ No newline at end of file diff --git a/utilities/files.bash b/utilities/files.bash index ebb867c..098698c 100644 --- a/utilities/files.bash +++ b/utilities/files.bash @@ -512,7 +512,7 @@ _sourceFile_() { [ ! -f "$c" ] \ && { - fatal "Attempted to source '$c' Not found" + fatal "Attempted to source '$c'. Not found" return 1 }