From 9751f76490ebd6b89c6d80701d5ee9afade2b00e Mon Sep 17 00:00:00 2001 From: Daniel Wagner-Hall Date: Tue, 24 Dec 2024 10:08:06 +0000 Subject: [PATCH 01/32] SDC Tools sprints 3 and 4 --- .../comparing-javascript-and-python/index.md | 68 ++++++++++ .../convert-script-between-languages/index.md | 11 -- .../module/tools/converted-program/index.md | 35 +++++ .../converting-javascript-to-python/index.md | 73 +++++++++++ .../en/module/tools/counting-words/index.md | 46 +++++++ .../tools/first-nodejs-program/index.md | 114 ++++++++++++++++ .../index.md | 12 -- .../tools/implement-tools-in-nodejs/index.md | 12 -- .../tools/implement-tools-in-python/index.md | 12 -- .../installing-npm-dependencies/index.md | 124 ++++++++++++++++++ .../en/module/tools/learn-nodejs/index.md | 12 -- .../en/module/tools/learn-python/index.md | 12 -- .../en/module/tools/nodejs/index.md | 33 +++++ .../read-about-operating-systems-1/index.md | 10 -- .../read-about-operating-systems-2/index.md | 10 -- .../read-about-operating-systems/index.md | 49 +++++++ .../en/module/tools/reading-a-file/index.md | 45 +++++++ .../tools/single-use-data-analysis/index.md | 83 ++++++++++++ .../module/tools/splitting-a-string/index.md | 22 ++++ .../tools/using-npm-dependencies/index.md | 32 +++++ .../tools/using-python-dependencies/index.md | 84 ++++++++++++ .../tools/virtual-environments/index.md | 30 +++++ .../content/tools/sprints/3/backlog/index.md | 4 +- .../content/tools/sprints/3/prep/index.md | 19 +-- .../content/tools/sprints/4/backlog/index.md | 4 +- .../content/tools/sprints/4/prep/index.md | 31 +++-- 26 files changed, 876 insertions(+), 111 deletions(-) create mode 100644 common-content/en/module/tools/comparing-javascript-and-python/index.md delete mode 100644 common-content/en/module/tools/convert-script-between-languages/index.md create mode 100644 common-content/en/module/tools/converted-program/index.md create mode 100644 common-content/en/module/tools/converting-javascript-to-python/index.md create mode 100644 common-content/en/module/tools/counting-words/index.md create mode 100644 common-content/en/module/tools/first-nodejs-program/index.md delete mode 100644 common-content/en/module/tools/implement-single-use-data-analysis/index.md delete mode 100644 common-content/en/module/tools/implement-tools-in-nodejs/index.md delete mode 100644 common-content/en/module/tools/implement-tools-in-python/index.md create mode 100644 common-content/en/module/tools/installing-npm-dependencies/index.md delete mode 100644 common-content/en/module/tools/learn-nodejs/index.md delete mode 100644 common-content/en/module/tools/learn-python/index.md create mode 100644 common-content/en/module/tools/nodejs/index.md delete mode 100644 common-content/en/module/tools/read-about-operating-systems-1/index.md delete mode 100644 common-content/en/module/tools/read-about-operating-systems-2/index.md create mode 100644 common-content/en/module/tools/read-about-operating-systems/index.md create mode 100644 common-content/en/module/tools/reading-a-file/index.md create mode 100644 common-content/en/module/tools/single-use-data-analysis/index.md create mode 100644 common-content/en/module/tools/splitting-a-string/index.md create mode 100644 common-content/en/module/tools/using-npm-dependencies/index.md create mode 100644 common-content/en/module/tools/using-python-dependencies/index.md create mode 100644 common-content/en/module/tools/virtual-environments/index.md diff --git a/common-content/en/module/tools/comparing-javascript-and-python/index.md b/common-content/en/module/tools/comparing-javascript-and-python/index.md new file mode 100644 index 000000000..a20f10c71 --- /dev/null +++ b/common-content/en/module/tools/comparing-javascript-and-python/index.md @@ -0,0 +1,68 @@ ++++ +title = "Comparing JavaScript and Python" +headless = true +time = 40 +facilitation = false +emoji= "📖" +objectives = [ + "Identify and explain equivalences between JavaScript and Python", + "Identify and explain differences between JavaScript and Python", + "Distinguish between essential and accidental complexity" +] ++++ + +JavaScript and Python are quite similar languages in a lot of ways. + +Most of the differences between them are quite cosmetic. e.g. +* Some functions and operators have different names. But often there are functions/operators which do exactly the same thing. +* JavaScript uses `{}` around blocks of code and we optionally _choose_ to indent code, whereas Python uses `:` and _required_ indents. +* In JavaScript we choose to name variables in `camelCase`, whereas in Python we choose to name variables in `snake_case` (but in both langues we _could_ do either). + +Let's take our "count containing words" JavaScript code from last week, and think about what it would look like in Python. + +```js +import { program } from "commander"; +import { promises as fs } from "node:fs"; +import process from "node:process"; + +program + .name("count-containing-words") + .description("Counts words in a file that contain a particular character") + .option("-c, --char ", "The character to search for", "-"); + +program.parse(); + +const argv = program.args; +if (argv.length != 1) { + console.error(`Expected exactly 1 argument (a path) to be passed but got ${argv.length}.`); + process.exit(1); +} +const path = argv[0]; +const char = program.opts().char; + +const content = await fs.readFile(path, "utf-8"); +const wordsContainingChar = content.split(" ").filter((word) => word.indexOf(char) > -1).length; +console.log(wordsContainingChar); +``` + +Let's think about what we're doing in this code. We're: +* Parsing command line flags - writing down what flags we expect to be passed, and reading values for them based on the actual command line. +* Validating the flags (i.e. checking that exactly one path was passed). +* Reading a file. +* Splitting the content of the file up into words. +* Counting how many of the words contained a particular character. +* Printing the count. + +These are the meaningful things we needed to do. If we wanted to solve the same problem with Python, we'd need to do all of these things. + +There are also some other things we did in our code, which were important, but not the point of the code. An example is, we imported some modules. We may need to import modules to write this code in Python. Or we may not. Importing modules wasn't one of our _goals_, it was just something we needed to do to help us. + +We split up things we need to do into two categories: essential and accidental. + +**Essential** means it is a core part of the problem. e.g. in order to count how many words are in a file, it is _essential_ that we read the file. + +**Accidental** means it isn't what we _care_ about doing, but we may need to do it anyway. e.g. importing the `process` module isn't _essential_ to our problem, but we needed to do it anyway so we could report errors. + +When we're thinking about how we use different languages, it's useful to think about what parts of our problem are _essential_ (we'll need to do them in any language), and which parts are _accidental_ (it's just something we had to do on the way to achieve our aim). + +Whether we write the JavaScript `someArray.length` or the Python `len(some_array)` isn't a big difference - both do the same thing, they just look a little a little different. diff --git a/common-content/en/module/tools/convert-script-between-languages/index.md b/common-content/en/module/tools/convert-script-between-languages/index.md deleted file mode 100644 index 9f94143fe..000000000 --- a/common-content/en/module/tools/convert-script-between-languages/index.md +++ /dev/null @@ -1,11 +0,0 @@ -+++ -title = "Convert a script between languages" -headless = true -time = 30 -emoji= "📖" -[objectives] - 1="Identify and explain equivalences between JavaScript and Python" - 2="Identify and explain differences between JavaScript and Python" -+++ - -### Convert a script between languages diff --git a/common-content/en/module/tools/converted-program/index.md b/common-content/en/module/tools/converted-program/index.md new file mode 100644 index 000000000..2aed7d252 --- /dev/null +++ b/common-content/en/module/tools/converted-program/index.md @@ -0,0 +1,35 @@ ++++ +title = "Putting it all together" +headless = true +time = 30 +facilitation = false +emoji= "📖" +hide_from_overview = true +objectives = [ +] ++++ + +Finally, instead of calling `console.log`, in Python we call `print`. + +```python +import argparse + +parser = argparse.ArgumentParser( + prog="count-containing-words", + description="Counts words in a file that contain a particular character", +) + +parser.add_argument("-c", "--char", help="The character to search for", default="-") +parser.add_argument("path", help="The file to search") + +args = parser.parse_args() + +with open(args.path, "r") as f: + content = f.read() +words_containing_char = len(filter(lambda word: args.char in word, content.split(" "))) +print(words_containing_char) +``` + +This looks pretty similar to the JavaScript version. The essential shape is the same. But every line is a least a little bit different. + +Some programming languages are a lot more different. But JavaScript and Python are, essentially, quite similar. diff --git a/common-content/en/module/tools/converting-javascript-to-python/index.md b/common-content/en/module/tools/converting-javascript-to-python/index.md new file mode 100644 index 000000000..7206bd339 --- /dev/null +++ b/common-content/en/module/tools/converting-javascript-to-python/index.md @@ -0,0 +1,73 @@ ++++ +title = "Converting JavaScript to Python" +headless = true +time = 40 +facilitation = false +emoji= "📖" +objectives = [ + "Rewrite JavaScript code as Python" +] ++++ + +### Parsing command line flags + +In JavaScript, we wrote this code (note: there was some other code in between some of these lines): + +```js +import { program } from "commander"; + +program + .name("count-containing-words") + .description("Counts words in a file that contain a particular character") + .option("-c, --char ", "The character to search for", "-"); + +program.parse(); + +const argv = program.args; +const path = argv[0]; +const char = program.opts().char; +``` + +The _essential_ goals here are to: +* Allow a user to pass a `-c` argument (defaulting to `-` if they don't). +* Allow a user to pass a path as a positional argument. +* Supply a nice `--help` implementation to help a user if they don't know how to use our tool. + +We _accidentally_ did a lot of things to achieve these goals. We used a library called commander. We imported that library. We called some particular functions, and made some particular variables. + +If we want to work out how to do this in Python, we should focus on the essential goals. We may want to search for things like "Parse command line flags Python" and "Default argument values Python" because they get to the essential problems we're trying to solve. + +Searching Google for "Parse command line flags Python" brought us to [the Python argparse documentation](https://docs.python.org/3/library/argparse.html). The example code looks pretty similar to what we were doing in Python. We can probably write something like: + +```python +import argparse + +parser = argparse.ArgumentParser( + prog="count-containing-words", + description="Counts words in a file that contain a particular character", +) + +parser.add_argument("-c", "--char", help="The character to search for", default="-") +parser.add_argument("path", help="The file to search") + +args = parser.parse_args() +``` + +There are some differences here. +* With commander we were calling functions on a global `program`, whereas with argparse we construct a new `ArgumentParser` which we use. +* `add_argument` takes separate parameters for the short (`-c`) and long (`--char`) forms of the option - `commander` expected them in one string. +* The Python version uses a lot of named arguments (e.g. `add_argument(...)` took `help=`, `default=`), whereas the JavaScript version (`option(...)`) used a lot of positional ones. +* The Python version handles positional arguments itself as arguments with names (`path`), whereas the JavaScript version just gives us an array of positional arguments and leaves us to understand them. + +### Validating command line flags + +In our JavaScript code, we needed to check that there was exactly one positional argument. + +We don't need to do this in our Python code. Because `argparse` treats positional arguments as arguments, it actually already errors if we pass no positional arguments, or more than one. + +So we can tick this essential requirement off of our list. Sometimes different languages, or different libraries, do things slightly differently, and that's ok! + +> [!TIP] +> We don't need to convert every line. +> +> We're trying to convert _essential requirements_. diff --git a/common-content/en/module/tools/counting-words/index.md b/common-content/en/module/tools/counting-words/index.md new file mode 100644 index 000000000..0250b99a8 --- /dev/null +++ b/common-content/en/module/tools/counting-words/index.md @@ -0,0 +1,46 @@ ++++ +title = "Counting words containing a character" +headless = true +time = 15 +facilitation = false +emoji= "📖" +hide_from_overview = true +objectives = [ +] ++++ + +In JavaScript we wrote: + +```js +content.split(" ").filter((word) => word.indexOf(char) > -1).length +``` + +It's useful to know that what JavaScript calls arrays, Python calls lists. (Arrays and lists are basically the same, other than the name, though!) + +Googling for "Python filter list" suggests there are two things we can use - a `filter` function, or something called a "list comprehension". Some people prefer one, other people prefer the other. + +Using filter (`lambda` is a keyword for making an anonymous function in Python): + +```python +filter(lambda word: args.char in word, content.split(" ")) +``` + +Using a list comprehension: + +```python +[word for word in content.split(" ") if args.char in word] +``` + +Then we need to get the length of the produced list. Googling "python length of list" tells us we wrap our list in a call to `len()`, giving: + +```python +len([word for word in content.split(" ") if args.char in word]) +``` + +or + +```python +len(filter(lambda word: args.char in word, content.split(" "))) +``` + +The list comprehension version of this works. The `filter` version gives an error. We can try to understand and fix the error, or just use the list comprehension version. diff --git a/common-content/en/module/tools/first-nodejs-program/index.md b/common-content/en/module/tools/first-nodejs-program/index.md new file mode 100644 index 000000000..0836ddf1b --- /dev/null +++ b/common-content/en/module/tools/first-nodejs-program/index.md @@ -0,0 +1,114 @@ ++++ +title = "Writing a NodeJS program" +headless = true +time = 60 +facilitation = false +emoji= "🛠️" +hide_from_overview = true +objectives = [ + "Write a zero-dependencies NodeJS program", +] ++++ + +Below we have a small NodeJS program. It is a bit like `wc`. It counts words in a file. Specifically, it counts words which contain a hyphen (`-`) character. + +It accepts one command line argument - the path of the file to read and count. + +Its output to stdout is just the number of words which contain a hyphen. + +It uses the same language (JavaScript) as we've written before, but uses some different APIs. + +```js +import process from "node:process"; +import { promises as fs } from "node:fs"; + +const argv = process.argv.slice(2); +if (argv.length != 1) { + console.error(`Expected exactly 1 argument (a path) to be passed but got ${argv.length}.`); + process.exit(1); +} +const path = argv[0]; + +const content = await fs.readFile(path, "utf-8"); +const wordsContainingHyphens = content.split(" ").filter((word) => word.indexOf("-") > -1).length; +console.log(wordsContainingHyphens); +``` + +Let's play computer with this program - line by line: + +```js +import process from "node:process"; +``` + +This is loading some code from somewhere that isn't this file. + +We've seen `import` before. Here, instead of importing from a file we've written, we're importing the `process` module which is built into NodeJS. + +This is an example of the same language features (`import`) being used slightly differently (the `"node:"` is a special prefix to say "specially from node"). + +The `process` module is built into NodeJS for managing our process. It can be used to do things like find out what arguments were passed to the process when it started, find out what user ran the process, exit the process, and more. + +```js +import { promises as fs } from "node:fs"; +``` + +We're importing another module. + +The `fs` module is built into NodeJS for interacting with the filesystem. + +This time, we're not importing the whole module. We are destructuring. The `node:fs` module exposes an object, and we are saying "import the `promises` property from the `fs` module, and bind it to the name `fs`". + +It's the equivalent to us writing `import { promises } from "node:fs"; const fs = promises;`. + +We are doing this because many of the things in the `fs` module don't support `async`/`await`, but `fs` has a sub-module called `promises` where everything supports `async`/`await`. Because we want to use `async`/`await`, we will use that. But having to write `fs.promises.readFile` is a bit annoying, so instead we import `fs.promises` as if it was just named `fs`. + +```js +const argv = process.argv.slice(2); +``` + +We're getting the `argv` array from the `process` module, and slicing it. We can see in [the `process.argv` documentation](https://nodejs.org/api/process.html#processargv) that `process.argv[0]` will be the path to `node`, and `process.argv[1]` will be the path to this file. We don't care about those, so we'll skip them - as far as we're concerned the arguments start at index 2. + +Again, `Array.slice` is exactly the same as we know from JavaScript, but `process.argv` is a new API we can use to get the array we need. + +```js +if (argv.length != 1) { + console.error(`Expected exactly 1 argument (a path) to be passed but got ${argv.length}.`); + process.exit(1); +} +``` + +We always expect our program to be given exactly one argument. Here we check this using an `if` statement, just like we've seen before. + +`console.error` writes a message to stderr (which is where error messages should go). + +`process.exit` is a function which, when called, will stop our program running. Passing a non-zero number to it indicates that our program did not succeed. + +```js +const path = argv[0]; +``` + +Giving a useful name to our argument. + +```js +const content = await fs.readFile(path, "utf-8"); +``` + +Reading the file at the path passed as an argument. We're using the `fs` module here from `node`, but everything else is just JavaScript - declaring a variable, using `await` because `fs.promises.readFile` is an `async` function, calling a function. + +```js +const wordsContainingHyphens = content.split(" ").filter((word) => word.indexOf("-") > -1).length; +``` + +Just some regular JavaScript. Taking a string, splitting it into an array, filtering the array, searching strings to see if they contain characters, and getting the length of an array. + +```js +console.log(wordsContainingHyphens); +``` + +`console.log` in a NodeJS environment logs to stdout, so this outputs our result to stdout. + +{{}} +Save the above program into a file. Run the file with `node`, and count how many words contain hyphens in a few different files. + +If you run into problems, ask for help. +{{}} \ No newline at end of file diff --git a/common-content/en/module/tools/implement-single-use-data-analysis/index.md b/common-content/en/module/tools/implement-single-use-data-analysis/index.md deleted file mode 100644 index ac9517e88..000000000 --- a/common-content/en/module/tools/implement-single-use-data-analysis/index.md +++ /dev/null @@ -1,12 +0,0 @@ -+++ -title = "Implement a single-use data analysis program" -headless = true -time = 30 -emoji= "💻" -[objectives] - 1="Write a program to extract information from a JSON file" - 2="Identify the trade-offs between using existing shell tools and writing custom programs" - 3="Choose whether to use existing tools or write a custom program to solve a particular problem" -+++ - -### Implement a single-use data analysis program diff --git a/common-content/en/module/tools/implement-tools-in-nodejs/index.md b/common-content/en/module/tools/implement-tools-in-nodejs/index.md deleted file mode 100644 index 03d3c2528..000000000 --- a/common-content/en/module/tools/implement-tools-in-nodejs/index.md +++ /dev/null @@ -1,12 +0,0 @@ -+++ -title = "Implement tools in NodeJS" -headless = true -time = 30 -emoji= "💻" -[objectives] - 1="Implement cat in NodeJS" - 2="Implement wc in NodeJS" - 3="Implement ls in NodeJS" -+++ - -### Implement tools in NodeJS \ No newline at end of file diff --git a/common-content/en/module/tools/implement-tools-in-python/index.md b/common-content/en/module/tools/implement-tools-in-python/index.md deleted file mode 100644 index 97b322207..000000000 --- a/common-content/en/module/tools/implement-tools-in-python/index.md +++ /dev/null @@ -1,12 +0,0 @@ -+++ -title = "Implement tools in Python" -headless = true -time = 30 -emoji= "💻" -[objectives] - 1="Implement cat in Python" - 2="Implement wc in Python" - 3="Implement ls in Python" -+++ - -### Implement tools in Python \ No newline at end of file diff --git a/common-content/en/module/tools/installing-npm-dependencies/index.md b/common-content/en/module/tools/installing-npm-dependencies/index.md new file mode 100644 index 000000000..dda303475 --- /dev/null +++ b/common-content/en/module/tools/installing-npm-dependencies/index.md @@ -0,0 +1,124 @@ ++++ +title = "Installing dependencies with npm" +headless = true +time = 60 +facilitation = false +emoji= "📚" +objectives = [ + "Use a dependency in a NodeJS program", +] ++++ + +To use a library, we need to fetch the code we're going to use. When using NodeJS, we use a tool called `npm` for this. + +First we need a `package.json` file - this a file that `npm` will read to understand your project. + +Make this `package.json` file in the same directory as your hyphen-counting program: + +```json +{ + "type": "module" +} +``` + +The `package.json` contains a JSON object with information about your project. For now, we're just telling `npm` that our project is a module - this means we are allowed to use `import` in our program. + +From a terminal which is `cd`'d to the same directory as your `package.json` file, run `npm install commander`. + +This command does two things: +1. Look in your `package.json` file - notice that now has a `dependencies` section listing `commander`. This means that if someone else downloads your program, they know they need to install `commander` to use it. +2. There's now a `node_modules` directory alongside your `package.json`. Inside that is a directory named `commander` which contains the code for the `commander` library. This means `node` now knows how to find the code when you try to import it. + +{{}} +Try running your program again. + +What has changed since the last time you tried to run it (and it didn't work)? + +What has changed since the last time you successfully ran it? +{{}} + +Now that we have `commander` installed, let's try using it in our program: + +```js +import { program } from "commander"; +import { promises as fs } from "node:fs"; +import process from "node:process"; + +program + .name("count-containing-words") + .description("Counts words in a file that contain a particular character") + .option("-c, --char ", "The character to search for", "-"); + +program.parse(); + +const argv = program.args; +if (argv.length != 1) { + console.error(`Expected exactly 1 argument (a path) to be passed but got ${argv.length}.`); + process.exit(1); +} +const path = argv[0]; +const char = program.opts().char; + +const content = await fs.readFile(path, "utf-8"); +const wordsContainingChar = content.split(" ").filter((word) => word.indexOf(char) > -1).length; +console.log(wordsContainingChar); +``` + +{{}} +Try running this program with the `--help` flag. + +What do you see? Where do you think this behaviour and text came from? + +We didn't have to write all the code for this functionality - `commander` did most of it for us. +{{}} + +{{}} +Try running the program with different values of the `-c` flag. Try also specifying some other flags, like `--count`. + +Make sure you understand how it's behaving, and why. +{{}} + +Let's run through what we changed: + +```js +program + .name("count-containing-words") + .description("Counts words in a file that contain a particular character") + .option("-c, --char ", "The character to search for", "-"); +``` + +We told `commander` information about our program. We gave it a name, a description, and told it that it should allow a user to pass a flag name `-c` (or equivalently `--char`), and use a default value of `-` for that flag if it's not specified. + +```js +program.parse(); +``` + +We asked `commander` to interpret the command line arguments our program was given, based on what options we wanted to allow. If it sees something it doesn't understand, it will error. + +```js +const argv = program.args; +``` + +Instead of asking NodeJS's process module for all of the program's arguments, we're asking `commander` to tell us "after you understood and removed all the flags, what arguments were left?" + +Then our `if` check about the number of arguments is exactly the same as before. + +```js +const char = program.opts().char; +``` + +We are getting the `char` flag that `commander` interpreted and storing it in a variable. + +```js +const wordsContainingChar = content.split(" ").filter((word) => word.indexOf(char) > -1).length; +console.log(wordsContainingChar); +``` + +We have renamed our `wordsContainingHyphens` variable to `wordsContainingChar` because we're no longer always looking for hyphens, and changed the `indexOf` call to look for the value of the `char` variable instead of always a `-`. + +We only needed to make a few small changes to get all of this new functionality: +* Support for accepting a new command line flag. +* `--help` support explaining how to use the program. +* Detection for if someone passes flags that aren't known, and warning them about this (and even suggesting what they maybe meant). + +We could have written all of this code ourselves. But using a library meant we could focus on what's unique about our problem, rather than spending time implementing flag parsing. diff --git a/common-content/en/module/tools/learn-nodejs/index.md b/common-content/en/module/tools/learn-nodejs/index.md deleted file mode 100644 index 4801018bc..000000000 --- a/common-content/en/module/tools/learn-nodejs/index.md +++ /dev/null @@ -1,12 +0,0 @@ -+++ -title = "Learn NodeJS" -headless = true -time = 30 -emoji= "❓" -[objectives] - 1="Identify and explain why someone may use NodeJS" - 2="Write a zero-dependencies NodeJS program" - 3="Use a dependency in a NodeJS program" -+++ - -### Learn NodeJS \ No newline at end of file diff --git a/common-content/en/module/tools/learn-python/index.md b/common-content/en/module/tools/learn-python/index.md deleted file mode 100644 index 021457cda..000000000 --- a/common-content/en/module/tools/learn-python/index.md +++ /dev/null @@ -1,12 +0,0 @@ -+++ -title = "Learn Python" -headless = true -time = 30 -emoji= "❓" -[objectives] - 1="Identify and explain why someone may use Python" - 2="Write a zero-dependencies Python program" - 3="Use a dependency in a Python program" -+++ - -### Learn Python diff --git a/common-content/en/module/tools/nodejs/index.md b/common-content/en/module/tools/nodejs/index.md new file mode 100644 index 000000000..0c5cb1422 --- /dev/null +++ b/common-content/en/module/tools/nodejs/index.md @@ -0,0 +1,33 @@ ++++ +title = "NodeJS" +headless = true +time = 20 +facilitation = false +emoji= "❓" +objectives = [ + "Identify and explain why someone may use NodeJS", +] ++++ + +We know that that JavaScript is an interpreted language. Running it needs some interpreter to read our lines of code and execute them. + +We've already seen that web browsers can run JavaScript. Web browsers provide a runtime environment for JavaScript. + +NodeJS is another runtime environment for running JavaScript. It allows us to run JavaScript files from a terminal. + +There are some similarities and differences between how NodeJS runs JavaScript, and how web browsers run JavaScript. For instance: +* Both support the same core language (e.g. defining variables, if statements, for loops, etc). +* Web browsers expose extra APIs that can be used from JavaScript, e.g. the DOM. +* NodeJS exposes extra APIs that can be used from JavaScript, e.g. reading and writing files in the filesystem. +* Some APIs are implemented differently, e.g. if you call `console.log` in a web browser it will log to the web inspector console (hidden by default), whereas in NodeJS it will log to stdout (the default output of a program). + +People use NodeJS so that they can run code they've written in a terminal. Some example reasons: +* Because they want to use NodeJS's extra capabilities in their code (e.g. reading files). +* Because they want to use a JavaScript as part of a shell pipeline. +* Because they want their program to run for a long time on a server. + +You've already written JavaScript programs and run them in the NodeJS runtime environment - every time you run a command like `node index.js` or `npm test` you're running JavaScript with NodeJS. + +Most of the programs you wrote and ran like this in the Introduction to Programming course were short-lived experiments (learning a concept and trying it out), or tests. + +We're going to start thinking about writing programs _intended to be run like this_. diff --git a/common-content/en/module/tools/read-about-operating-systems-1/index.md b/common-content/en/module/tools/read-about-operating-systems-1/index.md deleted file mode 100644 index 99d9c0f10..000000000 --- a/common-content/en/module/tools/read-about-operating-systems-1/index.md +++ /dev/null @@ -1,10 +0,0 @@ -+++ -title = "Read about operating systems" -headless = true -time = 30 -emoji= "💻" -[objectives] - 1="TODO" -+++ - -### Operating systems \ No newline at end of file diff --git a/common-content/en/module/tools/read-about-operating-systems-2/index.md b/common-content/en/module/tools/read-about-operating-systems-2/index.md deleted file mode 100644 index a4a7429e9..000000000 --- a/common-content/en/module/tools/read-about-operating-systems-2/index.md +++ /dev/null @@ -1,10 +0,0 @@ -+++ -title = "Read about operating systems (2)" -headless = true -time = 30 -emoji= "💻" -[objectives] - 1="TODO" -+++ - -### Operating systems (2) diff --git a/common-content/en/module/tools/read-about-operating-systems/index.md b/common-content/en/module/tools/read-about-operating-systems/index.md new file mode 100644 index 000000000..e81dc54c6 --- /dev/null +++ b/common-content/en/module/tools/read-about-operating-systems/index.md @@ -0,0 +1,49 @@ ++++ +title = "Operating systems" +headless = true +time = 120 +facilitation = false +emoji= "💻" +objectives = [ + "Define an operating system.", + "Describe what an kernel is.", + "Explain what a process is.", + "List what processes are created when running `ls | grep '[A-Z]'`.", + "Explain what a system call (syscall) is.", + "Give three examples of syscalls.", +] ++++ + +{{}} +Read chapter 10 of How Computers Really Work. + +Do every exercise listed in the chapters. + +You only need to do the projects listed below (though are welcome to try any others that you want!) + +Check you have achieved each learning objective listed on this page. +{{}} + +{{}} +Do project 23 from How Computers Really Work. + +You can do this on any Unix OS - you do not need a Raspberry Pi. +{{}} + +{{}} +Do project 20 from How Computers Really Work. + +You can do this on any Unix OS - you don't need a Raspberry Pi. + +Note: If you're on macOS, `ps -eH` doesn't exist. You can use `ps` or `ps aux` to get a list of processes. To get parent-child relationships, you'll need to install `pstree` using `brew` (`brew install pstree`), then run `pstree`. + +Note: If you're on macOS, process 1 will probably be `launchd` not `init`. +{{}} + +{{}} +If you're on a Linux machine, do projects 21, 22, and 24. + +If you're on macOS, pair up with someone who has a Linux machine to do these projects. + +Note: Several of these projects may not work inside Docker or virtual machines, you need to actually be using Linux. +{{}} diff --git a/common-content/en/module/tools/reading-a-file/index.md b/common-content/en/module/tools/reading-a-file/index.md new file mode 100644 index 000000000..583a2e4d9 --- /dev/null +++ b/common-content/en/module/tools/reading-a-file/index.md @@ -0,0 +1,45 @@ ++++ +title = "Reading a file" +headless = true +time = 10 +facilitation = false +emoji= "📖" +hide_from_overview = true +objectives = [ +] ++++ + +In JavaScript we wrote: + +```js +import { promises as fs } from "node:fs"; + +const content = await fs.readFile(path, "utf-8"); +``` + +If we search Google for "Read file Python", we get an example which suggests we can write something like: + +```python +with open(args.path, "r") as f: + content = f.read() +``` + +Comparing these shows some interesting differences, particularly around scope. + +### Scope + +In Python, we made our `content` variable in an indented block. + +In JavaScript this wouldn't have worked - in JavaScript when we declare a variable with `const` it only exists in the scope where it was defined. + +In Python, the `content` variable can be used for the rest of the function it's declared in. We call this {{}}Hoisting is where a variable is considered to exist at a broader scope than where it was declared.{{}}. + +### `with` blocks + +In Python, there's this `with` construct. Instead of writing `f = open(args.path, "r")` we wrote `with open(args.path, "r") as f:`. + +This has two interesting effects: + +One is that the variable we're declaring (`f`) doesn't get hoisted - it only exists within the `with` block. + +The other is that at the end of the `with` block, the file is closed. Not only does `f` stop existing at the end of the block, but some code also gets run to clean up the resources `f` was using. diff --git a/common-content/en/module/tools/single-use-data-analysis/index.md b/common-content/en/module/tools/single-use-data-analysis/index.md new file mode 100644 index 000000000..2a321c631 --- /dev/null +++ b/common-content/en/module/tools/single-use-data-analysis/index.md @@ -0,0 +1,83 @@ ++++ +title = "Single-use data analysis programs" +headless = true +time = 60 +facilitation = false +emoji= "💻" +[objectives] + 1="Write a program to extract information from a JSON file" + 2="Identify the trade-offs between using existing shell tools and writing custom programs" + 3="Choose whether to use existing tools or write a custom program to solve a particular problem" ++++ + +We've seen two different ways of analysing some input to produce an output. + +Sometimes we can use, or combine, existing tools to get answers. For instance, we can count the words in a file with `wc`. + +Sometimes we can write custom tools when existing tools don't quite do what we want. For instance, we wrote a program to count specific words. + +When we want to answer some question, sometimes it's useful to write a program that we may only use one time. Or we may re-use in the future. + +It's not always obvious whether it's easier to try to use tools that already exist, or to write our own. + +Sometimes the format of our data makes it easier or harder to use existing tools. + +Let's look at some sample data: + +```json +[ + { + "name": "Daniel", + "score": 100 + }, + { + "name": "Kristina", + "score": 120 + }, + { + "name": "Iulia", + "score": 95 + }, + { + "name": "Aleks", + "score": 190 + }, + { + "name": "Daniel", + "score": 80 + }, + { + "name": "Fatima", + "score": 110 + } +] +``` + +Here are a few questions we may want to answer about this data: +1. What was the name of the first person to play the game? +2. What was the name of the last person to play the game? +3. Who had the highest score? +4. The names of everyone who played the game directly after Daniel? + +We can probably answer all of these questions with `jq`. We can also definitely write a program to answer all of these questions for us. + +The first three are similarly hard to solve in `jq` or with a programming language. + +The last one is quite hard to solve in `jq`. + +{{}} +Solve all of the first three questions in both `jq` and your choice of JavaScript or Python. + +Which approach do you think is quicker to write? Which is easier to think about? +{{}} + +{{}} +Solve the fourth question in your choice of JavaScript or Python. + +Now spend no more than 20 minutes trying to solve it with `jq`. + +What do you think makes this harder to solve in `jq`? + +What {{}}A heuristic is a guideline. It's not an exact rule, but a "good enough" idea to guess what approach you should use to answer a question.{{}} can you think of about when to use existing tools vs writing your own? + +{{}} diff --git a/common-content/en/module/tools/splitting-a-string/index.md b/common-content/en/module/tools/splitting-a-string/index.md new file mode 100644 index 000000000..40d170453 --- /dev/null +++ b/common-content/en/module/tools/splitting-a-string/index.md @@ -0,0 +1,22 @@ ++++ +title = "Splitting the content of the file up into words" +headless = true +time = 5 +facilitation = false +emoji= "📖" +hide_from_overview = true +objectives = [ +] ++++ + +In JavaScript we wrote: + +```js +content.split(" ") +``` + +Googling for "Python split string" suggests we can write exactly the same code! + +```python +content.split(" ") +``` diff --git a/common-content/en/module/tools/using-npm-dependencies/index.md b/common-content/en/module/tools/using-npm-dependencies/index.md new file mode 100644 index 000000000..d41e5093b --- /dev/null +++ b/common-content/en/module/tools/using-npm-dependencies/index.md @@ -0,0 +1,32 @@ ++++ +title = "Using dependencies from npm" +headless = true +time = 20 +facilitation = false +emoji= "📚" +objectives = [ + "Define a library", +] ++++ + +We've seen that we can use code that was built into NodeJS - we don't need to write everything ourselves. + +We can also use code that other people have written, which isn't built into NodeJS. You've probably seen this before, e.g. using `jest` for testing. + +This can be really useful - it means we can benefit from work others have already done, and focus on just solving the part of a problem which is unique to us. It's like making shell pipelines - instead of having to solve every problem from scratch, we can plug together different tools that other people have already made. + +Let's expand the functionality of our program. Rather than always searching for words containing hyphens, let's allow the user to specify what character they're searching for. + +This means we want to introduce a flag. And programs that accept flags, should also document themselves. One common convention is that if you run a program with the flag `--help`, it will tell you how to use it. + +But writing all of this code to parse flags, to output information about the flags, and so on, is a lot of work. + +So let's use a {{}}A library is a collection of code that we can use, but which isn't part of our project.{{}} for this. We will use a library called `commander`. + +{{}} +Add `import { program } from "commander";` to the top of your hyphen-counting program. + +This line imports the `program` property from the object which is the `commander` library (using object destructuring). + +Try running you program. What happens? What does the output mean? +{{}} diff --git a/common-content/en/module/tools/using-python-dependencies/index.md b/common-content/en/module/tools/using-python-dependencies/index.md new file mode 100644 index 000000000..d5adf6645 --- /dev/null +++ b/common-content/en/module/tools/using-python-dependencies/index.md @@ -0,0 +1,84 @@ ++++ +title = "Using Python dependencies" +headless = true +time = 30 +facilitation = false +emoji= "📖" +objectives=[ + "Write and run a Python program which uses third-party dependencies.", +] ++++ + +Let's create a small program which uses a dependency. + +We're going to use the `cowsay` library to make a program which outputs a picture of a cow saying something. + +First let's create a Python file which tries to use `cowsay`: + +{{}} +It's important that you don't name your Python file the same as the name of a library you're trying to import. + +We can't call our file `cowsay.py` because we're going to try to import `cowsay`. + +We can call it `main.py` or `hello.py` or `cow.py`. Just not `cowsay.py`. + +In this example, we'll call it `cow.py`. +{{}} + +```python +import cowsay +``` + +If we try to run `python3 cow.py`, we'll get an error: + +``` +ModuleNotFoundError: No module named 'cowsay' +``` + +This is because we haven't installed cowsay yet. + +### Installing our dependency + +We will create a virtual environment, activate it, and install cowsay to it: + +```console +% python3 -m venv .venv +% . .venv/bin/activate +(.venv) % echo cowsay > requirements.txt +(.venv) % pip install -r requirements.txt +``` + +When we activate a virtual environment, its name gets shown before our terminal prompt. This is a useful reminder that we're in a virtual environment! + +### Running our program + +Now if we run `python3 cow.py` we don't get an error - we installed `cowsay` into our active virtual environment. + +If we open a new terminal and run `python3 cow.py` we'll get an error again. Because we haven't activated a virtual environment. + +If we run `. .venv/bin/activate` and then `python3 cow.py` it will start working again. + +Now we can finish our program - let's have the cow say the arguments back to the user (joining together the arguments with spaces). We need to use a slice to skip the first argument, which is our program name: + +```python +import cowsay +import sys + +cowsay.cow(" ".join(sys.argv[1:])) +``` + +Notice how `import cowsay` and `import sys` look the same - as long as we've installed dependencies, we can `import` them just like we can import things that are built into Python. + +```console +(.venv) % python3 cow.py Hello friend + ____________ +| Hello friend | + ============ + \ + \ + ^__^ + (oo)\_______ + (__)\ )\/\ + ||----w | + || || +``` diff --git a/common-content/en/module/tools/virtual-environments/index.md b/common-content/en/module/tools/virtual-environments/index.md new file mode 100644 index 000000000..b5372551a --- /dev/null +++ b/common-content/en/module/tools/virtual-environments/index.md @@ -0,0 +1,30 @@ ++++ +title = "Virtual environments" +headless = true +time = 30 +facilitation = false +emoji= "📖" +objectives=[ + "Create a virtual environment with some dependencies installed.", +] ++++ + +We often need to use libraries in Python. + +Python handles dependencies differently from JavaScript, but it has similarities. + +We've seen that in JavaScript we write down what dependencies we need in a `package.json` file, and when we run `npm install` they will get fetched into a folder called `node_modules`. + +In Python, we write down what dependencies we need in a file called `requirements.txt`. It doesn't contain JSON, it just contains a list of dependencies, one per line. + +### Virtual environments + +To install the dependencies, we need to make something called a virtual environment, where they will get installed to. + +First we need to _create_ the virtual environment. We do this by running `python3 -m venv .venv`. This will create a virtual environment in a directory named `.venv`. We could actually create it anywhere, e.g. we could run `python3 -m venv /tmp/python_modules` to create it in a directory named `/tmp/python_modules`. We tend to just use a directory called `.venv` at the root of our project. + +Next we need to _activate_ the virtual environment. We do this by running `. .venv/bin/activate` (yes, the command we're running is `.` with a path as an argument - the `.` is important). This will only activate the virtual environment for the terminal window we're in - if you're using more than one terminal window, you'll need to activate it in each of them. + +Finally we need to install our dependencies into the virtual environment. We do this by running `pip install -r requirements.txt`. This is saying "Please install all of the dependencies listed in `requirements.txt` into the currently active virtual environment". + +After we've done this, we should be able to `import` any installed dependencies into our Python code. This will work as long as we have activated the virtual environment in the terminal window where we're running our program. diff --git a/org-cyf-sdc/content/tools/sprints/3/backlog/index.md b/org-cyf-sdc/content/tools/sprints/3/backlog/index.md index d23d2978e..34de6ed71 100644 --- a/org-cyf-sdc/content/tools/sprints/3/backlog/index.md +++ b/org-cyf-sdc/content/tools/sprints/3/backlog/index.md @@ -4,6 +4,6 @@ layout = 'backlog' emoji= '🥞' menu_level = ['sprint'] weight = 2 -backlog= 'Module-Template' -backlog_filter='📅 Sprint 1' +backlog= 'Module-Tools' +backlog_filter='📅 Sprint 3' +++ diff --git a/org-cyf-sdc/content/tools/sprints/3/prep/index.md b/org-cyf-sdc/content/tools/sprints/3/prep/index.md index dd667ed26..7ebe86829 100644 --- a/org-cyf-sdc/content/tools/sprints/3/prep/index.md +++ b/org-cyf-sdc/content/tools/sprints/3/prep/index.md @@ -6,15 +6,18 @@ emoji= '🧑🏾‍💻' menu_level = ['sprint'] weight = 1 [[blocks]] -name="Read about operating systems" -src="module/tools/read-about-operating-systems-1" +name="NodeJS" +src="module/tools/nodejs" +[[blocks]] +name="Writing a NodeJS program" +src="module/tools/first-nodejs-program" [[blocks]] -name="Learn NodeJS" -src="module/tools/learn-nodejs" +name="Using dependencies from npm" +src="module/tools/using-npm-dependencies" [[blocks]] -name="Implement and test CLI tools in NodeJS" -src="module/tools/implement-tools-in-nodejs" +name="Installing dependencies with npm" +src="module/tools/installing-npm-dependencies" [[blocks]] -name="Implement a single-use data analysis program" -src="module/tools/implement-single-use-data-analysis" +name="Read about operating systems" +src="module/tools/read-about-operating-systems" +++ diff --git a/org-cyf-sdc/content/tools/sprints/4/backlog/index.md b/org-cyf-sdc/content/tools/sprints/4/backlog/index.md index d23d2978e..5eeb4563d 100644 --- a/org-cyf-sdc/content/tools/sprints/4/backlog/index.md +++ b/org-cyf-sdc/content/tools/sprints/4/backlog/index.md @@ -4,6 +4,6 @@ layout = 'backlog' emoji= '🥞' menu_level = ['sprint'] weight = 2 -backlog= 'Module-Template' -backlog_filter='📅 Sprint 1' +backlog= 'Module-Tools' +backlog_filter='📅 Sprint 4' +++ diff --git a/org-cyf-sdc/content/tools/sprints/4/prep/index.md b/org-cyf-sdc/content/tools/sprints/4/prep/index.md index b9c357aad..c9c9906c7 100644 --- a/org-cyf-sdc/content/tools/sprints/4/prep/index.md +++ b/org-cyf-sdc/content/tools/sprints/4/prep/index.md @@ -6,15 +6,30 @@ emoji= '🧑🏾‍💻' menu_level = ['sprint'] weight = 1 [[blocks]] -name="Read about operating systems" -src="module/tools/read-about-operating-systems-2" +name="Single-use data analysis programs" +src="module/tools/single-use-data-analysis" [[blocks]] -name="Learn Python" -src="module/tools/learn-python" +name="Comparing JavaScript and Python" +src="module/tools/comparing-javascript-and-python" [[blocks]] -name="Implement and test CLI tools in Python" -src="module/tools/implement-tools-in-python" +name="Converting a script from JavaScript to Python" +src="module/tools/converting-javascript-to-python" [[blocks]] -name="Convert a script between languages" -src="module/tools/convert-script-between-languages" +name="Reading a file" +src="module/tools/reading-a-file" +[[blocks]] +name="Splitting" +src="module/tools/splitting-a-string" +[[blocks]] +name="Counting words" +src="module/tools/counting-words" +[[blocks]] +name="Putting it all together" +src="module/tools/converted-program" +[[blocks]] +name = "Virtual environments" +src="module/tools/virtual-environments" +[[blocks]] +name = "Using python dependencies" +src="module/tools/using-python-dependencies" +++ From e16d252848b3b4bc8f1f1913b468e886c48a0e31 Mon Sep 17 00:00:00 2001 From: Daniel Wagner-Hall Date: Mon, 30 Dec 2024 15:55:20 +0000 Subject: [PATCH 02/32] Add module success criteria --- org-cyf-sdc/content/tools/success/index.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/org-cyf-sdc/content/tools/success/index.md b/org-cyf-sdc/content/tools/success/index.md index 465c0fbe2..99c6922aa 100644 --- a/org-cyf-sdc/content/tools/success/index.md +++ b/org-cyf-sdc/content/tools/success/index.md @@ -6,10 +6,10 @@ emoji= '✅' menu_level = ['module'] weight = 11 [[objectives]] -1="Translate requirements into high-level design outlines" -2="Break down solving problems into testable steps" -3="Ask questions systematically using a given formal language" -4="Demonstrate good citizenship on GitHub by participating in code review on PRs" -5="Solve at least 6 7kyu problems in Codewars" -6="Solve up to level 5 in the Bandit repeatedly" +1="Work with binary, hexadecimal, and decimal numbers" +2="Explain what a CPU is, what main memory is, and how they interact" +3="Describe an operating system, including the kernel, processes, and syscalls" +4="Process text files using standard command line tools, including in pipelines" +5="Write command line tools using JavaScript + NodeJS, including dependencies from npm" +6="Write command line tools using Python, including dependencies from pypi" +++ From a5c60c6a7fe3610b5b4b2ce521631a0338ec7293 Mon Sep 17 00:00:00 2001 From: Daniel Wagner-Hall Date: Mon, 30 Dec 2024 16:23:09 +0000 Subject: [PATCH 03/32] Simplify themes --- org-cyf-sdc/content/tools/sprints/3/_index.md | 2 +- org-cyf-sdc/content/tools/sprints/4/_index.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/org-cyf-sdc/content/tools/sprints/3/_index.md b/org-cyf-sdc/content/tools/sprints/3/_index.md index a8f04873a..a789394ee 100644 --- a/org-cyf-sdc/content/tools/sprints/3/_index.md +++ b/org-cyf-sdc/content/tools/sprints/3/_index.md @@ -5,5 +5,5 @@ layout = 'sprint' emoji= '⏱️' menu_level = ['module'] weight = 2 -theme = "Implementing and testing shell tools in NodeJS, and Operating Systems concepts" +theme = "Implementing shell tools in NodeJS, and Operating Systems concepts" +++ diff --git a/org-cyf-sdc/content/tools/sprints/4/_index.md b/org-cyf-sdc/content/tools/sprints/4/_index.md index bc1557cd3..7c0ec2609 100644 --- a/org-cyf-sdc/content/tools/sprints/4/_index.md +++ b/org-cyf-sdc/content/tools/sprints/4/_index.md @@ -5,5 +5,5 @@ layout = 'sprint' emoji= '⏱️' menu_level = ['module'] weight = 2 -theme = "Implementing and testing shell tools in Python, and Operating Systems concepts" +theme = "Implementing shell tools in Python" +++ From 3a37c85d32a21ed293aa7c85c927ca141bc28f13 Mon Sep 17 00:00:00 2001 From: Daniel Wagner-Hall Date: Wed, 22 Jan 2025 14:04:19 +0000 Subject: [PATCH 04/32] Update common-content/en/module/tools/comparing-javascript-and-python/index.md Co-authored-by: Sally McGrath --- .../en/module/tools/comparing-javascript-and-python/index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common-content/en/module/tools/comparing-javascript-and-python/index.md b/common-content/en/module/tools/comparing-javascript-and-python/index.md index a20f10c71..cbe741039 100644 --- a/common-content/en/module/tools/comparing-javascript-and-python/index.md +++ b/common-content/en/module/tools/comparing-javascript-and-python/index.md @@ -6,7 +6,7 @@ facilitation = false emoji= "📖" objectives = [ "Identify and explain equivalences between JavaScript and Python", - "Identify and explain differences between JavaScript and Python", + "Compare and contrast differences between JavaScript and Python", "Distinguish between essential and accidental complexity" ] +++ From 706d7a278587904a8780bb8ec3ff449997f25798 Mon Sep 17 00:00:00 2001 From: Daniel Wagner-Hall Date: Wed, 22 Jan 2025 14:04:32 +0000 Subject: [PATCH 05/32] Update common-content/en/module/tools/comparing-javascript-and-python/index.md Co-authored-by: Sally McGrath --- .../en/module/tools/comparing-javascript-and-python/index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common-content/en/module/tools/comparing-javascript-and-python/index.md b/common-content/en/module/tools/comparing-javascript-and-python/index.md index cbe741039..ca2f1248f 100644 --- a/common-content/en/module/tools/comparing-javascript-and-python/index.md +++ b/common-content/en/module/tools/comparing-javascript-and-python/index.md @@ -11,7 +11,7 @@ objectives = [ ] +++ -JavaScript and Python are quite similar languages in a lot of ways. +JavaScript and Python have many things in common. Most of the differences between them are quite cosmetic. e.g. * Some functions and operators have different names. But often there are functions/operators which do exactly the same thing. From beffbc7bb9fd5117bbf3c4e5f644c100002187fe Mon Sep 17 00:00:00 2001 From: Daniel Wagner-Hall Date: Wed, 22 Jan 2025 14:04:46 +0000 Subject: [PATCH 06/32] Update common-content/en/module/tools/comparing-javascript-and-python/index.md Co-authored-by: Sally McGrath --- .../en/module/tools/comparing-javascript-and-python/index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common-content/en/module/tools/comparing-javascript-and-python/index.md b/common-content/en/module/tools/comparing-javascript-and-python/index.md index ca2f1248f..19e414b80 100644 --- a/common-content/en/module/tools/comparing-javascript-and-python/index.md +++ b/common-content/en/module/tools/comparing-javascript-and-python/index.md @@ -13,7 +13,7 @@ objectives = [ JavaScript and Python have many things in common. -Most of the differences between them are quite cosmetic. e.g. +Most differences are "cosmetic". Here are some examples of cosmetic differnces: * Some functions and operators have different names. But often there are functions/operators which do exactly the same thing. * JavaScript uses `{}` around blocks of code and we optionally _choose_ to indent code, whereas Python uses `:` and _required_ indents. * In JavaScript we choose to name variables in `camelCase`, whereas in Python we choose to name variables in `snake_case` (but in both langues we _could_ do either). From 9f63e52e38458b222d423f85bf2d1a41fad949c0 Mon Sep 17 00:00:00 2001 From: Daniel Wagner-Hall Date: Wed, 22 Jan 2025 14:05:10 +0000 Subject: [PATCH 07/32] Update common-content/en/module/tools/comparing-javascript-and-python/index.md Co-authored-by: Sally McGrath --- .../en/module/tools/comparing-javascript-and-python/index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common-content/en/module/tools/comparing-javascript-and-python/index.md b/common-content/en/module/tools/comparing-javascript-and-python/index.md index 19e414b80..b1b830bfa 100644 --- a/common-content/en/module/tools/comparing-javascript-and-python/index.md +++ b/common-content/en/module/tools/comparing-javascript-and-python/index.md @@ -15,7 +15,7 @@ JavaScript and Python have many things in common. Most differences are "cosmetic". Here are some examples of cosmetic differnces: * Some functions and operators have different names. But often there are functions/operators which do exactly the same thing. -* JavaScript uses `{}` around blocks of code and we optionally _choose_ to indent code, whereas Python uses `:` and _required_ indents. +* JavaScript uses `{}` around blocks of code and we _choose_ if we indent code. Python uses `:` and indentation is required. * In JavaScript we choose to name variables in `camelCase`, whereas in Python we choose to name variables in `snake_case` (but in both langues we _could_ do either). Let's take our "count containing words" JavaScript code from last week, and think about what it would look like in Python. From 964da99e10c6631cb60efc7172284a953506900d Mon Sep 17 00:00:00 2001 From: Daniel Wagner-Hall Date: Wed, 22 Jan 2025 14:05:35 +0000 Subject: [PATCH 08/32] Update common-content/en/module/tools/comparing-javascript-and-python/index.md Co-authored-by: Sally McGrath --- .../en/module/tools/comparing-javascript-and-python/index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common-content/en/module/tools/comparing-javascript-and-python/index.md b/common-content/en/module/tools/comparing-javascript-and-python/index.md index b1b830bfa..bbadebfa6 100644 --- a/common-content/en/module/tools/comparing-javascript-and-python/index.md +++ b/common-content/en/module/tools/comparing-javascript-and-python/index.md @@ -18,7 +18,7 @@ Most differences are "cosmetic". Here are some examples of cosmetic differnces: * JavaScript uses `{}` around blocks of code and we _choose_ if we indent code. Python uses `:` and indentation is required. * In JavaScript we choose to name variables in `camelCase`, whereas in Python we choose to name variables in `snake_case` (but in both langues we _could_ do either). -Let's take our "count containing words" JavaScript code from last week, and think about what it would look like in Python. +Recall our "count containing words" JavaScript code. Now think about what it would look like in Python. ```js import { program } from "commander"; From 6f09666371910d81a551fcc38eee6d09fb0ba818 Mon Sep 17 00:00:00 2001 From: Daniel Wagner-Hall Date: Wed, 22 Jan 2025 14:06:16 +0000 Subject: [PATCH 09/32] Update common-content/en/module/tools/comparing-javascript-and-python/index.md Co-authored-by: Sally McGrath --- .../en/module/tools/comparing-javascript-and-python/index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common-content/en/module/tools/comparing-javascript-and-python/index.md b/common-content/en/module/tools/comparing-javascript-and-python/index.md index bbadebfa6..436881322 100644 --- a/common-content/en/module/tools/comparing-javascript-and-python/index.md +++ b/common-content/en/module/tools/comparing-javascript-and-python/index.md @@ -53,7 +53,7 @@ Let's think about what we're doing in this code. We're: * Counting how many of the words contained a particular character. * Printing the count. -These are the meaningful things we needed to do. If we wanted to solve the same problem with Python, we'd need to do all of these things. +These are the meaningful things we needed to do. To solve the same problem with Python, we'd still do all of these things. There are also some other things we did in our code, which were important, but not the point of the code. An example is, we imported some modules. We may need to import modules to write this code in Python. Or we may not. Importing modules wasn't one of our _goals_, it was just something we needed to do to help us. From 165b2d637ab1d8ba99440d9a080013a1437b71ab Mon Sep 17 00:00:00 2001 From: Daniel Wagner-Hall Date: Wed, 22 Jan 2025 14:06:48 +0000 Subject: [PATCH 10/32] Update common-content/en/module/tools/converted-program/index.md Co-authored-by: Sally McGrath --- common-content/en/module/tools/converted-program/index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common-content/en/module/tools/converted-program/index.md b/common-content/en/module/tools/converted-program/index.md index 2aed7d252..dbb210218 100644 --- a/common-content/en/module/tools/converted-program/index.md +++ b/common-content/en/module/tools/converted-program/index.md @@ -9,7 +9,7 @@ objectives = [ ] +++ -Finally, instead of calling `console.log`, in Python we call `print`. +Instead of calling `console.log`, in Python we call `print`. ```python import argparse From bd744cbb39a68fc60e0746ab139f42eab8bf3669 Mon Sep 17 00:00:00 2001 From: Daniel Wagner-Hall Date: Wed, 22 Jan 2025 14:07:34 +0000 Subject: [PATCH 11/32] Update common-content/en/module/tools/converted-program/index.md Co-authored-by: Sally McGrath --- common-content/en/module/tools/converted-program/index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common-content/en/module/tools/converted-program/index.md b/common-content/en/module/tools/converted-program/index.md index dbb210218..71fa64478 100644 --- a/common-content/en/module/tools/converted-program/index.md +++ b/common-content/en/module/tools/converted-program/index.md @@ -32,4 +32,4 @@ print(words_containing_char) This looks pretty similar to the JavaScript version. The essential shape is the same. But every line is a least a little bit different. -Some programming languages are a lot more different. But JavaScript and Python are, essentially, quite similar. +Some programming languages are very different, as different as Mandarin and English. But JavaScript and Python are, essentially, quite similar, like Spanish and Portugese. From d59e0acc64e4075c60440fbe6073ed572c65536c Mon Sep 17 00:00:00 2001 From: Daniel Wagner-Hall Date: Wed, 22 Jan 2025 14:08:17 +0000 Subject: [PATCH 12/32] Update common-content/en/module/tools/converting-javascript-to-python/index.md Co-authored-by: Sally McGrath --- .../en/module/tools/converting-javascript-to-python/index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common-content/en/module/tools/converting-javascript-to-python/index.md b/common-content/en/module/tools/converting-javascript-to-python/index.md index 7206bd339..6356160cc 100644 --- a/common-content/en/module/tools/converting-javascript-to-python/index.md +++ b/common-content/en/module/tools/converting-javascript-to-python/index.md @@ -65,7 +65,7 @@ In our JavaScript code, we needed to check that there was exactly one positional We don't need to do this in our Python code. Because `argparse` treats positional arguments as arguments, it actually already errors if we pass no positional arguments, or more than one. -So we can tick this essential requirement off of our list. Sometimes different languages, or different libraries, do things slightly differently, and that's ok! +So we can tick this essential requirement off our list. Different languages or libraries do things differently, and that's ok! > [!TIP] > We don't need to convert every line. From 8670f271e897e0fe58c1cfcd49169d9cca3e2369 Mon Sep 17 00:00:00 2001 From: Daniel Wagner-Hall Date: Wed, 22 Jan 2025 14:08:48 +0000 Subject: [PATCH 13/32] Update common-content/en/module/tools/counting-words/index.md Co-authored-by: Sally McGrath --- common-content/en/module/tools/counting-words/index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common-content/en/module/tools/counting-words/index.md b/common-content/en/module/tools/counting-words/index.md index 0250b99a8..1fc8117a7 100644 --- a/common-content/en/module/tools/counting-words/index.md +++ b/common-content/en/module/tools/counting-words/index.md @@ -15,7 +15,7 @@ In JavaScript we wrote: content.split(" ").filter((word) => word.indexOf(char) > -1).length ``` -It's useful to know that what JavaScript calls arrays, Python calls lists. (Arrays and lists are basically the same, other than the name, though!) +What JavaScript calls arrays, Python calls lists. Arrays and lists are basically the same. Googling for "Python filter list" suggests there are two things we can use - a `filter` function, or something called a "list comprehension". Some people prefer one, other people prefer the other. From 83b06ba49235bc5aae1a00f671bba6ca0f89b41c Mon Sep 17 00:00:00 2001 From: Daniel Wagner-Hall Date: Wed, 22 Jan 2025 14:10:04 +0000 Subject: [PATCH 14/32] Update common-content/en/module/tools/first-nodejs-program/index.md Co-authored-by: Sally McGrath --- common-content/en/module/tools/first-nodejs-program/index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common-content/en/module/tools/first-nodejs-program/index.md b/common-content/en/module/tools/first-nodejs-program/index.md index 0836ddf1b..bc3df250c 100644 --- a/common-content/en/module/tools/first-nodejs-program/index.md +++ b/common-content/en/module/tools/first-nodejs-program/index.md @@ -40,7 +40,7 @@ Let's play computer with this program - line by line: import process from "node:process"; ``` -This is loading some code from somewhere that isn't this file. +This `import` is loading some code from somewhere that isn't this file. We've seen `import` before. Here, instead of importing from a file we've written, we're importing the `process` module which is built into NodeJS. From ffbf68c1cd4f08c69b3f0b3d2b032e8ba280e707 Mon Sep 17 00:00:00 2001 From: Daniel Wagner-Hall Date: Wed, 22 Jan 2025 14:10:45 +0000 Subject: [PATCH 15/32] Update common-content/en/module/tools/first-nodejs-program/index.md Co-authored-by: Sally McGrath --- common-content/en/module/tools/first-nodejs-program/index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common-content/en/module/tools/first-nodejs-program/index.md b/common-content/en/module/tools/first-nodejs-program/index.md index bc3df250c..16bb91256 100644 --- a/common-content/en/module/tools/first-nodejs-program/index.md +++ b/common-content/en/module/tools/first-nodejs-program/index.md @@ -46,7 +46,7 @@ We've seen `import` before. Here, instead of importing from a file we've written This is an example of the same language features (`import`) being used slightly differently (the `"node:"` is a special prefix to say "specially from node"). -The `process` module is built into NodeJS for managing our process. It can be used to do things like find out what arguments were passed to the process when it started, find out what user ran the process, exit the process, and more. +The `process` module is built into NodeJS for managing our process. We can use it to find out what arguments were passed to the process when it started, find out what user ran the process, exit the process, and more. ```js import { promises as fs } from "node:fs"; From 246882f8c9aca1a4f0b93e03d9df73f4e93b9d5d Mon Sep 17 00:00:00 2001 From: Daniel Wagner-Hall Date: Wed, 22 Jan 2025 14:41:29 +0000 Subject: [PATCH 16/32] Update common-content/en/module/tools/single-use-data-analysis/index.md Co-authored-by: Sally McGrath --- .../en/module/tools/single-use-data-analysis/index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common-content/en/module/tools/single-use-data-analysis/index.md b/common-content/en/module/tools/single-use-data-analysis/index.md index 2a321c631..9c72c188f 100644 --- a/common-content/en/module/tools/single-use-data-analysis/index.md +++ b/common-content/en/module/tools/single-use-data-analysis/index.md @@ -16,7 +16,7 @@ Sometimes we can use, or combine, existing tools to get answers. For instance, w Sometimes we can write custom tools when existing tools don't quite do what we want. For instance, we wrote a program to count specific words. -When we want to answer some question, sometimes it's useful to write a program that we may only use one time. Or we may re-use in the future. +When we want to answer some question, sometimes it's useful to write a program we may only use once. (Or we may re-use in the future.) It's not always obvious whether it's easier to try to use tools that already exist, or to write our own. From 8de321f847fb9c2ae98846c9a843c570881c6e18 Mon Sep 17 00:00:00 2001 From: Daniel Wagner-Hall Date: Wed, 22 Jan 2025 14:54:21 +0000 Subject: [PATCH 17/32] Update common-content/en/module/tools/using-python-dependencies/index.md Co-authored-by: Sally McGrath --- .../en/module/tools/using-python-dependencies/index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common-content/en/module/tools/using-python-dependencies/index.md b/common-content/en/module/tools/using-python-dependencies/index.md index d5adf6645..8e5e7a495 100644 --- a/common-content/en/module/tools/using-python-dependencies/index.md +++ b/common-content/en/module/tools/using-python-dependencies/index.md @@ -52,7 +52,7 @@ When we activate a virtual environment, its name gets shown before our terminal ### Running our program -Now if we run `python3 cow.py` we don't get an error - we installed `cowsay` into our active virtual environment. +Run `python3 cow.py`. We don't get an error because we installed `cowsay` into our active virtual environment. If we open a new terminal and run `python3 cow.py` we'll get an error again. Because we haven't activated a virtual environment. From 5e784482740a329905999d8a093cfb31c7f3d941 Mon Sep 17 00:00:00 2001 From: Daniel Wagner-Hall Date: Wed, 22 Jan 2025 14:54:51 +0000 Subject: [PATCH 18/32] Update common-content/en/module/tools/using-python-dependencies/index.md Co-authored-by: Sally McGrath --- .../en/module/tools/using-python-dependencies/index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common-content/en/module/tools/using-python-dependencies/index.md b/common-content/en/module/tools/using-python-dependencies/index.md index 8e5e7a495..2a9782435 100644 --- a/common-content/en/module/tools/using-python-dependencies/index.md +++ b/common-content/en/module/tools/using-python-dependencies/index.md @@ -54,7 +54,7 @@ When we activate a virtual environment, its name gets shown before our terminal Run `python3 cow.py`. We don't get an error because we installed `cowsay` into our active virtual environment. -If we open a new terminal and run `python3 cow.py` we'll get an error again. Because we haven't activated a virtual environment. +Open a new terminal and run `python3 cow.py`. An error will occur because we haven't activated a virtual environment. If we run `. .venv/bin/activate` and then `python3 cow.py` it will start working again. From 9c2e4657f9d8117d1ac71c5ede43bc3775c3a3ec Mon Sep 17 00:00:00 2001 From: Daniel Wagner-Hall Date: Wed, 22 Jan 2025 14:55:35 +0000 Subject: [PATCH 19/32] Update common-content/en/module/tools/using-python-dependencies/index.md Co-authored-by: Sally McGrath --- .../en/module/tools/using-python-dependencies/index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common-content/en/module/tools/using-python-dependencies/index.md b/common-content/en/module/tools/using-python-dependencies/index.md index 2a9782435..397ae2ae9 100644 --- a/common-content/en/module/tools/using-python-dependencies/index.md +++ b/common-content/en/module/tools/using-python-dependencies/index.md @@ -56,7 +56,7 @@ Run `python3 cow.py`. We don't get an error because we installed `cowsay` into o Open a new terminal and run `python3 cow.py`. An error will occur because we haven't activated a virtual environment. -If we run `. .venv/bin/activate` and then `python3 cow.py` it will start working again. +Run `. .venv/bin/activate` and then `python3 cow.py`. It will start working again. Now we can finish our program - let's have the cow say the arguments back to the user (joining together the arguments with spaces). We need to use a slice to skip the first argument, which is our program name: From b3dca09519257943d91afb3a903722c64add5fa0 Mon Sep 17 00:00:00 2001 From: Daniel Wagner-Hall Date: Thu, 23 Jan 2025 13:52:57 +0000 Subject: [PATCH 20/32] Tooltip convention --- .../en/module/tools/comparing-javascript-and-python/index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common-content/en/module/tools/comparing-javascript-and-python/index.md b/common-content/en/module/tools/comparing-javascript-and-python/index.md index 436881322..80e2bb537 100644 --- a/common-content/en/module/tools/comparing-javascript-and-python/index.md +++ b/common-content/en/module/tools/comparing-javascript-and-python/index.md @@ -16,7 +16,7 @@ JavaScript and Python have many things in common. Most differences are "cosmetic". Here are some examples of cosmetic differnces: * Some functions and operators have different names. But often there are functions/operators which do exactly the same thing. * JavaScript uses `{}` around blocks of code and we _choose_ if we indent code. Python uses `:` and indentation is required. -* In JavaScript we choose to name variables in `camelCase`, whereas in Python we choose to name variables in `snake_case` (but in both langues we _could_ do either). +* In JavaScript we choose to name variables in `camelCase`, whereas in Python we choose to name variables in `snake_case`. In both langues we _could_ do either; this is called a {{}}A convention is something a group (maybe a team, or a company, or most users of a programming language) agree to do. It's not quite a rule - things _could_ work another way. But we agree one way we'll all do it anyway.

e.g. in Python you could name one variable `firstName` and another `middle_name` and another `LASTname`, but if everyone agrees to use `first_name` and `middle_name` and `last_name` it makes it a bit easier for everyone to read because they know what to expect.{{
}}. Recall our "count containing words" JavaScript code. Now think about what it would look like in Python. From 189113721e8f50b6c731ebe337692d888ed0fa14 Mon Sep 17 00:00:00 2001 From: Daniel Wagner-Hall Date: Fri, 24 Jan 2025 13:29:45 +0000 Subject: [PATCH 21/32] Review feedback --- .../en/module/tools/comparing-javascript-and-python/index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common-content/en/module/tools/comparing-javascript-and-python/index.md b/common-content/en/module/tools/comparing-javascript-and-python/index.md index 80e2bb537..5e9cd4143 100644 --- a/common-content/en/module/tools/comparing-javascript-and-python/index.md +++ b/common-content/en/module/tools/comparing-javascript-and-python/index.md @@ -45,7 +45,7 @@ const wordsContainingChar = content.split(" ").filter((word) => word.indexOf(cha console.log(wordsContainingChar); ``` -Let's think about what we're doing in this code. We're: +Think about what we're doing in this code: * Parsing command line flags - writing down what flags we expect to be passed, and reading values for them based on the actual command line. * Validating the flags (i.e. checking that exactly one path was passed). * Reading a file. From 72b11df783cd5cf491ca163cfe3665ffa881a1c3 Mon Sep 17 00:00:00 2001 From: Daniel Wagner-Hall Date: Fri, 24 Jan 2025 13:46:42 +0000 Subject: [PATCH 22/32] Review feedback --- .../comparing-javascript-and-python/index.md | 30 +++++++++++++++++-- .../module/tools/converted-program/index.md | 2 +- .../converting-javascript-to-python/index.md | 4 ++- 3 files changed, 31 insertions(+), 5 deletions(-) diff --git a/common-content/en/module/tools/comparing-javascript-and-python/index.md b/common-content/en/module/tools/comparing-javascript-and-python/index.md index 5e9cd4143..5a755585f 100644 --- a/common-content/en/module/tools/comparing-javascript-and-python/index.md +++ b/common-content/en/module/tools/comparing-javascript-and-python/index.md @@ -45,7 +45,18 @@ const wordsContainingChar = content.split(" ").filter((word) => word.indexOf(cha console.log(wordsContainingChar); ``` -Think about what we're doing in this code: +{{}} +===[[Exercise]]=== +Think about what we're doing in this code. + +Try to list the high-level ideas. This means describing in English what we're achieving, using sentences like like "Reading a file". + +We're not trying to think about the programming concepts we're doing here (we aren't talking about things like "Assigning a variable" or "An if statement"). Think about what a non-programmer would want to understand about our program. + +===[[Answer]]=== + +You may have slightly different answers, but the programme is doing roughly the following things: + * Parsing command line flags - writing down what flags we expect to be passed, and reading values for them based on the actual command line. * Validating the flags (i.e. checking that exactly one path was passed). * Reading a file. @@ -53,9 +64,11 @@ Think about what we're doing in this code: * Counting how many of the words contained a particular character. * Printing the count. +{{}} + These are the meaningful things we needed to do. To solve the same problem with Python, we'd still do all of these things. -There are also some other things we did in our code, which were important, but not the point of the code. An example is, we imported some modules. We may need to import modules to write this code in Python. Or we may not. Importing modules wasn't one of our _goals_, it was just something we needed to do to help us. +We did some other things in our code to make it work. For example, we imported some modules. To write this code in Python, we might need modules or we might not. Importing modules isn't one of our _goals_, it was just something we needed to do to help us. We split up things we need to do into two categories: essential and accidental. @@ -63,6 +76,17 @@ We split up things we need to do into two categories: essential and accidental. **Accidental** means it isn't what we _care_ about doing, but we may need to do it anyway. e.g. importing the `process` module isn't _essential_ to our problem, but we needed to do it anyway so we could report errors. +{{}} +Imagine we want to post a parcel, so we take the bus to the post office. + +_Essential_ to our goal is getting the parcel to someone who will deliver it. + +_Accidental_ to this, we took the bus. There may be ways we could achieve our essential goal without getting the bus. Maybe we could walk or cycle to the post office. Maybe we could arrange for someone from the post office to come to our home and collect the parcel. + +The accidental things we did were important - they helped us get our essential goal done. But we shouldn't get too attached to the accidental things - maybe we will replace them later. +{{}} + + When we're thinking about how we use different languages, it's useful to think about what parts of our problem are _essential_ (we'll need to do them in any language), and which parts are _accidental_ (it's just something we had to do on the way to achieve our aim). -Whether we write the JavaScript `someArray.length` or the Python `len(some_array)` isn't a big difference - both do the same thing, they just look a little a little different. +Whether we write the JavaScript `someArray.length` or the Python `len(some_array)` isn't a big difference. Both lines do the same thing, they just express it differently. diff --git a/common-content/en/module/tools/converted-program/index.md b/common-content/en/module/tools/converted-program/index.md index 71fa64478..ab66bc444 100644 --- a/common-content/en/module/tools/converted-program/index.md +++ b/common-content/en/module/tools/converted-program/index.md @@ -30,6 +30,6 @@ words_containing_char = len(filter(lambda word: args.char in word, content.split print(words_containing_char) ``` -This looks pretty similar to the JavaScript version. The essential shape is the same. But every line is a least a little bit different. +This looks similar to the JavaScript version. The shape is the same, but every line is a little bit different. Some programming languages are very different, as different as Mandarin and English. But JavaScript and Python are, essentially, quite similar, like Spanish and Portugese. diff --git a/common-content/en/module/tools/converting-javascript-to-python/index.md b/common-content/en/module/tools/converting-javascript-to-python/index.md index 6356160cc..445915afe 100644 --- a/common-content/en/module/tools/converting-javascript-to-python/index.md +++ b/common-content/en/module/tools/converting-javascript-to-python/index.md @@ -11,7 +11,7 @@ objectives = [ ### Parsing command line flags -In JavaScript, we wrote this code (note: there was some other code in between some of these lines): +In JavaScript, we wrote this code: ```js import { program } from "commander"; @@ -28,6 +28,8 @@ const path = argv[0]; const char = program.opts().char; ``` +Which of the following are _essential_ goals in this code, and which are _accidental_ goals? + The _essential_ goals here are to: * Allow a user to pass a `-c` argument (defaulting to `-` if they don't). * Allow a user to pass a path as a positional argument. From d6b8570d6c5f9c773177ef721a85262a962f1ed8 Mon Sep 17 00:00:00 2001 From: Daniel Wagner-Hall Date: Fri, 24 Jan 2025 13:48:33 +0000 Subject: [PATCH 23/32] Fix typo --- common-content/en/module/tools/converted-program/index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common-content/en/module/tools/converted-program/index.md b/common-content/en/module/tools/converted-program/index.md index ab66bc444..01f0a99fe 100644 --- a/common-content/en/module/tools/converted-program/index.md +++ b/common-content/en/module/tools/converted-program/index.md @@ -32,4 +32,4 @@ print(words_containing_char) This looks similar to the JavaScript version. The shape is the same, but every line is a little bit different. -Some programming languages are very different, as different as Mandarin and English. But JavaScript and Python are, essentially, quite similar, like Spanish and Portugese. +Some programming languages are very different, as different as Mandarin and English. But JavaScript and Python are, essentially, quite similar, like Spanish and Portuguese. From c771e221ded48971b318c1c72797c386926ad92f Mon Sep 17 00:00:00 2001 From: Daniel Wagner-Hall Date: Fri, 24 Jan 2025 14:08:56 +0000 Subject: [PATCH 24/32] Feedback on converting exercise --- .../converting-javascript-to-python/index.md | 24 ++++++++++++------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/common-content/en/module/tools/converting-javascript-to-python/index.md b/common-content/en/module/tools/converting-javascript-to-python/index.md index 445915afe..3ec141446 100644 --- a/common-content/en/module/tools/converting-javascript-to-python/index.md +++ b/common-content/en/module/tools/converting-javascript-to-python/index.md @@ -1,7 +1,7 @@ +++ title = "Converting JavaScript to Python" headless = true -time = 40 +time = 90 facilitation = false emoji= "📖" objectives = [ @@ -30,16 +30,20 @@ const char = program.opts().char; Which of the following are _essential_ goals in this code, and which are _accidental_ goals? -The _essential_ goals here are to: -* Allow a user to pass a `-c` argument (defaulting to `-` if they don't). -* Allow a user to pass a path as a positional argument. -* Supply a nice `--help` implementation to help a user if they don't know how to use our tool. - -We _accidentally_ did a lot of things to achieve these goals. We used a library called commander. We imported that library. We called some particular functions, and made some particular variables. +{{}} +[LABEL=Essential] Allow a user to pass a `-c` argument (defaulting to `-` if they don't). +[LABEL=Accidental] Made a `const` variable called `argv`. +[LABEL=Accidental] Import `program` from the `commander` library. +[LABEL=Essential] Allow a user to pass a path as a positional argument. +[LABEL=Accidental] Looked up element `0` in the `program.args` array. +[LABEL=Essential] Supply a nice `--help` implementation to help a user if they don't know how to use our tool. +[LABEL=Accidental] Use the commander library. +[LABEL=Accidental] Called the function `program.name()`. +{{}} If we want to work out how to do this in Python, we should focus on the essential goals. We may want to search for things like "Parse command line flags Python" and "Default argument values Python" because they get to the essential problems we're trying to solve. -Searching Google for "Parse command line flags Python" brought us to [the Python argparse documentation](https://docs.python.org/3/library/argparse.html). The example code looks pretty similar to what we were doing in Python. We can probably write something like: +Searching Google for "Parse command line flags Python" brought us to [the Python argparse documentation](https://docs.python.org/3/library/argparse.html). The example code looks pretty similar to what we were doing in JavaScript. We can probably write something like: ```python import argparse @@ -73,3 +77,7 @@ So we can tick this essential requirement off our list. Different languages or l > We don't need to convert every line. > > We're trying to convert _essential requirements_. + +{{}} +Identify all of the essential requirements from our JavaScript program, and finish implementing the Python version. +{{}} From ee3ede48e956395b4865992dab58ccd0fad583a7 Mon Sep 17 00:00:00 2001 From: Daniel Wagner-Hall Date: Mon, 27 Jan 2025 16:05:04 +0000 Subject: [PATCH 25/32] Much of Sally's first round of feedback --- .../tools/first-nodejs-program/index.md | 43 +++++++++++++++---- .../installing-npm-dependencies/index.md | 2 +- .../en/module/tools/reading-a-file/index.md | 2 +- .../tools/single-use-data-analysis/index.md | 10 +++++ .../tools/using-python-dependencies/index.md | 4 +- .../tools/virtual-environments/index.md | 8 +++- 6 files changed, 56 insertions(+), 13 deletions(-) diff --git a/common-content/en/module/tools/first-nodejs-program/index.md b/common-content/en/module/tools/first-nodejs-program/index.md index 16bb91256..e1c3987fb 100644 --- a/common-content/en/module/tools/first-nodejs-program/index.md +++ b/common-content/en/module/tools/first-nodejs-program/index.md @@ -10,13 +10,13 @@ objectives = [ ] +++ -Below we have a small NodeJS program. It is a bit like `wc`. It counts words in a file. Specifically, it counts words which contain a hyphen (`-`) character. +Below we have a small NodeJS program. It is a bit like `wc`. It counts words in a file which contain a hyphen (`-`) character. -It accepts one command line argument - the path of the file to read and count. +Our program accepts one command line argument - the path of the file to read and count. -Its output to stdout is just the number of words which contain a hyphen. +Our program's output to stdout is just the number of words which contain a hyphen. -It uses the same language (JavaScript) as we've written before, but uses some different APIs. +Our program uses the same language (JavaScript) as we've written before, but uses some different APIs. ```js import process from "node:process"; @@ -56,9 +56,9 @@ We're importing another module. The `fs` module is built into NodeJS for interacting with the filesystem. -This time, we're not importing the whole module. We are destructuring. The `node:fs` module exposes an object, and we are saying "import the `promises` property from the `fs` module, and bind it to the name `fs`". +This time, we're not importing the whole module. We are {{}}Destructuring is a form of variable assignment where we give variables values based on where we can find them structurally in another value. Examples:

We can write `const [first, second] = [3, 1];` to assign `first = 3` and `second = 1`.

We can write `const {name, age} = {name: "Amir", age: 34};` to assign `name = "Amir"` and `age = 34`.{{
}}. The `node:fs` module exposes an object, and we are saying "import the `promises` property from the `fs` module, and bind it to the name `fs`". -It's the equivalent to us writing `import { promises } from "node:fs"; const fs = promises;`. +It is like writing `import { promises } from "node:fs"; const fs = promises;`. We are doing this because many of the things in the `fs` module don't support `async`/`await`, but `fs` has a sub-module called `promises` where everything supports `async`/`await`. Because we want to use `async`/`await`, we will use that. But having to write `fs.promises.readFile` is a bit annoying, so instead we import `fs.promises` as if it was just named `fs`. @@ -70,42 +70,69 @@ We're getting the `argv` array from the `process` module, and slicing it. We can Again, `Array.slice` is exactly the same as we know from JavaScript, but `process.argv` is a new API we can use to get the array we need. +Play computer with the rest of the program - read each line, and explain what you think that line does. After you make your predictions, expand the explanations below and compare them to your predictions. + +
+ + ```js if (argv.length != 1) { console.error(`Expected exactly 1 argument (a path) to be passed but got ${argv.length}.`); process.exit(1); } ``` - + We always expect our program to be given exactly one argument. Here we check this using an `if` statement, just like we've seen before. `console.error` writes a message to stderr (which is where error messages should go). -`process.exit` is a function which, when called, will stop our program running. Passing a non-zero number to it indicates that our program did not succeed. +`process.exit` is a function which, when called, will stop our program running. Passing a non-zero number to it indicates that our program did not succeed. We can read more about it in the official NodeJS documentation for the `process` module. + +
+ +
+ ```js const path = argv[0]; ``` + Giving a useful name to our argument. +
+ +
+ ```js const content = await fs.readFile(path, "utf-8"); ``` + Reading the file at the path passed as an argument. We're using the `fs` module here from `node`, but everything else is just JavaScript - declaring a variable, using `await` because `fs.promises.readFile` is an `async` function, calling a function. +
+ +
+ ```js const wordsContainingHyphens = content.split(" ").filter((word) => word.indexOf("-") > -1).length; ``` + Just some regular JavaScript. Taking a string, splitting it into an array, filtering the array, searching strings to see if they contain characters, and getting the length of an array. +
+ +
+ ```js console.log(wordsContainingHyphens); ``` + `console.log` in a NodeJS environment logs to stdout, so this outputs our result to stdout. +
{{}} Save the above program into a file. Run the file with `node`, and count how many words contain hyphens in a few different files. diff --git a/common-content/en/module/tools/installing-npm-dependencies/index.md b/common-content/en/module/tools/installing-npm-dependencies/index.md index dda303475..3abf28dcf 100644 --- a/common-content/en/module/tools/installing-npm-dependencies/index.md +++ b/common-content/en/module/tools/installing-npm-dependencies/index.md @@ -11,7 +11,7 @@ objectives = [ To use a library, we need to fetch the code we're going to use. When using NodeJS, we use a tool called `npm` for this. -First we need a `package.json` file - this a file that `npm` will read to understand your project. +First we need a `package.json` file - this a file that `npm` will read to understand your project. This is the same as the `package.json` file you've seen when using `npm` in the past. Make this `package.json` file in the same directory as your hyphen-counting program: diff --git a/common-content/en/module/tools/reading-a-file/index.md b/common-content/en/module/tools/reading-a-file/index.md index 583a2e4d9..143fd89f5 100644 --- a/common-content/en/module/tools/reading-a-file/index.md +++ b/common-content/en/module/tools/reading-a-file/index.md @@ -24,7 +24,7 @@ with open(args.path, "r") as f: content = f.read() ``` -Comparing these shows some interesting differences, particularly around scope. +Comparing these shows some interesting differences, particularly around {{}}Scope is where a variable can be accessed from.{{}}. ### Scope diff --git a/common-content/en/module/tools/single-use-data-analysis/index.md b/common-content/en/module/tools/single-use-data-analysis/index.md index 9c72c188f..75f03336e 100644 --- a/common-content/en/module/tools/single-use-data-analysis/index.md +++ b/common-content/en/module/tools/single-use-data-analysis/index.md @@ -20,6 +20,16 @@ When we want to answer some question, sometimes it's useful to write a program w It's not always obvious whether it's easier to try to use tools that already exist, or to write our own. +> [!TIP] +> +> This is like in real life! Imagine if you had two differently sized bottles, and wanted to pour all of the liquid from one to the other without spilling any. +> +> You can imagine making the perfect tube that has exactly the right size connector at each end to connect to the bottles. +> +> Or maybe you already have a funnel that's about the right size - not perfect, but close enough, and you can probably use. +> +> But if you got a really wide or really narrow bottle, maybe that funnel wouldn't be good enough and you would need to make a custom solution. + Sometimes the format of our data makes it easier or harder to use existing tools. Let's look at some sample data: diff --git a/common-content/en/module/tools/using-python-dependencies/index.md b/common-content/en/module/tools/using-python-dependencies/index.md index 397ae2ae9..0d180282e 100644 --- a/common-content/en/module/tools/using-python-dependencies/index.md +++ b/common-content/en/module/tools/using-python-dependencies/index.md @@ -29,7 +29,7 @@ In this example, we'll call it `cow.py`. import cowsay ``` -If we try to run `python3 cow.py`, we'll get an error: +Run `python3 cow.py`. It will trigger this error: ``` ModuleNotFoundError: No module named 'cowsay' @@ -54,7 +54,7 @@ When we activate a virtual environment, its name gets shown before our terminal Run `python3 cow.py`. We don't get an error because we installed `cowsay` into our active virtual environment. -Open a new terminal and run `python3 cow.py`. An error will occur because we haven't activated a virtual environment. +Open a new terminal and run `python3 cow.py`. You will get an error again! This is because we haven't activated a virtual environment. Run `. .venv/bin/activate` and then `python3 cow.py`. It will start working again. diff --git a/common-content/en/module/tools/virtual-environments/index.md b/common-content/en/module/tools/virtual-environments/index.md index b5372551a..8dd826da9 100644 --- a/common-content/en/module/tools/virtual-environments/index.md +++ b/common-content/en/module/tools/virtual-environments/index.md @@ -9,7 +9,7 @@ objectives=[ ] +++ -We often need to use libraries in Python. +We often use libraries in Python. Python handles dependencies differently from JavaScript, but it has similarities. @@ -23,6 +23,12 @@ To install the dependencies, we need to make something called a virtual environm First we need to _create_ the virtual environment. We do this by running `python3 -m venv .venv`. This will create a virtual environment in a directory named `.venv`. We could actually create it anywhere, e.g. we could run `python3 -m venv /tmp/python_modules` to create it in a directory named `/tmp/python_modules`. We tend to just use a directory called `.venv` at the root of our project. +> [!NOTE] +> +> This is another example of a convention - you could name your virtual environment anything, but if we all agree to call it `.venv` then we all know what this directory is when we see it. +> +> It also means we can write scripts, or `.gitignore` file entries assuming that's where the virtual environment will be. + Next we need to _activate_ the virtual environment. We do this by running `. .venv/bin/activate` (yes, the command we're running is `.` with a path as an argument - the `.` is important). This will only activate the virtual environment for the terminal window we're in - if you're using more than one terminal window, you'll need to activate it in each of them. Finally we need to install our dependencies into the virtual environment. We do this by running `pip install -r requirements.txt`. This is saying "Please install all of the dependencies listed in `requirements.txt` into the currently active virtual environment". From febb904a9e6f5f982e8d87886df9a376faf8a825 Mon Sep 17 00:00:00 2001 From: Daniel Wagner-Hall Date: Mon, 27 Jan 2025 16:43:15 +0000 Subject: [PATCH 26/32] Most of Ali's review comments --- .../comparing-javascript-and-python/index.md | 9 ++++-- .../module/tools/converted-program/index.md | 6 ++-- .../converting-javascript-to-python/index.md | 6 ++-- .../en/module/tools/counting-words/index.md | 5 +++- .../tools/first-nodejs-program/index.md | 24 ++++++++++------ .../installing-npm-dependencies/index.md | 28 +++++++++++++------ .../en/module/tools/nodejs/index.md | 2 +- .../read-about-operating-systems/index.md | 4 +-- .../tools/using-npm-dependencies/index.md | 6 ++-- 9 files changed, 57 insertions(+), 33 deletions(-) diff --git a/common-content/en/module/tools/comparing-javascript-and-python/index.md b/common-content/en/module/tools/comparing-javascript-and-python/index.md index 5a755585f..aeef8151c 100644 --- a/common-content/en/module/tools/comparing-javascript-and-python/index.md +++ b/common-content/en/module/tools/comparing-javascript-and-python/index.md @@ -28,7 +28,7 @@ import process from "node:process"; program .name("count-containing-words") .description("Counts words in a file that contain a particular character") - .option("-c, --char ", "The character to search for", "-"); + .option("-c, --char ", "The character to search for", "e"); program.parse(); @@ -41,8 +41,11 @@ const path = argv[0]; const char = program.opts().char; const content = await fs.readFile(path, "utf-8"); -const wordsContainingChar = content.split(" ").filter((word) => word.indexOf(char) > -1).length; -console.log(wordsContainingChar); +const countOfWordsContainingChar = content + .split(" ") + .filter((word) => word.includes(char)) + .length; +console.log(countOfWordsContainingChar); ``` {{}} diff --git a/common-content/en/module/tools/converted-program/index.md b/common-content/en/module/tools/converted-program/index.md index 01f0a99fe..187229668 100644 --- a/common-content/en/module/tools/converted-program/index.md +++ b/common-content/en/module/tools/converted-program/index.md @@ -19,15 +19,15 @@ parser = argparse.ArgumentParser( description="Counts words in a file that contain a particular character", ) -parser.add_argument("-c", "--char", help="The character to search for", default="-") +parser.add_argument("-c", "--char", help="The character to search for", default="e") parser.add_argument("path", help="The file to search") args = parser.parse_args() with open(args.path, "r") as f: content = f.read() -words_containing_char = len(filter(lambda word: args.char in word, content.split(" "))) -print(words_containing_char) +count_of_words_containing_char = len([word for word in content.split(" ") if args.char in word]) +print(count_of_words_containing_char) ``` This looks similar to the JavaScript version. The shape is the same, but every line is a little bit different. diff --git a/common-content/en/module/tools/converting-javascript-to-python/index.md b/common-content/en/module/tools/converting-javascript-to-python/index.md index 3ec141446..554e87f75 100644 --- a/common-content/en/module/tools/converting-javascript-to-python/index.md +++ b/common-content/en/module/tools/converting-javascript-to-python/index.md @@ -19,7 +19,7 @@ import { program } from "commander"; program .name("count-containing-words") .description("Counts words in a file that contain a particular character") - .option("-c, --char ", "The character to search for", "-"); + .option("-c, --char ", "The character to search for", "e"); program.parse(); @@ -31,7 +31,7 @@ const char = program.opts().char; Which of the following are _essential_ goals in this code, and which are _accidental_ goals? {{}} -[LABEL=Essential] Allow a user to pass a `-c` argument (defaulting to `-` if they don't). +[LABEL=Essential] Allow a user to pass a `-c` argument (defaulting to `e` if they don't). [LABEL=Accidental] Made a `const` variable called `argv`. [LABEL=Accidental] Import `program` from the `commander` library. [LABEL=Essential] Allow a user to pass a path as a positional argument. @@ -53,7 +53,7 @@ parser = argparse.ArgumentParser( description="Counts words in a file that contain a particular character", ) -parser.add_argument("-c", "--char", help="The character to search for", default="-") +parser.add_argument("-c", "--char", help="The character to search for", default="e") parser.add_argument("path", help="The file to search") args = parser.parse_args() diff --git a/common-content/en/module/tools/counting-words/index.md b/common-content/en/module/tools/counting-words/index.md index 1fc8117a7..7480955e8 100644 --- a/common-content/en/module/tools/counting-words/index.md +++ b/common-content/en/module/tools/counting-words/index.md @@ -12,7 +12,10 @@ objectives = [ In JavaScript we wrote: ```js -content.split(" ").filter((word) => word.indexOf(char) > -1).length +content + .split(" ") + .filter((word) => word.includes(char)) + .length ``` What JavaScript calls arrays, Python calls lists. Arrays and lists are basically the same. diff --git a/common-content/en/module/tools/first-nodejs-program/index.md b/common-content/en/module/tools/first-nodejs-program/index.md index e1c3987fb..ed14a7eb5 100644 --- a/common-content/en/module/tools/first-nodejs-program/index.md +++ b/common-content/en/module/tools/first-nodejs-program/index.md @@ -10,11 +10,11 @@ objectives = [ ] +++ -Below we have a small NodeJS program. It is a bit like `wc`. It counts words in a file which contain a hyphen (`-`) character. +Below we have a small NodeJS program. It is a bit like `wc`. It counts words in a file which contain the letter `e`. Our program accepts one command line argument - the path of the file to read and count. -Our program's output to stdout is just the number of words which contain a hyphen. +Our program's output to stdout is just the number of words which contain an e. Our program uses the same language (JavaScript) as we've written before, but uses some different APIs. @@ -30,8 +30,11 @@ if (argv.length != 1) { const path = argv[0]; const content = await fs.readFile(path, "utf-8"); -const wordsContainingHyphens = content.split(" ").filter((word) => word.indexOf("-") > -1).length; -console.log(wordsContainingHyphens); +const countOfWordsContainingEs = content + .split(" ") + .filter((word) => word.includes("e")) + .length; +console.log(countOfWordsContainingEs); ``` Let's play computer with this program - line by line: @@ -116,18 +119,21 @@ Reading the file at the path passed as an argument. We're using the `fs` module ```js -const wordsContainingHyphens = content.split(" ").filter((word) => word.indexOf("-") > -1).length; +const countOfWordsContainingEs = content + .split(" ") + .filter((word) => word.includes("e")) + .length; ``` -Just some regular JavaScript. Taking a string, splitting it into an array, filtering the array, searching strings to see if they contain characters, and getting the length of an array. +Just some regular JavaScript. Taking a string, splitting it into an array, filtering the array, searching strings to see if they contain any e characters, and getting the length of an array.
```js -console.log(wordsContainingHyphens); +console.log(countOfWordsContainingEs); ``` @@ -135,7 +141,7 @@ console.log(wordsContainingHyphens);
{{}} -Save the above program into a file. Run the file with `node`, and count how many words contain hyphens in a few different files. +Save the above program into a file. Run the file with `node`, and count how many words contain "e"s in a few different files. If you run into problems, ask for help. -{{}} \ No newline at end of file +{{
}} diff --git a/common-content/en/module/tools/installing-npm-dependencies/index.md b/common-content/en/module/tools/installing-npm-dependencies/index.md index 3abf28dcf..05400c290 100644 --- a/common-content/en/module/tools/installing-npm-dependencies/index.md +++ b/common-content/en/module/tools/installing-npm-dependencies/index.md @@ -13,7 +13,7 @@ To use a library, we need to fetch the code we're going to use. When using NodeJ First we need a `package.json` file - this a file that `npm` will read to understand your project. This is the same as the `package.json` file you've seen when using `npm` in the past. -Make this `package.json` file in the same directory as your hyphen-counting program: +Make this `package.json` file in the same directory as your e-word-counting program: ```json { @@ -47,7 +47,7 @@ import process from "node:process"; program .name("count-containing-words") .description("Counts words in a file that contain a particular character") - .option("-c, --char ", "The character to search for", "-"); + .option("-c, --char ", "The character to search for", "e"); program.parse(); @@ -60,8 +60,11 @@ const path = argv[0]; const char = program.opts().char; const content = await fs.readFile(path, "utf-8"); -const wordsContainingChar = content.split(" ").filter((word) => word.indexOf(char) > -1).length; -console.log(wordsContainingChar); +const countOfWordsContainingChar = content + .split(" ") + .filter((word) => word.includes(char)) + .length; +console.log(countOfWordsContainingChar); ``` {{}} @@ -84,7 +87,7 @@ Let's run through what we changed: program .name("count-containing-words") .description("Counts words in a file that contain a particular character") - .option("-c, --char ", "The character to search for", "-"); + .option("-c, --char ", "The character to search for", "e"); ``` We told `commander` information about our program. We gave it a name, a description, and told it that it should allow a user to pass a flag name `-c` (or equivalently `--char`), and use a default value of `-` for that flag if it's not specified. @@ -110,11 +113,14 @@ const char = program.opts().char; We are getting the `char` flag that `commander` interpreted and storing it in a variable. ```js -const wordsContainingChar = content.split(" ").filter((word) => word.indexOf(char) > -1).length; -console.log(wordsContainingChar); +const countOfWordsContainingChar = content + .split(" ") + .filter((word) => word.includes(char)) + .length; +console.log(countOfWordsContainingChar); ``` -We have renamed our `wordsContainingHyphens` variable to `wordsContainingChar` because we're no longer always looking for hyphens, and changed the `indexOf` call to look for the value of the `char` variable instead of always a `-`. +We have renamed our `countOfWordsContainingEs` variable to `countOfWordsContainingChar` because we're no longer always looking for hyphens, and changed the `includes` call to look for the value of the `char` variable instead of always an `e`. We only needed to make a few small changes to get all of this new functionality: * Support for accepting a new command line flag. @@ -122,3 +128,9 @@ We only needed to make a few small changes to get all of this new functionality: * Detection for if someone passes flags that aren't known, and warning them about this (and even suggesting what they maybe meant). We could have written all of this code ourselves. But using a library meant we could focus on what's unique about our problem, rather than spending time implementing flag parsing. + +This is a very common task in software development in the real world, joining together libraries (written by other people) to create some new unique solution. + +> [!NOTE] +> +> We also could have used [the builtin `util.parseArgs` function from NodeJS](https://nodejs.org/api/util.html#utilparseargsconfig) for most of this functionality, but it doesn't support `--help` like `commander` does. diff --git a/common-content/en/module/tools/nodejs/index.md b/common-content/en/module/tools/nodejs/index.md index 0c5cb1422..8c8be0425 100644 --- a/common-content/en/module/tools/nodejs/index.md +++ b/common-content/en/module/tools/nodejs/index.md @@ -17,7 +17,7 @@ NodeJS is another runtime environment for running JavaScript. It allows us to ru There are some similarities and differences between how NodeJS runs JavaScript, and how web browsers run JavaScript. For instance: * Both support the same core language (e.g. defining variables, if statements, for loops, etc). -* Web browsers expose extra APIs that can be used from JavaScript, e.g. the DOM. +* Web browsers {{}}To expose an API means to provide functions or values to the programmer. Sometimes we expose these over the internet, using HTTP+JSON. Other times we expose them directly as symbols you can import into your program.{{}} extra APIs that can be used from JavaScript, e.g. the DOM. * NodeJS exposes extra APIs that can be used from JavaScript, e.g. reading and writing files in the filesystem. * Some APIs are implemented differently, e.g. if you call `console.log` in a web browser it will log to the web inspector console (hidden by default), whereas in NodeJS it will log to stdout (the default output of a program). diff --git a/common-content/en/module/tools/read-about-operating-systems/index.md b/common-content/en/module/tools/read-about-operating-systems/index.md index e81dc54c6..11a938371 100644 --- a/common-content/en/module/tools/read-about-operating-systems/index.md +++ b/common-content/en/module/tools/read-about-operating-systems/index.md @@ -27,13 +27,13 @@ Check you have achieved each learning objective listed on this page. {{}} Do project 23 from How Computers Really Work. -You can do this on any Unix OS - you do not need a Raspberry Pi. +You can do this on any {{}}Linux and macOS are both Unix-family operating systems. Windows is not.{{}} - you do not need a Raspberry Pi. {{}} {{}} Do project 20 from How Computers Really Work. -You can do this on any Unix OS - you don't need a Raspberry Pi. +You can do this on any Unix-family OS - you don't need a Raspberry Pi. Note: If you're on macOS, `ps -eH` doesn't exist. You can use `ps` or `ps aux` to get a list of processes. To get parent-child relationships, you'll need to install `pstree` using `brew` (`brew install pstree`), then run `pstree`. diff --git a/common-content/en/module/tools/using-npm-dependencies/index.md b/common-content/en/module/tools/using-npm-dependencies/index.md index d41e5093b..9349f5c9d 100644 --- a/common-content/en/module/tools/using-npm-dependencies/index.md +++ b/common-content/en/module/tools/using-npm-dependencies/index.md @@ -15,7 +15,7 @@ We can also use code that other people have written, which isn't built into Node This can be really useful - it means we can benefit from work others have already done, and focus on just solving the part of a problem which is unique to us. It's like making shell pipelines - instead of having to solve every problem from scratch, we can plug together different tools that other people have already made. -Let's expand the functionality of our program. Rather than always searching for words containing hyphens, let's allow the user to specify what character they're searching for. +Let's expand the functionality of our program. Rather than always searching for words containing the letter e, let's allow the user to specify what character they're searching for. This means we want to introduce a flag. And programs that accept flags, should also document themselves. One common convention is that if you run a program with the flag `--help`, it will tell you how to use it. @@ -24,9 +24,9 @@ But writing all of this code to parse flags, to output information about the fla So let's use a {{}}A library is a collection of code that we can use, but which isn't part of our project.{{}} for this. We will use a library called `commander`. {{}} -Add `import { program } from "commander";` to the top of your hyphen-counting program. +Add `import { program } from "commander";` to the top of your e-word-counting program. This line imports the `program` property from the object which is the `commander` library (using object destructuring). -Try running you program. What happens? What does the output mean? +Try running your program. What happens? What does the output mean? {{}} From 0baae4bdf93c3e4cb843bdef9c43271b89b0a545 Mon Sep 17 00:00:00 2001 From: Daniel Wagner-Hall Date: Thu, 30 Jan 2025 11:34:40 +0000 Subject: [PATCH 27/32] Clarify the fs.promises import --- .../en/module/tools/first-nodejs-program/index.md | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/common-content/en/module/tools/first-nodejs-program/index.md b/common-content/en/module/tools/first-nodejs-program/index.md index ed14a7eb5..a5390b618 100644 --- a/common-content/en/module/tools/first-nodejs-program/index.md +++ b/common-content/en/module/tools/first-nodejs-program/index.md @@ -63,7 +63,13 @@ This time, we're not importing the whole module. We are {{ Date: Thu, 30 Jan 2025 11:37:05 +0000 Subject: [PATCH 28/32] Add prompt to think about why fs is async --- .../en/module/tools/first-nodejs-program/index.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/common-content/en/module/tools/first-nodejs-program/index.md b/common-content/en/module/tools/first-nodejs-program/index.md index a5390b618..a2cc4bae3 100644 --- a/common-content/en/module/tools/first-nodejs-program/index.md +++ b/common-content/en/module/tools/first-nodejs-program/index.md @@ -71,6 +71,14 @@ We want to use the `promises` submodule, because it's much more convenient for u We are really doing this because we wish the `async`/`await` APIs were the default APIs exposed by the `fs` module, and this lets us pretend that they are in the rest of our code. +{{}} +`fs` uses callbacks or promises because its operations are asynchronous. + +Why would interacting with the filesystem (e.g. reading a file) be an asynchronous operation? + +Explain on a Slack thread why you think this is. If you're not sure, ask about it on Slack. +{{}} + ```js const argv = process.argv.slice(2); ``` From bc3246f793ce4104808c600c1a76554f81f37b92 Mon Sep 17 00:00:00 2001 From: Daniel Wagner-Hall Date: Thu, 30 Jan 2025 11:39:16 +0000 Subject: [PATCH 29/32] Add link to docs --- common-content/en/module/tools/first-nodejs-program/index.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/common-content/en/module/tools/first-nodejs-program/index.md b/common-content/en/module/tools/first-nodejs-program/index.md index a2cc4bae3..236eeff5e 100644 --- a/common-content/en/module/tools/first-nodejs-program/index.md +++ b/common-content/en/module/tools/first-nodejs-program/index.md @@ -127,6 +127,8 @@ const content = await fs.readFile(path, "utf-8"); Reading the file at the path passed as an argument. We're using the `fs` module here from `node`, but everything else is just JavaScript - declaring a variable, using `await` because `fs.promises.readFile` is an `async` function, calling a function. + +You can read more about this in [the documentation for `fs.promises.readFile`](https://nodejs.org/api/fs.html#fspromisesreadfilepath-options).
From 3dfc99eb17c055d8665abed0bd243cee219f7745 Mon Sep 17 00:00:00 2001 From: Daniel Wagner-Hall Date: Thu, 30 Jan 2025 11:49:44 +0000 Subject: [PATCH 30/32] Compare virtual environments to node_modules --- .../module/tools/virtual-environments/index.md | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/common-content/en/module/tools/virtual-environments/index.md b/common-content/en/module/tools/virtual-environments/index.md index 8dd826da9..3e82d54ff 100644 --- a/common-content/en/module/tools/virtual-environments/index.md +++ b/common-content/en/module/tools/virtual-environments/index.md @@ -11,7 +11,7 @@ objectives=[ We often use libraries in Python. -Python handles dependencies differently from JavaScript, but it has similarities. +Python handles dependencies differently from JavaScript, but they have similarities. We've seen that in JavaScript we write down what dependencies we need in a `package.json` file, and when we run `npm install` they will get fetched into a folder called `node_modules`. @@ -21,6 +21,22 @@ In Python, we write down what dependencies we need in a file called `requirement To install the dependencies, we need to make something called a virtual environment, where they will get installed to. +{{}} +A virtual environment is like a `node_modules` folder - it contains all of the dependencies that are installed into it. + +When we run `node`, `node` _automatically_ looks for a `node_modules` folder to find dependencies in. + +When we run `python3`, `python3` _doesn't_ automatically look for a virtual environment. We need to _activate_ it - tell `python3` which virtual environment we want to use. + +There are trade-offs here: +* `node` uses a _convention_ to locate installed dependencies - we don't need to _do_ anything except put the folder in the right place, and it will automatically get used. +* `python3` uses _configuration_ to locate installed dependencies - we need to _configure_ which virtual environment it should use by activating it. + +One of the benefits of using configuration is that we could have different virtual environments with different versions of the same dependencies (e.g. to test a version upgrade), and we can switch between them. + +One of the drawbacks of using configuration is that we need to do the configuration - we need to explicitly activate the virtual environment for it to be used. +{{}} + First we need to _create_ the virtual environment. We do this by running `python3 -m venv .venv`. This will create a virtual environment in a directory named `.venv`. We could actually create it anywhere, e.g. we could run `python3 -m venv /tmp/python_modules` to create it in a directory named `/tmp/python_modules`. We tend to just use a directory called `.venv` at the root of our project. > [!NOTE] From 70abc833b6813431462294f8dd0c244d965b9ad7 Mon Sep 17 00:00:00 2001 From: Daniel Wagner-Hall Date: Thu, 30 Jan 2025 12:06:05 +0000 Subject: [PATCH 31/32] Convert filter+lambda to an exploration exercise --- .../en/module/tools/counting-words/index.md | 29 ++++++++----------- 1 file changed, 12 insertions(+), 17 deletions(-) diff --git a/common-content/en/module/tools/counting-words/index.md b/common-content/en/module/tools/counting-words/index.md index 7480955e8..e542320ff 100644 --- a/common-content/en/module/tools/counting-words/index.md +++ b/common-content/en/module/tools/counting-words/index.md @@ -1,7 +1,7 @@ +++ title = "Counting words containing a character" headless = true -time = 15 +time = 45 facilitation = false emoji= "📖" hide_from_overview = true @@ -22,28 +22,23 @@ What JavaScript calls arrays, Python calls lists. Arrays and lists are basically Googling for "Python filter list" suggests there are two things we can use - a `filter` function, or something called a "list comprehension". Some people prefer one, other people prefer the other. -Using filter (`lambda` is a keyword for making an anonymous function in Python): +Let's try out both approaches. We can do this in a standalone program, rather than in the whole word-counting program. This gives us a lot more control, and makes it easier for us to experiment. -```python -filter(lambda word: args.char in word, content.split(" ")) -``` - -Using a list comprehension: +{{}} +Create a new file, `filter.py`. Start it with: ```python -[word for word in content.split(" ") if args.char in word] -``` +content = "this is a list of words" +char = "i" -Then we need to get the length of the produced list. Googling "python length of list" tells us we wrap our list in a call to `len()`, giving: +filtered = TODO -```python -len([word for word in content.split(" ") if args.char in word]) +print(filtered) ``` -or +Now fill in the TODO. First, use a list comprehension. Run the file and make sure you get the expected output. -```python -len(filter(lambda word: args.char in word, content.split(" "))) -``` +Next, replace your list comprehension with some code that calls the global function `filter`. (`filter` takes a function, and it may be useful to know that `lambda` is a keyword for making an anonymous function in Python, similar to arrow functions in JavaScript). Run the file and make sure you get the expected output. +{{}} -The list comprehension version of this works. The `filter` version gives an error. We can try to understand and fix the error, or just use the list comprehension version. +Now that we've learnt how to do the filtering, we can apply what we've learnt to the program we're converting. From 4b8ddf7d03f176e00c0ee1a708b4a32c65698b9a Mon Sep 17 00:00:00 2001 From: Daniel Wagner-Hall Date: Mon, 3 Feb 2025 11:01:46 +0000 Subject: [PATCH 32/32] Process tooltip --- common-content/en/module/tools/first-nodejs-program/index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common-content/en/module/tools/first-nodejs-program/index.md b/common-content/en/module/tools/first-nodejs-program/index.md index 236eeff5e..1e1a4a544 100644 --- a/common-content/en/module/tools/first-nodejs-program/index.md +++ b/common-content/en/module/tools/first-nodejs-program/index.md @@ -49,7 +49,7 @@ We've seen `import` before. Here, instead of importing from a file we've written This is an example of the same language features (`import`) being used slightly differently (the `"node:"` is a special prefix to say "specially from node"). -The `process` module is built into NodeJS for managing our process. We can use it to find out what arguments were passed to the process when it started, find out what user ran the process, exit the process, and more. +The `process` module is built into NodeJS for managing our {{}}The running instance of our program: the code, state, memory, and system resources.{{}}. We can use it to find out what arguments were passed to the process when it started, find out what user ran the process, exit the process, and more. ```js import { promises as fs } from "node:fs";