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 948dd3bb3..000000000 --- a/common-content/en/module/tools/convert-script-between-languages/index.md +++ /dev/null @@ -1,12 +0,0 @@ -+++ -title = "Convert a script between languages" -headless = true -time = 30 -facilitation = false -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 8a3e97e20..000000000 --- a/common-content/en/module/tools/implement-single-use-data-analysis/index.md +++ /dev/null @@ -1,13 +0,0 @@ -+++ -title = "Implement a single-use data analysis program" -headless = true -time = 30 -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" -+++ - -### 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 91d33e92d..000000000 --- a/common-content/en/module/tools/implement-tools-in-nodejs/index.md +++ /dev/null @@ -1,13 +0,0 @@ -+++ -title = "Implement tools in NodeJS" -headless = true -time = 30 -facilitation = false -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 660266a8e..000000000 --- a/common-content/en/module/tools/implement-tools-in-python/index.md +++ /dev/null @@ -1,13 +0,0 @@ -+++ -title = "Implement tools in Python" -headless = true -time = 30 -facilitation = false -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 9f4f30814..000000000 --- a/common-content/en/module/tools/learn-nodejs/index.md +++ /dev/null @@ -1,13 +0,0 @@ -+++ -title = "Learn NodeJS" -headless = true -time = 30 -facilitation = false -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 e7fe336a3..000000000 --- a/common-content/en/module/tools/learn-python/index.md +++ /dev/null @@ -1,13 +0,0 @@ -+++ -title = "Learn Python" -headless = true -time = 30 -facilitation = false -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 ad06048c6..000000000 --- a/common-content/en/module/tools/read-about-operating-systems-1/index.md +++ /dev/null @@ -1,11 +0,0 @@ -+++ -title = "Read about operating systems" -headless = true -time = 30 -facilitation = false -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 e8d718a7c..000000000 --- a/common-content/en/module/tools/read-about-operating-systems-2/index.md +++ /dev/null @@ -1,11 +0,0 @@ -+++ -title = "Read about operating systems (2)" -headless = true -time = 30 -facilitation = false -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" +++