From 4350f484da9132cb8f7f6b4e256f8678c0959688 Mon Sep 17 00:00:00 2001 From: Abubakar Meigag <113248042+Abubakar-Meigag@users.noreply.github.com> Date: Wed, 11 Mar 2026 17:31:33 +0000 Subject: [PATCH 01/24] Fix expected output in sed exercise instructions (#353) Fix expected output: change 'with' to 'wIth' in sed exercise instructions --- individual-shell-tools/sed/script-01.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/individual-shell-tools/sed/script-01.sh b/individual-shell-tools/sed/script-01.sh index 3eba6fa..d592970 100755 --- a/individual-shell-tools/sed/script-01.sh +++ b/individual-shell-tools/sed/script-01.sh @@ -4,4 +4,4 @@ set -euo pipefail # TODO: Write a command to output input.txt with all occurrences of the letter `i` replaced with `I`. # The output should contain 11 lines. -# The first line of the output should be: "ThIs Is a sample fIle for experImentIng with sed.". +# The first line of the output should be: "ThIs Is a sample fIle for experImentIng wIth sed.". From 743e4911fb9de80d1c5934bdc857738d9f60ed7e Mon Sep 17 00:00:00 2001 From: LonMcGregor <3817332+LonMcGregor@users.noreply.github.com> Date: Mon, 11 May 2026 10:28:21 +0100 Subject: [PATCH 02/24] Automate testing of initial sprints in tools module (#472) * Expected output now in expected folder * Github action which runs after validation, when Needs Review * Script that can be run to test locally before push * Split number sys qs into automatable/manualy checkable --------- Co-authored-by: l --- .github/workflows/validate-pr-metadata.yml | 57 +++++++- .gitignore | 1 + .../individual-shell-tools/awk/script-01.sh | 6 + .../individual-shell-tools/awk/script-02.sh | 6 + .../individual-shell-tools/awk/script-03.sh | 6 + .../individual-shell-tools/awk/script-04.sh | 3 + .../individual-shell-tools/awk/script-05.sh | 6 + .../awk/script-06-stretch.sh | 1 + .../awk/script-07-stretch.sh | 6 + .../individual-shell-tools/cat/script-01.sh | 1 + .../individual-shell-tools/cat/script-02.sh | 5 + .../individual-shell-tools/cat/script-03.sh | 3 + .../cat/script-04-stretch.sh | 5 + .../individual-shell-tools/grep/script-01.sh | 6 + .../individual-shell-tools/grep/script-02.sh | 9 ++ .../individual-shell-tools/grep/script-03.sh | 1 + .../individual-shell-tools/grep/script-04.sh | 10 ++ .../individual-shell-tools/grep/script-05.sh | 5 + .../individual-shell-tools/grep/script-06.sh | 2 + .../individual-shell-tools/grep/script-07.sh | 3 + expect/individual-shell-tools/ls/script-01.sh | 5 + expect/individual-shell-tools/ls/script-02.sh | 3 + expect/individual-shell-tools/ls/script-03.sh | 11 ++ expect/individual-shell-tools/ls/script-04.sh | 8 ++ .../individual-shell-tools/sed/script-01.sh | 11 ++ .../individual-shell-tools/sed/script-02.sh | 11 ++ .../individual-shell-tools/sed/script-03.sh | 6 + .../individual-shell-tools/sed/script-04.sh | 11 ++ .../individual-shell-tools/sed/script-05.sh | 11 ++ .../individual-shell-tools/sed/script-06.sh | 11 ++ expect/individual-shell-tools/wc/script-01.sh | 1 + expect/individual-shell-tools/wc/script-02.sh | 1 + expect/individual-shell-tools/wc/script-03.sh | 4 + expect/jq/script-01.sh | 1 + expect/jq/script-02.sh | 1 + expect/jq/script-03.sh | 2 + expect/jq/script-04.sh | 6 + expect/jq/script-05.sh | 6 + expect/jq/script-06.sh | 6 + expect/jq/script-07.sh | 6 + expect/jq/script-08.sh | 6 + expect/jq/script-09.sh | 6 + expect/jq/script-10.sh | 1 + expect/jq/script-11.sh | 1 + expect/number-systems/Part-1.json | 1 + expect/shell-pipelines/ls-grep/script-01.sh | 11 ++ expect/shell-pipelines/ls-grep/script-02.sh | 10 ++ expect/shell-pipelines/ls-grep/script-03.sh | 7 + expect/shell-pipelines/ls-grep/script-04.sh | 1 + .../sort-uniq-head-tail/script-01.sh | 6 + .../sort-uniq-head-tail/script-02.sh | 6 + .../sort-uniq-head-tail/script-03.sh | 3 + .../sort-uniq-head-tail/script-04.sh | 1 + .../sort-uniq-head-tail/script-05.sh | 6 + .../sort-uniq-head-tail/script-06.sh | 2 + .../sort-uniq-head-tail/script-07.sh | 2 + expect/shell-pipelines/tr/script-01.sh | 11 ++ expect/shell-pipelines/tr/script-02.sh | 11 ++ number-systems/Part-1.md | 54 +++++++ number-systems/Part-2.md | 22 +++ number-systems/README.md | 65 --------- test-sdc.sh | 132 ++++++++++++++++++ 62 files changed, 561 insertions(+), 68 deletions(-) create mode 100755 expect/individual-shell-tools/awk/script-01.sh create mode 100755 expect/individual-shell-tools/awk/script-02.sh create mode 100755 expect/individual-shell-tools/awk/script-03.sh create mode 100755 expect/individual-shell-tools/awk/script-04.sh create mode 100755 expect/individual-shell-tools/awk/script-05.sh create mode 100755 expect/individual-shell-tools/awk/script-06-stretch.sh create mode 100755 expect/individual-shell-tools/awk/script-07-stretch.sh create mode 100755 expect/individual-shell-tools/cat/script-01.sh create mode 100755 expect/individual-shell-tools/cat/script-02.sh create mode 100755 expect/individual-shell-tools/cat/script-03.sh create mode 100755 expect/individual-shell-tools/cat/script-04-stretch.sh create mode 100755 expect/individual-shell-tools/grep/script-01.sh create mode 100755 expect/individual-shell-tools/grep/script-02.sh create mode 100755 expect/individual-shell-tools/grep/script-03.sh create mode 100755 expect/individual-shell-tools/grep/script-04.sh create mode 100755 expect/individual-shell-tools/grep/script-05.sh create mode 100755 expect/individual-shell-tools/grep/script-06.sh create mode 100755 expect/individual-shell-tools/grep/script-07.sh create mode 100755 expect/individual-shell-tools/ls/script-01.sh create mode 100755 expect/individual-shell-tools/ls/script-02.sh create mode 100755 expect/individual-shell-tools/ls/script-03.sh create mode 100755 expect/individual-shell-tools/ls/script-04.sh create mode 100755 expect/individual-shell-tools/sed/script-01.sh create mode 100755 expect/individual-shell-tools/sed/script-02.sh create mode 100755 expect/individual-shell-tools/sed/script-03.sh create mode 100755 expect/individual-shell-tools/sed/script-04.sh create mode 100755 expect/individual-shell-tools/sed/script-05.sh create mode 100755 expect/individual-shell-tools/sed/script-06.sh create mode 100755 expect/individual-shell-tools/wc/script-01.sh create mode 100755 expect/individual-shell-tools/wc/script-02.sh create mode 100755 expect/individual-shell-tools/wc/script-03.sh create mode 100755 expect/jq/script-01.sh create mode 100755 expect/jq/script-02.sh create mode 100755 expect/jq/script-03.sh create mode 100755 expect/jq/script-04.sh create mode 100755 expect/jq/script-05.sh create mode 100755 expect/jq/script-06.sh create mode 100755 expect/jq/script-07.sh create mode 100755 expect/jq/script-08.sh create mode 100755 expect/jq/script-09.sh create mode 100755 expect/jq/script-10.sh create mode 100755 expect/jq/script-11.sh create mode 100644 expect/number-systems/Part-1.json create mode 100755 expect/shell-pipelines/ls-grep/script-01.sh create mode 100755 expect/shell-pipelines/ls-grep/script-02.sh create mode 100755 expect/shell-pipelines/ls-grep/script-03.sh create mode 100755 expect/shell-pipelines/ls-grep/script-04.sh create mode 100755 expect/shell-pipelines/sort-uniq-head-tail/script-01.sh create mode 100755 expect/shell-pipelines/sort-uniq-head-tail/script-02.sh create mode 100755 expect/shell-pipelines/sort-uniq-head-tail/script-03.sh create mode 100755 expect/shell-pipelines/sort-uniq-head-tail/script-04.sh create mode 100755 expect/shell-pipelines/sort-uniq-head-tail/script-05.sh create mode 100755 expect/shell-pipelines/sort-uniq-head-tail/script-06.sh create mode 100755 expect/shell-pipelines/sort-uniq-head-tail/script-07.sh create mode 100755 expect/shell-pipelines/tr/script-01.sh create mode 100755 expect/shell-pipelines/tr/script-02.sh create mode 100644 number-systems/Part-1.md create mode 100644 number-systems/Part-2.md delete mode 100644 number-systems/README.md create mode 100755 test-sdc.sh diff --git a/.github/workflows/validate-pr-metadata.yml b/.github/workflows/validate-pr-metadata.yml index 10ef3c7..8ff276f 100644 --- a/.github/workflows/validate-pr-metadata.yml +++ b/.github/workflows/validate-pr-metadata.yml @@ -1,9 +1,8 @@ -name: Validate PR Metadata +name: Validate PR Metadata and Check SDC-Tools Tasks on: - pull_request_target: + pull_request: types: - labeled - - unlabeled - opened - edited - reopened @@ -16,3 +15,55 @@ jobs: - uses: CodeYourFuture/actions/validate-pr-metadata@main with: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + test_sdc_tasks: + name: Test SDC Tasks + runs-on: ubuntu-slim + permissions: + pull-requests: write + if: ${{ github.event.label.name == 'Needs Review' }} + steps: + - name: checkout base branch + uses: actions/checkout@v5 + - name: Get changed files + id: changed-files + uses: tj-actions/changed-files@v47.0.5 + - name: test individual shell tools + id: test-individual-shell-tools + if: contains(steps.changed-files.outputs.modified_files, 'individual-shell-tools/') + run: ./test-sdc.sh individual-shell-tools + shell: bash + - name: test jq + id: test-jq + if: contains(steps.changed-files.outputs.modified_files, 'jq/') + run: ./test-sdc.sh jq + shell: bash + - name: test shell-pipelines + id: test-shell-pipelines + if: contains(steps.changed-files.outputs.modified_files, 'shell-pipelines/') + run: ./test-sdc.sh shell-pipelines + shell: bash + - name: test number-systems + id: test-number-systems + if: contains(steps.changed-files.outputs.modified_files, 'number-systems/') + run: ./test-sdc.sh number-systems + shell: bash + - name: make output comment + if: steps.test-individual-shell-tools.outputs.attempted == 'y' || steps.test-jq.outputs.attempted == 'y' || steps.test-shell-pipelines.outputs.attempted == 'y' || steps.test-number-systems.outputs.attempted == 'y' + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + ISSUE_URL: ${{ github.event.pull_request.html_url }} + run: | + gh pr comment $ISSUE_URL --body-file testoutput.txt + - name: add appropriate labels + if: steps.test-individual-shell-tools.outputs.complete == 'y' || steps.test-jq.outputs.complete == 'y' || steps.test-shell-pipelines.outputs.complete == 'y' + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + ISSUE_URL: ${{ github.event.pull_request.html_url }} + run: | + gh pr edit $ISSUE_URL --add-label "Complete" + gh pr edit $ISSUE_URL --remove-label "Needs Review" + - name: fail if not complete + if: (steps.test-individual-shell-tools.outputs.attempted == 'y' || steps.test-jq.outputs.attempted == 'y' || steps.test-shell-pipelines.outputs.attempted == 'y') && !(steps.test-individual-shell-tools.outputs.complete == 'y' || steps.test-jq.outputs.complete == 'y' || steps.test-shell-pipelines.outputs.complete == 'y') + run: | + exit -1 diff --git a/.gitignore b/.gitignore index 3c3629e..c4234ab 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ node_modules +testoutput.txt diff --git a/expect/individual-shell-tools/awk/script-01.sh b/expect/individual-shell-tools/awk/script-01.sh new file mode 100755 index 0000000..050f043 --- /dev/null +++ b/expect/individual-shell-tools/awk/script-01.sh @@ -0,0 +1,6 @@ +Ahmed +Basia +Mehmet +Leila +Piotr +Chandra diff --git a/expect/individual-shell-tools/awk/script-02.sh b/expect/individual-shell-tools/awk/script-02.sh new file mode 100755 index 0000000..ee7e0fe --- /dev/null +++ b/expect/individual-shell-tools/awk/script-02.sh @@ -0,0 +1,6 @@ +Ahmed London +Basia London +Mehmet Birmingham +Leila London +Piotr Glasgow +Chandra Birmingham diff --git a/expect/individual-shell-tools/awk/script-03.sh b/expect/individual-shell-tools/awk/script-03.sh new file mode 100755 index 0000000..f5d0b1f --- /dev/null +++ b/expect/individual-shell-tools/awk/script-03.sh @@ -0,0 +1,6 @@ +Ahmed 1 +Basia 22 +Mehmet 3 +Leila 1 +Piotr 15 +Chandra 12 diff --git a/expect/individual-shell-tools/awk/script-04.sh b/expect/individual-shell-tools/awk/script-04.sh new file mode 100755 index 0000000..6e95a2f --- /dev/null +++ b/expect/individual-shell-tools/awk/script-04.sh @@ -0,0 +1,3 @@ +Ahmed 4 +Basia 6 +Leila 1 diff --git a/expect/individual-shell-tools/awk/script-05.sh b/expect/individual-shell-tools/awk/script-05.sh new file mode 100755 index 0000000..dd7e1d3 --- /dev/null +++ b/expect/individual-shell-tools/awk/script-05.sh @@ -0,0 +1,6 @@ +Ahmed 3 +Basia 3 +Mehmet 3 +Leila 1 +Piotr 5 +Chandra 2 diff --git a/expect/individual-shell-tools/awk/script-06-stretch.sh b/expect/individual-shell-tools/awk/script-06-stretch.sh new file mode 100755 index 0000000..fb1e7bc --- /dev/null +++ b/expect/individual-shell-tools/awk/script-06-stretch.sh @@ -0,0 +1 @@ +54 diff --git a/expect/individual-shell-tools/awk/script-07-stretch.sh b/expect/individual-shell-tools/awk/script-07-stretch.sh new file mode 100755 index 0000000..33ff777 --- /dev/null +++ b/expect/individual-shell-tools/awk/script-07-stretch.sh @@ -0,0 +1,6 @@ +Ahmed 15 +Basia 37 +Mehmet 32 +Leila 1 +Piotr 61 +Chandra 18 diff --git a/expect/individual-shell-tools/cat/script-01.sh b/expect/individual-shell-tools/cat/script-01.sh new file mode 100755 index 0000000..ed7bf7d --- /dev/null +++ b/expect/individual-shell-tools/cat/script-01.sh @@ -0,0 +1 @@ +Once upon a time... diff --git a/expect/individual-shell-tools/cat/script-02.sh b/expect/individual-shell-tools/cat/script-02.sh new file mode 100755 index 0000000..769b7e0 --- /dev/null +++ b/expect/individual-shell-tools/cat/script-02.sh @@ -0,0 +1,5 @@ +Once upon a time... +There was a house made of gingerbread. +It looked delicious. +I was tempted to take a bite of it. +But this seemed like a bad idea... diff --git a/expect/individual-shell-tools/cat/script-03.sh b/expect/individual-shell-tools/cat/script-03.sh new file mode 100755 index 0000000..f97bd50 --- /dev/null +++ b/expect/individual-shell-tools/cat/script-03.sh @@ -0,0 +1,3 @@ + 1 It looked delicious. + 2 I was tempted to take a bite of it. + 3 But this seemed like a bad idea... diff --git a/expect/individual-shell-tools/cat/script-04-stretch.sh b/expect/individual-shell-tools/cat/script-04-stretch.sh new file mode 100755 index 0000000..6f35e89 --- /dev/null +++ b/expect/individual-shell-tools/cat/script-04-stretch.sh @@ -0,0 +1,5 @@ + 1 Once upon a time... + 2 There was a house made of gingerbread. + 3 It looked delicious. + 4 I was tempted to take a bite of it. + 5 But this seemed like a bad idea... diff --git a/expect/individual-shell-tools/grep/script-01.sh b/expect/individual-shell-tools/grep/script-01.sh new file mode 100755 index 0000000..bc96ffe --- /dev/null +++ b/expect/individual-shell-tools/grep/script-01.sh @@ -0,0 +1,6 @@ +Doctor: Hello +Doctor: What's wrong today? +Doctor: That sounds frustrating. When did this start? +Doctor: Say "Hi". +Doctor: You didn't say hello +Doctor: You're welcome, goodbye diff --git a/expect/individual-shell-tools/grep/script-02.sh b/expect/individual-shell-tools/grep/script-02.sh new file mode 100755 index 0000000..bad606e --- /dev/null +++ b/expect/individual-shell-tools/grep/script-02.sh @@ -0,0 +1,9 @@ +Doctor: Hello +Patient: Hello Doctor +Doctor: What's wrong today? +Doctor: That sounds frustrating. When did this start? +Doctor: Say "Hi". +Doctor: You didn't say hello +Doctor: You're welcome, goodbye +Patient: I went to the doctor! +Spouse: I'm glad you saw the Doctor: did they cure you? diff --git a/expect/individual-shell-tools/grep/script-03.sh b/expect/individual-shell-tools/grep/script-03.sh new file mode 100755 index 0000000..ec63514 --- /dev/null +++ b/expect/individual-shell-tools/grep/script-03.sh @@ -0,0 +1 @@ +9 diff --git a/expect/individual-shell-tools/grep/script-04.sh b/expect/individual-shell-tools/grep/script-04.sh new file mode 100755 index 0000000..5814afa --- /dev/null +++ b/expect/individual-shell-tools/grep/script-04.sh @@ -0,0 +1,10 @@ +Doctor: What's wrong today? +Doctor: That sounds frustrating. When did this start? +Doctor: Say "Hi". +Patient: Hi +Patient: I seem to be cured! +Doctor: You're welcome, goodbye + +Patient: I went to the doctor! +Spouse: I'm glad you saw the Doctor: did they cure you? +Patient: Yes! diff --git a/expect/individual-shell-tools/grep/script-05.sh b/expect/individual-shell-tools/grep/script-05.sh new file mode 100755 index 0000000..2ed07ed --- /dev/null +++ b/expect/individual-shell-tools/grep/script-05.sh @@ -0,0 +1,5 @@ +Doctor: You didn't say hello +Patient: I seem to be cured! +-- +Patient: I went to the doctor! +Spouse: I'm glad you saw the Doctor: did they cure you? diff --git a/expect/individual-shell-tools/grep/script-06.sh b/expect/individual-shell-tools/grep/script-06.sh new file mode 100755 index 0000000..87b3a4f --- /dev/null +++ b/expect/individual-shell-tools/grep/script-06.sh @@ -0,0 +1,2 @@ +dialogue-2.txt +dialogue.txt diff --git a/expect/individual-shell-tools/grep/script-07.sh b/expect/individual-shell-tools/grep/script-07.sh new file mode 100755 index 0000000..aed78c6 --- /dev/null +++ b/expect/individual-shell-tools/grep/script-07.sh @@ -0,0 +1,3 @@ +dialogue-2.txt:2 +dialogue-3.txt:0 +dialogue.txt:6 diff --git a/expect/individual-shell-tools/ls/script-01.sh b/expect/individual-shell-tools/ls/script-01.sh new file mode 100755 index 0000000..dd590d6 --- /dev/null +++ b/expect/individual-shell-tools/ls/script-01.sh @@ -0,0 +1,5 @@ +child-directory +script-01.sh +script-02.sh +script-03.sh +script-04.sh diff --git a/expect/individual-shell-tools/ls/script-02.sh b/expect/individual-shell-tools/ls/script-02.sh new file mode 100755 index 0000000..bfd087e --- /dev/null +++ b/expect/individual-shell-tools/ls/script-02.sh @@ -0,0 +1,3 @@ +helper-1.txt +helper-2.txt +helper-3.txt diff --git a/expect/individual-shell-tools/ls/script-03.sh b/expect/individual-shell-tools/ls/script-03.sh new file mode 100755 index 0000000..36acd71 --- /dev/null +++ b/expect/individual-shell-tools/ls/script-03.sh @@ -0,0 +1,11 @@ +.: +child-directory +script-01.sh +script-02.sh +script-03.sh +script-04.sh + +./child-directory: +helper-1.txt +helper-2.txt +helper-3.txt diff --git a/expect/individual-shell-tools/ls/script-04.sh b/expect/individual-shell-tools/ls/script-04.sh new file mode 100755 index 0000000..86e7570 --- /dev/null +++ b/expect/individual-shell-tools/ls/script-04.sh @@ -0,0 +1,8 @@ +First exercise (sorted newest to oldest): +helper-3.txt +helper-1.txt +helper-2.txt +Second exercise (sorted oldest to newest): +helper-2.txt +helper-1.txt +helper-3.txt diff --git a/expect/individual-shell-tools/sed/script-01.sh b/expect/individual-shell-tools/sed/script-01.sh new file mode 100755 index 0000000..d92b03a --- /dev/null +++ b/expect/individual-shell-tools/sed/script-01.sh @@ -0,0 +1,11 @@ +ThIs Is a sample fIle for experImentIng wIth sed. + +It contaIns many lInes, and there are some thIngs you may want to do wIth each of them. + +We'll Include some score InformatIon: +37 AlIsha +15 Jacob +7 PIetro +3 Katya + +We also should remember, when we go shoppIng, to get 4 Items: oranges,cheese,bread,olIves. diff --git a/expect/individual-shell-tools/sed/script-02.sh b/expect/individual-shell-tools/sed/script-02.sh new file mode 100755 index 0000000..e326bd4 --- /dev/null +++ b/expect/individual-shell-tools/sed/script-02.sh @@ -0,0 +1,11 @@ +This is a sample file for experimenting with sed. + +It contains many lines, and there are some things you may want to do with each of them. + +We'll include some score information: + Alisha + Jacob + Pietro + Katya + +We also should remember, when we go shopping, to get items: oranges,cheese,bread,olives. diff --git a/expect/individual-shell-tools/sed/script-03.sh b/expect/individual-shell-tools/sed/script-03.sh new file mode 100755 index 0000000..088b17d --- /dev/null +++ b/expect/individual-shell-tools/sed/script-03.sh @@ -0,0 +1,6 @@ +This is a sample file for experimenting with sed. + +It contains many lines, and there are some things you may want to do with each of them. + +We'll include some score information: + diff --git a/expect/individual-shell-tools/sed/script-04.sh b/expect/individual-shell-tools/sed/script-04.sh new file mode 100755 index 0000000..852fa15 --- /dev/null +++ b/expect/individual-shell-tools/sed/script-04.sh @@ -0,0 +1,11 @@ +This is a sample file for experimenting with sed. + +It contains many lines, and there are some things you may want to do with each of them. + +We'll include some score information: +37 Alisha +15 Jacob +7 Pietro +3 Katya + +We also should remember, when we go shopping, to get 4 items: oranges,cheese,bread,olives. diff --git a/expect/individual-shell-tools/sed/script-05.sh b/expect/individual-shell-tools/sed/script-05.sh new file mode 100755 index 0000000..fe4d9d1 --- /dev/null +++ b/expect/individual-shell-tools/sed/script-05.sh @@ -0,0 +1,11 @@ +This is a sample file for experimenting with sed. + +It contains many lines, and there are some things you may want to do with each of them. + +We'll include some score information: +Alisha 37 +Jacob 15 +Pietro 7 +Katya 3 + +We also should remember, when we go shopping, to get 4 items: oranges,cheese,bread,olives. diff --git a/expect/individual-shell-tools/sed/script-06.sh b/expect/individual-shell-tools/sed/script-06.sh new file mode 100755 index 0000000..d9f950f --- /dev/null +++ b/expect/individual-shell-tools/sed/script-06.sh @@ -0,0 +1,11 @@ +This is a sample file for experimenting with sed. + +It contains many lines, and there are some things you may want to do with each of them. + +We'll include some score information: +37 Alisha +15 Jacob +7 Pietro +3 Katya + +We also should remember, when we go shopping, to get 4 items: oranges, cheese, bread, olives. diff --git a/expect/individual-shell-tools/wc/script-01.sh b/expect/individual-shell-tools/wc/script-01.sh new file mode 100755 index 0000000..d1ad53e --- /dev/null +++ b/expect/individual-shell-tools/wc/script-01.sh @@ -0,0 +1 @@ +19 ../helper-files/helper-3.txt diff --git a/expect/individual-shell-tools/wc/script-02.sh b/expect/individual-shell-tools/wc/script-02.sh new file mode 100755 index 0000000..6bd060b --- /dev/null +++ b/expect/individual-shell-tools/wc/script-02.sh @@ -0,0 +1 @@ +3 ../helper-files/helper-3.txt diff --git a/expect/individual-shell-tools/wc/script-03.sh b/expect/individual-shell-tools/wc/script-03.sh new file mode 100755 index 0000000..572716a --- /dev/null +++ b/expect/individual-shell-tools/wc/script-03.sh @@ -0,0 +1,4 @@ + 1 4 20 ../helper-files/helper-1.txt + 1 7 39 ../helper-files/helper-2.txt + 3 19 92 ../helper-files/helper-3.txt + 5 30 151 total diff --git a/expect/jq/script-01.sh b/expect/jq/script-01.sh new file mode 100755 index 0000000..e16f4ed --- /dev/null +++ b/expect/jq/script-01.sh @@ -0,0 +1 @@ +Selma diff --git a/expect/jq/script-02.sh b/expect/jq/script-02.sh new file mode 100755 index 0000000..18711af --- /dev/null +++ b/expect/jq/script-02.sh @@ -0,0 +1 @@ +35 Fashion Street, London, E1 6PX diff --git a/expect/jq/script-03.sh b/expect/jq/script-03.sh new file mode 100755 index 0000000..d63f6ec --- /dev/null +++ b/expect/jq/script-03.sh @@ -0,0 +1,2 @@ +Selma, Software Engineer +Selma, Software Engineer diff --git a/expect/jq/script-04.sh b/expect/jq/script-04.sh new file mode 100755 index 0000000..050f043 --- /dev/null +++ b/expect/jq/script-04.sh @@ -0,0 +1,6 @@ +Ahmed +Basia +Mehmet +Leila +Piotr +Chandra diff --git a/expect/jq/script-05.sh b/expect/jq/script-05.sh new file mode 100755 index 0000000..ee7e0fe --- /dev/null +++ b/expect/jq/script-05.sh @@ -0,0 +1,6 @@ +Ahmed London +Basia London +Mehmet Birmingham +Leila London +Piotr Glasgow +Chandra Birmingham diff --git a/expect/jq/script-06.sh b/expect/jq/script-06.sh new file mode 100755 index 0000000..f5d0b1f --- /dev/null +++ b/expect/jq/script-06.sh @@ -0,0 +1,6 @@ +Ahmed 1 +Basia 22 +Mehmet 3 +Leila 1 +Piotr 15 +Chandra 12 diff --git a/expect/jq/script-07.sh b/expect/jq/script-07.sh new file mode 100755 index 0000000..59c783f --- /dev/null +++ b/expect/jq/script-07.sh @@ -0,0 +1,6 @@ +Ahmed 4 +Basia 6 +Mehmet 17 +Leila 1 +Piotr 8 +Chandra 6 diff --git a/expect/jq/script-08.sh b/expect/jq/script-08.sh new file mode 100755 index 0000000..dd7e1d3 --- /dev/null +++ b/expect/jq/script-08.sh @@ -0,0 +1,6 @@ +Ahmed 3 +Basia 3 +Mehmet 3 +Leila 1 +Piotr 5 +Chandra 2 diff --git a/expect/jq/script-09.sh b/expect/jq/script-09.sh new file mode 100755 index 0000000..33ff777 --- /dev/null +++ b/expect/jq/script-09.sh @@ -0,0 +1,6 @@ +Ahmed 15 +Basia 37 +Mehmet 32 +Leila 1 +Piotr 61 +Chandra 18 diff --git a/expect/jq/script-10.sh b/expect/jq/script-10.sh new file mode 100755 index 0000000..fb1e7bc --- /dev/null +++ b/expect/jq/script-10.sh @@ -0,0 +1 @@ +54 diff --git a/expect/jq/script-11.sh b/expect/jq/script-11.sh new file mode 100755 index 0000000..4e9bdff --- /dev/null +++ b/expect/jq/script-11.sh @@ -0,0 +1 @@ +164 diff --git a/expect/number-systems/Part-1.json b/expect/number-systems/Part-1.json new file mode 100644 index 0000000..d96c538 --- /dev/null +++ b/expect/number-systems/Part-1.json @@ -0,0 +1 @@ +{"answers": [0, 1110, 45, 1000, "01011", 11111, 100010, 15, 8, 2, "(10$|10[^0-9])", "E", 182, 902, 11, 33]} diff --git a/expect/shell-pipelines/ls-grep/script-01.sh b/expect/shell-pipelines/ls-grep/script-01.sh new file mode 100755 index 0000000..5481774 --- /dev/null +++ b/expect/shell-pipelines/ls-grep/script-01.sh @@ -0,0 +1,11 @@ +Ariane +Daniel +HoChiMinh +KualaLumpur +Levi +London +NewYork +Niamh +Olga +York +dHondt diff --git a/expect/shell-pipelines/ls-grep/script-02.sh b/expect/shell-pipelines/ls-grep/script-02.sh new file mode 100755 index 0000000..35f2e44 --- /dev/null +++ b/expect/shell-pipelines/ls-grep/script-02.sh @@ -0,0 +1,10 @@ +Ariane +Daniel +HoChiMinh +KualaLumpur +Levi +London +NewYork +Niamh +Olga +York diff --git a/expect/shell-pipelines/ls-grep/script-03.sh b/expect/shell-pipelines/ls-grep/script-03.sh new file mode 100755 index 0000000..37ec450 --- /dev/null +++ b/expect/shell-pipelines/ls-grep/script-03.sh @@ -0,0 +1,7 @@ +Ariane +Daniel +Levi +London +Niamh +Olga +York diff --git a/expect/shell-pipelines/ls-grep/script-04.sh b/expect/shell-pipelines/ls-grep/script-04.sh new file mode 100755 index 0000000..7f8f011 --- /dev/null +++ b/expect/shell-pipelines/ls-grep/script-04.sh @@ -0,0 +1 @@ +7 diff --git a/expect/shell-pipelines/sort-uniq-head-tail/script-01.sh b/expect/shell-pipelines/sort-uniq-head-tail/script-01.sh new file mode 100755 index 0000000..a622bc9 --- /dev/null +++ b/expect/shell-pipelines/sort-uniq-head-tail/script-01.sh @@ -0,0 +1,6 @@ +Ahmed London 1 10 4 +Basia London 22 9 6 +Chandra Birmingham 12 6 +Leila London 1 +Mehmet Birmingham 3 12 17 +Piotr Glasgow 15 2 25 11 8 diff --git a/expect/shell-pipelines/sort-uniq-head-tail/script-02.sh b/expect/shell-pipelines/sort-uniq-head-tail/script-02.sh new file mode 100755 index 0000000..c560f60 --- /dev/null +++ b/expect/shell-pipelines/sort-uniq-head-tail/script-02.sh @@ -0,0 +1,6 @@ +Basia London 22 9 6 +Piotr Glasgow 15 2 25 11 8 +Chandra Birmingham 12 6 +Mehmet Birmingham 3 12 17 +Leila London 1 +Ahmed London 1 10 4 diff --git a/expect/shell-pipelines/sort-uniq-head-tail/script-03.sh b/expect/shell-pipelines/sort-uniq-head-tail/script-03.sh new file mode 100755 index 0000000..52d05bc --- /dev/null +++ b/expect/shell-pipelines/sort-uniq-head-tail/script-03.sh @@ -0,0 +1,3 @@ +Basia London 22 9 6 +Piotr Glasgow 15 2 25 11 8 +Chandra Birmingham 12 6 diff --git a/expect/shell-pipelines/sort-uniq-head-tail/script-04.sh b/expect/shell-pipelines/sort-uniq-head-tail/script-04.sh new file mode 100755 index 0000000..9c18f93 --- /dev/null +++ b/expect/shell-pipelines/sort-uniq-head-tail/script-04.sh @@ -0,0 +1 @@ +Piotr Glasgow 15 2 25 11 8 diff --git a/expect/shell-pipelines/sort-uniq-head-tail/script-05.sh b/expect/shell-pipelines/sort-uniq-head-tail/script-05.sh new file mode 100755 index 0000000..5b6440e --- /dev/null +++ b/expect/shell-pipelines/sort-uniq-head-tail/script-05.sh @@ -0,0 +1,6 @@ +Entry German +Entry Mariana +Entry Sally +Exit German +Exit Mariana +Exit Sally diff --git a/expect/shell-pipelines/sort-uniq-head-tail/script-06.sh b/expect/shell-pipelines/sort-uniq-head-tail/script-06.sh new file mode 100755 index 0000000..93bd06b --- /dev/null +++ b/expect/shell-pipelines/sort-uniq-head-tail/script-06.sh @@ -0,0 +1,2 @@ + 5 Entry + 4 Exit diff --git a/expect/shell-pipelines/sort-uniq-head-tail/script-07.sh b/expect/shell-pipelines/sort-uniq-head-tail/script-07.sh new file mode 100755 index 0000000..93bd06b --- /dev/null +++ b/expect/shell-pipelines/sort-uniq-head-tail/script-07.sh @@ -0,0 +1,2 @@ + 5 Entry + 4 Exit diff --git a/expect/shell-pipelines/tr/script-01.sh b/expect/shell-pipelines/tr/script-01.sh new file mode 100755 index 0000000..8dea4eb --- /dev/null +++ b/expect/shell-pipelines/tr/script-01.sh @@ -0,0 +1,11 @@ +Dear Yara, + +Mz apologies for sending this response so late. As zou know, there's been a lot going on. + +Unfortunatelz I don't think I'll be able to make it to Yimbabwe, but but sounds amaying. + +Hope zou're doing well, and enjoz the trip. + +Thanks, + +Karolina diff --git a/expect/shell-pipelines/tr/script-02.sh b/expect/shell-pipelines/tr/script-02.sh new file mode 100755 index 0000000..e00561a --- /dev/null +++ b/expect/shell-pipelines/tr/script-02.sh @@ -0,0 +1,11 @@ +Dear Zara, + +My apologies for sending this response so late. As you know, there's been a lot going on! + +Unfortunately I don't think I'll be able to make it to Zimbabwe, but but sounds amazing! + +Hope you're doing well, and enjoy the trip! + +Thanks, + +Karolina diff --git a/number-systems/Part-1.md b/number-systems/Part-1.md new file mode 100644 index 0000000..d8f9c29 --- /dev/null +++ b/number-systems/Part-1.md @@ -0,0 +1,54 @@ +Do not use any tools or programming to solve these problems. Work it out yourself by hand, and fill in the answers. + +Do not convert any binary numbers to decimal when solving a question unless the question explicitly tells you to. + +The goal of these exercises is for you to gain an intuition for binary numbers. Using tools to solve the problems defeats the point. + +The answers to these questions should be a number, either in binary, hex, or decimal. + +Q1: Convert the decimal number 14 to binary. +Answer: + +Q2: Convert the binary number 101101 to decimal: +Answer: + +Q3: Which is larger: 1000 or 0111? +Answer: + +Q4: Which is larger: 00100 or 01011? +Answer: + +Q5: What is 10101 + 01010? +Answer: + +Q6: What is 10001 + 10001? +Answer: + +Q7: What's the largest number you can store with 4 bits, if you want to be able to represent the number 0? +Answer: + +Q8: How many bits would you need in order to store the numbers between 0 and 255 inclusive? +Answer: + +Q9: How many bits would you need in order to store the numbers between 0 and 3 inclusive? +Answer: + +Q10: How many bits would you need in order to store the numbers between 0 and 1000 inclusive? +Answer: + +Q11: Convert the decimal number 14 to hex. +Answer: + +Q12: Convert the decimal number 386 to hex. +Answer: + +Q13: Convert the hex number 386 to decimal. +Answer: + +Q14: Convert the hex number B to decimal. +Answer: + +Q15: If reading the byte 0x21 as a number, what decimal number would it mean? +Answer: + +Q16: Continues in Part-2 diff --git a/number-systems/Part-2.md b/number-systems/Part-2.md new file mode 100644 index 0000000..92b7005 --- /dev/null +++ b/number-systems/Part-2.md @@ -0,0 +1,22 @@ +Do not use any tools or programming to solve these problems. Work it out yourself by hand, and fill in the answers. + +Do not convert any binary numbers to decimal when solving a question unless the question explicitly tells you to. + +The goal of these exercises is for you to gain an intuition for binary numbers. Using tools to solve the problems defeats the point. + +The answers to these questions will require a bit of explanation, not just a simple answer. + +Q16: How can you test if a binary number is a power of two (e.g. 1, 2, 4, 8, 16, ...)? +Answer: + +Q17: If reading the byte 0x21 as an ASCII character, what character would it mean? +Answer: + +Q18: If reading the byte 0x21 as a greyscale colour, as described in "Approaches for Representing Colors and Images", what colour would it mean? +Answer: + +Q19: If reading the bytes 0xAA00FF as an RGB colour, as described in "Approaches for Representing Colors and Images", what colour would it mean? +Answer: + +Q20: If reading the bytes 0xAA00FF as a sequence of three one-byte decimal numbers, what decimal numbers would they be? +Answer: diff --git a/number-systems/README.md b/number-systems/README.md deleted file mode 100644 index 77a3bde..0000000 --- a/number-systems/README.md +++ /dev/null @@ -1,65 +0,0 @@ -Do not use any tools or programming to solve these problems. Work it out yourself by hand, and fill in the answers. - -Do not convert any binary numbers to decimal when solving a question unless the question explicitly tells you to. - -The goal of these exercises is for you to gain an intuition for binary numbers. Using tools to solve the problems defeats the point. - -Convert the decimal number 14 to binary. -Answer: - -Convert the binary number 101101 to decimal: -Answer: - -Which is larger: 1000 or 0111? -Answer: - -Which is larger: 00100 or 01011? -Answer: - -What is 10101 + 01010? -Answer: - -What is 10001 + 10001? -Answer: - -What's the largest number you can store with 4 bits, if you want to be able to represent the number 0? -Answer: - -How many bits would you need in order to store the numbers between 0 and 255 inclusive? -Answer: - -How many bits would you need in order to store the numbers between 0 and 3 inclusive? -Answer: - -How many bits would you need in order to store the numbers between 0 and 1000 inclusive? -Answer: - -How can you test if a binary number is a power of two (e.g. 1, 2, 4, 8, 16, ...)? -Answer: - -Convert the decimal number 14 to hex. -Answer: - -Convert the decimal number 386 to hex. -Answer: - -Convert the hex number 386 to decimal. -Answer: - -Convert the hex number B to decimal. -Answer: - -If reading the byte 0x21 as a number, what decimal number would it mean? -Answer: - -If reading the byte 0x21 as an ASCII character, what character would it mean? -Answer: - -If reading the byte 0x21 as a greyscale colour, as described in "Approaches for Representing Colors and Images", what colour would it mean? -Answer: - -If reading the bytes 0xAA00FF as an RGB colour, as described in "Approaches for Representing Colors and Images", what colour would it mean? -Answer: - -If reading the bytes 0xAA00FF as a sequence of three one-byte decimal numbers, what decimal numbers would they be? -Answer: diff --git a/test-sdc.sh b/test-sdc.sh new file mode 100755 index 0000000..b8f32cf --- /dev/null +++ b/test-sdc.sh @@ -0,0 +1,132 @@ +# This test file will be automatically run when you submit a pull request +# You should not modify this file +# You can run this file using ./test-sdc.sh task-directory-name to check your output + +echo "" > testoutput.txt +echo "Results of test" >> testoutput.txt + +if [[ "$1" == "individual-shell-tools" ]]; then + cd individual-shell-tools + pass=0 + total=0 + for directory in */; do + cd $directory + if [ "$directory" == "helper-files/" ]; then + cd .. + continue + fi + for exercise in *.sh; do + total=$(($total+1)) + ./$exercise > ../../test.tmp + cmp ../../test.tmp ../../expect/individual-shell-tools/$directory$exercise --quiet + if [ $? -eq 0 ]; then + pass=$(($pass+1)) + else + if [[ "$exercise" == *"stretch"* && $(stat -c%s ../../test.tmp) == "0" ]]; then + # Stretch task not attempted, ignoring" + total=$(($total-1)) + else + echo "Failed $directory$exercise, please check" >> ../../testoutput.txt + fi + fi + done + cd .. + done + cd .. + rm test.tmp + echo "You passed $pass/$total tasks." >> testoutput.txt + if [ -v GITHUB_OUTPUT ]; then + echo "attempted=y" >> "$GITHUB_OUTPUT" + fi + if [ $pass -ge $total ]; then + echo "This task is complete!" >> testoutput.txt + if [ -v GITHUB_OUTPUT ]; then + echo "complete=y" >> "$GITHUB_OUTPUT" + fi + fi + cat testoutput.txt +elif [[ "$1" == "shell-pipelines" ]]; then + cd shell-pipelines + pass=0 + total=0 + for directory in */; do + cd $directory + for exercise in *.sh; do + total=$(($total+1)) + ./$exercise > ../../test.tmp + cmp ../../test.tmp ../../expect/shell-pipelines/$directory$exercise --quiet + if [ $? -eq 0 ]; then + pass=$(($pass+1)) + else + echo "Failed $directory$exercise, please check" >> ../../testoutput.txt + fi + done + cd .. + done + cd .. + rm test.tmp + echo "You passed $pass/$total tasks." >> testoutput.txt + if [ -v GITHUB_OUTPUT ]; then + echo "attempted=y" >> "$GITHUB_OUTPUT" + fi + if [ $pass -eq $total ]; then + echo "This task is complete!" >> testoutput.txt + if [ -v GITHUB_OUTPUT ]; then + echo "complete=y" >> "$GITHUB_OUTPUT" + fi + fi + cat testoutput.txt +elif [[ "$1" == "jq" ]]; then + cd jq + pass=0 + total=0 + for exercise in *.sh; do + total=$(($total+1)) + ./$exercise > ../test.tmp + cmp ../test.tmp ../expect/jq/$exercise --quiet + if [ $? -eq 0 ]; then + pass=$(($pass+1)) + else + echo "Failed $exercise, please check" >> ../testoutput.txt + fi + done + cd .. + rm test.tmp + echo "You passed $pass/$total tasks." >> testoutput.txt + if [ -v GITHUB_OUTPUT ]; then + echo "attempted=y" >> "$GITHUB_OUTPUT" + fi + if [ $pass -eq $total ]; then + echo "This task is complete!" >> testoutput.txt + if [ -v GITHUB_OUTPUT ]; then + echo "complete=y" >> "$GITHUB_OUTPUT" + fi + fi + cat testoutput.txt +elif [[ "$1" == "number-systems" ]]; then + pass=0 + for question in $(seq 1 15); do + answer=$(jq -r ".answers[$question]" expect/number-systems/Part-1.json) + nextq=$(($question+1)) + Q_START=$(grep -n "Q$question:" number-systems/Part-1.md | cut -d: -f1) + ANS_START=$(($Q_START + 1)) + NEXT_Q_START=$(grep -n "Q$nextq:" number-systems/Part-1.md | cut -d: -f1) + ANS_END=$(($NEXT_Q_START - 1)) + sed -n "$ANS_START,${ANS_END}p;${NEXT_Q_START}q" number-systems/Part-1.md | cut -d: -f2- > answerfile + grep --quiet -iE $answer answerfile + if [ $? -eq 0 ]; then + pass=$(($pass+1)) + else + echo "Q$question incorrect, please check" >> testoutput.txt + fi + rm answerfile + done + echo "You passed $pass/15 tasks in part 1." >> testoutput.txt + if [ -v GITHUB_OUTPUT ]; then + echo "attempted=y" >> "$GITHUB_OUTPUT" + fi + echo "Please let a volunteer check the answers for part 2." >> testoutput.txt + cat testoutput.txt +else + echo "Please run this with a valid test directory name as argument" +fi From f38fee54def1961f51947ca50ef9dde930a00c39 Mon Sep 17 00:00:00 2001 From: Daniel Wagner-Hall Date: Tue, 12 May 2026 14:26:50 +0100 Subject: [PATCH 03/24] Make test-sdc script more portable (#520) * Add a shebang because `-v` is a bash-ism not present in whatever my default shell is on macOS. * Remove `--quiet` from `cmp` - macOS's `cmp` doesn't support it. --- test-sdc.sh | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/test-sdc.sh b/test-sdc.sh index b8f32cf..acf9897 100755 --- a/test-sdc.sh +++ b/test-sdc.sh @@ -1,3 +1,5 @@ +#!/bin/bash + # This test file will be automatically run when you submit a pull request # You should not modify this file # You can run this file using ./test-sdc.sh task-directory-name to check your output @@ -18,7 +20,7 @@ if [[ "$1" == "individual-shell-tools" ]]; then for exercise in *.sh; do total=$(($total+1)) ./$exercise > ../../test.tmp - cmp ../../test.tmp ../../expect/individual-shell-tools/$directory$exercise --quiet + cmp ../../test.tmp ../../expect/individual-shell-tools/$directory$exercise if [ $? -eq 0 ]; then pass=$(($pass+1)) else @@ -54,7 +56,7 @@ elif [[ "$1" == "shell-pipelines" ]]; then for exercise in *.sh; do total=$(($total+1)) ./$exercise > ../../test.tmp - cmp ../../test.tmp ../../expect/shell-pipelines/$directory$exercise --quiet + cmp ../../test.tmp ../../expect/shell-pipelines/$directory$exercise if [ $? -eq 0 ]; then pass=$(($pass+1)) else @@ -83,7 +85,7 @@ elif [[ "$1" == "jq" ]]; then for exercise in *.sh; do total=$(($total+1)) ./$exercise > ../test.tmp - cmp ../test.tmp ../expect/jq/$exercise --quiet + cmp ../test.tmp ../expect/jq/$exercise if [ $? -eq 0 ]; then pass=$(($pass+1)) else From ab33fa5254177acf1df97d1557f5f87ef9a5eeeb Mon Sep 17 00:00:00 2001 From: Daniel Wagner-Hall Date: Tue, 12 May 2026 15:26:14 +0100 Subject: [PATCH 04/24] Add automated tests for cowsay implementation (#521) --- .github/workflows/validate-pr-metadata.yml | 11 +++-- expect/implement-cowsay/cow-grass.txt | 10 +++++ expect/implement-cowsay/turtle-fish.txt | 22 ++++++++++ test-sdc.sh | 47 ++++++++++++++++++++++ 4 files changed, 87 insertions(+), 3 deletions(-) create mode 100644 expect/implement-cowsay/cow-grass.txt create mode 100644 expect/implement-cowsay/turtle-fish.txt diff --git a/.github/workflows/validate-pr-metadata.yml b/.github/workflows/validate-pr-metadata.yml index 8ff276f..b4ff602 100644 --- a/.github/workflows/validate-pr-metadata.yml +++ b/.github/workflows/validate-pr-metadata.yml @@ -48,15 +48,20 @@ jobs: if: contains(steps.changed-files.outputs.modified_files, 'number-systems/') run: ./test-sdc.sh number-systems shell: bash + - name: test implement-cowsay + id: test-implement-cowsay + if: contains(steps.changed-files.outputs.modified_files, 'implement-cowsay/') + run: ./test-sdc.sh implement-cowsay + shell: bash - name: make output comment - if: steps.test-individual-shell-tools.outputs.attempted == 'y' || steps.test-jq.outputs.attempted == 'y' || steps.test-shell-pipelines.outputs.attempted == 'y' || steps.test-number-systems.outputs.attempted == 'y' + if: steps.test-individual-shell-tools.outputs.attempted == 'y' || steps.test-jq.outputs.attempted == 'y' || steps.test-shell-pipelines.outputs.attempted == 'y' || steps.test-number-systems.outputs.attempted == 'y' || steps.test-implement-cowsay.outputs.attempted == 'y' env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} ISSUE_URL: ${{ github.event.pull_request.html_url }} run: | gh pr comment $ISSUE_URL --body-file testoutput.txt - name: add appropriate labels - if: steps.test-individual-shell-tools.outputs.complete == 'y' || steps.test-jq.outputs.complete == 'y' || steps.test-shell-pipelines.outputs.complete == 'y' + if: steps.test-individual-shell-tools.outputs.complete == 'y' || steps.test-jq.outputs.complete == 'y' || steps.test-shell-pipelines.outputs.complete == 'y' || steps.test-implement-cowsay.outputs.complete == 'y' env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} ISSUE_URL: ${{ github.event.pull_request.html_url }} @@ -64,6 +69,6 @@ jobs: gh pr edit $ISSUE_URL --add-label "Complete" gh pr edit $ISSUE_URL --remove-label "Needs Review" - name: fail if not complete - if: (steps.test-individual-shell-tools.outputs.attempted == 'y' || steps.test-jq.outputs.attempted == 'y' || steps.test-shell-pipelines.outputs.attempted == 'y') && !(steps.test-individual-shell-tools.outputs.complete == 'y' || steps.test-jq.outputs.complete == 'y' || steps.test-shell-pipelines.outputs.complete == 'y') + if: (steps.test-individual-shell-tools.outputs.attempted == 'y' || steps.test-jq.outputs.attempted == 'y' || steps.test-shell-pipelines.outputs.attempted == 'y' || steps.test-implement-cowsay.outputs.attempted == 'y') && !(steps.test-individual-shell-tools.outputs.complete == 'y' || steps.test-jq.outputs.complete == 'y' || steps.test-shell-pipelines.outputs.complete == 'y' || steps.test-implement-cowsay.outputs.complete == 'y') run: | exit -1 diff --git a/expect/implement-cowsay/cow-grass.txt b/expect/implement-cowsay/cow-grass.txt new file mode 100644 index 0000000..d9c665a --- /dev/null +++ b/expect/implement-cowsay/cow-grass.txt @@ -0,0 +1,10 @@ + ________________ +| Grass, delicious | + ================ + \ + \ + ^__^ + (oo)\_______ + (__)\ )\/\ + ||----w | + || || diff --git a/expect/implement-cowsay/turtle-fish.txt b/expect/implement-cowsay/turtle-fish.txt new file mode 100644 index 0000000..5dadfa0 --- /dev/null +++ b/expect/implement-cowsay/turtle-fish.txt @@ -0,0 +1,22 @@ + ______________ +| Fish are cool! | + ============== + \ + \ + \ + \ + ___-------___ + _-~~ ~~-_ + _-~ /~-_ + /^\__/^\ /~ \ / \ + /| O|| O| / \_______________/ \ + | |___||__| / / \ \ + | \ / / \ \ + | (_______) /______/ \_________ \ + | / / \ / \ + \ \^\\ \ / \ / + \ || \______________/ _-_ //\__// + \ ||------_-~~-_ ------------- \ --/~ ~\ || __/ + ~-----||====/~ |==================| |/~~~~~ + (_(__/ ./ / \_\ \. + (_(___/ \_____)_) diff --git a/test-sdc.sh b/test-sdc.sh index acf9897..adc8dc4 100755 --- a/test-sdc.sh +++ b/test-sdc.sh @@ -129,6 +129,53 @@ elif [[ "$1" == "number-systems" ]]; then fi echo "Please let a volunteer check the answers for part 2." >> testoutput.txt cat testoutput.txt +elif [[ "$1" == "implement-cowsay" ]]; then + if [ -v GITHUB_OUTPUT ]; then + echo "attempted=y" >> "$GITHUB_OUTPUT" + fi + if [[ -e .cowsay-venv ]]; then + echo ".cowsay-venv already exists - couldn't test cowsay" >> testoutput.txt + else + if [ ! -e implement-cowsay/requirements.txt ]; then + echo "Expected implement-cowsay/requirements.txt to exist but it didn't" >> testoutput.txt + else + python3 -m venv .cowsay-venv + . .cowsay-venv/bin/activate + pip3 install -r implement-cowsay/requirements.txt + + all_ok=true + + python3 implement-cowsay/cow.py Grass, delicious > test.tmp + cmp test.tmp expect/implement-cowsay/cow-grass.txt + if [ $? -ne 0 ]; then + echo "Unexpected cowsay output for Grass, delicious" >> testoutput.txt + all_ok=false + fi + + python3 implement-cowsay/cow.py --animal turtle "Fish are cool!" > test.tmp + cmp test.tmp expect/implement-cowsay/turtle-fish.txt + if [ $? -ne 0 ]; then + echo "Unexpected cowsay output for Fish are cool!" >> testoutput.txt + all_ok=false + fi + + python3 implement-cowsay/cow.py --help | grep dragon + if [ $? -ne 0 ]; then + echo "Expected python3 cow.py --help to include dragon as it's one of the animal options" >> testoutput.txt + all_ok=false + fi + + grep dragon implement-cowsay/cow.py > /dev/null + if [ $? -eq 0 ]; then + echo "Didn't expect cow.py to include the word dragon - you should pull in the animal options from the library not write them yourself" >> testoutput.txt + all_ok=false + fi + + if [[ "${all_ok}" == "true" && -v GITHUB_OUTPUT ]]; then + echo "completed=y" >> "$GITHUB_OUTPUT" + fi + fi + fi else echo "Please run this with a valid test directory name as argument" fi From d3857c25cc7a1a94111183aa2901ef73c45c4865 Mon Sep 17 00:00:00 2001 From: Daniel Wagner-Hall Date: Wed, 13 May 2026 10:05:37 +0100 Subject: [PATCH 05/24] Improve cowsay comments (#522) * Trigger on added files - Cowsay involves writing brand new files * Hide previous comments * Fix completed -> complete * Clean up output a little --- .github/workflows/validate-pr-metadata.yml | 4 +++- test-sdc.sh | 9 +++++---- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/.github/workflows/validate-pr-metadata.yml b/.github/workflows/validate-pr-metadata.yml index b4ff602..6fa2e1d 100644 --- a/.github/workflows/validate-pr-metadata.yml +++ b/.github/workflows/validate-pr-metadata.yml @@ -50,7 +50,7 @@ jobs: shell: bash - name: test implement-cowsay id: test-implement-cowsay - if: contains(steps.changed-files.outputs.modified_files, 'implement-cowsay/') + if: contains(steps.changed-files.outputs.all_modified_files, 'implement-cowsay/') run: ./test-sdc.sh implement-cowsay shell: bash - name: make output comment @@ -59,6 +59,8 @@ jobs: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} ISSUE_URL: ${{ github.event.pull_request.html_url }} run: | + gh release -R CodeYourFuture/trainee-tracker view --json assets -q ".assets[] | select(.name | startswith(\"pr-metadata-validator-musl-\")).url" | xargs curl --fail -L -o /tmp/pr-metadata-validator && chmod 0755 /tmp/pr-metadata-validator + /tmp/pr-metadata-validator --only-close-existing-comments-with-tag="sdc-test-results" "${ISSUE_URL}" gh pr comment $ISSUE_URL --body-file testoutput.txt - name: add appropriate labels if: steps.test-individual-shell-tools.outputs.complete == 'y' || steps.test-jq.outputs.complete == 'y' || steps.test-shell-pipelines.outputs.complete == 'y' || steps.test-implement-cowsay.outputs.complete == 'y' diff --git a/test-sdc.sh b/test-sdc.sh index adc8dc4..62d1c7e 100755 --- a/test-sdc.sh +++ b/test-sdc.sh @@ -5,7 +5,7 @@ # You can run this file using ./test-sdc.sh task-directory-name to check your output echo "" > testoutput.txt -echo "Results of test" >> testoutput.txt +echo "Results of test:" >> testoutput.txt if [[ "$1" == "individual-shell-tools" ]]; then cd individual-shell-tools @@ -148,14 +148,14 @@ elif [[ "$1" == "implement-cowsay" ]]; then python3 implement-cowsay/cow.py Grass, delicious > test.tmp cmp test.tmp expect/implement-cowsay/cow-grass.txt if [ $? -ne 0 ]; then - echo "Unexpected cowsay output for Grass, delicious" >> testoutput.txt + echo "Unexpected cowsay output for 'Grass, delicious'." >> testoutput.txt all_ok=false fi python3 implement-cowsay/cow.py --animal turtle "Fish are cool!" > test.tmp cmp test.tmp expect/implement-cowsay/turtle-fish.txt if [ $? -ne 0 ]; then - echo "Unexpected cowsay output for Fish are cool!" >> testoutput.txt + echo "Unexpected cowsay output for --animal turtle Fish are cool!" >> testoutput.txt all_ok=false fi @@ -172,7 +172,8 @@ elif [[ "$1" == "implement-cowsay" ]]; then fi if [[ "${all_ok}" == "true" && -v GITHUB_OUTPUT ]]; then - echo "completed=y" >> "$GITHUB_OUTPUT" + echo "complete=y" >> "$GITHUB_OUTPUT" + echo "Your implementation passed all checks." >> testoutput.txt fi fi fi From 47e4131a87e815f7a089e6393b43b7ef9354f413 Mon Sep 17 00:00:00 2001 From: Poonam <61753159+Poonam-raj@users.noreply.github.com> Date: Wed, 10 Jun 2026 11:07:31 +0100 Subject: [PATCH 06/24] switching last two hex questions (#533) So a trainee has to convert the hex into a number before considering how this might look as colours. Ideally to improve lead into the idea of that % of bytes used can create the colour mix. Same could be said for the grey question needing more suggestion of how to calculate a colour mix (somehow hinting or leading to the idea of proportion of bytes used). Suggestions welcome for whether this fixes a misunderstanding or not, or if it can be improved. --- number-systems/Part-2.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/number-systems/Part-2.md b/number-systems/Part-2.md index 92b7005..68b0933 100644 --- a/number-systems/Part-2.md +++ b/number-systems/Part-2.md @@ -7,16 +7,16 @@ The goal of these exercises is for you to gain an intuition for binary numbers. The answers to these questions will require a bit of explanation, not just a simple answer. Q16: How can you test if a binary number is a power of two (e.g. 1, 2, 4, 8, 16, ...)? -Answer: +Answer: Q17: If reading the byte 0x21 as an ASCII character, what character would it mean? -Answer: +Answer: Q18: If reading the byte 0x21 as a greyscale colour, as described in "Approaches for Representing Colors and Images", what colour would it mean? -Answer: +Answer: -Q19: If reading the bytes 0xAA00FF as an RGB colour, as described in "Approaches for Representing Colors and Images", what colour would it mean? -Answer: +Q19: If reading the bytes 0xAA00FF as a sequence of three one-byte decimal numbers, what decimal numbers would they be? +Answer: -Q20: If reading the bytes 0xAA00FF as a sequence of three one-byte decimal numbers, what decimal numbers would they be? -Answer: +Q20: If reading the bytes 0xAA00FF as an RGB colour, as described in "Approaches for Representing Colors and Images", what colour would it mean? +Answer: From 3f26722eca564c25fc8ac11e99327544b55445a3 Mon Sep 17 00:00:00 2001 From: Daniel Wagner-Hall Date: Wed, 24 Jun 2026 15:34:38 +0100 Subject: [PATCH 07/24] Add more expected test cases for ls implementations (#441) Some trainees are doing a bit less than I was expecting when originally setting this. Not to be merged util the current cohort have completed Module-Tools. --- implement-shell-tools/ls/README.md | 3 +++ implement-shell-tools/wc/README.md | 2 ++ 2 files changed, 5 insertions(+) diff --git a/implement-shell-tools/ls/README.md b/implement-shell-tools/ls/README.md index edbfb81..c0b81e0 100644 --- a/implement-shell-tools/ls/README.md +++ b/implement-shell-tools/ls/README.md @@ -6,6 +6,9 @@ Your task is to implement your own version of `ls`. It must act the same as `ls` would, if run from the directory containing this README.md file, for the following command lines: +* `ls sample-files` +* `ls sample-files/1.txt` +* `ls sample-files/*` * `ls -1` * `ls -1 sample-files` * `ls -1 -a sample-files` diff --git a/implement-shell-tools/wc/README.md b/implement-shell-tools/wc/README.md index bd76b65..925e522 100644 --- a/implement-shell-tools/wc/README.md +++ b/implement-shell-tools/wc/README.md @@ -11,6 +11,8 @@ It must act the same as `wc` would, if run from the directory containing this RE * `wc -w sample-files/3.txt` * `wc -c sample-files/3.txt` * `wc -l sample-files/*` +* `wc -w -l sample-files/3.txt` +* `wc -w -l sample-files/*` Matching any additional behaviours or flags are optional stretch goals. From 78bdfd5f5130b16853693f87d5b920acfa710cda Mon Sep 17 00:00:00 2001 From: Alex Jamshidi Date: Tue, 28 Jul 2026 10:08:03 +0100 Subject: [PATCH 08/24] cat implemented --- implement-shell-tools/cat/cat.js | 46 ++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 implement-shell-tools/cat/cat.js diff --git a/implement-shell-tools/cat/cat.js b/implement-shell-tools/cat/cat.js new file mode 100644 index 0000000..e21ac51 --- /dev/null +++ b/implement-shell-tools/cat/cat.js @@ -0,0 +1,46 @@ +const fs = require("fs"); + +const arguments = process.argv; +const filePaths = arguments.slice(2); + +const index = arguments[1].lastIndexOf("/"); +const currentWorkingDirectory = arguments[1].slice(0, index + 1); + +let flag = filePaths[0]; +if (flag.charAt(0) == "-") { + flag = filePaths.shift(); +} + +function splitFile(fileName) { + const filePath = currentWorkingDirectory + fileName; + const text = fs.readFileSync(filePath, "utf8").trimEnd(); + return text.split("\n"); +} + +function printFile(lines) { + lines.forEach((line) => { + console.log(line); + }); +} + +function executeFlag(lines) { + if (flag === "-n") { + return lines.map((line, index) => `${String(index + 1).padStart(6, " ")} ${line}`); + } else if (flag === "-b") { + let lineNumber = 1; + return lines.map((line) => { + if (line === "") { + return `${line}`; + } + return `${String(lineNumber++).padStart(6, " ")} ${line}`; + }); + } else { + return lines; + } +} + +filePaths.forEach((filePath) => { + let lines = splitFile(filePath); + lines = executeFlag(lines); + printFile(lines); +}); From 60b323e23467c6d21b1abe736ddcb10bd41f8c4e Mon Sep 17 00:00:00 2001 From: Alex Jamshidi Date: Tue, 28 Jul 2026 20:10:51 +0100 Subject: [PATCH 09/24] cat updated --- implement-shell-tools/cat/cat.js | 77 +++++++++++++++++++++----------- 1 file changed, 52 insertions(+), 25 deletions(-) diff --git a/implement-shell-tools/cat/cat.js b/implement-shell-tools/cat/cat.js index e21ac51..e9dcd91 100644 --- a/implement-shell-tools/cat/cat.js +++ b/implement-shell-tools/cat/cat.js @@ -1,46 +1,73 @@ const fs = require("fs"); const arguments = process.argv; -const filePaths = arguments.slice(2); +const userArguments = arguments.slice(2); const index = arguments[1].lastIndexOf("/"); const currentWorkingDirectory = arguments[1].slice(0, index + 1); -let flag = filePaths[0]; -if (flag.charAt(0) == "-") { - flag = filePaths.shift(); +const flags = userArguments + .filter((argument) => argument.startsWith("-")) + .map((argument) => argument.slice(1)) + .join(""); + +const flagHandlers = { + b: bFlag, + n: nFlag, +}; + +const fileNames = userArguments.filter((argument) => !argument.startsWith("-")); +let allFilesContents = []; + +readFiles(); +executeFlags(); +printLines(); + +function readFiles() { + fileNames.forEach((fileName) => { + fileContent = readFile(fileName); + allFilesContents.push(fileContent.split("\n")); + }); } -function splitFile(fileName) { +function readFile(fileName) { const filePath = currentWorkingDirectory + fileName; - const text = fs.readFileSync(filePath, "utf8").trimEnd(); - return text.split("\n"); + return fs.readFileSync(filePath, "utf8").trimEnd(); } -function printFile(lines) { - lines.forEach((line) => { - console.log(line); - }); +function executeFlags() { + for (const flag of flags) { + if (flagHandlers[flag]) { + allFilesContents = flagHandlers[flag](); + } else { + console.error(`cat: illegal option -- ${flag}\nusage: cat [-belnstuv] [file ...]`); + process.exit(1); + } + } } -function executeFlag(lines) { - if (flag === "-n") { - return lines.map((line, index) => `${String(index + 1).padStart(6, " ")} ${line}`); - } else if (flag === "-b") { +function bFlag() { + return allFilesContents.map((fileContent) => { let lineNumber = 1; - return lines.map((line) => { - if (line === "") { + return fileContent.map((line, index) => { + if (line == "") { return `${line}`; } return `${String(lineNumber++).padStart(6, " ")} ${line}`; }); - } else { - return lines; - } + }); +} + +function nFlag() { + return allFilesContents.map((fileContent) => { + return fileContent.map((line, index) => `${String(index + 1).padStart(6, " ")} ${line}`); + }); } -filePaths.forEach((filePath) => { - let lines = splitFile(filePath); - lines = executeFlag(lines); - printFile(lines); -}); +function printLines() { + allFilesContents.forEach((fileContent) => { + fileContent.forEach((line) => { + console.log(line); + }); + }); +} From 3a35a7a48f3d8a52b2c79ead79cfd7db0a1bc30d Mon Sep 17 00:00:00 2001 From: Alex Jamshidi Date: Wed, 29 Jul 2026 00:56:56 +0100 Subject: [PATCH 10/24] wc implemented --- implement-shell-tools/wc/wc.js | 141 +++++++++++++++++++++++++++++++++ 1 file changed, 141 insertions(+) create mode 100644 implement-shell-tools/wc/wc.js diff --git a/implement-shell-tools/wc/wc.js b/implement-shell-tools/wc/wc.js new file mode 100644 index 0000000..9635e8e --- /dev/null +++ b/implement-shell-tools/wc/wc.js @@ -0,0 +1,141 @@ +const fs = require("fs"); +const { allowedNodeEnvironmentFlags } = require("process"); + +const arguments = process.argv; +const userArguments = arguments.slice(2); + +const index = arguments[1].lastIndexOf("/"); +const currentWorkingDirectory = arguments[1].slice(0, index + 1); + +const flags = userArguments + .filter((argument) => argument.startsWith("-")) + .map((argument) => argument.slice(1)) + .join(""); + +const flagHandlers = { + l: lFlag, + w: wFlag, + c: cFlag, +}; + +const metrics = ["lineCount", "wordCount", "byteSize"]; +const fileNames = userArguments.filter((argument) => !argument.startsWith("-")); +const allFilesData = []; + +extractFilesData(); +addTotals(); +const outputData = structuredClone(allFilesData); + +let deleted = false; +executeFlags(); +printOutput(); + +function extractFilesData() { + fileNames.forEach((fileName) => { + const fileData = {}; + fileData.name = fileName; + fileData.text = readFile(fileName); + fileData.lineCount = calculateLineCount(fileData.text); + fileData.wordCount = calculateWordCount(fileData.text); + fileData.byteSize = readByteSize(fileName); + + allFilesData.push(fileData); + }); +} + +function addTotals() { + if (allFilesData.length == 1) { + return; + } + const totalsData = { name: "total" }; + metrics.forEach((metric) => { + let sum = 0; + allFilesData.forEach((file) => { + sum += file[metric]; + }); + totalsData[metric] = sum; + }); + allFilesData.push(totalsData); +} + +function readFile(fileName) { + const filePath = currentWorkingDirectory + fileName; + return fs.readFileSync(filePath, "utf8").trimEnd(); +} + +function calculateLineCount(text) { + return text.split("\n").length; +} + +function calculateWordCount(text) { + return text.split(/\s+/).length; +} + +function readByteSize(fileName) { + return fs.statSync(currentWorkingDirectory + fileName).size; +} + +function executeFlags() { + for (const flag of flags) { + if (flagHandlers[flag]) { + allFilesContents = flagHandlers[flag](); + } else { + console.error(`wc: illegal option -- ${flag}\nusage: wc [-Lclmw] [file ...]`); + process.exit(1); + } + } +} + +function lFlag() { + deleteOutputs(); + allFilesData.forEach((sourceFile) => { + const targetFile = outputData.find((file) => file.name === sourceFile.name); + targetFile.lineCount = getIfTrue(sourceFile, "lineCount"); + }); +} + +function wFlag() { + deleteOutputs(); + allFilesData.forEach((sourceFile) => { + const targetFile = outputData.find((file) => file.name === sourceFile.name); + targetFile.wordCount = getIfTrue(sourceFile, "wordCount"); + }); +} + +function cFlag() { + deleteOutputs(); + allFilesData.forEach((sourceFile) => { + const targetFile = outputData.find((file) => file.name === sourceFile.name); + targetFile.byteSize = getIfTrue(sourceFile, "byteSize"); + }); +} + +function deleteOutputs() { + if (!deleted) { + outputData.forEach((file) => { + metrics.forEach((metric) => { + delete file[metric]; + }); + }); + } + deleted = true; +} + +function printOutput() { + outputs = []; + outputData.forEach((file) => { + let outputString = ""; + metrics.forEach((metric) => { + outputString += getIfTrue(file, metric); + }); + outputString += ` ${file.name}`; + console.log(outputString); + }); +} + +function getIfTrue(file, metric) { + if (file[metric]) { + return String(file[metric]).padStart(8, " "); + } + return ""; +} From 085224e36b79d11885b390b6ab7cbb165b1ff481 Mon Sep 17 00:00:00 2001 From: Alex Jamshidi Date: Thu, 30 Jul 2026 17:17:26 +0100 Subject: [PATCH 11/24] ls complete --- implement-shell-tools/ls/ls.js | 124 +++++++++++++++++++++++++++++++++ 1 file changed, 124 insertions(+) create mode 100644 implement-shell-tools/ls/ls.js diff --git a/implement-shell-tools/ls/ls.js b/implement-shell-tools/ls/ls.js new file mode 100644 index 0000000..6f11136 --- /dev/null +++ b/implement-shell-tools/ls/ls.js @@ -0,0 +1,124 @@ +const fs = require("fs"); +const { allowedNodeEnvironmentFlags } = require("process"); + +const args = process.argv; +const userArgs = args.slice(2); + +const index = args[1].lastIndexOf("/"); +const currentWorkingDirectory = args[1].slice(0, index); + +const flags = userArgs + .filter((arg) => arg.startsWith("-")) + .map((arg) => arg.slice(1)) + .join(""); + +const flagHandlers = { + 1: Flag1, + a: Flaga, +}; + +let printInList = false; +let showAll = false; + +const fsItems = userArgs.filter((arg) => !arg.startsWith("-")); +let dirArgs; +let fileArgs; +let outputString = ""; + +const rawData = fs.readdirSync(currentWorkingDirectory); + +checkArgsLength(); +executeFlags(); +filterFilesAndDirs(); +populateOutput(); +print(); + +function executeFlags() { + for (const flag of flags) { + if (flagHandlers[flag]) { + allFilesContents = flagHandlers[flag](); + } else { + console.error( + `ls: invalid option -- ${flag}\nusage: ls [-@ABCFGHILOPRSTUWXabcdefghiklmnopqrstuvwxy1%,] [--color=when] [-D format] [file ...]`, + ); + process.exit(1); + } + } +} + +function Flag1() { + if (printInList) { + outputString = outputString.replaceAll("\t", "\n"); + outputString = outputString.replaceAll("\n\n", "\n"); + } else { + printInList = true; + } +} + +function Flaga() { + showAll = true; +} + +function checkArgsLength() { + if (fsItems.length == 0) { + fsItems.push("."); + } +} + +function filterFilesAndDirs() { + dirArgs = fsItems.filter((p) => fs.statSync(currentWorkingDirectory + "/" + p).isDirectory()); + fileArgs = fsItems.filter((p) => !fs.statSync(currentWorkingDirectory + "/" + p).isDirectory()); + if (showAll == false) { + removeDotFiles(fileArgs); + } +} + +function populateOutput() { + if (fsItems.length == 1) { + fileArgs.forEach((file) => { + outputString += file + " "; + }); + dirArgs.forEach((dir) => { + outputString += dirOutput(dir); + }); + } else { + fileArgs.forEach((file) => { + outputString += file + "\t"; + }); + dirArgs.forEach((dir) => { + outputString += "\n\n" + dir + ":\n" + dirOutput(dir); + }); + } +} + +function dirOutput(dir) { + let contents = fs.readdirSync(currentWorkingDirectory + "/" + dir); + let outputStr = ""; + if (showAll) { + outputStr += ".\t..\t"; + } else { + for (let i = contents.length - 1; i >= 0; i--) { + contents = removeDotFiles(contents); + } + } + contents.forEach((item) => { + outputStr += item + "\t"; + }); + return outputStr; +} + +function removeDotFiles(fileList) { + for (let i = fileList.length - 1; i >= 0; i--) { + if (fileList[i][0] == ".") { + fileList.splice(i, 1); + } + } + return fileList; +} + +function print() { + if (printInList == true) { + Flag1(); + } + console.log(outputString.trimEnd()); +} From b28f41dbb8b34c4594b13b4dd36aee9b001e6ba8 Mon Sep 17 00:00:00 2001 From: Alex Jamshidi Date: Fri, 31 Jul 2026 03:27:27 +0100 Subject: [PATCH 12/24] remove unused code --- implement-shell-tools/ls/ls.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/implement-shell-tools/ls/ls.js b/implement-shell-tools/ls/ls.js index 6f11136..8646dfe 100644 --- a/implement-shell-tools/ls/ls.js +++ b/implement-shell-tools/ls/ls.js @@ -25,8 +25,6 @@ let dirArgs; let fileArgs; let outputString = ""; -const rawData = fs.readdirSync(currentWorkingDirectory); - checkArgsLength(); executeFlags(); filterFilesAndDirs(); From 7d6a2986197f28cce46160941144831353c63a0e Mon Sep 17 00:00:00 2001 From: Alex Jamshidi Date: Wed, 12 Aug 2026 04:21:24 +0100 Subject: [PATCH 13/24] updated eronious boolean in ls --- implement-shell-tools/ls/ls.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/implement-shell-tools/ls/ls.js b/implement-shell-tools/ls/ls.js index 8646dfe..0efb21d 100644 --- a/implement-shell-tools/ls/ls.js +++ b/implement-shell-tools/ls/ls.js @@ -115,7 +115,7 @@ function removeDotFiles(fileList) { } function print() { - if (printInList == true) { + if (printInList) { Flag1(); } console.log(outputString.trimEnd()); From fce72f0c63b83180ab45dc058314a49e83d63d22 Mon Sep 17 00:00:00 2001 From: Alex Jamshidi Date: Fri, 28 Aug 2026 06:28:08 +0100 Subject: [PATCH 14/24] updated ls so that data doesn't rely on global variables and is passed through functions as arguments - also fixed an if boolean --- implement-shell-tools/ls/ls.js | 133 ++++++++++++++++++--------------- 1 file changed, 72 insertions(+), 61 deletions(-) diff --git a/implement-shell-tools/ls/ls.js b/implement-shell-tools/ls/ls.js index 0efb21d..08af639 100644 --- a/implement-shell-tools/ls/ls.js +++ b/implement-shell-tools/ls/ls.js @@ -1,17 +1,6 @@ const fs = require("fs"); const { allowedNodeEnvironmentFlags } = require("process"); -const args = process.argv; -const userArgs = args.slice(2); - -const index = args[1].lastIndexOf("/"); -const currentWorkingDirectory = args[1].slice(0, index); - -const flags = userArgs - .filter((arg) => arg.startsWith("-")) - .map((arg) => arg.slice(1)) - .join(""); - const flagHandlers = { 1: Flag1, a: Flaga, @@ -20,81 +9,106 @@ const flagHandlers = { let printInList = false; let showAll = false; -const fsItems = userArgs.filter((arg) => !arg.startsWith("-")); -let dirArgs; -let fileArgs; -let outputString = ""; +ls(process.argv); + +// ---------- + +function ls(args) { + const userArgs = getUserArgs(args); + const currentWorkingDirectory = getCurrentWorkingDirectory(args); + const fsItems = checkArgsLength(getFsItems(userArgs)); + + executeFlags(getFlags(userArgs)); + + const dirArgs = getDirArgs(fsItems, currentWorkingDirectory); + const fileArgs = getFileArgs(fsItems, currentWorkingDirectory); + + print(populateOutput(fsItems, dirArgs, fileArgs, currentWorkingDirectory)); +} + +function getUserArgs(args) { + return args.slice(2); +} + +function getFsItems(userArgs) { + return userArgs.filter((arg) => !arg.startsWith("-")); +} + +function getCurrentWorkingDirectory(args) { + const index = args[1].lastIndexOf("/"); + return args[1].slice(0, index); +} -checkArgsLength(); -executeFlags(); -filterFilesAndDirs(); -populateOutput(); -print(); +function getFlags(userArgs) { + return userArgs + .filter((arg) => arg.startsWith("-")) + .map((arg) => arg.slice(1)) + .join(""); +} -function executeFlags() { +function executeFlags(flags) { for (const flag of flags) { - if (flagHandlers[flag]) { - allFilesContents = flagHandlers[flag](); - } else { - console.error( - `ls: invalid option -- ${flag}\nusage: ls [-@ABCFGHILOPRSTUWXabcdefghiklmnopqrstuvwxy1%,] [--color=when] [-D format] [file ...]`, - ); - process.exit(1); - } + if (flagHandlers[flag]) flagHandlers[flag](); + else invalidFlagError(); } } -function Flag1() { - if (printInList) { - outputString = outputString.replaceAll("\t", "\n"); - outputString = outputString.replaceAll("\n\n", "\n"); - } else { - printInList = true; - } +function invalidFlagError() { + console.error( + `ls: invalid option -- ${flag}\nusage: ls [-@ABCFGHILOPRSTUWXabcdefghiklmnopqrstuvwxy1%,] [--color=when] [-D format] [file ...]`, + ); + process.exit(1); +} + +function Flag1(outputString) { + if (printInList) return outputString.replaceAll("\t", "\n").replaceAll("\n\n", "\n"); + else printInList = true; } function Flaga() { showAll = true; } -function checkArgsLength() { - if (fsItems.length == 0) { - fsItems.push("."); - } +function checkArgsLength(fsItems) { + if (fsItems.length == 0) fsItems.push("."); + return fsItems; } -function filterFilesAndDirs() { - dirArgs = fsItems.filter((p) => fs.statSync(currentWorkingDirectory + "/" + p).isDirectory()); - fileArgs = fsItems.filter((p) => !fs.statSync(currentWorkingDirectory + "/" + p).isDirectory()); - if (showAll == false) { - removeDotFiles(fileArgs); - } +function getDirArgs(fsItems, currentWorkingDirectory) { + return fsItems.filter((p) => fs.statSync(currentWorkingDirectory + "/" + p).isDirectory()); +} + +function getFileArgs(fsItems, currentWorkingDirectory) { + const fileArgs = fsItems.filter((p) => !fs.statSync(currentWorkingDirectory + "/" + p).isDirectory()); + if (showAll == false) removeDotFiles(fileArgs); + return fileArgs; } -function populateOutput() { +function populateOutput(fsItems, dirArgs, fileArgs, currentWorkingDirectory) { + let outputString = ""; if (fsItems.length == 1) { fileArgs.forEach((file) => { outputString += file + " "; }); dirArgs.forEach((dir) => { - outputString += dirOutput(dir); + outputString += dirOutput(dir, currentWorkingDirectory); }); } else { fileArgs.forEach((file) => { outputString += file + "\t"; }); dirArgs.forEach((dir) => { - outputString += "\n\n" + dir + ":\n" + dirOutput(dir); + outputString += "\n\n" + dir + ":\n" + dirOutput(dir, currentWorkingDirectory); }); } + return outputString; } -function dirOutput(dir) { +function dirOutput(dir, currentWorkingDirectory) { let contents = fs.readdirSync(currentWorkingDirectory + "/" + dir); let outputStr = ""; - if (showAll) { - outputStr += ".\t..\t"; - } else { + if (showAll) outputStr += ".\t..\t"; + else { for (let i = contents.length - 1; i >= 0; i--) { contents = removeDotFiles(contents); } @@ -107,16 +121,13 @@ function dirOutput(dir) { function removeDotFiles(fileList) { for (let i = fileList.length - 1; i >= 0; i--) { - if (fileList[i][0] == ".") { - fileList.splice(i, 1); - } + if (fileList[i][0] == ".") fileList.splice(i, 1); } return fileList; } -function print() { - if (printInList) { - Flag1(); - } - console.log(outputString.trimEnd()); +function print(outputString) { + let output = outputString; + if (printInList) output = Flag1(output); + console.log(output.trimEnd()); } From 14d4d3348fd4e49dcb7dcc9ff98bb24125aec158 Mon Sep 17 00:00:00 2001 From: Alex Jamshidi Date: Fri, 28 Aug 2026 06:36:59 +0100 Subject: [PATCH 15/24] updated flag a --- implement-shell-tools/ls/ls.js | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/implement-shell-tools/ls/ls.js b/implement-shell-tools/ls/ls.js index 08af639..967bbc1 100644 --- a/implement-shell-tools/ls/ls.js +++ b/implement-shell-tools/ls/ls.js @@ -60,9 +60,8 @@ function invalidFlagError() { process.exit(1); } -function Flag1(outputString) { - if (printInList) return outputString.replaceAll("\t", "\n").replaceAll("\n\n", "\n"); - else printInList = true; +function Flag1() { + printInList = true; } function Flaga() { @@ -128,6 +127,10 @@ function removeDotFiles(fileList) { function print(outputString) { let output = outputString; - if (printInList) output = Flag1(output); + if (printInList) output = makeList(output); console.log(output.trimEnd()); } + +function makeList(outputString) { + return outputString.replaceAll("\t", "\n").replaceAll("\n\n", "\n"); +} From 2b981ed488822916544cb4f275f79b8e652ca49a Mon Sep 17 00:00:00 2001 From: Alex Jamshidi Date: Fri, 28 Aug 2026 06:51:48 +0100 Subject: [PATCH 16/24] collapsed getuserargs function --- implement-shell-tools/ls/ls.js | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/implement-shell-tools/ls/ls.js b/implement-shell-tools/ls/ls.js index 967bbc1..30726c5 100644 --- a/implement-shell-tools/ls/ls.js +++ b/implement-shell-tools/ls/ls.js @@ -14,22 +14,18 @@ ls(process.argv); // ---------- function ls(args) { - const userArgs = getUserArgs(args); - const currentWorkingDirectory = getCurrentWorkingDirectory(args); - const fsItems = checkArgsLength(getFsItems(userArgs)); + const userArgs = args.slice(2); executeFlags(getFlags(userArgs)); + const fsItems = checkArgsLength(getFsItems(userArgs)); + const currentWorkingDirectory = getCurrentWorkingDirectory(args); const dirArgs = getDirArgs(fsItems, currentWorkingDirectory); const fileArgs = getFileArgs(fsItems, currentWorkingDirectory); print(populateOutput(fsItems, dirArgs, fileArgs, currentWorkingDirectory)); } -function getUserArgs(args) { - return args.slice(2); -} - function getFsItems(userArgs) { return userArgs.filter((arg) => !arg.startsWith("-")); } From fa7436b603d8aa92cc71ba6b3a4fce7594df13df Mon Sep 17 00:00:00 2001 From: Alex Jamshidi Date: Fri, 28 Aug 2026 16:17:31 +0100 Subject: [PATCH 17/24] rearranged some argument orders in ls --- implement-shell-tools/ls/ls.js | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/implement-shell-tools/ls/ls.js b/implement-shell-tools/ls/ls.js index 30726c5..3a6b45b 100644 --- a/implement-shell-tools/ls/ls.js +++ b/implement-shell-tools/ls/ls.js @@ -18,12 +18,12 @@ function ls(args) { executeFlags(getFlags(userArgs)); - const fsItems = checkArgsLength(getFsItems(userArgs)); const currentWorkingDirectory = getCurrentWorkingDirectory(args); - const dirArgs = getDirArgs(fsItems, currentWorkingDirectory); - const fileArgs = getFileArgs(fsItems, currentWorkingDirectory); + const fsItems = checkArgsLength(getFsItems(userArgs)); + const dirArgs = getDirArgs(currentWorkingDirectory, fsItems); + const fileArgs = getFileArgs(currentWorkingDirectory, fsItems); - print(populateOutput(fsItems, dirArgs, fileArgs, currentWorkingDirectory)); + print(populateOutput(currentWorkingDirectory, fsItems, dirArgs, fileArgs)); } function getFsItems(userArgs) { @@ -69,17 +69,17 @@ function checkArgsLength(fsItems) { return fsItems; } -function getDirArgs(fsItems, currentWorkingDirectory) { +function getDirArgs(currentWorkingDirectory, fsItems) { return fsItems.filter((p) => fs.statSync(currentWorkingDirectory + "/" + p).isDirectory()); } -function getFileArgs(fsItems, currentWorkingDirectory) { +function getFileArgs(currentWorkingDirectory, fsItems) { const fileArgs = fsItems.filter((p) => !fs.statSync(currentWorkingDirectory + "/" + p).isDirectory()); if (showAll == false) removeDotFiles(fileArgs); return fileArgs; } -function populateOutput(fsItems, dirArgs, fileArgs, currentWorkingDirectory) { +function populateOutput(currentWorkingDirectory, fsItems, dirArgs, fileArgs) { let outputString = ""; if (fsItems.length == 1) { fileArgs.forEach((file) => { From d4720b18bf4e9d28519819c3b8fea3f1f783dae1 Mon Sep 17 00:00:00 2001 From: Alex Jamshidi Date: Fri, 28 Aug 2026 16:33:15 +0100 Subject: [PATCH 18/24] added comments to ls --- implement-shell-tools/cat/cat.js | 24 ++++++++++++++++------- implement-shell-tools/ls/ls.js | 33 ++++++++++++++++++++------------ 2 files changed, 38 insertions(+), 19 deletions(-) diff --git a/implement-shell-tools/cat/cat.js b/implement-shell-tools/cat/cat.js index e9dcd91..e3097d6 100644 --- a/implement-shell-tools/cat/cat.js +++ b/implement-shell-tools/cat/cat.js @@ -47,15 +47,25 @@ function executeFlags() { } function bFlag() { - return allFilesContents.map((fileContent) => { + const resultFiles = []; + + for (const fileContent of allFilesContents) { + const formattedLines = []; let lineNumber = 1; - return fileContent.map((line, index) => { - if (line == "") { - return `${line}`; + + for (const line of fileContent) { + if (line === "") { + formattedLines.push(line); + } else { + const formattedLine = `${String(lineNumber++).padStart(6, " ")} ${line}`; + formattedLines.push(formattedLine); } - return `${String(lineNumber++).padStart(6, " ")} ${line}`; - }); - }); + } + + resultFiles.push(formattedLines); + } + + return resultFiles; } function nFlag() { diff --git a/implement-shell-tools/ls/ls.js b/implement-shell-tools/ls/ls.js index 3a6b45b..cb7106a 100644 --- a/implement-shell-tools/ls/ls.js +++ b/implement-shell-tools/ls/ls.js @@ -26,14 +26,8 @@ function ls(args) { print(populateOutput(currentWorkingDirectory, fsItems, dirArgs, fileArgs)); } -function getFsItems(userArgs) { - return userArgs.filter((arg) => !arg.startsWith("-")); -} - -function getCurrentWorkingDirectory(args) { - const index = args[1].lastIndexOf("/"); - return args[1].slice(0, index); -} +// +// ===== Flag Handling ===== function getFlags(userArgs) { return userArgs @@ -60,10 +54,26 @@ function Flag1() { printInList = true; } +function makeList(outputString) { + return outputString.replaceAll("\t", "\n").replaceAll("\n\n", "\n"); +} + function Flaga() { showAll = true; } +// +// ===== Extracting data from arguments ===== + +function getCurrentWorkingDirectory(args) { + const index = args[1].lastIndexOf("/"); + return args[1].slice(0, index); +} + +function getFsItems(userArgs) { + return userArgs.filter((arg) => !arg.startsWith("-")); +} + function checkArgsLength(fsItems) { if (fsItems.length == 0) fsItems.push("."); return fsItems; @@ -79,6 +89,9 @@ function getFileArgs(currentWorkingDirectory, fsItems) { return fileArgs; } +// +// ===== Outputting Data ===== + function populateOutput(currentWorkingDirectory, fsItems, dirArgs, fileArgs) { let outputString = ""; if (fsItems.length == 1) { @@ -126,7 +139,3 @@ function print(outputString) { if (printInList) output = makeList(output); console.log(output.trimEnd()); } - -function makeList(outputString) { - return outputString.replaceAll("\t", "\n").replaceAll("\n\n", "\n"); -} From 5c6442916f947d588d10a7175a13be006ed3dc65 Mon Sep 17 00:00:00 2001 From: Alex Jamshidi Date: Fri, 28 Aug 2026 16:38:03 +0100 Subject: [PATCH 19/24] further added comments to ls --- implement-shell-tools/ls/ls.js | 31 +++++++++++++++---------------- 1 file changed, 15 insertions(+), 16 deletions(-) diff --git a/implement-shell-tools/ls/ls.js b/implement-shell-tools/ls/ls.js index cb7106a..f82a977 100644 --- a/implement-shell-tools/ls/ls.js +++ b/implement-shell-tools/ls/ls.js @@ -1,18 +1,8 @@ const fs = require("fs"); const { allowedNodeEnvironmentFlags } = require("process"); -const flagHandlers = { - 1: Flag1, - a: Flaga, -}; - -let printInList = false; -let showAll = false; - -ls(process.argv); - -// ---------- - +// +// ===== ls Procedure ===== function ls(args) { const userArgs = args.slice(2); @@ -28,6 +18,13 @@ function ls(args) { // // ===== Flag Handling ===== +const flagHandlers = { + 1: Flag1, + a: Flaga, +}; + +let printInList = false; +let showAll = false; function getFlags(userArgs) { return userArgs @@ -63,8 +60,7 @@ function Flaga() { } // -// ===== Extracting data from arguments ===== - +// ===== Extracting Data From Arguments ===== function getCurrentWorkingDirectory(args) { const index = args[1].lastIndexOf("/"); return args[1].slice(0, index); @@ -90,8 +86,7 @@ function getFileArgs(currentWorkingDirectory, fsItems) { } // -// ===== Outputting Data ===== - +// ===== Populating and Outputting Data ===== function populateOutput(currentWorkingDirectory, fsItems, dirArgs, fileArgs) { let outputString = ""; if (fsItems.length == 1) { @@ -139,3 +134,7 @@ function print(outputString) { if (printInList) output = makeList(output); console.log(output.trimEnd()); } + +// +// ===== Run ls ===== +ls(process.argv); From 769706f84bbcf72d6b03963921e6c9dbe076d5b5 Mon Sep 17 00:00:00 2001 From: Alex Jamshidi Date: Fri, 28 Aug 2026 17:59:35 +0100 Subject: [PATCH 20/24] wc refactored --- implement-shell-tools/wc/wc.js | 174 ++++++++++++++++----------------- 1 file changed, 86 insertions(+), 88 deletions(-) diff --git a/implement-shell-tools/wc/wc.js b/implement-shell-tools/wc/wc.js index 9635e8e..aecd11c 100644 --- a/implement-shell-tools/wc/wc.js +++ b/implement-shell-tools/wc/wc.js @@ -1,64 +1,92 @@ const fs = require("fs"); const { allowedNodeEnvironmentFlags } = require("process"); -const arguments = process.argv; -const userArguments = arguments.slice(2); +const metrics = ["lineCount", "wordCount", "byteSize"]; +const displayedMetrics = []; + +// +// ===== wc Procedure ===== +function wc(args) { + const userArgs = args.slice(2); -const index = arguments[1].lastIndexOf("/"); -const currentWorkingDirectory = arguments[1].slice(0, index + 1); + executeFlags(getFlags(userArgs)); -const flags = userArguments - .filter((argument) => argument.startsWith("-")) - .map((argument) => argument.slice(1)) - .join(""); + const currentWorkingDirectory = getCurrentWorkingDirectory(args); + const fileNames = getFileNames(userArgs); + + const allFilesData = addTotals(extractFilesData(fileNames, currentWorkingDirectory)); + printOutput(allFilesData); +} +// +// ===== Flag Handling ===== const flagHandlers = { l: lFlag, w: wFlag, c: cFlag, }; -const metrics = ["lineCount", "wordCount", "byteSize"]; -const fileNames = userArguments.filter((argument) => !argument.startsWith("-")); -const allFilesData = []; +function getFlags(userArgs) { + return userArgs + .filter((arg) => arg.startsWith("-")) + .map((arg) => arg.slice(1)) + .join(""); +} + +function executeFlags(flags) { + for (const flag of flags) { + if (flagHandlers[flag]) flagHandlers[flag](); + else invalidFlagError(); + } +} + +function invalidFlagError() { + console.error(`wc: illegal option -- ${flag}\nusage: wc [-Lclmw] [file ...]`); + process.exit(1); +} + +function lFlag() { + displayedMetrics.push("lineCount"); +} + +function wFlag() { + displayedMetrics.push("wordCount"); +} + +function cFlag() { + displayedMetrics.push("byteSize"); +} + +// +// ===== Extracting Data From Arguments ===== +function getCurrentWorkingDirectory(args) { + const index = args[1].lastIndexOf("/"); + return args[1].slice(0, index + 1); +} -extractFilesData(); -addTotals(); -const outputData = structuredClone(allFilesData); +function getFileNames(userArgs) { + return userArgs.filter((argument) => !argument.startsWith("-")); +} -let deleted = false; -executeFlags(); -printOutput(); +// +// ===== Extracting Files Data ===== +function extractFilesData(fileNames, currentWorkingDirectory) { + const allFilesData = []; -function extractFilesData() { fileNames.forEach((fileName) => { const fileData = {}; fileData.name = fileName; - fileData.text = readFile(fileName); + fileData.text = readFile(fileName, currentWorkingDirectory); fileData.lineCount = calculateLineCount(fileData.text); fileData.wordCount = calculateWordCount(fileData.text); - fileData.byteSize = readByteSize(fileName); + fileData.byteSize = readByteSize(fileName, currentWorkingDirectory); allFilesData.push(fileData); }); + return allFilesData; } -function addTotals() { - if (allFilesData.length == 1) { - return; - } - const totalsData = { name: "total" }; - metrics.forEach((metric) => { - let sum = 0; - allFilesData.forEach((file) => { - sum += file[metric]; - }); - totalsData[metric] = sum; - }); - allFilesData.push(totalsData); -} - -function readFile(fileName) { +function readFile(fileName, currentWorkingDirectory) { const filePath = currentWorkingDirectory + fileName; return fs.readFileSync(filePath, "utf8").trimEnd(); } @@ -71,71 +99,41 @@ function calculateWordCount(text) { return text.split(/\s+/).length; } -function readByteSize(fileName) { +function readByteSize(fileName, currentWorkingDirectory) { return fs.statSync(currentWorkingDirectory + fileName).size; } -function executeFlags() { - for (const flag of flags) { - if (flagHandlers[flag]) { - allFilesContents = flagHandlers[flag](); - } else { - console.error(`wc: illegal option -- ${flag}\nusage: wc [-Lclmw] [file ...]`); - process.exit(1); - } - } -} - -function lFlag() { - deleteOutputs(); - allFilesData.forEach((sourceFile) => { - const targetFile = outputData.find((file) => file.name === sourceFile.name); - targetFile.lineCount = getIfTrue(sourceFile, "lineCount"); - }); -} - -function wFlag() { - deleteOutputs(); - allFilesData.forEach((sourceFile) => { - const targetFile = outputData.find((file) => file.name === sourceFile.name); - targetFile.wordCount = getIfTrue(sourceFile, "wordCount"); - }); -} +function addTotals(allFilesData) { + if (allFilesData.length == 1) return allFilesData; -function cFlag() { - deleteOutputs(); - allFilesData.forEach((sourceFile) => { - const targetFile = outputData.find((file) => file.name === sourceFile.name); - targetFile.byteSize = getIfTrue(sourceFile, "byteSize"); - }); -} - -function deleteOutputs() { - if (!deleted) { - outputData.forEach((file) => { - metrics.forEach((metric) => { - delete file[metric]; - }); + const totalsData = { name: "total" }; + metrics.forEach((metric) => { + let sum = 0; + allFilesData.forEach((file) => { + sum += file[metric]; }); - } - deleted = true; + totalsData[metric] = sum; + }); + allFilesData.push(totalsData); + return allFilesData; } -function printOutput() { - outputs = []; +// +// ===== Outputting Data ===== +function printOutput(outputData) { outputData.forEach((file) => { let outputString = ""; metrics.forEach((metric) => { - outputString += getIfTrue(file, metric); + if (displayedMetrics.length === 0 || displayedMetrics.includes(metric)) { + outputString += String(file[metric]).padStart(8, " "); + } }); + outputString += ` ${file.name}`; console.log(outputString); }); } -function getIfTrue(file, metric) { - if (file[metric]) { - return String(file[metric]).padStart(8, " "); - } - return ""; -} +// +// ===== Run wc ===== +wc(process.argv); From 65e60aea66eb93582a451691dae116bb24e6eae3 Mon Sep 17 00:00:00 2001 From: Alex Jamshidi Date: Fri, 28 Aug 2026 19:09:00 +0100 Subject: [PATCH 21/24] refactored cat --- implement-shell-tools/cat/cat.js | 121 +++++++++++++++++-------------- 1 file changed, 68 insertions(+), 53 deletions(-) diff --git a/implement-shell-tools/cat/cat.js b/implement-shell-tools/cat/cat.js index e3097d6..6774bee 100644 --- a/implement-shell-tools/cat/cat.js +++ b/implement-shell-tools/cat/cat.js @@ -1,83 +1,98 @@ const fs = require("fs"); -const arguments = process.argv; -const userArguments = arguments.slice(2); - -const index = arguments[1].lastIndexOf("/"); -const currentWorkingDirectory = arguments[1].slice(0, index + 1); - -const flags = userArguments - .filter((argument) => argument.startsWith("-")) - .map((argument) => argument.slice(1)) - .join(""); - -const flagHandlers = { - b: bFlag, - n: nFlag, -}; +// +// ===== cat Procedure ===== +function cat(args) { + const userArgs = args.slice(2); + const currentWorkingDirectory = getCurrentWorkingDirectory(args); + const fileNames = getFileNames(userArgs, currentWorkingDirectory); + + const allFilesContents = readFiles(fileNames, currentWorkingDirectory); + executeFlags(getFlags(userArgs), allFilesContents); + printLines(allFilesContents); +} -const fileNames = userArguments.filter((argument) => !argument.startsWith("-")); -let allFilesContents = []; +// +// ===== Extracting Data From Arguments ===== +function getCurrentWorkingDirectory(args) { + const index = args[1].lastIndexOf("/"); + return args[1].slice(0, index + 1); +} -readFiles(); -executeFlags(); -printLines(); +function getFileNames(userArgs) { + return userArgs.filter((argument) => !argument.startsWith("-")); +} -function readFiles() { +function readFiles(fileNames, currentWorkingDirectory) { + const allFilesContents = []; fileNames.forEach((fileName) => { - fileContent = readFile(fileName); + fileContent = readFile(fileName, currentWorkingDirectory); allFilesContents.push(fileContent.split("\n")); }); + return allFilesContents; } -function readFile(fileName) { +function readFile(fileName, currentWorkingDirectory) { const filePath = currentWorkingDirectory + fileName; return fs.readFileSync(filePath, "utf8").trimEnd(); } -function executeFlags() { +// +// ===== Flag Handling ===== +const flagHandlers = { + b: bFlag, + n: nFlag, +}; + +function getFlags(userArgs) { + return userArgs + .filter((arg) => arg.startsWith("-")) + .map((arg) => arg.slice(1)) + .join(""); +} + +function executeFlags(flags, allFilesContents) { for (const flag of flags) { - if (flagHandlers[flag]) { - allFilesContents = flagHandlers[flag](); - } else { - console.error(`cat: illegal option -- ${flag}\nusage: cat [-belnstuv] [file ...]`); - process.exit(1); - } + if (flagHandlers[flag]) flagHandlers[flag](allFilesContents, flags); + else invalidFlagError(); } } -function bFlag() { - const resultFiles = []; - - for (const fileContent of allFilesContents) { - const formattedLines = []; - let lineNumber = 1; +function invalidFlagError() { + console.error(`cat: illegal option -- ${flag}\nusage: cat [-belnstuv] [file ...]`); + process.exit(1); +} - for (const line of fileContent) { - if (line === "") { - formattedLines.push(line); - } else { - const formattedLine = `${String(lineNumber++).padStart(6, " ")} ${line}`; - formattedLines.push(formattedLine); +function bFlag(allFilesContents) { + let linenumber = 1; + allFilesContents.forEach((fileContent) => { + fileContent.forEach((line, index) => { + if (line !== "") { + fileContent[index] = `${String(linenumber).padStart(6, " ")} ${line}`; + linenumber += 1; } - } - - resultFiles.push(formattedLines); - } - - return resultFiles; + }); + }); } -function nFlag() { - return allFilesContents.map((fileContent) => { - return fileContent.map((line, index) => `${String(index + 1).padStart(6, " ")} ${line}`); - }); +function nFlag(allFilesContents, flags) { + if (!flags.includes("b")) { + allFilesContents.forEach((fileContent) => { + fileContent.forEach((line, index) => (fileContent[index] = `${String(index + 1).padStart(6, " ")} ${line}`)); + }); + } } -function printLines() { +// +// ==== Print Output ===== +function printLines(allFilesContents) { allFilesContents.forEach((fileContent) => { fileContent.forEach((line) => { console.log(line); }); }); } + +// +// ===== Run cat ===== +cat(process.argv); From efdd20b3a8e99a3ee6f4dfc611e325ef6522f1b5 Mon Sep 17 00:00:00 2001 From: Alex Jamshidi Date: Sat, 29 Aug 2026 05:56:28 +0100 Subject: [PATCH 22/24] updated getcwd --- implement-shell-tools/ls/ls.js | 8 ++++---- implement-shell-tools/wc/wc.js | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/implement-shell-tools/ls/ls.js b/implement-shell-tools/ls/ls.js index f82a977..ae03453 100644 --- a/implement-shell-tools/ls/ls.js +++ b/implement-shell-tools/ls/ls.js @@ -63,7 +63,7 @@ function Flaga() { // ===== Extracting Data From Arguments ===== function getCurrentWorkingDirectory(args) { const index = args[1].lastIndexOf("/"); - return args[1].slice(0, index); + return args[1].slice(0, index + 1); } function getFsItems(userArgs) { @@ -76,11 +76,11 @@ function checkArgsLength(fsItems) { } function getDirArgs(currentWorkingDirectory, fsItems) { - return fsItems.filter((p) => fs.statSync(currentWorkingDirectory + "/" + p).isDirectory()); + return fsItems.filter((p) => fs.statSync(currentWorkingDirectory + p).isDirectory()); } function getFileArgs(currentWorkingDirectory, fsItems) { - const fileArgs = fsItems.filter((p) => !fs.statSync(currentWorkingDirectory + "/" + p).isDirectory()); + const fileArgs = fsItems.filter((p) => !fs.statSync(currentWorkingDirectory + p).isDirectory()); if (showAll == false) removeDotFiles(fileArgs); return fileArgs; } @@ -108,7 +108,7 @@ function populateOutput(currentWorkingDirectory, fsItems, dirArgs, fileArgs) { } function dirOutput(dir, currentWorkingDirectory) { - let contents = fs.readdirSync(currentWorkingDirectory + "/" + dir); + let contents = fs.readdirSync(currentWorkingDirectory + dir); let outputStr = ""; if (showAll) outputStr += ".\t..\t"; else { diff --git a/implement-shell-tools/wc/wc.js b/implement-shell-tools/wc/wc.js index aecd11c..130c347 100644 --- a/implement-shell-tools/wc/wc.js +++ b/implement-shell-tools/wc/wc.js @@ -33,9 +33,9 @@ function getFlags(userArgs) { .join(""); } -function executeFlags(flags) { +function executeFlags(flags, allFilesData) { for (const flag of flags) { - if (flagHandlers[flag]) flagHandlers[flag](); + if (flagHandlers[flag]) flagHandlers[flag](allFilesData); else invalidFlagError(); } } From 61a6eaa6c854a89a3182c01cc5a0a5f3129f8861 Mon Sep 17 00:00:00 2001 From: Alex Jamshidi Date: Sat, 29 Aug 2026 06:41:09 +0100 Subject: [PATCH 23/24] Add .venv to gitignore --- implement-cowsay/.gitignore | 1 + implement-cowsay/cow.py | 18 ++++++++++++++++++ implement-cowsay/requirements.txt | 1 + 3 files changed, 20 insertions(+) create mode 100644 implement-cowsay/.gitignore create mode 100644 implement-cowsay/cow.py create mode 100644 implement-cowsay/requirements.txt diff --git a/implement-cowsay/.gitignore b/implement-cowsay/.gitignore new file mode 100644 index 0000000..21d0b89 --- /dev/null +++ b/implement-cowsay/.gitignore @@ -0,0 +1 @@ +.venv/ diff --git a/implement-cowsay/cow.py b/implement-cowsay/cow.py new file mode 100644 index 0000000..236f4f3 --- /dev/null +++ b/implement-cowsay/cow.py @@ -0,0 +1,18 @@ +import cowsay +import sys +import argparse + +parser = argparse.ArgumentParser( + prog="cowsay", + description="Make animals say things", +) + +parser.add_argument("--animal", choices=cowsay.char_names, help="The animal to be saying things.", default="cow") +parser.add_argument("message", nargs="*", help="The message to say.") + +args = parser.parse_args() + +if args.animal not in cowsay.char_names: + sys.exit(f"Unknown animal: {args.animal!r}. Choose from: {', '.join(cowsay.char_names)}") + +cowsay.char_funcs[args.animal](" ".join(args.message)) \ No newline at end of file diff --git a/implement-cowsay/requirements.txt b/implement-cowsay/requirements.txt new file mode 100644 index 0000000..c6b9ffd --- /dev/null +++ b/implement-cowsay/requirements.txt @@ -0,0 +1 @@ +cowsay From eeb4d7a01cba159a49faf3df1e611150adcc62ae Mon Sep 17 00:00:00 2001 From: Alex Jamshidi Date: Sat, 29 Aug 2026 06:51:46 +0100 Subject: [PATCH 24/24] removed implement shell tools files --- implement-shell-tools/cat/cat.js | 98 ---------------------- implement-shell-tools/ls/ls.js | 140 ------------------------------- implement-shell-tools/wc/wc.js | 139 ------------------------------ 3 files changed, 377 deletions(-) delete mode 100644 implement-shell-tools/cat/cat.js delete mode 100644 implement-shell-tools/ls/ls.js delete mode 100644 implement-shell-tools/wc/wc.js diff --git a/implement-shell-tools/cat/cat.js b/implement-shell-tools/cat/cat.js deleted file mode 100644 index 6774bee..0000000 --- a/implement-shell-tools/cat/cat.js +++ /dev/null @@ -1,98 +0,0 @@ -const fs = require("fs"); - -// -// ===== cat Procedure ===== -function cat(args) { - const userArgs = args.slice(2); - const currentWorkingDirectory = getCurrentWorkingDirectory(args); - const fileNames = getFileNames(userArgs, currentWorkingDirectory); - - const allFilesContents = readFiles(fileNames, currentWorkingDirectory); - executeFlags(getFlags(userArgs), allFilesContents); - printLines(allFilesContents); -} - -// -// ===== Extracting Data From Arguments ===== -function getCurrentWorkingDirectory(args) { - const index = args[1].lastIndexOf("/"); - return args[1].slice(0, index + 1); -} - -function getFileNames(userArgs) { - return userArgs.filter((argument) => !argument.startsWith("-")); -} - -function readFiles(fileNames, currentWorkingDirectory) { - const allFilesContents = []; - fileNames.forEach((fileName) => { - fileContent = readFile(fileName, currentWorkingDirectory); - allFilesContents.push(fileContent.split("\n")); - }); - return allFilesContents; -} - -function readFile(fileName, currentWorkingDirectory) { - const filePath = currentWorkingDirectory + fileName; - return fs.readFileSync(filePath, "utf8").trimEnd(); -} - -// -// ===== Flag Handling ===== -const flagHandlers = { - b: bFlag, - n: nFlag, -}; - -function getFlags(userArgs) { - return userArgs - .filter((arg) => arg.startsWith("-")) - .map((arg) => arg.slice(1)) - .join(""); -} - -function executeFlags(flags, allFilesContents) { - for (const flag of flags) { - if (flagHandlers[flag]) flagHandlers[flag](allFilesContents, flags); - else invalidFlagError(); - } -} - -function invalidFlagError() { - console.error(`cat: illegal option -- ${flag}\nusage: cat [-belnstuv] [file ...]`); - process.exit(1); -} - -function bFlag(allFilesContents) { - let linenumber = 1; - allFilesContents.forEach((fileContent) => { - fileContent.forEach((line, index) => { - if (line !== "") { - fileContent[index] = `${String(linenumber).padStart(6, " ")} ${line}`; - linenumber += 1; - } - }); - }); -} - -function nFlag(allFilesContents, flags) { - if (!flags.includes("b")) { - allFilesContents.forEach((fileContent) => { - fileContent.forEach((line, index) => (fileContent[index] = `${String(index + 1).padStart(6, " ")} ${line}`)); - }); - } -} - -// -// ==== Print Output ===== -function printLines(allFilesContents) { - allFilesContents.forEach((fileContent) => { - fileContent.forEach((line) => { - console.log(line); - }); - }); -} - -// -// ===== Run cat ===== -cat(process.argv); diff --git a/implement-shell-tools/ls/ls.js b/implement-shell-tools/ls/ls.js deleted file mode 100644 index ae03453..0000000 --- a/implement-shell-tools/ls/ls.js +++ /dev/null @@ -1,140 +0,0 @@ -const fs = require("fs"); -const { allowedNodeEnvironmentFlags } = require("process"); - -// -// ===== ls Procedure ===== -function ls(args) { - const userArgs = args.slice(2); - - executeFlags(getFlags(userArgs)); - - const currentWorkingDirectory = getCurrentWorkingDirectory(args); - const fsItems = checkArgsLength(getFsItems(userArgs)); - const dirArgs = getDirArgs(currentWorkingDirectory, fsItems); - const fileArgs = getFileArgs(currentWorkingDirectory, fsItems); - - print(populateOutput(currentWorkingDirectory, fsItems, dirArgs, fileArgs)); -} - -// -// ===== Flag Handling ===== -const flagHandlers = { - 1: Flag1, - a: Flaga, -}; - -let printInList = false; -let showAll = false; - -function getFlags(userArgs) { - return userArgs - .filter((arg) => arg.startsWith("-")) - .map((arg) => arg.slice(1)) - .join(""); -} - -function executeFlags(flags) { - for (const flag of flags) { - if (flagHandlers[flag]) flagHandlers[flag](); - else invalidFlagError(); - } -} - -function invalidFlagError() { - console.error( - `ls: invalid option -- ${flag}\nusage: ls [-@ABCFGHILOPRSTUWXabcdefghiklmnopqrstuvwxy1%,] [--color=when] [-D format] [file ...]`, - ); - process.exit(1); -} - -function Flag1() { - printInList = true; -} - -function makeList(outputString) { - return outputString.replaceAll("\t", "\n").replaceAll("\n\n", "\n"); -} - -function Flaga() { - showAll = true; -} - -// -// ===== Extracting Data From Arguments ===== -function getCurrentWorkingDirectory(args) { - const index = args[1].lastIndexOf("/"); - return args[1].slice(0, index + 1); -} - -function getFsItems(userArgs) { - return userArgs.filter((arg) => !arg.startsWith("-")); -} - -function checkArgsLength(fsItems) { - if (fsItems.length == 0) fsItems.push("."); - return fsItems; -} - -function getDirArgs(currentWorkingDirectory, fsItems) { - return fsItems.filter((p) => fs.statSync(currentWorkingDirectory + p).isDirectory()); -} - -function getFileArgs(currentWorkingDirectory, fsItems) { - const fileArgs = fsItems.filter((p) => !fs.statSync(currentWorkingDirectory + p).isDirectory()); - if (showAll == false) removeDotFiles(fileArgs); - return fileArgs; -} - -// -// ===== Populating and Outputting Data ===== -function populateOutput(currentWorkingDirectory, fsItems, dirArgs, fileArgs) { - let outputString = ""; - if (fsItems.length == 1) { - fileArgs.forEach((file) => { - outputString += file + " "; - }); - dirArgs.forEach((dir) => { - outputString += dirOutput(dir, currentWorkingDirectory); - }); - } else { - fileArgs.forEach((file) => { - outputString += file + "\t"; - }); - dirArgs.forEach((dir) => { - outputString += "\n\n" + dir + ":\n" + dirOutput(dir, currentWorkingDirectory); - }); - } - return outputString; -} - -function dirOutput(dir, currentWorkingDirectory) { - let contents = fs.readdirSync(currentWorkingDirectory + dir); - let outputStr = ""; - if (showAll) outputStr += ".\t..\t"; - else { - for (let i = contents.length - 1; i >= 0; i--) { - contents = removeDotFiles(contents); - } - } - contents.forEach((item) => { - outputStr += item + "\t"; - }); - return outputStr; -} - -function removeDotFiles(fileList) { - for (let i = fileList.length - 1; i >= 0; i--) { - if (fileList[i][0] == ".") fileList.splice(i, 1); - } - return fileList; -} - -function print(outputString) { - let output = outputString; - if (printInList) output = makeList(output); - console.log(output.trimEnd()); -} - -// -// ===== Run ls ===== -ls(process.argv); diff --git a/implement-shell-tools/wc/wc.js b/implement-shell-tools/wc/wc.js deleted file mode 100644 index 130c347..0000000 --- a/implement-shell-tools/wc/wc.js +++ /dev/null @@ -1,139 +0,0 @@ -const fs = require("fs"); -const { allowedNodeEnvironmentFlags } = require("process"); - -const metrics = ["lineCount", "wordCount", "byteSize"]; -const displayedMetrics = []; - -// -// ===== wc Procedure ===== -function wc(args) { - const userArgs = args.slice(2); - - executeFlags(getFlags(userArgs)); - - const currentWorkingDirectory = getCurrentWorkingDirectory(args); - const fileNames = getFileNames(userArgs); - - const allFilesData = addTotals(extractFilesData(fileNames, currentWorkingDirectory)); - printOutput(allFilesData); -} - -// -// ===== Flag Handling ===== -const flagHandlers = { - l: lFlag, - w: wFlag, - c: cFlag, -}; - -function getFlags(userArgs) { - return userArgs - .filter((arg) => arg.startsWith("-")) - .map((arg) => arg.slice(1)) - .join(""); -} - -function executeFlags(flags, allFilesData) { - for (const flag of flags) { - if (flagHandlers[flag]) flagHandlers[flag](allFilesData); - else invalidFlagError(); - } -} - -function invalidFlagError() { - console.error(`wc: illegal option -- ${flag}\nusage: wc [-Lclmw] [file ...]`); - process.exit(1); -} - -function lFlag() { - displayedMetrics.push("lineCount"); -} - -function wFlag() { - displayedMetrics.push("wordCount"); -} - -function cFlag() { - displayedMetrics.push("byteSize"); -} - -// -// ===== Extracting Data From Arguments ===== -function getCurrentWorkingDirectory(args) { - const index = args[1].lastIndexOf("/"); - return args[1].slice(0, index + 1); -} - -function getFileNames(userArgs) { - return userArgs.filter((argument) => !argument.startsWith("-")); -} - -// -// ===== Extracting Files Data ===== -function extractFilesData(fileNames, currentWorkingDirectory) { - const allFilesData = []; - - fileNames.forEach((fileName) => { - const fileData = {}; - fileData.name = fileName; - fileData.text = readFile(fileName, currentWorkingDirectory); - fileData.lineCount = calculateLineCount(fileData.text); - fileData.wordCount = calculateWordCount(fileData.text); - fileData.byteSize = readByteSize(fileName, currentWorkingDirectory); - - allFilesData.push(fileData); - }); - return allFilesData; -} - -function readFile(fileName, currentWorkingDirectory) { - const filePath = currentWorkingDirectory + fileName; - return fs.readFileSync(filePath, "utf8").trimEnd(); -} - -function calculateLineCount(text) { - return text.split("\n").length; -} - -function calculateWordCount(text) { - return text.split(/\s+/).length; -} - -function readByteSize(fileName, currentWorkingDirectory) { - return fs.statSync(currentWorkingDirectory + fileName).size; -} - -function addTotals(allFilesData) { - if (allFilesData.length == 1) return allFilesData; - - const totalsData = { name: "total" }; - metrics.forEach((metric) => { - let sum = 0; - allFilesData.forEach((file) => { - sum += file[metric]; - }); - totalsData[metric] = sum; - }); - allFilesData.push(totalsData); - return allFilesData; -} - -// -// ===== Outputting Data ===== -function printOutput(outputData) { - outputData.forEach((file) => { - let outputString = ""; - metrics.forEach((metric) => { - if (displayedMetrics.length === 0 || displayedMetrics.includes(metric)) { - outputString += String(file[metric]).padStart(8, " "); - } - }); - - outputString += ` ${file.name}`; - console.log(outputString); - }); -} - -// -// ===== Run wc ===== -wc(process.argv);