diff --git a/install.sh b/install.sh index 433a9ee..14bf0d1 100755 --- a/install.sh +++ b/install.sh @@ -19,7 +19,7 @@ function getFilteredModules() { if [ "${#module}" -eq 0 ]; then echo "${allModules[@]}" else - local modulesToKeep=() + local mod modulesToKeep=() for mod in "${allModules[@]}"; do local foundAtIndex="${module[(Ie)${mod}]}" if [ "${inverse}" != 'true' -a "${foundAtIndex}" -gt 0 ]; then @@ -32,6 +32,130 @@ function getFilteredModules() { fi } +function runModule() { + local mod="$1" + shift + ${_DIR:-.}/${mod} "$@" +} + +function parseQuestionLine() { + local questionType parameterName question value arguments args + local -A typeMap=([i]=info [p]=password [c]=confirm [s]=select) + [ -z "${line}" ] && return + [ "${line[2]}" != ':' ] && return 10 + + questionType="${typeMap[${line[1]}]}" + [ -z "${questionType}" ] && return 11 + + # remove question type + [ "${line[3]}" = ' ' ] && line="${line:3}" || line="${line:2}" + + line=("${(s.=.)line[@]}") + parameterName="${line[1]}" + [ -z "${parameterName}" ] && return 12 + [ "${parameterName[1]}" = '-' ] && return 13 + + # remove parameter name + line="${(j.=.)${(@)line:1}}" + + line=("${(s. #.)line}") + question="${line[1]}" + [ -z "${question}" ] && return 14 + + # remove question part + line="${(j. #.)${(@)line:1}}" + + if [ -n "${line}" ]; then + arguments=("${(s.;.)line}") + for arg in "${arguments[@]}"; do + arg=("${(s.:.)arg}") + [ -z "${arg[1]}" ] && return 15 + arg[1]="`trim "${arg[1]}"`" + arg[2]="`trim "${arg[2]}"`" + questionType+=";${(j.:.)arg}" + done + fi + + + printf -v value '%s\n%s' "${question}" "${questionType}" + questions+=("${parameterName}" "${value}") +} + +function populateQuestionsWithModuleRequiredInformation() { + for line in "${(f)$(runModule "${mod}" --show-required-information)}"; do + parseQuestionLine + done +} + +function convertQuestionArgsToAskUserArgs() { + local arg argName argValue + local instructions=("${(s.;.)questionArgs}") + local questionType="${instructions[1]}" + shift instructions + + if [ "${questionType}" = 'info' ]; then + args=(info) + elif [ "${questionType}" = 'password' ]; then + args=('-p' info) + elif [ "${questionType}" = 'confirm' ]; then + args=(confirm) + elif [ "${questionType}" = 'select' ]; then + for arg in "${instructions[@]}"; do + arg=("${(s.:.)arg}") + [ "${#arg}" -lt 2 ] && continue + argName="${arg[1]}" + argValue="${arg[2]}" + [ "${argName}" != 'choose from' ] && continue + choices=("${(s.,.)argValue}") + done + [ "${#choices}" -ge 1 ] || return 10 + args=(choose) + fi +} + +function askUserQuestion() { + local choices + local questionAndArgs=("${(f)questions[$questionID]}") args=() + local question="${questionAndArgs[1]}" questionArgs="${questionAndArgs[2]}" + convertQuestionArgsToAskUserArgs + askUser "${args[@]}" "${question}" + value="${REPLY}" +} + +function generateConfigKeysFromQuestionID() { + setopt localoptions extendedglob + [ $# -lt 2 -o -z "$1" -o -z "$2" ] && return + local modName="${1}" questID="${2}" + modName="${${${${modName//-##/_}/#_/}/%_/}//[^A-Za-z_]/}" + questID="${${${${questID//-##/_}/#_/}/%_/}//[^A-Za-z_]/}" + configkeys=("${modName}" questions "${questID}") +} + +function answerQuestionsFromConfigOrAskUser() { + local questionID + config setappname "de.astzweig.macos.system-setup" + for questionID in "${(k)questions[@]}"; do + local value configkeys=() + generateConfigKeysFromQuestionID "${mod}" "${questionID}" + value="`config read "${configkeys[@]}"`" + if [ -z "${value}" ]; then + askUserQuestion + config write "${value}" "${configkeys[@]}" + fi + answers+=("${mod}_${questionID}" "${value}") + done +} + +function askNecessaryQuestions() { + local mod + local -A answers + for mod in "${modulesToInstall[@]}"; do + local -A questions=() + populateQuestionsWithModuleRequiredInformation + answerQuestionsFromConfigOrAskUser + done +} + function main() { eval "`docopts -f -V - -h - : "$@" <<- USAGE Usage: $0 [options] [...] @@ -50,9 +174,11 @@ function main() { local modulesToInstall=(`getFilteredModules`) ensureDocopts autoloadZShLib + askNecessaryQuestions hio debug "Current working dir is: `pwd`" } if [[ "${ZSH_EVAL_CONTEXT}" == toplevel ]]; then + _DIR="${0:A:h}" main fi diff --git a/spec/answerQuestionsFromConfigOrAskUser_spec.sh b/spec/answerQuestionsFromConfigOrAskUser_spec.sh new file mode 100644 index 0000000..4efdb6c --- /dev/null +++ b/spec/answerQuestionsFromConfigOrAskUser_spec.sh @@ -0,0 +1,63 @@ +Describe 'answerQuestionsFromConfigOrAskUser' + Include ./install.sh + mod="testmod" + + It 'does nothing if module has no questions' + declare -A questions=() + When call answerQuestionsFromConfigOrAskUser + The output should eq '' + The variable 'questions' should eq '' + The status should be success + End + + It 'asks the user if the question is not in the config' + declare -A questions=([question-one]=$'What is your favorite color?\ninfo') + Data 'blue' + config() {} + When call answerQuestionsFromConfigOrAskUser + The output should eq "What is your favorite color? " + The status should be success + End + + It 'does not ask the user if the question is stored in the config' + declare -A questions=([question-one]=$'What is your favorite color?\ninfo') + config() { [ "${1}" = read ] && echo red; } + When call answerQuestionsFromConfigOrAskUser + The output should eq '' + The status should be success + End + + It 'stores the answer in the answers array if asking user' + declare -A answers + declare -A questions=([question-one]=$'What is your favorite color?\ninfo') + config() {} + Data 'blue' + When call answerQuestionsFromConfigOrAskUser + The output should eq "What is your favorite color? " + The variable "answers[${mod}_question-one]" should eq 'blue' + The status should be success + End + + It 'stores the answer in the answers array if retrieving from config' + declare -A answers + declare -A questions=([question-one]=$'What is your favorite color?\ninfo') + config() { [ "${1}" = read ] && echo red; } + When call answerQuestionsFromConfigOrAskUser + The output should eq '' + The variable "answers[${mod}_question-one]" should eq 'red' + The status should be success + End + + It 'stores the user answer to config' + declare -A answers + declare -A questions=([question-one]=$'What is your favorite color?\ninfo') + writtenValue="" + Data 'red' + config() { [ "${1}" = read ] && return; [ "${1}" = write ] && writtenValue="${2}" } + When call answerQuestionsFromConfigOrAskUser + The output should eq "What is your favorite color? " + The variable "answers[${mod}_question-one]" should eq 'red' + Assert test "${writtenValue}" = 'red' + The status should be success + End +End diff --git a/spec/convertQuestionArgsToAskUserArgs_spec.sh b/spec/convertQuestionArgsToAskUserArgs_spec.sh new file mode 100644 index 0000000..ecf03b7 --- /dev/null +++ b/spec/convertQuestionArgsToAskUserArgs_spec.sh @@ -0,0 +1,52 @@ +Describe 'convertQuestionArgsToAskUserArgs' + Include ./install.sh + + It 'converts info to info' + args=() + choices=() + questionArgs='info' + When call convertQuestionArgsToAskUserArgs + The output should eq '' + The variable args should eq 'info' + The variable choices should eq '' + The status should be success + End + + It 'converts password to -p info' + args=() choices=() questionArgs='password' + When call convertQuestionArgsToAskUserArgs + The variable args should eq '-p info' + The variable choices should eq '' + End + + It 'converts confirm to confirm' + args=() choices=() questionArgs='confirm' + When call convertQuestionArgsToAskUserArgs + The variable args should eq 'confirm' + The variable choices should eq '' + End + + It 'returns if type is select but no choose from arg is provided' + args=() choices=() questionArgs='select' + When call convertQuestionArgsToAskUserArgs + The variable args should eq '' + The variable choices should eq '' + The status should eq 10 + End + + It 'converts select to choose' + args=() choices=() questionArgs='select;choose from:blue,light green,red' + When call convertQuestionArgsToAskUserArgs + The variable args should eq 'choose' + The variable choices should eq 'blue light green red' + The variable '#choices' should eq 3 + End + + It 'finds choose from arg even if many args are provided' + args=() choices=() questionArgs='select;first arg:red;choose from:blue,light green,red;after arg:nine;after arg:nine;' + When call convertQuestionArgsToAskUserArgs + The variable args should eq 'choose' + The variable choices should eq 'blue light green red' + The variable '#choices' should eq 3 + End +End diff --git a/spec/generateConfigKeysFromQuestionID_spec.sh b/spec/generateConfigKeysFromQuestionID_spec.sh new file mode 100644 index 0000000..359f570 --- /dev/null +++ b/spec/generateConfigKeysFromQuestionID_spec.sh @@ -0,0 +1,59 @@ +Describe 'generateConfigKeysFromQuestionID' + Include ./install.sh + + It 'does nothing if given no arguments' + declare -A configkeys=() + When call generateConfigKeysFromQuestionID '' + The output should eq '' + The variable 'configkeys' should eq '' + The status should be success + End + + It 'does nothing if question id is empty' + declare configkeys=() + When call generateConfigKeysFromQuestionID 'somemod' '' + The output should eq '' + The variable 'configkeys' should eq '' + The status should be success + End + + It 'generates key when given module name and question id' + declare configkeys=() + When call generateConfigKeysFromQuestionID 'somemod' 'somekey' + The output should eq '' + The variable 'configkeys' should eq 'somemod questions somekey' + The status should be success + End + + It 'replaces minus with underscore' + declare configkeys=() + When call generateConfigKeysFromQuestionID 'some-mod' 'somekey' + The output should eq '' + The variable 'configkeys' should eq 'some_mod questions somekey' + The status should be success + End + + It 'replaces multiple minus with single underscore' + declare configkeys=() + When call generateConfigKeysFromQuestionID 'some---mod' 'so--mekey' + The output should eq '' + The variable 'configkeys' should eq 'some_mod questions so_mekey' + The status should be success + End + + It 'replaces underscores at the begin and end of string' + declare configkeys=() + When call generateConfigKeysFromQuestionID '--some-mod' '--somekey-' + The output should eq '' + The variable 'configkeys' should eq 'some_mod questions somekey' + The status should be success + End + + It 'removes all chars but [A-Za-z_]' + declare configkeys=() + When call generateConfigKeysFromQuestionID '*some≠{mod' '?`somekey' + The output should eq '' + The variable 'configkeys' should eq 'somemod questions somekey' + The status should be success + End +End diff --git a/spec/parseQuestionLine_spec.sh b/spec/parseQuestionLine_spec.sh new file mode 100644 index 0000000..5422d70 --- /dev/null +++ b/spec/parseQuestionLine_spec.sh @@ -0,0 +1,88 @@ +Describe 'parseQuestionLine' + Include ./install.sh + + It 'does nothing if the line is empty' + line='' + When call parseQuestionLine + The output should eq '' + The status should be success + End + + It 'does nothing if the line does not have a question type' + line='some value here' + When call parseQuestionLine + The output should eq '' + The status should eq 10 + End + + It 'does nothing if the line does not have a valid question type' + line='z: some value here' + When call parseQuestionLine + The output should eq '' + The status should eq 11 + End + + It 'does nothing if the line does not have a parameter name' + line='i: ' + When call parseQuestionLine + The output should eq '' + The status should eq 12 + End + + It 'does nothing if the parameter name starts with a dash' + line='i: -parameter-name=Is this my question?' + When call parseQuestionLine + The output should eq '' + The status should eq 13 + End + + It 'does nothing if the line does not have a question' + line='i: parameter-name=' + When call parseQuestionLine + The output should eq '' + The status should eq 14 + End + + It 'extends outer questions associative array with question' + declare -A questions + line='i: parameter-name=What parameter do you like? #' + When call parseQuestionLine + The output should eq '' + The line 1 of variable 'questions[parameter-name]' should eq 'What parameter do you like?' + The line 2 of variable 'questions[parameter-name]' should eq info + The status should be success + End + + It 'does nothing if an argument does not contain a name' + declare -A questions + line='i: parameter-name=What parameter do you like? # some arg = some value;;' + When call parseQuestionLine + The output should eq '' + The variable 'questions' should eq '' + The status should eq 15 + End + + It 'writes question type to outer questions associative array' + declare -A questions + line='s: parameter-name=What parameter do you like? # choose from: blue,red,light green; default: blue' + When call parseQuestionLine + The line 2 of variable 'questions[parameter-name]' should eq 'select;choose from:blue,red,light green;default:blue' + End +End + +Describe "parseQuestionLine type renaming" + Include ./install.sh + Parameters + i info + p password + c confirm + s select + End + + It "converts $1 to $2" + declare -A questions + line="${1}: parameter-name=What parameter do you like? #" + When call parseQuestionLine + The line 2 of variable 'questions[parameter-name]' should eq "$2" + End +End