├── public ├── .nojekyll └── images │ ├── YT.png │ ├── pust.png │ ├── vim.png │ ├── TWITCH.png │ ├── author.jpg │ ├── santa.png │ ├── dev-prod.png │ ├── course-icon.png │ ├── BRAND-WHearts.png │ ├── mut-ref-error.png │ └── social-share-cover.jpg ├── test ├── lessons ├── 02-part-1-getting-familiar │ ├── meta.json │ ├── F-project-structure.md │ ├── C-problem-2.md │ ├── D-problem-3.md │ ├── B-problem-1.md │ ├── E-probably-should-be-A-language-gotchas.md │ └── A-ts-go-rust-fundamentals.md ├── 04-cli │ ├── meta.json │ ├── D-discuss.md │ ├── C-rust-cli.md │ ├── B-golang-cli.md │ └── A-typescript-cli.md ├── 09-outro │ ├── meta.json │ └── A-that-is-it.md ├── 05-config │ ├── meta.json │ ├── D-questions.md │ ├── B-golang-projector-object.md │ ├── A-typescript-projector-object.md │ └── C-rust-projector-object.md ├── 07-projector │ ├── meta.json │ ├── D-discussion-time.md │ ├── B-golang-projector.md │ ├── A-typescript-projector.md │ └── C-rust-project.md ├── 03-the-project │ ├── meta.json │ ├── B-questions.md │ └── A-what-are-we-building.md ├── 08-tying-it-together │ ├── meta.json │ ├── C-rust-4-real.md │ ├── B-gopher-time.md │ └── A-typescript.md ├── 06-test-it │ ├── D-thoughts.md │ ├── C-rust-testing.md │ ├── B-golang-testing.md │ └── A-typescript-testing.md └── 01-introduction │ ├── A-intro.md │ └── B-getting-started.md ├── test.js ├── context ├── headerContext.js └── courseInfoContext.js ├── test.cpp ├── pages ├── _app.js ├── lessons │ └── [section] │ │ └── [slug].js └── index.js ├── components ├── twitch.js ├── header.js ├── layout.js ├── footer.js ├── twitter.js ├── corner.js └── github.js ├── next.config.js ├── .gitignore ├── styles ├── variables.css ├── footer.css └── courses.css ├── package.json ├── .github └── workflows │ └── next.yaml ├── course.json ├── data ├── course.js └── lesson.js └── README.md /public/.nojekyll: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ThePrimeagen/ts-go-rust/HEAD/test -------------------------------------------------------------------------------- /lessons/02-part-1-getting-familiar/meta.json: -------------------------------------------------------------------------------- 1 | { 2 | "icon": "dumpster-fire" 3 | } 4 | -------------------------------------------------------------------------------- /public/images/YT.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ThePrimeagen/ts-go-rust/HEAD/public/images/YT.png -------------------------------------------------------------------------------- /public/images/pust.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ThePrimeagen/ts-go-rust/HEAD/public/images/pust.png -------------------------------------------------------------------------------- /public/images/vim.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ThePrimeagen/ts-go-rust/HEAD/public/images/vim.png -------------------------------------------------------------------------------- /lessons/04-cli/meta.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "The CLI Options", 3 | "icon": "dumpster-fire" 4 | } 5 | -------------------------------------------------------------------------------- /lessons/09-outro/meta.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "Outro", 3 | "icon": "dumpster-fire" 4 | } 5 | 6 | 7 | -------------------------------------------------------------------------------- /public/images/TWITCH.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ThePrimeagen/ts-go-rust/HEAD/public/images/TWITCH.png -------------------------------------------------------------------------------- /public/images/author.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ThePrimeagen/ts-go-rust/HEAD/public/images/author.jpg -------------------------------------------------------------------------------- /public/images/santa.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ThePrimeagen/ts-go-rust/HEAD/public/images/santa.png -------------------------------------------------------------------------------- /public/images/dev-prod.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ThePrimeagen/ts-go-rust/HEAD/public/images/dev-prod.png -------------------------------------------------------------------------------- /lessons/05-config/meta.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "The Projector object", 3 | "icon": "dumpster-fire" 4 | } 5 | 6 | -------------------------------------------------------------------------------- /public/images/course-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ThePrimeagen/ts-go-rust/HEAD/public/images/course-icon.png -------------------------------------------------------------------------------- /lessons/07-projector/meta.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "The Projector object", 3 | "icon": "dumpster-fire" 4 | } 5 | 6 | -------------------------------------------------------------------------------- /public/images/BRAND-WHearts.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ThePrimeagen/ts-go-rust/HEAD/public/images/BRAND-WHearts.png -------------------------------------------------------------------------------- /public/images/mut-ref-error.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ThePrimeagen/ts-go-rust/HEAD/public/images/mut-ref-error.png -------------------------------------------------------------------------------- /lessons/03-the-project/meta.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "The Project called Projector", 3 | "icon": "dumpster-fire" 4 | } 5 | -------------------------------------------------------------------------------- /lessons/08-tying-it-together/meta.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "Tying it all together", 3 | "icon": "dumpster-fire" 4 | } 5 | 6 | -------------------------------------------------------------------------------- /public/images/social-share-cover.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ThePrimeagen/ts-go-rust/HEAD/public/images/social-share-cover.jpg -------------------------------------------------------------------------------- /test.js: -------------------------------------------------------------------------------- 1 | 2 | const a = []; 3 | const b = a; 4 | 5 | b.push(6); 6 | a.push(9); 7 | 8 | console.log(a.join('')); 9 | 10 | -------------------------------------------------------------------------------- /context/headerContext.js: -------------------------------------------------------------------------------- 1 | import { createContext } from "react"; 2 | 3 | const headerContext = createContext([{}, () => {}]); 4 | 5 | export const Provider = headerContext.Provider; 6 | export const Consumer = headerContext.Consumer; 7 | export const Context = headerContext; 8 | -------------------------------------------------------------------------------- /context/courseInfoContext.js: -------------------------------------------------------------------------------- 1 | import { createContext } from "react"; 2 | 3 | const courseInfoContext = createContext([{}, () => {}]); 4 | 5 | export const Provider = courseInfoContext.Provider; 6 | export const Consumer = courseInfoContext.Consumer; 7 | export const Context = courseInfoContext; 8 | -------------------------------------------------------------------------------- /lessons/03-the-project/B-questions.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Questions?" 3 | description: "Lets get these out of the way because we want to go fast." 4 | --- 5 | 6 | ### Questions? 7 | 8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 | 25 | 26 | -------------------------------------------------------------------------------- /test.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | int print(std::vector &a, const char* name) { 5 | printf("%s: ", name); 6 | for (const auto& it : a) { 7 | printf("%d", it); 8 | } 9 | printf("\n"); 10 | } 11 | 12 | int main() { 13 | std::vector a; 14 | std::vector* b = &a; 15 | b->push_back(5); 16 | 17 | print(a, "a"); 18 | print(*b, "b"); 19 | } 20 | -------------------------------------------------------------------------------- /lessons/06-test-it/D-thoughts.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "How did you feel about testing?" 3 | description: "Lets test this bad boi" 4 | --- 5 | 6 | ### Enough about my thoughts, what are yours? 7 | How did you feel about testing? 8 | 9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 | 26 | -------------------------------------------------------------------------------- /pages/_app.js: -------------------------------------------------------------------------------- 1 | import "@fortawesome/fontawesome-free/js/all.js"; 2 | 3 | import "highlight.js/styles/a11y-light.css"; 4 | import "../styles/variables.css"; 5 | import "../styles/footer.css"; 6 | import "../styles/courses.css"; 7 | 8 | import Layout from "../components/layout"; 9 | 10 | // TODO favicons 11 | 12 | export default function App({ Component, pageProps }) { 13 | return ( 14 | 15 | 16 | 17 | ); 18 | } 19 | -------------------------------------------------------------------------------- /lessons/05-config/D-questions.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Questions?" 3 | description: "it will probably be about rustlang" 4 | --- 5 | 6 | ### Questions? 7 | * Ask them, because if you have one, someone else does. 8 | * If you are in chat, i'll try to keep an eye on the chat, so ask them too! 9 | 10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 | 27 | 28 | -------------------------------------------------------------------------------- /components/twitch.js: -------------------------------------------------------------------------------- 1 | export default function Twitch() { 2 | return ( 3 | 4 | 5 | 6 | ); 7 | } 8 | -------------------------------------------------------------------------------- /next.config.js: -------------------------------------------------------------------------------- 1 | const course = require("./course.json"); 2 | const BASE_URL = course?.productionBaseUrl || ""; 3 | 4 | module.exports = { 5 | basePath: BASE_URL, 6 | env: { 7 | ROOT: __dirname, 8 | BASE_URL, 9 | }, 10 | async redirects() { 11 | if (BASE_URL) { 12 | return [ 13 | { 14 | source: "/", 15 | destination: BASE_URL, 16 | basePath: false, 17 | permanent: false, 18 | }, 19 | ]; 20 | } 21 | return []; 22 | }, 23 | }; 24 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | .ccls-cache/ 4 | 5 | # dependencies 6 | /node_modules 7 | /.pnp 8 | .pnp.js 9 | 10 | # testing 11 | /coverage 12 | 13 | # next.js 14 | /.next/ 15 | /out/ 16 | 17 | # production 18 | /build 19 | 20 | # misc 21 | .DS_Store 22 | 23 | # debug 24 | npm-debug.log* 25 | yarn-debug.log* 26 | yarn-error.log* 27 | 28 | # local env files 29 | .env.local 30 | .env.development.local 31 | .env.test.local 32 | .env.production.local 33 | -------------------------------------------------------------------------------- /styles/variables.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --primary: #dfa067; 3 | --secondary: #93aca7; 4 | --highlight: #224159; 5 | 6 | --text-header: var(--primary); 7 | --text-main-headers: var(--highlight); 8 | --text-links: #007bff; 9 | --text-footer: #333; 10 | 11 | --bg-main: white; 12 | --bg-dots: var(--highlight); 13 | --bg-lesson: white; 14 | 15 | --nav-buttons: var(--highlight); 16 | --nav-buttons-text: white; 17 | 18 | --corner-active: var(--highlight); 19 | --corner-inactive: #f4f4f4; 20 | --icons: var(--highlight); 21 | 22 | --emphasized-bg: #dce8ff; 23 | --emphasized-border: #aab6d2; 24 | } 25 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "license": "(CC-BY-NC-4.0 OR Apache-2.0)", 4 | "author": "Brian Holt ", 5 | "scripts": { 6 | "dev": "next dev", 7 | "build": "next build", 8 | "export": "next build && next export", 9 | "start": "next start" 10 | }, 11 | "dependencies": { 12 | "@fortawesome/fontawesome-free": "^6.1.1", 13 | "gray-matter": "^4.0.3", 14 | "highlight.js": "^11.4.0", 15 | "marked": "^4.0.9", 16 | "next": "^12.0.7", 17 | "react": "^18.0.0", 18 | "react-dom": "^18.0.0", 19 | "title-case": "^3.0.3" 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /.github/workflows/next.yaml: -------------------------------------------------------------------------------- 1 | name: Deploy NextJS Course Site to GitHub Pages 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | 8 | jobs: 9 | deploy: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: actions/checkout@master 13 | - name: npm install, export 14 | run: | 15 | npm install 16 | npm run export 17 | - name: Deploy site to gh-pages branch 18 | uses: crazy-max/ghaction-github-pages@v2 19 | with: 20 | target_branch: gh-pages 21 | build_dir: out 22 | env: 23 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 24 | -------------------------------------------------------------------------------- /lessons/04-cli/D-discuss.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Discussion on CLI Parsing" 3 | description: "How did we feel about CLI parsing in Rust, Go, and TypeScript" 4 | --- 5 | 6 | ### What are your thoughts? 7 | Do you think any of them will create challenges later on? 8 | 9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 | 26 | ### Which one are you most excited about? 27 | Rust, TypeScript, or Go? 28 | 29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 | -------------------------------------------------------------------------------- /course.json: -------------------------------------------------------------------------------- 1 | { 2 | "author": { 3 | "name": "ThePrimeagen", 4 | "company": "Netflix" 5 | }, 6 | "title": "Polyglot Programming - TS, Go, and Rust", 7 | "subtitle": "The course that teaches you new languages with languages you know.", 8 | "frontendMastersLink": "https://frontendmasters.com/courses/typescript-go-rust", 9 | "social": { 10 | "twitter": "theprimeagen", 11 | "twitch": "theprimeagen", 12 | "youtube": "theprimeagen" 13 | }, 14 | "description": "This course is designed for those that want to expand their knowledge to other languages by building a tool within the new language, and not reading yet another language tutorial. This will be a fast paced, high octain learning experience.", 15 | "keywords": ["ThePrimeagen", "Live Coding", "TypeScript", "JavaScript", "Golang", "RustLang", "Go", "Rust", "TS", "JS"], 16 | "productionBaseUrl": "/ts-go-rust" 17 | } 18 | -------------------------------------------------------------------------------- /styles/footer.css: -------------------------------------------------------------------------------- 1 | .footer { 2 | width: 100%; 3 | padding: 50px 15px; 4 | background-color: var(--primary); 5 | display: flex; 6 | align-items: center; 7 | justify-content: center; 8 | color: var(--text-footer); 9 | } 10 | 11 | .socials { 12 | display: flex; 13 | align-items: center; 14 | max-width: 900px; 15 | width: 100%; 16 | margin: 0; 17 | padding: 0; 18 | } 19 | 20 | .social { 21 | display: inline-block; 22 | list-style: none; 23 | margin-right: 40px; 24 | } 25 | 26 | .social img:hover { 27 | opacity: 0.4; 28 | } 29 | 30 | .social img { 31 | transition: opacity 0.25s; 32 | width: 30px; 33 | } 34 | 35 | .terms { 36 | font-size: 10px; 37 | } 38 | 39 | .terms p { 40 | margin: 3px; 41 | } 42 | 43 | .footer a { 44 | color: inherit; 45 | text-decoration: underline; 46 | } 47 | 48 | .social svg { 49 | transition: opacity 0.25s; 50 | } 51 | 52 | .social svg:hover { 53 | opacity: 0.4; 54 | } 55 | -------------------------------------------------------------------------------- /lessons/07-projector/D-discussion-time.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "The discussion of the universe" 3 | description: "What are your thoughts on Rust, Go, or TypeScript?" 4 | --- 5 | 6 | ### Questions for you! 7 | * If you have only done TypeScript, what do you think about GoLang? 8 | * The simplicity + speed + ease of use seems neet 9 | * What about error handling? 10 | 11 | * If you have only done TypeScript, what do you think about Rust? 12 | * Powerlevel well over 9000 13 | 14 | ```rust 15 | #[derive(Debug)] // this is awesome 16 | struct Foo { 17 | } 18 | ``` 19 | 20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 | 37 | ### Questions for me? 38 | 39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 | 56 | -------------------------------------------------------------------------------- /components/header.js: -------------------------------------------------------------------------------- 1 | import { useContext } from "react"; 2 | import Link from "next/link"; 3 | import { Context as HeaderContext } from "../context/headerContext"; 4 | import { Context as CourseContext } from "../context/courseInfoContext"; 5 | 6 | export default function Header(props) { 7 | const [{ section, title, icon }] = useContext(HeaderContext); 8 | const { frontendMastersLink } = useContext(CourseContext); 9 | return ( 10 |
11 |

12 | {props.title} 13 |

14 |
15 | {frontendMastersLink ? ( 16 | 17 | Watch on Frontend Masters 18 | 19 | ) : null} 20 | {section ? ( 21 |

22 | {section} {title} 23 |

24 | ) : null} 25 |
26 |
27 | ); 28 | } 29 | -------------------------------------------------------------------------------- /data/course.js: -------------------------------------------------------------------------------- 1 | import config from "../course.json"; 2 | 3 | const DEFAULT_CONFIG = { 4 | author: { 5 | name: "ThePrimeagen", 6 | company: "Netflix", 7 | }, 8 | title: "Polyglot Programming - TS, Go, Rust", 9 | subtitle: "The course that teaches you new languages with languages you know.", 10 | frontendMastersLink: "https://frontendmasters.com/courses/typescript-go-rust", 11 | description: "This course is designed for those that want to expand their knowledge to other languages by building a tool within the new language, and not reading yet another language tutorial. This will be a fast paced, high octain learning experience.", 12 | keywords: ["ThePrimeagen", "Live Coding", "TypeScript", "JavaScript", "Golang", "RustLang", "Go", "Rust", "TS", "JS"], 13 | social: { 14 | twitter: "ThePrimeagen", 15 | twitch: "ThePrimeagen", 16 | youtube: "ThePrimeagen", 17 | }, 18 | productionBaseUrl: "/", 19 | }; 20 | 21 | export default function getCourseConfig() { 22 | return Object.assign({}, DEFAULT_CONFIG, config); 23 | } 24 | -------------------------------------------------------------------------------- /lessons/09-outro/A-that-is-it.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "This is the end my friend" 3 | description: "Lets go forth" 4 | --- 5 | 6 | ### That is it 7 | We have made it through a rigerous multiday course on rust, golang, and 8 | typescript. 9 | 10 | I hope everyone loved this! 11 | 12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 | 29 | ### I wasn't sure if I was going to have fun 30 | At the time of writing this I don't know how the course went 31 | 32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 | 49 | ### Final Questions? 50 | there must be at least one more question. 51 | 52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 | 69 | -------------------------------------------------------------------------------- /components/layout.js: -------------------------------------------------------------------------------- 1 | import { useState } from "react"; 2 | 3 | import Footer from "./footer"; 4 | import Header from "./header"; 5 | import getCourseConfig from "../data/course"; 6 | import { Provider as HeaderProvider } from "../context/headerContext"; 7 | import { Provider as CourseInfoProvider } from "../context/courseInfoContext"; 8 | 9 | function Layout({ children }) { 10 | const courseInfo = getCourseConfig(); 11 | const headerHook = useState({}); 12 | return ( 13 | 14 | 15 |
16 |
17 |
18 |
{children}
19 |
20 |
25 |
26 |
27 |
28 | ); 29 | } 30 | 31 | export default function App({ children }) { 32 | return {children}; 33 | } 34 | -------------------------------------------------------------------------------- /components/footer.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import Gh from "./github"; 3 | import Tw from "./twitter"; 4 | import Twitch from "./twitch"; 5 | 6 | export default function Footer({ twitter, twitch, youtube }) { 7 | return ( 8 |
9 |
    10 | {twitter ? ( 11 |
  • 12 | 13 | 14 | 15 |
  • 16 | ) : null} 17 | {twitch ? ( 18 |
  • 19 | 20 | 21 | 22 |
  • 23 | ) : null} 24 | {youtube ? ( 25 |
  • 26 | 27 | 28 | 29 |
  • 30 | ) : null} 31 |
  • 32 |
    33 |

    Content Licensed Under CC-BY-NC-4.0

    34 |

    Code Samples and Excercises Licensed Under Apache 2.0

    35 |

    36 | Site Designed by{" "} 37 | Alex Danielson 38 |

    39 |
    40 |
  • 41 |
42 |
43 | ); 44 | } 45 | -------------------------------------------------------------------------------- /lessons/02-part-1-getting-familiar/F-project-structure.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Project Structures" 3 | description: "The way we shape our projects" 4 | --- 5 | ### TypeScript 6 | 7 | ```bash 8 | node_modules/ 9 | ... ~the weight of the universe~ ... 10 | src/ 11 | ... where we keep our source code ... 12 | package.json 13 | ... other files ... 14 | ``` 15 | 16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 | 32 | ### Golang 33 | Its a bit different, but here is a nice way to setup 34 | 35 | ```bash 36 | cmd/ 37 | main.go 38 | pkg/ 39 | export_name/ 40 | thing.go 41 | ... 42 | ... 43 | go.mod 44 | ``` 45 | 46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 | 62 | ### Rustlang 63 | 64 | ```bash 65 | src/ 66 | bin/ 67 | binary_1.rs 68 | binary_2.rs 69 | ... 70 | lib.rs 71 | file_1.rs 72 | errors.rs 73 | target/ 74 | ... build stuff ... 75 | Cargo.toml # who is tom anyways? 76 | ``` 77 | 78 | ### Little note 79 | lib.rs needs to contain all the files you are using. 80 | 81 | ``` 82 | pub mod file_1.rs 83 | pub mod errors.rs 84 | ``` 85 | 86 | this way your `bin` folder can refer to these files. 87 | 88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 | 104 | -------------------------------------------------------------------------------- /lessons/06-test-it/C-rust-testing.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Rust : Projector Tests" 3 | description: "Lets test this bad boi" 4 | --- 5 | 6 | ### Wanna guess how i feel about rust testing? 7 | 8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 | 25 | ### Fantastic 26 | 27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 | 44 | ### Here is the code! 45 | 46 | ```rust 47 | 48 | #[cfg(test)] 49 | mod test { 50 | use anyhow::Result; 51 | 52 | use crate::{opts::CLIOptions, config::Operation}; 53 | 54 | use super::get_projector_config; 55 | 56 | #[test] 57 | fn test_print() -> Result<()> { 58 | let opts = CLIOptions { 59 | config: None, 60 | pwd: None, 61 | arguments: vec![], 62 | }; 63 | 64 | let config = get_projector_config(opts)?; 65 | 66 | assert_eq!(config.operation, Operation::Print(None)); 67 | 68 | return Ok(()); 69 | } 70 | 71 | #[test] 72 | fn test_add() -> Result<()> { 73 | let opts = CLIOptions { 74 | config: None, 75 | pwd: None, 76 | arguments: vec![ 77 | "add".to_string(), "foo".into(), String::from("bar") 78 | ], 79 | }; 80 | 81 | let config = get_projector_config(opts)?; 82 | 83 | assert_eq!(config.operation, Operation::Add((String::from("foo"), String::from("bar")))); 84 | 85 | return Ok(()); 86 | } 87 | } 88 | ``` 89 | -------------------------------------------------------------------------------- /components/twitter.js: -------------------------------------------------------------------------------- 1 | export default function Twitter() { 2 | return ( 3 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 21 | 27 | 28 | 29 | 30 | ); 31 | } 32 | -------------------------------------------------------------------------------- /components/corner.js: -------------------------------------------------------------------------------- 1 | export default function Corner() { 2 | return ( 3 |
4 | 11 | 12 | 13 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 35 | 42 | 43 | 44 | 45 |
46 | ); 47 | } 48 | -------------------------------------------------------------------------------- /lessons/02-part-1-getting-familiar/C-problem-2.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: "its l337 code time" 3 | --- 4 | 5 | ## Advent of Code Again? 6 | yes, lets do something that will require a bit more effort 7 | 8 | Don't forget to install `cargo-edit` it will give you `cargo add` 9 | [cargo-edit](https://github.com/killercup/cargo-edit) 10 | ```bash 11 | cargo install cargo-edit 12 | ``` 13 | 14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 | 31 | ## Again, Thank you Mr Santa 32 | Check out [Advent of Code](https://www.adventofcode.com) for yourself this year! 33 | 34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 | 51 | ## Problem 2 : Fissures 52 | No festivities 53 | 54 | 1. You are given lines ((x1, y1) -> (x2, y2)) 55 | 1. Only consider horizontal and vertical lines 56 | 1. We wont solve the actual problem as its a bit verbose! 57 | 58 |
59 | 60 | Example input 61 | ``` 62 | 0,9 -> 5,9 63 | 8,0 -> 0,8 64 | 9,4 -> 3,4 65 | 2,2 -> 2,1 66 | 7,0 -> 7,4 67 | 6,4 -> 2,0 68 | 0,9 -> 2,9 69 | 3,4 -> 1,4 70 | 0,0 -> 8,8 71 | 5,5 -> 8,2 72 | ``` 73 | 74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 | 91 | ## Typescript 92 | this is be a bit verbose but we should be able to do this. 93 | 94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 | 111 | ## Golang 112 | Not much different. 113 | 114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 |
123 |
124 |
125 |
126 |
127 |
128 |
129 |
130 | 131 | ## Rust 132 | Most fun, most excellent, did I mention I like rust, btw? 133 | 134 |
135 |
136 |
137 |
138 |
139 |
140 |
141 |
142 |
143 |
144 |
145 |
146 |
147 |
148 |
149 |
150 | 151 | 152 | -------------------------------------------------------------------------------- /components/github.js: -------------------------------------------------------------------------------- 1 | export default function GitHub() { 2 | return ( 3 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 21 | 28 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | ); 43 | } 44 | -------------------------------------------------------------------------------- /lessons/06-test-it/B-golang-testing.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Golang : Projector Tests" 3 | description: "Lets test this bad boi" 4 | --- 5 | 6 | ### Tests you are likely not familiar with 7 | 8 | There are some libraries you can use with golang to make this nicer, but real 9 | talk, go testing sucks. 10 | 11 | ```bash 12 | go get -u github.com/google/go-cmp/cmp 13 | ``` 14 | 15 | ```bash 16 | pkg/projector/config_test.go 17 | ``` 18 | 19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 | 36 | ### Your code could look like this! 37 | 38 | ```go 39 | package projector_test 40 | 41 | import ( 42 | "testing" 43 | 44 | "github.com/theprimeagen/go-tem/pkg/projector" 45 | ) 46 | 47 | func TestConfigPrint(t *testing.T) { 48 | opts := projector.CLIOptions { 49 | Config: "", 50 | Pwd: "", 51 | Arguments: []string{}, 52 | } 53 | 54 | config, err := projector.NewProjectorConfig(&opts) 55 | 56 | if err != nil { 57 | t.Errorf("error returned from projector config %v", err) 58 | } 59 | 60 | if config.Operation != projector.Print { 61 | t.Errorf("operation expected was print but got %v", config.Operation) 62 | } 63 | } 64 | 65 | func TestConfigAdd(t *testing.T) { 66 | opts := projector.CLIOptions { 67 | Config: "", 68 | Pwd: "", 69 | Arguments: []string{"add", "foo", "bar"}, 70 | } 71 | 72 | config, err := projector.NewProjectorConfig(&opts) 73 | 74 | if err != nil { 75 | t.Errorf("error returned from projector config %v", err) 76 | } 77 | 78 | if config.Operation != projector.Add { 79 | t.Errorf("operation expected was add but got %v", config.Operation) 80 | } 81 | 82 | if config.Arguments[0] != "foo" || config.Arguments[1] != "bar" { 83 | t.Errorf("expected arguments to equal {'foo', 'bar'} but got %+v", config.Arguments) 84 | } 85 | } 86 | ``` 87 | 88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 | 105 | ### Time for the greatest.. 106 | 107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 |
123 | 124 | -------------------------------------------------------------------------------- /lessons/02-part-1-getting-familiar/D-problem-3.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: "its l337 code time" 3 | --- 4 | 5 | ## Advent of Code Again?? 6 | Yes, but its 2020 question! 7 | 8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 | 25 | ## Again, Thank you Mr Santa 26 | 27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 | 44 | ## Problem 3 : Trees and something like free ski 45 | [AdventOfCode - Day 3](https://adventofcode.com/2020/day/3) 46 | 47 | 1. You are given the following input: 48 | ``` 49 | ..##....... 50 | #...#...#.. 51 | .#....#..#. 52 | ..#.#...#.# 53 | .#...##..#. 54 | ..#.##..... 55 | .#.#.#....# 56 | .#........# 57 | #.##...#... 58 | #...##....# 59 | .#..#...#.# 60 | ``` 61 | 62 | 1. It wraps over and over again 63 | ``` 64 | ..##.........##....... 65 | #...#...#..#...#...#.. 66 | .#....#..#..#....#..#. 67 | ..#.#...#.#..#.#...#.# 68 | .#...##..#..#...##..#. 69 | ..#.##.......#.##..... ---- > repeats for ever! 70 | .#.#.#....#.#.#.#....# 71 | .#........#.#........# 72 | #.##...#...#.##...#... 73 | #...##....##...##....# 74 | .#..#...#.#.#..#...#.# 75 | ``` 76 | 1. We need to travel 3 right and 1 down and count how many trees we hit. 77 | 78 | ``` 79 | # = tree 80 | . = snow 81 | ``` 82 | 83 |
84 | 85 | Example input 86 | ``` 87 | ..##....... 88 | #...#...#.. 89 | .#....#..#. 90 | ..#.#...#.# 91 | .#...##..#. 92 | ..#.##..... 93 | .#.#.#....# 94 | .#........# 95 | #.##...#... 96 | #...##....# 97 | .#..#...#.# 98 | ``` 99 | 100 |
101 | 102 | #### Note 103 | * You don't get the size. 104 | 105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 | 122 | ## Typescript 123 | S I M P L E 124 | 125 |
126 |
127 |
128 |
129 |
130 |
131 |
132 |
133 |
134 |
135 |
136 |
137 |
138 |
139 |
140 |
141 | 142 | ## Golang 143 | S I M P L E 144 | 145 |
146 |
147 |
148 |
149 |
150 |
151 |
152 |
153 |
154 |
155 |
156 |
157 |
158 |
159 |
160 |
161 | 162 | ## Rust 163 | Easy or hard depending on how you do it 164 | 165 |
166 |
167 |
168 |
169 |
170 |
171 |
172 |
173 |
174 |
175 |
176 |
177 |
178 |
179 |
180 |
181 | -------------------------------------------------------------------------------- /pages/lessons/[section]/[slug].js: -------------------------------------------------------------------------------- 1 | import { useContext, useEffect } from "react"; 2 | import Head from "next/head"; 3 | import { getLesson, getLessons } from "../../../data/lesson"; 4 | import getCourseConfig from "../../../data/course"; 5 | import Corner from "../../../components/corner"; 6 | import { Context } from "../../../context/headerContext"; 7 | 8 | // export const meta = (routeData) => { 9 | // return { 10 | // title: `${routeData.data.section} – ${routeData.data.title}`, 11 | // description: routeData.data.attributes.description, 12 | // }; 13 | // }; 14 | 15 | export default function LessonSlug({ post }) { 16 | const courseInfo = getCourseConfig(); 17 | const [_, setHeader] = useContext(Context); 18 | useEffect(() => { 19 | setHeader({ 20 | section: post.section, 21 | title: post.title, 22 | icon: post.icon, 23 | }); 24 | return () => setHeader({}); 25 | }, []); 26 | 27 | const title = post.title 28 | ? `${post.title} – ${courseInfo.title}` 29 | : courseInfo.title; 30 | const description = post.description 31 | ? post.description 32 | : courseInfo.description; 33 | 34 | return ( 35 | <> 36 | 37 | {title} 38 | 39 | {/* */} 40 | 41 | 42 | 46 | 47 | 48 |
49 |
50 |
54 |
55 | {post.prevSlug ? ( 56 | 57 | ← Previous 58 | 59 | ) : null} 60 | {post.nextSlug ? ( 61 | 62 | Next → 63 | 64 | ) : null} 65 |
66 |
67 | 68 |
69 | 70 | ); 71 | } 72 | 73 | export async function getStaticProps({ params }) { 74 | const post = await getLesson(params.section, params.slug); 75 | return { 76 | props: { 77 | post, 78 | }, 79 | }; 80 | } 81 | 82 | export async function getStaticPaths() { 83 | const sections = await getLessons(); 84 | const lessons = sections.map((section) => section.lessons); 85 | const slugs = lessons.flat().map((lesson) => lesson.fullSlug); 86 | 87 | return { paths: slugs, fallback: false }; 88 | } 89 | -------------------------------------------------------------------------------- /lessons/02-part-1-getting-familiar/B-problem-1.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: "its l337 code time" 3 | --- 4 | 5 | ## Advent of Code 6 | So every year on December 1st an advent calendar of the most nerdiest type is 7 | created for us to enjoy the winter time in style. This, of course, is Advent 8 | of Code. Its a series of festive problem solving inloving Santa in an unusual 9 | pickle where his quick wits and problem solving skills helps him navigate his 10 | way through the world and ultimately save Christmas. 11 | 12 | ### For me 13 | This is one of the best ways to learn a language. Simple and small problems 14 | that are accomplishable. 15 | 16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 | 33 | ## Yes, Mr Santa himself gave me the thumbs up 34 | Check out [Advent of Code](https://www.adventofcode.com) for yourself this year! 35 | 36 |
37 | 38 | ![Santa](./images/santa.png) 39 | 40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 | 57 | ## Problem 1 : Submarine navigation 58 | Without all the fluff and festive fun of advent of code here is th deets. 59 | 60 |
61 | 62 | 1. Your submarine starts at position 0,0 (x position, depth) 63 | 1. Parse input to direct your submarine. 64 | 1. Multiply your depth * x progression to get an answer 65 | 66 |
67 | 68 | Example input 69 | ``` 70 | forward 5 71 | down 5 72 | forward 8 73 | up 3 74 | down 8 75 | forward 2 76 | ``` 77 | 78 |
79 | 80 | #### Note 81 | this means that up 3 is actually -3 from the y axis. 82 | 83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 | 100 | ## Typescript 101 | Very easy. I am going to intentionally over program it, to make it simple and 102 | to hopefully show some things that are weird in other languages. 103 | 104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 | 121 | ## Golang 122 | this should feel surprisingly easy. 123 | 124 |
125 |
126 |
127 |
128 |
129 |
130 |
131 |
132 |
133 |
134 |
135 |
136 |
137 |
138 |
139 |
140 | 141 | ## Rust 142 | Ok, its hard 143 | 144 |
145 |
146 |
147 |
148 |
149 |
150 |
151 |
152 |
153 |
154 |
155 |
156 |
157 |
158 |
159 |
160 | 161 | -------------------------------------------------------------------------------- /lessons/08-tying-it-together/C-rust-4-real.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Rust is the bestest" 3 | description: "finally back to the best programming ever" 4 | --- 5 | 6 | ### Some intro i didn't write 7 | TODO: the thing we leave in which we never fix 8 | 9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 | 26 | ### We will write it in two phases 27 | * Saving the data 28 | * The main control file 29 | 30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 | 47 | ### The projector file 48 | this is where i feel our good decisions start to compound 49 | 50 | ```rust 51 | pub fn save(&self) -> Result<()> { 52 | let result = serde_json::to_string(&self.data)?; 53 | // todo... mkdir 54 | if let Some(p) = self.config.config.parent() { 55 | if std::fs::metadata(p).is_err() { 56 | std::fs::create_dir_all(p).ok(); 57 | } 58 | } 59 | 60 | std::fs::write(&self.config.config, result)?; 61 | 62 | return Ok(()); 63 | } 64 | 65 | ``` 66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 | 83 | ### The main file 84 | Tying it all together 85 | 86 | ```rust 87 | use clap::StructOpt; 88 | use rust::{opts::ProjectorOpts, config::{ProjectorConfig, Operation}, error::ProjectorError, projector::Projector}; 89 | 90 | fn main() -> Result<(), ProjectorError> { 91 | let config: ProjectorConfig = ProjectorOpts::parse().try_into()?; 92 | let mut proj = Projector::load_from_disk(&config)?; 93 | 94 | match &config.operation { 95 | Operation::Add((k, v)) => { 96 | proj.set_value(k, v.clone()); 97 | proj.save(&config)?; 98 | }, 99 | Operation::Print(k) => { 100 | match proj.get_value(k) { 101 | Some(v) => println!("{}", v), 102 | None => eprintln!("key {} not found", k), 103 | } 104 | } 105 | Operation::Remove(k) => { 106 | proj.remove_value(k); 107 | proj.save(&config)?; 108 | } 109 | } 110 | 111 | return Ok(()); 112 | } 113 | ``` 114 | 115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 |
123 |
124 |
125 |
126 |
127 |
128 |
129 |
130 |
131 | 132 | ### Wowza! 133 | We actually finished! 134 | 135 |
136 |
137 |
138 |
139 |
140 |
141 |
142 |
143 |
144 |
145 |
146 |
147 |
148 |
149 |
150 |
151 | 152 | -------------------------------------------------------------------------------- /lessons/04-cli/C-rust-cli.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Rustlang - CLI" 3 | description: "Lets build the arguments parsing for rust" 4 | --- 5 | 6 | ### Rust 7 | I am going to make the assumption that rust will be a foreign invader to your 8 | brain and feel very hard. 9 | 10 | If you stick with rust, you will soon say its the most fun and least fun 11 | language you have ever worked in. 12 | 13 | ### CLI Argument parsing 14 | Rust has the best. Hands down. 15 | 16 | - Rust : clap 17 | - The greatest CLI parser by the mostest 18 | 19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 | 36 | ### I use tmux, btw 37 | Such fast swapping 38 | 39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 | 56 | ### So to get started lets create the project 57 | Create it where you like 58 | 59 | #### Before you get started 60 | `cargo add` is not a default feature, instead you need to add it. 61 | 62 | #### github.com/killercup/cargo-edit 63 | Install: [Cargo Edit](https://github.com/killercup/cargo-edit) 64 | 65 | ```bash 66 | cargo init 67 | cargo add clap --features=derive 68 | vim . # you can open up other moderately slow editor of your choice 69 | ``` 70 | 71 | This is the rust time... it will be a bit interesting. 72 | 73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 | 90 | ### Project Structure 91 | 92 | After cargo init 93 | ```bash 94 | .git 95 | .gitignore 96 | src/main.rs 97 | Cargo.toml 98 | Cargo.lock 99 | ``` 100 | 101 | Change it 102 | ```bash 103 | src/main.rs -> src/bin/projector.rs 104 | src/lib.rs 105 | src/opts.rs 106 | ``` 107 | 108 | ### Lets program it! 109 | 110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 |
123 |
124 |
125 |
126 | 127 | ### Example of the opts.rs 128 | 129 | ```rust 130 | // src/opts.rs 131 | use std::path::PathBuf; 132 | 133 | use clap::Parser; 134 | 135 | #[derive(Parser, Debug)] 136 | #[clap()] 137 | pub struct ProjectorOpts { 138 | 139 | #[clap(short = 'p', long = "pwd")] 140 | pub pwd: Option, 141 | 142 | #[clap(short = 'c', long = "config")] 143 | pub config: Option, 144 | 145 | #[clap(default_value = "")] 146 | pub operation: Vec, 147 | } 148 | ``` 149 | 150 |
151 |
152 |
153 |
154 |
155 |
156 |
157 |
158 |
159 |
160 |
161 |
162 |
163 |
164 |
165 |
166 | 167 | -------------------------------------------------------------------------------- /lessons/08-tying-it-together/B-gopher-time.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Golang" 3 | description: "We are finishing up the whole thing" 4 | --- 5 | 6 | ### Some intro i didn't write 7 | TODO: the thing we leave in which we never fix 8 | 9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 | 26 | ### We will write it in two phases 27 | * Saving the data 28 | * The main control file 29 | 30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 | 47 | ### The projector file 48 | 49 | ```go 50 | func (p *Projector) Save() error { 51 | dir := path.Dir(p.config.Config) 52 | if _, err := os.Stat(dir); os.IsNotExist(err) { 53 | os.MkdirAll(dir, fs.FileMode(0755)) 54 | } 55 | 56 | bytes, err := json.Marshal(p.data) 57 | if err != nil { 58 | return err 59 | } 60 | 61 | os.WriteFile(p.config.Config, bytes, fs.FileMode(0755)) 62 | return nil 63 | } 64 | ``` 65 | 66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 | 83 | ### The main file 84 | This is the file that is the executable, lets use that to put everything together. 85 | 86 | ```go 87 | package main 88 | 89 | import ( 90 | "fmt" 91 | "log" 92 | "os" 93 | 94 | "github.com/theprimeagen/projector/pkg/projector" 95 | ) 96 | 97 | func main() { 98 | opts, err := projector.GetOptions() 99 | if err != nil { 100 | log.Fatalf("error: %v", err) 101 | } 102 | 103 | config, err := projector.NewProjectorConfig(opts) 104 | if err != nil { 105 | log.Fatalf("error: %v", err) 106 | } 107 | 108 | prj, err := projector.FromConfig(config) 109 | if err != nil { 110 | log.Fatalf("error: %v", err) 111 | } 112 | 113 | switch config.Operation { 114 | case projector.Add: 115 | prj.SetValue(config.Arguments[0], config.Arguments[1]) 116 | prj.Save(config) 117 | case projector.Remove: 118 | prj.RemoveValue(config.Arguments[0]) 119 | prj.Save(config) 120 | case projector.Print: 121 | value, found := prj.GetValue(config.Arguments[0]) 122 | if found { 123 | fmt.Print(value) 124 | } else { 125 | fmt.Fprintf(os.Stderr, "Could not find results for %v", config.Arguments[0]) 126 | } 127 | } 128 | } 129 | ``` 130 | 131 |
132 |
133 |
134 |
135 |
136 |
137 |
138 |
139 |
140 |
141 |
142 |
143 |
144 |
145 |
146 |
147 | 148 | ### Rust is the greatest, the bestest!! 149 | GO FORTH WITH RUSTACEASOUSNESSES 150 | 151 |
152 |
153 |
154 |
155 |
156 |
157 |
158 |
159 |
160 |
161 |
162 |
163 |
164 |
165 |
166 |
167 | 168 | -------------------------------------------------------------------------------- /lessons/02-part-1-getting-familiar/E-probably-should-be-A-language-gotchas.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "The gotchas" 3 | description: "A couple quick tips" 4 | --- 5 | ### Go and pointer receivers 6 | When defining methods on a struct you have two options 7 | 8 | ```go 9 | type Foo struct { 10 | thing int 11 | } 12 | 13 | // value receiver 14 | func (f Foo) fA() { 15 | ... 16 | f.thing = 5; // DOESNT DO ANYTHING 17 | } 18 | 19 | // pointer receiver 20 | func (f *Foo) fB() { 21 | ... 22 | f.thing = 5; // CHANGES 23 | } 24 | ``` 25 | 26 | my general rule of thumb is use a pointer receiver. value receivers copy, 27 | pointers don't. For the most part, small structs there probably isn't going to 28 | be any noticeable perf win, but it can bite you on mutation. 29 | 30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 | 46 | ### One more on go structs vs rust structs 47 | I have been had by this one, and I am sure that anyone with any go experience 48 | has been had by this as well. 49 | 50 | ```go 51 | type GoLangStruct struct { 52 | y int 53 | x int 54 | } 55 | 56 | func NewStruct(x, y int) GoLangStruct { 57 | return GoLangStruct{x, y} 58 | } 59 | 60 | func main() { 61 | fmt.Printf("%+v\n", NewStruct(9, 6)); 62 | } 63 | ``` 64 | 65 | ```rust 66 | #[derive(Debug)] 67 | struct RustLangStruct { 68 | y i32 69 | x i32 70 | } 71 | 72 | fn new_struct(x i32, y i32) -> RustLangStruct { 73 | return RustLangStruct{x, y}; 74 | } 75 | 76 | func main() { 77 | println!("{:?}\n", new_struct(9, 6)); 78 | } 79 | ``` 80 | 81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 | 97 | ### Errors 98 | when you may encounter an error, your function should return pointer 99 | 100 | ```go 101 | type Foo struct {} 102 | 103 | func CreateFoo() (*Foo, error) { 104 | if some_err_condition { 105 | return nil, fmt.Error("Here is an error!") 106 | } 107 | 108 | return &Foo{}, nil 109 | } 110 | 111 | ... 112 | this is where the infamous 113 | 114 | if err != nil { 115 | return nil, err 116 | } 117 | 118 | comes from 119 | ``` 120 | 121 |
122 |
123 |
124 |
125 |
126 |
127 |
128 |
129 |
130 |
131 |
132 |
133 |
134 |
135 |
136 | 137 | ### Rust, once you have the basics down, is actually pretty footgunless 138 | Its only when you go into async does it go terrible. Luckily we are not doing 139 | that. I still suck at doing it well. 140 | 141 | * Use traits where possible to define new behavior 142 | 143 | ```rust 144 | impl Display for Foo { 145 | /** allows for println!("{}", foo) to display **/ 146 | } 147 | 148 | impl From for Foo { 149 | // allows for .into() operator to be used 150 | } 151 | ``` 152 | 153 | * Iterators are really powerful 154 | 155 | * Don't forget Option and Result `.map` function and on iterator `flat_map` 156 | 157 |
158 |
159 |
160 |
161 |
162 |
163 |
164 |
165 |
166 |
167 |
168 |
169 |
170 |
171 |
172 | 173 | -------------------------------------------------------------------------------- /lessons/06-test-it/A-typescript-testing.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "TypeScript : Projector Tests" 3 | description: "Lets test this bad boi" 4 | --- 5 | 6 | ### Lets add some tests 7 | 8 | ```bash 9 | src/__tests__/config.ts 10 | ``` 11 | 12 | *NOTE* I had errors with jest v28 and ts-jest. Make sure we use 27, unless its 13 | fixed by the time i do this presentation or you are watching it and we can 14 | ignore it :) 15 | 16 | ```bash 17 | yarn add -D jest ts-jest @types/jest 18 | npx jest --init 19 | ``` 20 | 21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 | 38 | ### When running npm run test 39 | 40 | if you get the following error its likely because preset: "ts-jest" isn't added 41 | 42 | ```bash 43 | src/__tests__/config.ts 44 | ● Test suite failed to run 45 | 46 | Jest encountered an unexpected token 47 | 48 | Jest failed to parse a file. This happens e.g. when your code or its dependencies use non-standard JavaScript syntax, or when Jest is not 49 | configured to support such syntax. 50 | 51 | Out of the box Jest supports Babel, which will be used to transform your files into valid JS based on your Babel configuration. 52 | 53 | By default "node_modules" folder is ignored by transformers. 54 | 55 | Here's what you can do: 56 | • If you are trying to use ECMAScript Modules, see https://jestjs.io/docs/ecmascript-modules for how to enable it. 57 | • If you are trying to use TypeScript, see https://jestjs.io/docs/getting-started#using-typescript 58 | • To have some of your "node_modules" files transformed, you can specify a custom "transformIgnorePatterns" in your config. 59 | • If you need a custom transformation specify a "transform" option in your config. • If you simply want to mock your non-JS modules (e.g. binary assets) you can stub them out with the "moduleNameMapper" config option. 60 | You'll find more details and examples of these config options in the docs: 61 | https://jestjs.io/docs/configuration 62 | For information about custom transformations, see: 63 | https://jestjs.io/docs/code-transformation 64 | ``` 65 | 66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 | 83 | 84 | 85 | ### Your code could look like this! 86 | 87 | ```typescript 88 | import getProjectorConfig, { Operation } from "../config"; 89 | 90 | test("should create a print projector config", function() { 91 | const config = getProjectorConfig({}); 92 | expect(config.operation).toEqual(Operation.Print); 93 | }); 94 | 95 | test("should create an add projector config", function() { 96 | const config = getProjectorConfig({ 97 | arguments: ["add", "foo", "bar"], 98 | }); 99 | expect(config.operation).toEqual(Operation.Add); 100 | expect(config.arguments).toEqual(["foo", "bar"]); 101 | }); 102 | ``` 103 | 104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 | 121 | 122 | -------------------------------------------------------------------------------- /pages/index.js: -------------------------------------------------------------------------------- 1 | import Head from "next/head"; 2 | import Link from "next/link"; 3 | 4 | import { getLessons } from "../data/lesson"; 5 | 6 | import Corner from "../components/corner"; 7 | import getCourseConfig from "../data/course"; 8 | 9 | export default function Lessons({ sections }) { 10 | const courseInfo = getCourseConfig(); 11 | return ( 12 | <> 13 | 14 | {courseInfo.title} 15 | 16 | 17 | 18 | 19 | 23 | 24 | 25 |
26 |
27 |
28 |
29 |

{courseInfo.title}

30 |

{courseInfo.subtitle}

31 |
32 |
33 | author image 38 |
39 |
40 |
{courseInfo.author.name}
41 |
{courseInfo.author.company}
42 |
43 |
44 |
45 |
46 |
47 | course icon 51 |
52 |
53 | {courseInfo.frontendMastersLink ? ( 54 | 55 | Watch on Frontend Masters 56 | 57 | ) : null} 58 |
59 |

Table of Contents

60 |
61 |
    62 | {sections.map((section) => ( 63 |
  1. 64 |
    65 |
    66 | 67 |
    68 |
    69 |

    {section.title}

    70 |
      71 | {section.lessons.map((lesson) => ( 72 |
    1. 73 | {lesson.title} 74 |
    2. 75 | ))} 76 |
    77 |
    78 | 79 |
    80 |
  2. 81 | ))} 82 |
83 |
84 |
85 |
86 | 87 | ); 88 | } 89 | 90 | export async function getStaticProps() { 91 | const sections = await getLessons(); 92 | return { 93 | props: { 94 | sections, 95 | }, 96 | }; 97 | } 98 | -------------------------------------------------------------------------------- /lessons/01-introduction/A-intro.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "TS, Go, And Rust" 3 | description: "The polyglot programming experience." 4 | --- 5 | 6 | ## Welcome! 7 | Much excite! 8 | 9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 | 25 | ## NOTE TO THE COURSE WATCHERS 26 | I am writing this to you because there is going to be some things you need to 27 | know. First off, i move pretty fast so the complete code is available at 28 | [Here](https://github.com/ThePrimeagen/ts-go-rust-projector). 29 | 30 | # BEFORE YOU READ THE CODE 31 | A very important note about the code. It does contain the surprise at the end 32 | of the course that will help you understand rust's borrow checker. So I would 33 | be careful just rushing in and checking out the code. As there will be a 34 | slight difference in the code online due to a last minute refactoring. 35 | 36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 | 52 | ## So why 3 languages 53 | The chance is that you want to learn rust or go and simple language tutorials 54 | just don't cut it for you. 55 | 56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 | 72 | ## There are going to be questions 73 | I will provide stopping points, fequently, along the way. 74 | 75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 | 91 | ## Whoami 92 | name: ThePrimeagen 93 | 94 | Its not that odd of a name if you know the back story. 95 | 96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 | 112 | [twitch](twitch.tv/ThePrimeagen) 113 | ![Twitch](./images/TWITCH.png) 114 | [youtube](youtube.com/ThePrimeagen) 115 | ![Twitch](./images/YT.png) 116 | 117 |
118 |
119 |
120 |
121 |
122 |
123 |
124 |
125 |
126 |
127 |
128 |
129 |
130 |
131 |
132 | 133 | Work at a small dvd shipping company. I think we will revolutionize the 134 | industry. 135 | 136 |
137 |
138 |
139 |
140 |
141 |
142 |
143 |
144 |
145 |
146 |
147 |
148 |
149 |
150 |
151 | 152 | ## The Pace 153 | We have a couple days. There is a lot of programming to do. So its going to 154 | be fast. Really fast. And if you want to move as fast as I do, you can always 155 | check out these courses. 156 | 157 |
158 | 159 | [Vim Fundamentals](https://frontendmasters.com/courses/vim-fundamentals/) 160 | ![Vim](./images/vim.png) 161 | 162 |
163 | 164 | [Dev Productivity](https://frontendmasters.com/courses/developer-productivity/) 165 | ![dev](./images/dev-prod.png) 166 | 167 |
168 |
169 |
170 |
171 |
172 |
173 |
174 |
175 |
176 |
177 |
178 |
179 |
180 |
181 |
182 | 183 | ## I hope you are excited 184 | And ready to rock 185 | 186 |
187 |
188 |
189 |
190 |
191 |
192 |
193 |
194 |
195 |
196 |
197 |
198 |
199 |
200 |
201 | 202 | ## Google is your friend, sometimes 203 | One more tip. If I forget something today and have to google, you will see me 204 | try a few times first without googling. Why? 205 | 206 |
207 |
208 |
209 |
210 |
211 |
212 |
213 |
214 |
215 |
216 |
217 |
218 |
219 |
220 |
221 | -------------------------------------------------------------------------------- /lessons/04-cli/B-golang-cli.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Golang : CLI" 3 | description: "Lets build the cli argument portion of the golang program" 4 | --- 5 | 6 | ### Golang 7 | I am going to make a new assumption. Most of you are not familiar with golang, 8 | therefore I'll go a bit slower and I'll hop back to TypeScript regularly. 9 | 10 | ### CLI Argument parsing 11 | There are some pretty dang powerful CLI arg parsers, thinking of urfave/cli. 12 | Its really awesome, but its just overkill for what we are doing here. So I 13 | picked the simpliest one for our benefit 14 | 15 | - Golang : github.com/hellflame/argparse 16 | - A really nice api on this one 17 | 18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 | 35 | ### I use tmux, btw 36 | Such fast swapping 37 | 38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 | 55 | ### I use vim, btw 56 | Such fast editing 57 | 58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 | 75 | ### I use dvorak, btw 76 | Such wrist non pain, but I also cannot use a coworkers computer... 77 | 78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 | 95 | ### Before we get started, lets talk ptrs 96 | TO THE WHITE BOARD 97 | 98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 | 115 | ### So to get started lets create the project 116 | Create it where you like 117 | 118 | ```bash 119 | go mod init github.com/theprimeagen/projector 120 | go get github.com/hellflame/argparse 121 | mkdir cmd 122 | mkdir -p pkg/cli 123 | vim . # you can open up other non coconut oil'd editors 124 | ``` 125 | 126 | Oh, and there is no terrible build experience like there is with ts. 127 | 128 | ### Example of CLI Options (pkg/cli/opts.go) 129 | 130 | ```go 131 | package projector 132 | 133 | import ( 134 | "github.com/hellflame/argparse" 135 | ) 136 | 137 | type ProjectorOpts struct { 138 | Pwd string 139 | Config string 140 | Arguments []string 141 | } 142 | 143 | func GetOptions() (*ProjectorOpts, error) { 144 | parser := argparse.NewParser("projector", "gets all the values", &argparse.ParserConfig{DisableDefaultShowHelp: true}) 145 | args := parser.Strings("f", "foo", &argparse.Option{ 146 | Positional: true, 147 | Default: "", 148 | Required: false, 149 | }) 150 | 151 | config := parser.String("c", "config", &argparse.Option{Required: false, Default: ""}) 152 | pwd := parser.String("p", "pwd", &argparse.Option{Required: false, Default: ""}) 153 | 154 | err := parser.Parse(nil) 155 | if err != nil { 156 | return nil, err 157 | } 158 | 159 | return &ProjectorOpts { 160 | Pwd: *pwd, 161 | Config: *config, 162 | Arguments: *args, 163 | }, nil 164 | } 165 | ``` 166 | 167 |
168 |
169 |
170 |
171 |
172 |
173 |
174 |
175 |
176 |
177 |
178 |
179 |
180 |
181 |
182 |
183 | 184 | ### So lets build it again, but in Rust!!! 185 | This is the exciting part! We get to use rust just for a moment! 186 | 187 |
188 |
189 |
190 |
191 |
192 |
193 |
194 |
195 |
196 |
197 |
198 |
199 |
200 |
201 |
202 |
203 | 204 | 205 | -------------------------------------------------------------------------------- /lessons/08-tying-it-together/A-typescript.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Typescript Projector" 3 | description: "So now its time to create the project" 4 | --- 5 | 6 | 7 | ### Time to tie it all together 8 | The last part is to take this little library we wrote and make it do the actual 9 | operations. 10 | 11 | For add and remove, they both need to update the config file which may or may 12 | not exist. 13 | 14 | For print operations, we need to simply print off the results 15 | 16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 | 33 | ### The ascii diagram again. 34 | 35 | ``` 36 | v <-- you are here 37 | such clossness 38 | much progress 39 | +----------+ +----------+ +----------+ +----------+ 40 | | cli opts | -> | project | -+-> | print | -> | display | 41 | +----------+ | config | | +----------+ +----------+ 42 | +----------+ | 43 | | +----------+ +----------+ 44 | +-> | add | -> | save | 45 | | +----------+ +----------+ 46 | | 47 | | +----------+ +----------+ 48 | +-> | rm | -> | save | 49 | +----------+ +----------+ 50 | 51 | ``` 52 | 53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 | 70 | ### Lets work through the config saving first 71 | lets have projector take care of saving. 72 | 73 | ```typescript 74 | import fs from "fs"; 75 | 76 | // .. 77 | 78 | class Projector { 79 | // ... 80 | save() { 81 | fs.writeFileSync(this.config.config, JSON.stringify(this.data)); 82 | } 83 | 84 | // ... 85 | } 86 | ``` 87 | 88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 | 105 | ### The main file 106 | I tend to like to do all the linking / printing / saving / whatever is "harder" 107 | to test in the main file. Its a personal style, you don't have to do it this 108 | way or am I recommending it as a good way to go 109 | 110 | ```typescript 111 | import { parseCLIOptions } from "./opts"; 112 | import getProjectorConfig, { Operation } from "./config" 113 | import { Projector } from "./projector"; 114 | 115 | const config = getProjectorConfig(parseCLIOptions()); 116 | const projector = Projector.fromConfig(config); 117 | 118 | // we need to be able to operate on operations 119 | switch (config.operation) { 120 | case Operation.Print: 121 | const value = projector.getValue(config.arguments[0]); 122 | if (value !== undefined) { 123 | console.log(value); 124 | } 125 | break; 126 | 127 | case Operation.Add: 128 | projector.setValue(config.arguments[0], config.arguments[1]); 129 | projector.save(); 130 | break; 131 | 132 | case Operation.Remove: 133 | projector.deleteValue(config.arguments[0]); 134 | projector.save(); 135 | break; 136 | } 137 | ``` 138 | 139 |
140 |
141 |
142 |
143 |
144 |
145 |
146 |
147 |
148 |
149 |
150 |
151 |
152 |
153 |
154 |
155 | 156 | ### Onto the gophers 157 | Have i shown you this image? Gross huh? 158 | 159 |
160 |
161 |
162 |
163 |
164 |
165 |
166 |
167 |
168 |
169 |
170 |
171 |
172 |
173 |
174 |
175 | 176 | -------------------------------------------------------------------------------- /lessons/05-config/B-golang-projector-object.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "TypeScript : Projector Object" 3 | description: "The projector object will contain the operations" 4 | --- 5 | 6 | 7 | ### The problem (again) 8 | 9 | ``` 10 | +----------+ +----------+ 11 | | cli opts | -> | project | -+-> 12 | | (done) | | config | | 13 | +----------+ +----------+ | 14 | | 15 | +-> 16 | | 17 | | 18 | | 19 | +-> 20 | 21 | 22 | ``` 23 | 24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 | 41 | #### Interested in go vs typescript 42 | If you are curious [Go - TS](https://www.youtube.com/watch?v=h7UEwBaGoVo). 43 | With rust included! [TS - Go - Rust](https://www.youtube.com/watch?v=Z0GX2mTUtfo). 44 | 45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 | 62 | ### Lets build the Projector object 63 | 64 | ```bash 65 | src/projector/config.go 66 | ``` 67 | 68 | LETS BUILD!!! 69 | 70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 | 87 | #### The code! 88 | 89 | ```go 90 | package projector 91 | 92 | import ( 93 | "os" 94 | "path" 95 | ) 96 | 97 | type Operation = int 98 | 99 | const ( 100 | Print Operation = iota 101 | Add 102 | Remove 103 | ) 104 | 105 | type ProjectorConfig struct { 106 | Pwd string // projector --pwd ... 107 | Config string // projector --config ... 108 | Operation Operation // print, add, remove 109 | Arguments []string // *, , 110 | } 111 | 112 | func getConfig(config string) (string, error) { 113 | if config == "" { 114 | configDir, err := os.UserConfigDir() 115 | if err != nil { 116 | return "", err 117 | } 118 | 119 | return path.Join(configDir, "projector", "projector.json"), nil 120 | } 121 | 122 | return config, nil 123 | } 124 | 125 | func getPwd(pwd string) (string, error) { 126 | if pwd == "" { 127 | wd, err := os.Getwd() 128 | return wd, err 129 | } 130 | 131 | return pwd, nil 132 | } 133 | 134 | func isOperationCommand(op string) bool { 135 | return op == "add" || op == "rm"; 136 | } 137 | 138 | func getArguments(commands []string) []string { 139 | 140 | if (len(commands) > 0 && isOperationCommand(commands[0])) { 141 | return commands[1:] 142 | } 143 | 144 | return commands 145 | } 146 | 147 | func getOperation(commands []string) Operation { 148 | if len(commands) == 0 { 149 | return Add 150 | } 151 | 152 | switch (commands[0]) { 153 | case "add": return Add 154 | case "rm": return Remove 155 | } 156 | return Print 157 | } 158 | 159 | func NewProjectorConfig(opts *ProjectorOpts) (*ProjectorConfig, error) { 160 | pwd, err := getPwd(opts.Pwd) 161 | if err != nil { 162 | return nil, err 163 | } 164 | 165 | config, err := getConfig(opts.Config) 166 | if err != nil { 167 | return nil, err 168 | } 169 | 170 | return &ProjectorConfig { 171 | Operation: getOperation(opts.Arguments), 172 | Arguments: getArguments(opts.Arguments), 173 | Pwd: pwd, 174 | Config: config, 175 | }, nil 176 | } 177 | ``` 178 | 179 |
180 |
181 |
182 |
183 |
184 |
185 |
186 |
187 |
188 |
189 |
190 |
191 |
192 |
193 |
194 |
195 | 196 | ### Onto the Lords language 197 | no i am not talking about Php 198 | 199 |
200 |
201 |
202 |
203 |
204 |
205 |
206 |
207 |
208 |
209 |
210 |
211 |
212 |
213 |
214 |
215 | 216 | -------------------------------------------------------------------------------- /lessons/05-config/A-typescript-projector-object.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "TypeScript : Projector Object" 3 | description: "The projector object will contain the operations" 4 | --- 5 | 6 | 7 | ### Breaking the problem up 8 | 9 | ``` 10 | 2. Converting the options into something that we can use to 11 | get everything we need to make projector 12 | 13 | +----------+ +----------+ 14 | | cli opts | -> | project | -+-> 15 | | (done) | | config | | 16 | +----------+ +----------+ | 17 | | 18 | +-> 19 | | 20 | | 21 | | 22 | +-> 23 | 24 | 25 | ``` 26 | 27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 | 44 | ### I am going to use classes 45 | I know there are a lot of you out there that will be horrified as you were told 46 | to never use classes. 47 | 48 | #### Fun Side Note 49 | If you use classes you can greatly improve the performance javascript using 50 | managed memory techniques that are not available with other techniques. Alas, 51 | that can be for another time. 52 | 53 | If you are curious [Blazingly Fast JavaScript](https://www.youtube.com/watch?v=Sp5_d6coiqU). 54 | 55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 | 72 | ### So lets go over what we need 73 | What do we have right now? We have a possible pwd, config path, and terms. We 74 | need to take these and create them into something useful. 75 | 76 | - config should point to a config. This means if none is provided we need a 77 | default location. 78 | - pwd is either provided, or the cwd from the point of execution. 79 | - terms. This needs to be converted into two things. 80 | - Operation : the type of operation to perform (print, add, remove) 81 | - Arguments : the arguments to the operation. 82 | 83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 | 100 | ### Lets build the Projector object 101 | 102 | ```bash 103 | src/config.ts 104 | ``` 105 | 106 | LETS BUILD!!! 107 | 108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 |
123 |
124 | 125 | #### Here is the likely very similar code 126 | 127 | ```typescript 128 | import path from "path"; 129 | import { ProjectorOpts } from "./opts"; 130 | 131 | export enum Operation { 132 | Print, 133 | Add, 134 | Remove 135 | } 136 | 137 | export type ProjectorConfig = { 138 | pwd: string; 139 | config: string; 140 | operation: Operation; 141 | arguments: string[]; 142 | } 143 | 144 | function isOperationCommand(op?: string): boolean { 145 | return op === "add" || op === "rm"; 146 | } 147 | 148 | function getTerms(args: ProjectorOpts): string[] { 149 | if (isOperationCommand(args.arguments?.[0])) { 150 | return args.arguments?.slice(1) || []; 151 | } 152 | 153 | return args.arguments || []; 154 | } 155 | 156 | function getOperation(args: ProjectorOpts): Operation { 157 | switch (args.arguments?.[0]) { 158 | case "add": return Operation.Add; 159 | case "rm": return Operation.Remove; 160 | default: return Operation.Print; 161 | } 162 | } 163 | 164 | function getConfig() { 165 | return path.join(process.env.XDG_CONFIG_HOME, "projector", "projector.json"); 166 | } 167 | 168 | export default function projector(opts: ProjectorOpts): Projector { 169 | return { 170 | pwd: opts.pwd || process.cwd(), 171 | config: opts.config || getConfig(), 172 | operation: getOperation(opts), 173 | arguments: getTerms(opts), 174 | }; 175 | } 176 | ``` 177 | 178 |
179 |
180 |
181 |
182 |
183 |
184 |
185 |
186 |
187 |
188 |
189 |
190 |
191 |
192 |
193 |
194 | 195 | -------------------------------------------------------------------------------- /lessons/04-cli/A-typescript-cli.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "TypeScript : CLI" 3 | description: "Lets build the cli argument portion of the typescript program" 4 | --- 5 | 6 | ### REMEMBER: Breaking the problem up 7 | 8 | ``` 9 | 1. Creating the CLI options. These are things we can parse 10 | from the command line interface. 11 | 12 | +----------+ 13 | | cli opts | -> 14 | +----------+ 15 | ``` 16 | 17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 | 34 | ### TypeScript 35 | I am going to make some basic assumptions that everyone watching this is 36 | familiar with TypeScript and there are probably some of you out there that are 37 | even better than me at it. 38 | 39 | ### CLI Argument parsing 40 | There is no reason why you cannot use any other argument parser. I literally 41 | saw about 15 of them and played a game of pick whichever one felt good in the 42 | moment! 43 | 44 | - NodeJS : command-line-args 45 | - simple, easy to use. 46 | 47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 | 64 | ### Lets setup our project 65 | To make it easy, set it up any way you want. Personally here is how my folder 66 | structer will work. 67 | 68 | ```bash 69 | ~/projects/projector-typescript 70 | ~/projects/projector-go 71 | ~/projects/projector-rust 72 | ``` 73 | 74 | Reason being is that this will make it easiest for me to swap back and forth 75 | betwixt the projects 76 | 77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 | 94 | ### I use tmux, btw 95 | 96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 | 113 | ### I use vim, btw 114 | 115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 |
123 |
124 |
125 |
126 |
127 |
128 |
129 |
130 |
131 | 132 | ### I use dvorak, btw 133 | 134 |
135 |
136 |
137 |
138 |
139 |
140 |
141 |
142 |
143 |
144 |
145 |
146 |
147 |
148 |
149 |
150 | 151 | ### I use a Kinesis 360, btw 152 | 153 |
154 |
155 |
156 |
157 |
158 |
159 |
160 |
161 |
162 |
163 |
164 |
165 |
166 |
167 |
168 |
169 | 170 | ### So to get started lets create the project 171 | Create it where you like 172 | 173 | ```bash 174 | yarn init -y 175 | yarn add command-line-args @types/command-line-args 176 | yarn add ts-node typescript @types/node 177 | git init 178 | echo "node_modules" > .gitignore 179 | mkdir src 180 | vim . # you can open up other less awesome editors 181 | ``` 182 | 183 | Yes, of course i am not going to setup a build to make this into an shebang'd 184 | script. 185 | 186 | ### Example of CLI (wont match exactly) 187 | 188 | Here is an example i'll be putting under `src/opts.ts` 189 | 190 | ```typescript 191 | import cli from "command-line-args"; 192 | 193 | export type ProjectorOptions = { 194 | pwd?: string; // projector --pwd ... 195 | config?: string; // projector --config ... 196 | arguments?: string[]; 197 | } 198 | 199 | export default function getOptions(): ProjectorOptions { 200 | return cli([ 201 | { name: 'config', type: String }, 202 | { name: 'pwd', type: String }, 203 | { name: 'arguments', type: String, defaultOption: true, multiple: true }, 204 | ]) as ProjectorOptions; 205 | } 206 | ``` 207 | 208 |
209 |
210 |
211 |
212 |
213 |
214 |
215 |
216 |
217 |
218 |
219 |
220 |
221 |
222 |
223 |
224 | 225 | ### So lets build it again, but in Go! 226 | This is what I think is very valuable. You have a concrete idea of what we are 227 | doing, now we do it in a language you are not familiar. 228 | 229 |
230 |
231 |
232 |
233 |
234 |
235 |
236 |
237 |
238 |
239 |
240 |
241 |
242 |
243 |
244 |
245 | 246 | -------------------------------------------------------------------------------- /lessons/05-config/C-rust-projector-object.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Rust : Projector Object" 3 | description: "The greatest language ever created by man" 4 | --- 5 | 6 | 7 | ### Do you really need to see this? 8 | 9 | ``` 10 | +----------+ +----------+ 11 | | cli opts | -> | project | -+-> 12 | | (done) | | config | | 13 | +----------+ +----------+ | 14 | | 15 | +-> 16 | | 17 | | 18 | | 19 | +-> 20 | 21 | 22 | ``` 23 | 24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 | 41 | #### We may not be doing this the canonical way, but its the fun way 42 | We are going to do this a bit weird, but I think its fantastic. It will really 43 | show you the power of the trait system. 44 | 45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 | 62 | ### Lets build the Projector object 63 | 64 | ```bash 65 | src/projector/config.go 66 | ``` 67 | 68 | LETS BUILD!!! 69 | 70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 | 87 | #### The code! 88 | 89 | ```rust 90 | use std::path::PathBuf; 91 | 92 | use crate::opts::CLIOptions; 93 | use anyhow::{Result, anyhow, Context}; 94 | 95 | pub enum Operation { 96 | Print(Option), 97 | Add((String, String)), 98 | Remove(String), 99 | } 100 | 101 | pub struct ProjectorConfig { 102 | pub operation: Operation, 103 | pub config: PathBuf, 104 | pub pwd: PathBuf, 105 | } 106 | 107 | impl TryFrom> for Operation { 108 | type Error = anyhow::Error; 109 | 110 | fn try_from(mut value: Vec) -> Result { 111 | if value.len() == 0 { 112 | return Ok(Operation::Print(None)); 113 | } 114 | 115 | let term = value.get(0).unwrap(); 116 | 117 | if term == "add" { 118 | if value.len() != 3 { 119 | return Err(anyhow!("expected 2 arguments but you provided {}", value.len() - 1)); 120 | } 121 | let mut drain = value.drain(1..=2); 122 | return Ok(Operation::Add((drain.next().unwrap(), drain.next().unwrap()))); 123 | } 124 | 125 | if term == "rm" { 126 | if value.len() != 2 { 127 | return Err(anyhow!("expected 1 arguments but you provided {}", value.len() - 1)); 128 | } 129 | let mut drain = value.drain(1..2); 130 | return Ok(Operation::Remove(drain.next().unwrap())); 131 | } 132 | 133 | if value.len() != 1 { 134 | return Err(anyhow!("expected 0 or 1 arguments but you provided {}", value.len())); 135 | } 136 | 137 | return Ok(Operation::Print(Some(term.clone()))); 138 | } 139 | } 140 | 141 | fn get_config(config: Option) -> Result { 142 | if let Some(c) = config { 143 | return Ok(c); 144 | } 145 | 146 | if let Ok(home) = std::env::var("XDG_CONFIG_HOME") { 147 | 148 | let mut home = PathBuf::from(home); 149 | home.push("projector"); 150 | home.push("projector.json"); 151 | return Ok(home); 152 | } 153 | 154 | if let Ok(home) = std::env::var("HOME") { 155 | let mut home = PathBuf::from(home); 156 | home.push("projector"); 157 | home.push("projector.json"); 158 | return Ok(home); 159 | } 160 | 161 | return Err(anyhow!("unable to find config location")); 162 | } 163 | 164 | fn get_pwd(pwd: Option) -> Result { 165 | if let Some(p) = pwd { 166 | return Ok(p); 167 | } 168 | 169 | return std::env::current_dir().context("unable to get std::env::current_dir"); 170 | } 171 | 172 | pub fn get_projector_config(opts: CLIOptions) -> Result { 173 | return Ok(ProjectorConfig { 174 | operation: opts.arguments.try_into()?, 175 | config: get_config(opts.config)?, 176 | pwd: get_pwd(opts.pwd)?, 177 | }); 178 | } 179 | ``` 180 | 181 |
182 |
183 |
184 |
185 |
186 |
187 |
188 |
189 |
190 |
191 |
192 |
193 |
194 |
195 |
196 |
197 | 198 | -------------------------------------------------------------------------------- /lessons/03-the-project/A-what-are-we-building.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "What are we building?" 3 | description: "The what and how of what we are going to build" 4 | --- 5 | 6 | ### Projector 7 | A simple CLI application that stores, deletes, or presents variables based on 8 | the current working directory or a path provided. 9 | 10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 | 27 | #### Simple Example 28 | 29 | ##### Print All Values 30 | ```bash 31 | > cd /foo/bar 32 | > /foo/bar> projector 33 | > {} 34 | ``` 35 | 36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 | 53 | 54 | ##### Add/Get Value 55 | ```bash 56 | > /foo/bar> projector add foo bar 57 | > /foo/bar> projector 58 | > {"foo": "bar"} 59 | > /foo/bar> projector foo 60 | > bar 61 | ``` 62 | 63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 | 80 | 81 | ##### Merging Data 82 | ```bash 83 | > /foo/bar> cd baz 84 | > /foo/bar/baz> projector 85 | > {"foo": "bar"} 86 | > /foo/bar/baz> projector foo 87 | > bar 88 | > /foo/bar/baz> projector add foo twitch 89 | > /foo/bar/baz> projector 90 | > {"foo": "twitch"} 91 | > cd .. 92 | > /foo/bar> projector 93 | > {"foo": "bar"} 94 | > /foo/bar> projector add bar baz 95 | > /foo/bar> cd baz 96 | > /foo/bar/baz> projector 97 | > { 98 | "foo": "twitch", # from /foo/bar/baz 99 | "bar": "baz" # from /foo/bar 100 | } 101 | > /foo/bar/baz> projector bar 102 | > baz 103 | ``` 104 | 105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 | 122 | ### Breaking the problem up 123 | 124 | ``` 125 | 126 | +----------+ +----------+ +----------+ +----------+ 127 | | cli opts | -> | project | -+-> | print | -> | display | 128 | +----------+ | config | | +----------+ +----------+ 129 | +----------+ | 130 | | +----------+ +----------+ 131 | +-> | add | -> | save | 132 | | +----------+ +----------+ 133 | | 134 | | +----------+ +----------+ 135 | +-> | rm | -> | save | 136 | +----------+ +----------+ 137 | 138 | ``` 139 | 140 |
141 |
142 |
143 |
144 |
145 |
146 |
147 |
148 |
149 |
150 |
151 |
152 |
153 |
154 |
155 |
156 | 157 | ### CLI Arg Parsing 158 | we are going to use libraries for these. 159 | 160 | - NodeJS : command-line-args 161 | - simple, easy to use. 162 | 163 | - Golang : github.com/hellflame/argparse 164 | - A bit of config, but becomes easy once you get it 165 | 166 | - Rust : clap 167 | - The greatest CLI parser by the mostest 168 | 169 |
170 |
171 |
172 |
173 |
174 |
175 |
176 |
177 |
178 |
179 |
180 |
181 |
182 |
183 |
184 |
185 | 186 | ### Finding key 187 | This is the fun part! The _algorithm_ part. 188 | 189 | ```bash 190 | /foo/bar/baz/path/to/folder projector 191 | 192 | # look for entries with 193 | /foo/bar/baz/path/to/folder 194 | /foo/bar/baz/path/to 195 | /foo/bar/baz/path 196 | /foo/bar/baz 197 | /foo/bar 198 | /foo 199 | / 200 | 201 | # Merge each of the value set together from right to left, 202 | # left being lowest priority 203 | ``` 204 | 205 | #### The algo is the same for specific keys 206 | 207 | ```bash 208 | /foo/bar/baz/path/to/folder projector foo 209 | 210 | # look for entries with 211 | /foo/bar/baz/path/to/folder # no foo 212 | /foo/bar/baz/path/to # no foo 213 | /foo/bar/baz/path # found foo < return value now 214 | ``` 215 | 216 | ### Deleting/Adding Key 217 | For deleting/adding the key, we should probably only delete at the `pwd`. 218 | 219 | #### Does deleting coming before adding trigger your ocd? 220 | Buckle up 221 | 222 |
223 |
224 |
225 |
226 |
227 |
228 |
229 |
230 |
231 |
232 |
233 |
234 |
235 |
236 |
237 |
238 | 239 | -------------------------------------------------------------------------------- /lessons/01-introduction/B-getting-started.md: -------------------------------------------------------------------------------- 1 | ## How we will crush such great feats 2 | We are going to be cruising through a small cli program that will help you be 3 | able to grasp these languages. 4 | 5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 | 22 | ### Before we begin, lets take a quick poll. 23 | * Who came here to learn rust, potentially better? 24 | * How much rust do you know? 25 | * Who came here to learn go, potentially better? 26 | * How much go do you know? 27 | * Who knows ts? 28 | 29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 | 46 | ## Where did this course come from? 47 | As I have mentioned i stream and make yt videos. I get asked this question constantly. 48 | 49 | * What should I learn next? 50 | * How should I learn it? 51 | * What should I build? 52 | * Where do I start? 53 | 54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 | 71 | ## I love building cli apps 72 | They are a great way to get to learn the ecosystem. 73 | 74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 | 91 | ## Roadmap 92 | ### Part 1 : Getting familiar 93 | Go and its simplicity
94 | Rust and its complexity
95 | Testing in each language
96 | Strategies of testing
97 | 98 |
99 | 100 | ### Part 2 : First look 101 | Some basics about each language + some simple string parsing problems. This 102 | will help us just get acquainted with each language. 103 | 104 |
105 | 106 | ### Part 3 : CLI Application - Parsing command line arguments 107 | We will start the cli program the best way we can, CLI arguments. This is 108 | where the rubber will meet the road. 109 | 110 |
111 | 112 | ### Part 4 : Building the core of the program 113 | The operations our program needs to perform. 114 | 115 |
116 | 117 | ### Part 7 : Conclusion 118 | Where to go next 119 | 120 |
121 | 122 |
123 |
124 |
125 |
126 |
127 |
128 |
129 |
130 |
131 |
132 |
133 |
134 |
135 |
136 |
137 |
138 | 139 | ## My One Regret 140 | The hardest part about making this talk is that there isnt going to be 141 | concurrency. Which means we don't get the opportunity to explore go routines 142 | and tokio tears. This is where Golang will quickly become your most favorited 143 | language of all time. 144 | 145 |
146 | 147 | The main problem is that things quickly explode the moment you go into web 148 | world with complexity. If you are trying to learn rust, I do feel like web 149 | world kind of sucks to start in. 150 | 151 |
152 | 153 | ### Why I didn't 154 | Rust will likely eat up most of our time. Not because we have to write a 155 | significant amount of code, but because its so hard at first. So to set us up 156 | for success, I wanted confine this to a specific boundary set that is most 157 | consumable from a typescript perspective. 158 | 159 |
160 | 161 | ### The second thing I omitted 162 | I will not be doing any lifetimes with rust. The primary reason is that you 163 | don't run into them a lot until you really drill down for performance. Since 164 | the goal is to teach / expose we will stay more on the happy path. 165 | 166 |
167 | 168 | ### Canonical 169 | So since there is going to be three languages used, in some sense I will keep 170 | the implementations similar. But in other ways I will try to use more of the 171 | "canonical" way of doing things. Canonical looks differently in each language 172 | and javascript, I am unsure if there is such things as canonical. 173 | 174 |
175 |
176 |
177 |
178 |
179 |
180 |
181 |
182 |
183 |
184 |
185 |
186 |
187 |
188 |
189 |
190 | 191 | ## So its time 192 | 193 |
194 |
195 |
196 |
197 |
198 |
199 |
200 |
201 |
202 |
203 |
204 |
205 |
206 |
207 |
208 |
209 | 210 | ## WAIT 211 | do you have rust installed? 212 | do you have go installed? 213 | 214 | Now would be an excellent time to get that done. In fact, I think we should 215 | take a quick small break just to ensure it happens. 216 | 217 | ### Go 218 | [Go Dev Docs](https://go.dev/doc/install) (https://go.dev/doc/install) 219 | - Install the one for your operating system 220 | - You will want a later version of go. 1.18 has generics, very good. 221 | 222 | ### Rust 223 | ``` 224 | curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh 225 | ``` 226 | - You can rustup to get nightly to have more fun features, and there are some 227 | good features there. 228 | - or just stick to stable for a stable journey. 229 | 230 |
231 |
232 |
233 |
234 |
235 |
236 |
237 |
238 |
239 |
240 |
241 |
242 |
243 |
244 |
245 |
246 | 247 | -------------------------------------------------------------------------------- /data/lesson.js: -------------------------------------------------------------------------------- 1 | import path from "path"; 2 | import fs from "fs/promises"; 3 | import * as matter from "gray-matter"; 4 | import { titleCase } from "title-case"; 5 | import { marked } from "marked"; 6 | import hljs from "highlight.js"; 7 | 8 | marked.setOptions({ 9 | baseUrl: process.env.BASE_URL ? process.env.BASE_URL + "/" : "/", 10 | highlight: function (code, lang) { 11 | const language = hljs.getLanguage(lang) ? lang : "plaintext"; 12 | return hljs.highlight(code, { language }).value; 13 | }, 14 | langPrefix: "hljs language-", 15 | }); 16 | 17 | const DEFAULT_ICON = "info-circle"; 18 | const lessonsPath = path.join(process.env.ROOT, "lessons"); 19 | 20 | function getTitle(slug, override) { 21 | let title = override; 22 | if (!title) { 23 | title = titleCase(slug.split("-").join(" ")); 24 | } 25 | 26 | return title; 27 | } 28 | 29 | async function getMeta(section) { 30 | let meta = {}; 31 | try { 32 | const file = await fs.readFile( 33 | path.join(lessonsPath, section, "meta.json") 34 | ); 35 | meta = JSON.parse(file.toString()); 36 | } catch (e) { 37 | // no meta.json, nothing to do 38 | } 39 | 40 | return meta; 41 | } 42 | 43 | function slugify(inputPath) { 44 | const pathParts = inputPath.split("-"); 45 | const pathOrder = pathParts.shift(); 46 | const pathSlug = pathParts.join("-"); 47 | return { 48 | slug: pathSlug, 49 | order: pathOrder, 50 | title: titleCase(pathParts.join(" ")), 51 | }; 52 | } 53 | 54 | export async function getLessons() { 55 | const dir = await fs.readdir(lessonsPath); 56 | const sections = []; 57 | 58 | for (let dirFilename of dir) { 59 | const dirStats = await fs.lstat(path.join(lessonsPath, dirFilename)); 60 | 61 | if (dirStats.isFile()) { 62 | continue; 63 | } 64 | 65 | const lessonsDir = await fs.readdir(path.join(lessonsPath, dirFilename)); 66 | 67 | let { 68 | title: sectionTitle, 69 | order: sectionOrder, 70 | slug: sectionSlug, 71 | } = slugify(dirFilename); 72 | 73 | let icon = DEFAULT_ICON; 74 | 75 | const meta = await getMeta(dirFilename); 76 | if (meta.title) { 77 | sectionTitle = meta.title; 78 | } 79 | if (meta.icon) { 80 | icon = meta.icon; 81 | } 82 | 83 | const lessons = []; 84 | for (let lessonFilename of lessonsDir) { 85 | if (lessonFilename.slice(-3) !== ".md") { 86 | continue; 87 | } 88 | 89 | const filePath = path.join(lessonsPath, dirFilename, lessonFilename); 90 | 91 | const file = await fs.readFile(filePath); 92 | const { data } = matter(file.toString()); 93 | let slug = lessonFilename.replace(/\.md$/, ""); 94 | 95 | const slugParts = slug.split("-"); 96 | const lessonOrder = slugParts.shift(); 97 | 98 | slug = slugParts.join("-"); 99 | 100 | const title = getTitle(slug, data.title); 101 | 102 | lessons.push({ 103 | slug, 104 | fullSlug: `/lessons/${sectionSlug}/${slug}`, 105 | title, 106 | order: `${sectionOrder}${lessonOrder}`, 107 | path: filePath, 108 | }); 109 | } 110 | 111 | sections.push({ 112 | icon, 113 | title: sectionTitle, 114 | slug: sectionSlug, 115 | lessons, 116 | order: sectionOrder, 117 | }); 118 | } 119 | 120 | return sections; 121 | } 122 | 123 | export async function getLesson(targetDir, targetFile) { 124 | const dir = await fs.readdir(lessonsPath); 125 | 126 | for (let i = 0; i < dir.length; i++) { 127 | const dirPath = dir[i]; 128 | if (dirPath.endsWith(targetDir)) { 129 | const lessonDir = ( 130 | await fs.readdir(path.join(lessonsPath, dirPath)) 131 | ).filter((str) => str.endsWith(".md")); 132 | 133 | for (let j = 0; j < lessonDir.length; j++) { 134 | const slugPath = lessonDir[j]; 135 | if (slugPath.endsWith(targetFile + ".md")) { 136 | const filePath = path.join(lessonsPath, dirPath, slugPath); 137 | const file = await fs.readFile(filePath); 138 | const { data, content } = matter(file.toString()); 139 | const html = marked(content); 140 | const title = getTitle(targetFile, data.title); 141 | const meta = await getMeta(dirPath); 142 | 143 | const section = getTitle(targetDir, meta.title); 144 | const icon = meta.icon ? meta.icon : DEFAULT_ICON; 145 | 146 | let nextSlug; 147 | let prevSlug; 148 | 149 | // get next 150 | if (lessonDir[j + 1]) { 151 | // has next in section 152 | const { slug: next } = slugify(lessonDir[j + 1]); 153 | nextSlug = `${targetDir}/${next.replace(/\.md$/, "")}`; 154 | } else if (dir[i + 1]) { 155 | // has next in next section 156 | const nextDir = ( 157 | await fs.readdir(path.join(lessonsPath, dir[i + 1])) 158 | ).filter((str) => str.endsWith(".md")); 159 | const nextDirSlug = slugify(dir[i + 1]).slug; 160 | const nextLessonSlug = slugify(nextDir[0]).slug.replace( 161 | /\.md$/, 162 | "" 163 | ); 164 | nextSlug = `${nextDirSlug}/${nextLessonSlug}`; 165 | } else { 166 | // last section 167 | nextSlug = null; 168 | } 169 | 170 | // get prev 171 | if (lessonDir[j - 1]) { 172 | // has prev in section 173 | const { slug: prev } = slugify(lessonDir[j - 1]); 174 | prevSlug = `${targetDir}/${prev.replace(/\.md$/, "")}`; 175 | } else if (dir[i - 1]) { 176 | // has prev in prev section 177 | const prevDir = ( 178 | await fs.readdir(path.join(lessonsPath, dir[i - 1])) 179 | ).filter((str) => str.endsWith(".md")); 180 | const prevDirSlug = slugify(dir[i - 1]).slug; 181 | const prevLessonSlug = slugify( 182 | prevDir[prevDir.length - 1] 183 | ).slug.replace(/\.md$/, ""); 184 | prevSlug = `${prevDirSlug}/${prevLessonSlug}`; 185 | } else { 186 | // first section 187 | prevSlug = null; 188 | } 189 | 190 | const base = process.env.BASE_URL ? process.env.BASE_URL : "/"; 191 | 192 | return { 193 | attributes: data, 194 | html, 195 | slug: targetFile, 196 | title, 197 | section, 198 | icon, 199 | filePath, 200 | nextSlug: nextSlug ? path.join(base, "lessons", nextSlug) : null, 201 | prevSlug: prevSlug ? path.join(base, "lessons", prevSlug) : null, 202 | }; 203 | } 204 | } 205 | } 206 | } 207 | 208 | return false; 209 | } 210 | -------------------------------------------------------------------------------- /lessons/07-projector/B-golang-projector.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Golang Projector" 3 | description: "Now its time to unleash the inner gopher" 4 | --- 5 | 6 | 7 | ### We are entering into the actual project part 8 | So now its time to do the actual application programming. 9 | 10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 | 27 | ### The ascii diagram again. 28 | 29 | ``` 30 | v <-- you are here (still) 31 | (but now you are a gopher) 32 | +----------+ +----------+ +----------+ +----------+ 33 | | cli opts | -> | project | -+-> | print | -> | display | 34 | +----------+ | config | | +----------+ +----------+ 35 | +----------+ | 36 | | +----------+ +----------+ 37 | +-> | add | -> | save | 38 | | +----------+ +----------+ 39 | | 40 | | +----------+ +----------+ 41 | +-> | rm | -> | save | 42 | +----------+ +----------+ 43 | 44 | ``` 45 | 46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 | 63 | ### So lets create the file (pkg/projector/projector.go) 64 | 65 | ```bash 66 | > pkg/projector/projector.go 67 | ``` 68 | 69 | GOPHER TIME 70 | 71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 | 88 | ### Here is what the code could look like 89 | 90 | ```go 91 | package projector 92 | 93 | import ( 94 | "encoding/json" 95 | "os" 96 | "path" 97 | ) 98 | 99 | type Lookup = map[string]map[string]string 100 | type ProjectorData struct { 101 | Projector Lookup `json:"projector"` 102 | } 103 | 104 | type Projector struct { 105 | data ProjectorData 106 | config *ProjectorConfig 107 | } 108 | 109 | func (p *Projector) GetValue(key string) (string, bool) { 110 | found := false 111 | out := "" 112 | 113 | curr := p.config.Pwd 114 | prev := "" 115 | 116 | for { 117 | 118 | if dir, prs := p.data.Projector[curr]; prs { 119 | if value, prs := dir[key]; prs { 120 | found = true 121 | out = value 122 | break 123 | } 124 | } 125 | 126 | if curr == prev { 127 | break 128 | } 129 | 130 | prev = curr 131 | curr = path.Dir(curr) 132 | } 133 | 134 | return out, found 135 | } 136 | 137 | func (p *Projector) SetValue(key string, value string) { 138 | pwd := p.config.Pwd 139 | if _, prs := p.data.Projector[pwd]; !prs { 140 | p.data.Projector[pwd] = map[string]string{} 141 | } 142 | 143 | p.data.Projector[pwd][key] = value 144 | } 145 | 146 | func (p *Projector) DeleteValue(key string) { 147 | pwd := p.config.Pwd 148 | if dir, prs := p.data.Projector[pwd]; prs { 149 | delete(dir, key) 150 | } 151 | } 152 | 153 | func defaultProjector(config *ProjectorConfig) *Projector { 154 | return &Projector{ 155 | config: config, 156 | data: ProjectorData{}, 157 | } 158 | } 159 | 160 | func FromConfig(config *ProjectorConfig) (*Projector, error) { 161 | if _, err := os.Stat(config.Config); os.IsNotExist(err) { 162 | return defaultProjector(config), nil 163 | } 164 | 165 | bytes, err := os.ReadFile(config.Config) 166 | if err != nil { 167 | return defaultProjector(config), nil 168 | } 169 | 170 | var data ProjectorData 171 | err = json.Unmarshal(bytes, &data) 172 | if err != nil { 173 | return defaultProjector(config), nil 174 | } 175 | return &Projector{ 176 | data, 177 | config, 178 | }, nil 179 | } 180 | ``` 181 | 182 |
183 |
184 |
185 |
186 |
187 |
188 |
189 |
190 |
191 |
192 |
193 |
194 |
195 |
196 |
197 |
198 | 199 | ### Did you skip leg day? 200 | Lets test 201 | 202 |
203 |
204 |
205 |
206 |
207 |
208 |
209 |
210 |
211 |
212 |
213 |
214 |
215 |
216 |
217 |
218 | 219 | ### Your tests could look like 220 | 221 | ```go 222 | package projector_test 223 | 224 | import ( 225 | "testing" 226 | 227 | "github.com/theprimeagen/go-tem/pkg/projector" 228 | ) 229 | 230 | func getData() *projector.ProjectorData { 231 | return &projector.ProjectorData{ 232 | Projector: map[string]map[string]string{ 233 | "/": { 234 | "bar": "buzz", 235 | "foo": "bar1", 236 | }, 237 | "/foo": { 238 | "foo": "bar2", 239 | }, 240 | "/foo/bar": { 241 | "foo": "bar3", 242 | }, 243 | "/foo/bar/baz": { 244 | "foo": "bar4", 245 | }, 246 | }, 247 | } 248 | } 249 | 250 | func getConfig(pwd string) *projector.ProjectorConfig { 251 | return &projector.ProjectorConfig{ 252 | Pwd: pwd, 253 | Config: "dnm", 254 | Operation: projector.Print, 255 | Arguments: []string{}, 256 | } 257 | } 258 | 259 | func TestGetValue(t *testing.T) { 260 | p := projector.NewProjector(getConfig("/foo/bar"), getData()) 261 | 262 | val, ok := p.GetValue("foo") 263 | if !ok { 264 | t.Error("couldn't find value") 265 | } 266 | 267 | if val != "bar3" { 268 | t.Errorf("expected bar3 but got %v", val) 269 | } 270 | 271 | _, ok = p.GetValue("bazbar") 272 | if ok { 273 | t.Error("expected to find no value") 274 | } 275 | 276 | 277 | val, ok = p.GetValue("bar") 278 | if !ok { 279 | t.Error("couldn't find value") 280 | } 281 | 282 | if val != "buzz" { 283 | t.Errorf("expected buzz but got %v", val) 284 | } 285 | } 286 | 287 | func TestSetValue(t *testing.T) { 288 | p := projector.NewProjector(getConfig("/foo/bar"), getData()) 289 | p.SetValue("foo", "bar69") 290 | val, ok := p.GetValue("foo") 291 | 292 | if !ok { 293 | t.Error("couldn't find value") 294 | } 295 | 296 | if val != "bar69" { 297 | t.Errorf("expected bar69 but got %v", val) 298 | } 299 | } 300 | 301 | func TestRemoveValue(t *testing.T) { 302 | p := projector.NewProjector(getConfig("/foo/bar"), getData()) 303 | p.DeleteValue("foo") 304 | val, ok := p.GetValue("foo") 305 | 306 | if !ok { 307 | t.Error("couldn't find value") 308 | } 309 | 310 | if val != "bar2" { 311 | t.Errorf("expected bar2 but got %v", val) 312 | } 313 | } 314 | ``` 315 | 316 |
317 |
318 |
319 |
320 |
321 |
322 |
323 |
324 |
325 |
326 |
327 |
328 |
329 |
330 |
331 |
332 | 333 | ### Crab people 334 | ![Twitch](./images/pust.png) 335 | 336 |
337 |
338 |
339 |
340 |
341 |
342 |
343 |
344 |
345 |
346 |
347 |
348 |
349 |
350 |
351 |
352 | 353 | -------------------------------------------------------------------------------- /lessons/07-projector/A-typescript-projector.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Typescript Projector" 3 | description: "So now its time to create the project" 4 | --- 5 | 6 | 7 | ### We are entering into the actual project part 8 | So now its time to do the actual application programming. 9 | 10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 | 27 | ### The ascii diagram again. 28 | 29 | ``` 30 | v <-- you are here 31 | +----------+ +----------+ +----------+ +----------+ 32 | | cli opts | -> | project | -+-> | print | -> | display | 33 | +----------+ | config | | +----------+ +----------+ 34 | +----------+ | 35 | | +----------+ +----------+ 36 | +-> | add | -> | save | 37 | | +----------+ +----------+ 38 | | 39 | | +----------+ +----------+ 40 | +-> | rm | -> | save | 41 | +----------+ +----------+ 42 | 43 | ``` 44 | 45 | So this is where the bulk of the program will exist. 46 | 47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 | 64 | ### So lets create the file (src/projector.ts) 65 | 66 | ```bash 67 | > src/projector.ts 68 | ``` 69 | 70 | Lets get a codin! Also remember, we want to build this is a way that makes it 71 | easy to test. 72 | 73 | 74 | Also, we will build the full projector object (more of a note for me than you) 75 | 76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 | 93 | ### Here is what the code could look like 94 | 95 | ```typescript 96 | import fs from "fs"; 97 | import path from "path"; 98 | import { ProjectorConfig } from "./config"; 99 | 100 | type ProjectorData = { 101 | // todo: if we had other top level items, we could put them here 102 | // such as settings or links 103 | projector: { 104 | [key: string]: { 105 | [key: string]: string 106 | } 107 | } 108 | } 109 | 110 | type Value = string | undefined; 111 | 112 | const DEFAULT_VALUE = {projector: {}} as ProjectorData; 113 | export class Projector { 114 | constructor(private config: ProjectorConfig, 115 | private data: ProjectorData = DEFAULT_VALUE) { } 116 | 117 | getValue(key: string): Value { 118 | // pwd 119 | // dirname(pwd) until empty 120 | let prev: Value = undefined; 121 | let curr = this.config.pwd; 122 | 123 | let out: Value = undefined; 124 | do { 125 | 126 | let val = this.data.projector[curr]?.[key]; 127 | if (val !== undefined) { 128 | out = val; 129 | break; 130 | } 131 | 132 | prev = curr; 133 | curr = path.dirname(curr); 134 | } while (prev !== curr); 135 | 136 | return out; 137 | } 138 | 139 | setValue(key: string, value: string) { 140 | let pwd = this.config.pwd; 141 | if (!this.data.projector[pwd]) { 142 | this.data.projector[pwd] = {}; 143 | } 144 | 145 | this.data.projector[pwd][key] = value; 146 | } 147 | 148 | deleteValue(key: string) { 149 | delete this.data.projector[this.config.pwd]?.[key]; 150 | } 151 | 152 | static fromConfig(config: ProjectorConfig): Projector { 153 | let data: ProjectorData = undefined; 154 | try { 155 | if (fs.existsSync(config.config)) { 156 | data = JSON.parse(fs.readFileSync(config.config).toString()); 157 | } 158 | } catch { 159 | data = undefined; 160 | } 161 | 162 | return new Projector(config, data); 163 | } 164 | } 165 | ``` 166 | 167 |
168 |
169 |
170 |
171 |
172 |
173 |
174 |
175 |
176 |
177 |
178 |
179 |
180 |
181 |
182 |
183 | 184 | ### Did you eat your vegetables? 185 | Lets test 186 | 187 |
188 |
189 |
190 |
191 |
192 |
193 |
194 |
195 |
196 |
197 |
198 |
199 |
200 |
201 |
202 |
203 | 204 | ### Your tests could look like 205 | 206 | ```typescript 207 | import { Operation } from "../config"; 208 | import { Projector } from "../projector"; 209 | 210 | function getConfig(pwd: string) { 211 | return { 212 | pwd, 213 | config: "/foo/bar/baz", 214 | operation: Operation.Add, 215 | arguments: [], 216 | }; 217 | } 218 | 219 | function getData() { 220 | return { 221 | projector: { 222 | "/foo/bar/baz/buzz": { 223 | "foo": "bar1" 224 | }, 225 | "/foo/bar/baz": { 226 | "foo": "bar2" 227 | }, 228 | "/foo/bar": { 229 | "foo": "bar3" 230 | }, 231 | "/foo": { 232 | "foo": "bar4" 233 | }, 234 | "/": { 235 | "foo": "bar5", 236 | "bar": "bazz1", 237 | }, 238 | } 239 | } 240 | } 241 | 242 | test("getting values", function() { 243 | const projector = new Projector(getConfig("/foo/bar"), getData()); 244 | 245 | expect(projector.getValue("foo")).toEqual("bar3"); 246 | expect(projector.getValue("blaz")).toEqual(undefined); 247 | expect(projector.getValue("bar")).toEqual("bazz1"); 248 | }); 249 | 250 | test("setting values", function() { 251 | const projector = new Projector(getConfig("/foo/bar"), getData()); 252 | 253 | expect(projector.getValue("foo")).toEqual("bar3"); 254 | projector.setValue("foo", "barNever"); 255 | expect(projector.getValue("foo")).toEqual("barNever"); 256 | 257 | const p2 = new Projector(getConfig("/foo"), getData()); 258 | expect(p2.getValue("foo")).toEqual("bar4"); 259 | 260 | const p3 = new Projector(getConfig("/foo/bar/baz"), getData()); 261 | expect(p3.getValue("foo")).toEqual("bar2"); 262 | }); 263 | 264 | test("deleting values", function() { 265 | const projector = new Projector(getConfig("/foo/bar/baz"), getData()); 266 | 267 | expect(projector.getValue("foo")).toEqual("bar2"); 268 | projector.deleteValue("foo"); 269 | expect(projector.getValue("foo")).toEqual("bar3"); 270 | projector.deleteValue("foo"); 271 | expect(projector.getValue("foo")).toEqual("bar3"); 272 | 273 | const p2 = new Projector(getConfig("/foo/bar"), getData()); 274 | expect(p2.getValue("foo")).toEqual("bar3"); 275 | p2.deleteValue("foo"); 276 | expect(p2.getValue("foo")).toEqual("bar4"); 277 | }); 278 | ``` 279 | 280 |
281 |
282 |
283 |
284 |
285 |
286 |
287 |
288 |
289 |
290 |
291 |
292 |
293 |
294 |
295 |
296 | 297 | ### Onto the Gopher! 298 | 299 |
300 |
301 |
302 |
303 |
304 |
305 |
306 |
307 |
308 |
309 |
310 |
311 |
312 |
313 |
314 |
315 | 316 | 317 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

next-course-starter


2 | 3 |

4 | A NextJS starter to get you started creating educational materials using Markdown 5 |

6 | 7 | ## Get Started 8 | 9 | 1. Set up Node.js v14+ 10 | 1. Clone this repo 11 | 1. Run `npm install` 12 | 1. Run `npm run dev` to start the dev server 13 | 1. Open http://localhost:3000 in a browser 14 | 15 | ## Configure Your Course 16 | 17 | There are several things to configure before getting started. 18 | 19 | ### course.json 20 | 21 | This json file allows you to configure the details of the site. Update the info here and it'll update it everywhere throughout the course website. 22 | 23 | - _author.name_ – Your name 24 | - _author.company_ – The company you work at or whatever you want as your subtitle. Optional. 25 | - _title_ – The title of your course 26 | - _subtitle_ – The subtitle of your course. Optional. 27 | - _frontendMastersLink_ – A link to the published video on FrontendMasters.com. Optional. 28 | - _social.linkedin_ - Your LinkedIn public user name, just the name, not the full link. Optional 29 | - _social.twitter_ – Your Twitter user name. Optional. 30 | - _social.github_ – Your GitHub user name. Optional. 31 | - _description_ – The description you want to show up in search engine results. 32 | - _keywords_ – The SEO keywords for this course. An array of strings 33 | - _productionBaseUrl_ – Typically useful for GitHub Pages. This adds a base path to your project. For GitHub Pages, this will be the name of your repo. For example, this site's base URL is `/next-course-starter` because the production URL for this site is `btholt.github.io/next-course-starer`. Do note this will also make your dev server's base URL this as well so you can catch problems before they go to production. 34 | 35 | ### styles/variables.css 36 | 37 | Here is where you can theme your site. You can retheme the whole site with just these. 38 | 39 | ### public/images 40 | 41 | Here is where you should stick all your images. Inside of your markdown, refer to images in this folder as `./images/`. 42 | 43 | Note this site doesn't use `next/image` because that requires the server component. 44 | 45 | ### public/images/author.jpg 46 | 47 | Your image. If you call it this, you won't have to change any code. If you need to change it, it's in `pages/index.js`. 48 | 49 | ### public/images/social-share-cover.jpg 50 | 51 | The image that will be used if someone shares your website to Twitter/Facebook/etc. If you call it this, you won't have to change any code. If you do need to change it, it's in `pages/index.js` 52 | 53 | ### public/images/course-icon.png 54 | 55 | The image at the top of the course. If you call it this, you won't have to change any code. If you do need to change it, it's in `pages/index.js` 56 | 57 | ## Lessons 58 | 59 | All your markdown lesson files will go in `lessons/`. They **must** be organized an named this way: 60 | 61 | The folders must be named `01-section-one-name`, `02-section-two-name`, `03-section-three`, etc. 62 | 63 | The lessons are then grouped inside of these, the lessons are ordered by letters, `A-lesson-one.md`, `B-lesson-two.md`, `C-lesson-three.md`, etc. This numbering scheme matches how Frontend Masters organizes their content. 64 | 65 | The titles of your lessons and sections are generated from the folder and lesson names (and can be overridden.) The first, organizing part of the name is stripped (the `01-` part of `01-section-one` and the `A-` part of `A-lesson-one`), the hyphens are turned into spaces (`section-one` becomes `section one`) and then those are run through [title-case](https://github.com/blakeembrey/change-case#titlecase) (so `section one` becomes `Section One`.) If you need to override these, use the frontmatter (explained below.) 66 | 67 | The folder and lesson names are also used for the slugs. `02-section-two/C-lesson-three.md` becomes `yoursite.com/lessons/section-two/lesson-three`. 68 | 69 | Each of these lessons can have a [frontmatter](https://github.com/jonschlinkert/gray-matter#readme) for the following properties 70 | 71 | - _title_ – If you want the title to be different from the file name, you can specify here what that title should be. Frequently this useful for things where the capitalization would be off e.g. TailwindCSS instead of Tailwindcss. Optional. 72 | - _description_ – If you want to give individual lessons descriptions for SEO and for Frontend Masters, you can write a brief description here. 73 | 74 | Be aware because of how the numbers and letters are stripped out, it is possible to have ambigious paths. `01-welcome/A-intro.md` and `03-welcome/D-intro.md` would resolve to the same thing and only the first one would be visitable. 75 | 76 | ## meta.json 77 | 78 | Each **section** (e.g. inside of `03-section-three` folder) folder can have a meta.json file, and is totally optional. 79 | 80 | - _title_ – an override for the title of the section. Frequently useful for capitalization e.g. `JS Tools` instead of `Js Tools`. 81 | - _icon_ – so you can set the icon used in the home page and the header. These icons are pulled from [the free Font Awesome v5 icons](https://fontawesome.com/v5.15/icons). If you want [fa-hammer](https://fontawesome.com/v5.15/icons/hammer), use "hammer" as the value for icon. 82 | 83 | ## highlight.js Theme 84 | 85 | The code blocks use [Highlight.js](https://highlightjs.org/static/demo/). By default it will use `a11y-light` as the theme for the code blocks. Change the CSS import in `pages/_app.js` to the theme you want to use. 86 | 87 | ## GitHub Pages / GitHub Actions 88 | 89 | By default this repo works with GitHub Pages. Just make sure you set the `productionBaseUrl` in the course.json to the name of the repo. 90 | 91 | It also includes a GitHub Action to automatically deploy to your gh-pages branch. Just make sure that your repo has GitHub Pages enabled and the branch is set to gh-pages. If you're not deploying to GitHub Pages go ahead and delete the `.github` directory. 92 | 93 | By default the GitHub Action looks for a `main` branch, so be sure you're using that instead of `master`. 94 | 95 | ## Example Sites 96 | 97 | - [This repo itself](https://btholt.github.io/next-course-starter/) 98 | - [Complete Intro to React v6](https://btholt.github.io/next-react-v6/) 99 | 100 | ## CSV 101 | 102 | **Not implemented yet, but coming soon**. 103 | 104 | If you run `npm run csv`, a CSV will be generated with all the various lessons' frontmatter outputted to `public/lessons.csv`. You can change the path by changing the `OUTPUT_CSV_PATH` environment variable. 105 | 106 | Another CSV will be output to `public/links.csv` where it pull all the links out of each lesson and put them into a CSV. This path can be modified by setting the `LINKS_CSV_PATH` environment variable. 107 | 108 | ## npm commands 109 | 110 | - `npm run dev` - Next's dev command. Start a local dev server. Note if you have a productionBasePath set in your course.json, your dev server will respect that (so you don't mess up your paths in production.) 111 | - `npm run build` - Build your site for production. This will still include the Next.js server run time. Use this if you're using something like Vercel to host your site. 112 | - `npm run export` - Builds your site statically, use this if you're going to deploy to GitHub Pages, S3, or somewhere else with no server. This will run next build and then next export (no need to run build yourself first.) 113 | - `npm run start` - Start an already-built server. 114 | 115 | ## License 116 | 117 | The **code** is this repo is licensed under the Apache 2.0 license. 118 | 119 | I include the CC-BY-NC-4.0 license for the content; this is what I recommend you license your **content** under: anyone can use and share the content but they cannot sell it; only you can. 120 | -------------------------------------------------------------------------------- /lessons/07-projector/C-rust-project.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Rustlang projector" 3 | description: "Gophers be damned" 4 | --- 5 | 6 | 7 | ### The real language, doing the real work 8 | Lets get rusty 9 | 10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 | 27 | ### The ascii diagram again. 28 | 29 | ``` 30 | v <-- you are here (still) 31 | (but now you are a rustacean) 32 | +----------+ +----------+ +----------+ +----------+ 33 | | cli opts | -> | project | -+-> | print | -> | display | 34 | +----------+ | config | | +----------+ +----------+ 35 | +----------+ | 36 | | +----------+ +----------+ 37 | +-> | add | -> | save | 38 | | +----------+ +----------+ 39 | | 40 | | +----------+ +----------+ 41 | +-> | rm | -> | save | 42 | +----------+ +----------+ 43 | 44 | ``` 45 | 46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 | 63 | ### So lets create the file (src/projector.rs) 64 | 65 | ```bash 66 | > src/projector.rs 67 | ``` 68 | 69 | RUST IT UP 70 | 71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 | 88 | ### Here is what the code could look like 89 | 90 | ```rust 91 | use std::{collections::HashMap, path::PathBuf}; 92 | use serde::{Deserialize, Serialize}; 93 | use crate::config::ProjectorConfig; 94 | 95 | #[derive(Debug, Default, Deserialize, Serialize)] 96 | struct ProjectorData { 97 | projector: HashMap> 98 | } 99 | 100 | pub struct Projector { 101 | config: ProjectorConfig, 102 | data: ProjectorData, 103 | } 104 | 105 | fn default_projector(config: ProjectorConfig) -> Projector { 106 | let data = ProjectorData::default(); 107 | return Projector { 108 | config, 109 | data, 110 | } 111 | } 112 | 113 | impl From for Projector { 114 | fn from(config: ProjectorConfig) -> Self { 115 | if std::fs::metadata(&config.config).is_err() { 116 | return default_projector(config); 117 | } 118 | 119 | if let Ok(data) = std::fs::read_to_string(&config.config) { 120 | let data = serde_json::from_str(&data); 121 | if let Ok(data) = data { 122 | return Projector { 123 | config, 124 | data, 125 | } 126 | } 127 | } 128 | 129 | return default_projector(config); 130 | } 131 | } 132 | 133 | impl Projector { 134 | pub fn get_value(&self, key: &str) -> Option<&String> { 135 | let mut out = None; 136 | let mut curr = Some(self.config.pwd.as_path()); 137 | 138 | while let Some(p) = curr { 139 | if let Some(dir) = self.data.projector.get(p) { 140 | let value = dir.get(key); 141 | if value.is_some() { 142 | out = value; 143 | break; 144 | } 145 | } 146 | curr = p.parent(); 147 | } 148 | 149 | return out; 150 | } 151 | 152 | pub fn set_value(&mut self, key: &str, value: String) { 153 | self.data.projector 154 | .entry(self.config.pwd.clone()) 155 | .or_insert_with(|| HashMap::new()) 156 | .insert(key.to_string(), value); 157 | } 158 | 159 | pub fn delete_value(&mut self, key: &str) { 160 | self.data.projector 161 | .entry(self.config.pwd.clone()) 162 | .or_insert_with(|| HashMap::new()) 163 | .remove(key); 164 | } 165 | } 166 | ``` 167 | 168 |
169 |
170 |
171 |
172 |
173 |
174 |
175 |
176 |
177 |
178 |
179 |
180 |
181 |
182 |
183 |
184 | 185 | ### Forgot the anniversary? 186 | Testing Time 187 | 188 | ```bash 189 | cargo add collection_macros 190 | ``` 191 | 192 |
193 |
194 |
195 |
196 |
197 |
198 |
199 |
200 |
201 |
202 |
203 |
204 |
205 |
206 |
207 |
208 | 209 | ### Your tests could look like 210 | 211 | ```rust 212 | #[cfg(test)] 213 | mod test { 214 | use std::{path::PathBuf, collections::HashMap}; 215 | 216 | use collection_macros::hashmap; 217 | 218 | use crate::config::{ProjectorConfig, Operation}; 219 | 220 | use super::{ProjectorData, Projector}; 221 | 222 | 223 | fn get_config(pwd: PathBuf) -> ProjectorConfig { 224 | return ProjectorConfig { 225 | config: PathBuf::from("/foo"), 226 | operation: Operation::Print(None), 227 | pwd, 228 | } 229 | } 230 | 231 | fn get_data() -> ProjectorData { 232 | return ProjectorData { 233 | projector: hashmap! { 234 | PathBuf::from("/") => hashmap! { 235 | "foo".into() => "bar1".into(), 236 | "bar".into() => "bazz".into(), 237 | }, 238 | PathBuf::from("/foo") => hashmap! { 239 | "foo".into() => "bar2".into() 240 | }, 241 | PathBuf::from("/foo/bar") => hashmap! { 242 | "foo".into() => "bar3".into() 243 | }, 244 | PathBuf::from("/foo/bar/baz") => hashmap! { 245 | "foo".into() => "bar3".into() 246 | }, 247 | }, 248 | } 249 | } 250 | 251 | #[test] 252 | fn get_value() { 253 | let proj = Projector { 254 | data: get_data(), 255 | config: get_config(PathBuf::from("/foo/bar")), 256 | }; 257 | 258 | assert_eq!(proj.get_value("foo"), Some(&String::from("bar3"))); 259 | assert_eq!(proj.get_value("bar"), Some(&String::from("bazz"))); 260 | assert_eq!(proj.get_value("notehu"), None); 261 | } 262 | 263 | #[test] 264 | fn set_value() { 265 | let mut proj = Projector { 266 | data: get_data(), 267 | config: get_config(PathBuf::from("/foo/bar")), 268 | }; 269 | 270 | assert_eq!(proj.get_value("foo"), Some(&String::from("bar3"))); 271 | proj.set_value("foo", "hello, fem".into()); 272 | assert_eq!(proj.get_value("foo"), Some(&String::from("hello, fem"))); 273 | } 274 | 275 | #[test] 276 | fn delete_value() { 277 | let mut proj = Projector { 278 | data: get_data(), 279 | config: get_config(PathBuf::from("/foo/bar")), 280 | }; 281 | 282 | assert_eq!(proj.get_value("foo"), Some(&String::from("bar3"))); 283 | proj.delete_value("foo"); 284 | assert_eq!(proj.get_value("foo"), Some(&String::from("bar2"))); 285 | } 286 | } 287 | ``` 288 | 289 |
290 |
291 |
292 |
293 |
294 |
295 |
296 |
297 |
298 |
299 |
300 |
301 |
302 |
303 |
304 |
305 | 306 | ### ONTO VICTORY!!! 307 | 308 |
309 |
310 |
311 |
312 |
313 |
314 |
315 |
316 |
317 |
318 |
319 |
320 |
321 |
322 |
323 |
324 | 325 | -------------------------------------------------------------------------------- /styles/courses.css: -------------------------------------------------------------------------------- 1 | @import url("https://fonts.googleapis.com/css2?family=Open+Sans:ital,wght@0,400;0,700;1,400;1,700&display=swap"); 2 | 3 | /* mini css reset */ 4 | html { 5 | font-size: 16px; 6 | } 7 | 8 | body, 9 | h1, 10 | h2, 11 | h3, 12 | h4, 13 | h5, 14 | h6, 15 | p, 16 | ol, 17 | ul { 18 | margin: 0; 19 | padding: 0; 20 | font-weight: normal; 21 | } 22 | 23 | img { 24 | max-width: 100%; 25 | height: auto; 26 | } 27 | 28 | * { 29 | box-sizing: border-box; 30 | } 31 | 32 | body { 33 | font-family: "Open Sans"; 34 | background: linear-gradient(90deg, var(--bg-main) 15px, transparent 1%) center, 35 | linear-gradient(var(--bg-main) 15px, transparent 1%) center, var(--bg-dots); 36 | background-size: 16px 16px; 37 | margin: 0; 38 | } 39 | 40 | a { 41 | color: var(--text-links); 42 | text-decoration: none; 43 | } 44 | 45 | .navbar { 46 | border-bottom: 1px solid #ccc; 47 | position: fixed; 48 | width: 100%; 49 | top: 0; 50 | z-index: 10; 51 | display: flex; 52 | justify-content: space-between; 53 | align-items: center; 54 | background-color: var(--bg-main); 55 | padding: 10px; 56 | } 57 | 58 | .navbar h1 { 59 | font-size: 20px; 60 | margin: inherit; 61 | padding: inherit; 62 | font-weight: bold; 63 | color: var(--text-main); 64 | } 65 | 66 | .navbar h2 { 67 | font-size: 14px; 68 | margin: inherit; 69 | margin-left: 15px; 70 | padding: inherit; 71 | text-transform: uppercase; 72 | } 73 | 74 | .navbar-info { 75 | display: flex; 76 | flex-direction: row; 77 | align-items: center; 78 | justify-content: center; 79 | } 80 | 81 | header .cta-btn { 82 | display: none; /* only displays at large screen sizes */ 83 | } 84 | 85 | .main .cta-btn { 86 | width: 90%; 87 | margin: 20px auto 0px auto; 88 | max-width: 500px; 89 | padding: 12px 20px; 90 | } 91 | 92 | .cta-btn { 93 | border-radius: 10px; 94 | background: var(--nav-buttons); 95 | color: var(--nav-buttons-text); 96 | padding: 7px 20px; 97 | display: flex; 98 | justify-content: center; 99 | align-items: center; 100 | } 101 | 102 | .jumbotron { 103 | padding: 0; 104 | } 105 | 106 | .jumbotron .courseInfo, 107 | .jumbotron .courseIcon { 108 | padding: 20px; 109 | } 110 | 111 | .jumbotron .courseInfo, 112 | .jumbotron .courseIcon { 113 | text-align: center; 114 | } 115 | 116 | .author { 117 | margin-top: 40px; 118 | display: flex; 119 | justify-content: center; 120 | } 121 | 122 | @media (min-width: 1000px) { 123 | header .cta-btn { 124 | display: flex; 125 | } 126 | 127 | .main .cta-btn { 128 | display: none; 129 | } 130 | 131 | .jumbotron { 132 | display: flex; 133 | width: 100%; 134 | min-height: 45vh; 135 | } 136 | .jumbotron .courseInfo, 137 | .jumbotron .courseIcon { 138 | display: flex; 139 | justify-content: center; 140 | align-items: center; 141 | } 142 | .jumbotron .courseInfo { 143 | width: 65%; 144 | text-align: right; 145 | } 146 | .jumbotron .courseIcon { 147 | width: 35%; 148 | display: flex; 149 | align-items: center; 150 | justify-content: center; 151 | } 152 | 153 | .author { 154 | justify-content: flex-end; 155 | } 156 | .jumbotron .courseInfo-inner { 157 | max-width: 85%; 158 | } 159 | } 160 | 161 | .jumbotron h1, 162 | .jumbotron h2 { 163 | color: var(--text-main-headers); 164 | } 165 | 166 | .jumbotron h1 { 167 | font-size: 50px; 168 | margin-bottom: 20px; 169 | } 170 | 171 | .jumbotron .courseInfo { 172 | background: var(--primary); 173 | } 174 | 175 | .jumbotron .courseIcon { 176 | background: var(--secondary); 177 | } 178 | 179 | .jumbotron .courseIcon img { 180 | max-width: 180px; 181 | } 182 | 183 | .author .info { 184 | padding: 10px; 185 | } 186 | 187 | .author .name { 188 | font-size: 18px; 189 | font-weight: bold; 190 | color: var(--text-main-headers); 191 | } 192 | 193 | .author .company { 194 | color: var(--text-main-headers); 195 | font-size: 16px; 196 | } 197 | 198 | .author .image { 199 | border-radius: 75px; 200 | overflow: hidden; 201 | height: 75px; 202 | width: 75px; 203 | } 204 | 205 | .navbar-brand.navbar-brand a { 206 | text-transform: uppercase; 207 | font-weight: bold; 208 | color: var(--text-main-headers); 209 | } 210 | 211 | .lesson-section-title { 212 | color: var(--text-main-headers); 213 | } 214 | 215 | .lesson-container { 216 | position: relative; 217 | max-width: 900px; 218 | margin: 0 auto 45px auto; 219 | padding: 10px 40px; 220 | box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.2); 221 | background-color: var(--bg-lesson); 222 | border-radius: 5px; 223 | margin-top: 40px; 224 | } 225 | 226 | .lesson { 227 | margin: 15px; 228 | padding: 15px; 229 | border-radius: 8px; 230 | } 231 | 232 | .lesson > h1 { 233 | color: var(--text-header); 234 | font-size: 24px; 235 | } 236 | 237 | .lesson h2 { 238 | font-size: 24px; 239 | margin-top: 20px; 240 | margin-bottom: 10px; 241 | } 242 | 243 | .lesson h2::after { 244 | content: ""; 245 | display: block; 246 | height: 3px; 247 | margin-top: 5px; 248 | background: var(--text-header); 249 | max-width: 300px; 250 | } 251 | 252 | .lesson p { 253 | clear: both; 254 | } 255 | 256 | .lesson p, 257 | .lesson li { 258 | line-height: 180%; 259 | } 260 | 261 | .lesson-links { 262 | font-size: 18px; 263 | padding: 15px 0; 264 | } 265 | 266 | .next { 267 | float: right; 268 | } 269 | 270 | .prev { 271 | float: left; 272 | } 273 | 274 | .lesson-title { 275 | text-transform: uppercase; 276 | font-weight: bold; 277 | } 278 | 279 | .gatsby-highlight { 280 | padding: 4px; 281 | border-radius: 4px; 282 | display: flex; 283 | justify-content: space-between; 284 | flex-direction: column; 285 | align-items: stretch; 286 | } 287 | 288 | .lesson-content td { 289 | border: 1px solid black; 290 | padding: 8px; 291 | } 292 | 293 | .lesson-content td input { 294 | min-width: 300px; 295 | } 296 | 297 | .lesson-content img { 298 | margin: 5px auto; 299 | display: block; 300 | } 301 | 302 | .lesson-flex { 303 | display: flex; 304 | flex-direction: column; 305 | justify-content: center; 306 | align-items: center; 307 | } 308 | 309 | .random-tweet { 310 | width: 100%; 311 | margin-top: 100px; 312 | } 313 | 314 | .fem-link { 315 | text-align: center; 316 | } 317 | 318 | .content-container { 319 | display: flex; 320 | flex-direction: column; 321 | justify-content: space-between; 322 | min-height: 100vh; 323 | padding-top: 50px; 324 | } 325 | 326 | blockquote { 327 | padding: 15px; 328 | background-color: var(--emphasized-bg); 329 | border: 2px solid var(--emphasized-border); 330 | border-radius: 5px; 331 | width: 100%; 332 | margin: 10px 0; 333 | } 334 | 335 | blockquote > *:last-child { 336 | margin-bottom: 0; 337 | } 338 | 339 | .lesson-content img { 340 | max-width: 100%; 341 | } 342 | 343 | .main-card { 344 | max-width: 900px; 345 | margin: 0 auto; 346 | overflow: hidden; 347 | } 348 | 349 | .lesson-title { 350 | font-size: 20px; 351 | padding: 15px 30px; 352 | } 353 | 354 | .lesson-content { 355 | line-height: 1.5; 356 | } 357 | 358 | .lesson-text { 359 | width: 100%; 360 | padding: 25px 5px 25px 35px; 361 | min-height: 200px; 362 | } 363 | 364 | .sections-name { 365 | margin: 0; 366 | padding: 0; 367 | } 368 | 369 | ol.sections-name { 370 | counter-reset: my-awesome-counter; 371 | list-style: none; 372 | padding-left: 40px; 373 | width: 98%; 374 | margin: 0; 375 | padding: 0; 376 | } 377 | 378 | ol.sections-name > li { 379 | counter-increment: my-awesome-counter; 380 | display: flex; 381 | flex-direction: row; 382 | flex-wrap: wrap; 383 | margin-bottom: 35px; 384 | width: 100%; 385 | box-shadow: 0 4px 10px 0 rgba(0, 0, 0, 0.2); 386 | border-bottom-right-radius: 5px; 387 | border-top-right-radius: 5px; 388 | } 389 | ol.sections-name .lesson-preface { 390 | /* content: counter(my-awesome-counter); */ 391 | color: var(--icons); 392 | display: flex; 393 | position: relative; 394 | align-items: center; 395 | justify-content: center; 396 | background: #93aca7; 397 | font-size: 30px; 398 | padding: 25px; 399 | width: 30%; 400 | } 401 | 402 | .lesson-preface.lesson-preface > svg { 403 | width: 80%; 404 | height: inherit; 405 | max-height: 100px; 406 | } 407 | 408 | ol.sections-name .lesson-preface::before { 409 | content: counter(my-awesome-counter); 410 | position: absolute; 411 | top: 0; 412 | left: 5px; 413 | font-size: 20px; 414 | font-weight: bold; 415 | color: var(--icons); 416 | } 417 | 418 | ol.sections-name .lesson-details { 419 | display: flex; 420 | flex-basis: 100%; 421 | flex: 1; 422 | background: var(--bg-lesson); 423 | position: relative; 424 | } 425 | 426 | .details-bg { 427 | --corner-fill: var(--corner-inactive); 428 | transition: fill 0.25s; 429 | width: 10%; 430 | height: 0; 431 | padding-bottom: 10%; 432 | background-size: cover; 433 | background-repeat: no-repeat; 434 | position: absolute; 435 | top: 0; 436 | right: 0; 437 | } 438 | 439 | .details-bg > svg { 440 | width: 100%; 441 | height: auto; 442 | } 443 | 444 | .details-bg > svg path { 445 | transition: fill 0.25s; 446 | } 447 | 448 | .lesson-details:hover .details-bg, 449 | .lesson-container .details-bg { 450 | --corner-fill: var(--corner-active); 451 | } 452 | 453 | @media (min-width: 1000px) { 454 | ol.sections-name > li::before { 455 | border-bottom-left-radius: 5px; 456 | border-top-left-radius: 5px; 457 | } 458 | ol.sections-name .lesson-details { 459 | border-bottom-right-radius: 5px; 460 | border-top-right-radius: 5px; 461 | } 462 | } 463 | 464 | @media (max-width: 600px) { 465 | .lesson-container { 466 | padding: 2px; 467 | } 468 | } 469 | 470 | .lesson-details h3 { 471 | font-size: 22px; 472 | border-bottom: 1px solid var(--less); 473 | padding-bottom: 10px; 474 | display: inline-block; 475 | font-weight: bold; 476 | margin-bottom: 20px; 477 | } 478 | 479 | .lesson-links { 480 | margin-top: 45px; 481 | margin-bottom: 80px; 482 | } 483 | 484 | .lesson-links a { 485 | border-radius: 10px; 486 | background: var(--nav-buttons); 487 | color: var(--nav-buttons-text); 488 | padding: 15px 20px; 489 | display: inline-block; 490 | display: flex; 491 | justify-content: center; 492 | align-items: center; 493 | } 494 | 495 | .lesson-links a.prev { 496 | padding-left: 10px; 497 | } 498 | 499 | .lesson-links a.next { 500 | padding-right: 10px; 501 | } 502 | 503 | .lesson-links a:hover { 504 | background: #152837; 505 | text-decoration: none; 506 | } 507 | 508 | .lesson-links .arrow { 509 | font-size: 24px; 510 | line-height: 24px; 511 | padding: 0px 5px; 512 | } 513 | -------------------------------------------------------------------------------- /lessons/02-part-1-getting-familiar/A-ts-go-rust-fundamentals.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: "Let me borrow that for a moment" 3 | --- 4 | 5 | ## The fundamentals of the languages. 6 | We are going to go over some fundamentals of each language to hopefully make 7 | the transitioning easier between these 3 languages. 8 | 9 |
10 | 11 | Remember, this is fast paced, but there is room for questions, as I have even 12 | made explicit stops. If you are viewing this live on FrontEndMasters.com or 13 | twitch.tv/ThePrimeagen and have a question, please feel free to throw it in the 14 | chat and hopefully I can answer it! 15 | 16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 | 33 | ### Syntax 34 | I am not going to cover language specifics and syntax. 35 | 36 | Example: what happens here? If you have some experience with rust, please 37 | don't answer. 38 | 39 | ```rust 40 | let foo = if boolean_statement { 41 | 0 42 | } else { 43 | 1 44 | }; 45 | ``` 46 | 47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 | 64 | ### TypeScript version 65 | ```typescript 66 | const foo = boolean_statement ? 0 : 1; 67 | ``` 68 | 69 |
70 | 71 | So if you do have a question, speak up during those times and I'll go over 72 | anything specific. But the goal here is to make a course that is geared 73 | towards people who feel comfortable with programming and would like to pick up 74 | a second or third language. 75 | 76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 | 93 | ### Rust Borrow Checker 94 | 95 | Rust is famous for difficulty. For its borrow checker. There are memes and I 96 | personally have rage quit using rust because I didn't understand the basics of 97 | rust. 98 | 99 | From the rearview mirror, this is simple 100 | 101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 | 118 | ## Lets start with other programming languages 119 | 120 | ### JavaScript 121 | ```javascript 122 | const a = []; 123 | const b = a; 124 | b.push(5); 125 | console.log(a); 126 | console.log(b); 127 | ``` 128 | 129 |
130 |
131 |
132 |
133 |
134 |
135 |
136 |
137 |
138 |
139 |
140 |
141 |
142 |
143 |
144 |
145 | 146 | ### C++ 147 | ```cpp 148 | std::vector a; 149 | std::vector b = a; 150 | b.push_back(5); 151 | 152 | printf("a size: %zu\n", a.size()); 153 | printf("b size: %zu\n", b.size()); 154 | ``` 155 | 156 |
157 |
158 |
159 |
160 |
161 |
162 |
163 |
164 |
165 |
166 |
167 |
168 |
169 |
170 |
171 |
172 | 173 | ### Rust 174 | ```rust 175 | let a: Vec = vec![]; 176 | let mut b = a; 177 | b.push(5); 178 | 179 | println!("a size: {}", a.len()); 180 | println!("b size: {}", b.len()); 181 | ``` 182 | 183 |
184 |
185 |
186 |
187 |
188 |
189 |
190 |
191 |
192 |
193 |
194 |
195 |
196 |
197 |
198 |
199 | 200 | ## The three types 201 | * Value 202 | * Reference 203 | * Mutable Reference 204 | 205 | ### The three simple rules 206 | #### Rule #1: Value 207 | Only one value owner. 208 | 209 | ##### Note 210 | If the object implements copy, it can be implicitly copied 211 | 212 | ```rust 213 | let x = 5; 214 | let y = x; 215 | 216 | println!("{}", x + y); 217 | ``` 218 | 219 | #### Rule #2: Reference 220 | You can have as many references as you like with the constraint that there are 221 | no mutable references alive. 222 | 223 | ```rust 224 | let x = 5; 225 | let y = &x; 226 | 227 | println!("here is {} and {}", x, y); 228 | ``` 229 | 230 | #### Rule #3:1 Mut Reference 231 | You can have one mut reference and no reference at the same time. 232 | 233 | ```rust 234 | fn main() { 235 | let mut x = 5; 236 | let y = &x; 237 | let z = &mut x; // cannot borrow x as mutable 238 | // because its already as immutable 239 | println!("{}", x + y + z); 240 | } 241 | ``` 242 |
243 |
244 |
245 |
246 |
247 |
248 |
249 |
250 |
251 |
252 |
253 |
254 |
255 |
256 |
257 |
258 | 259 | ### Questions? 260 | 261 |
262 |
263 |
264 |
265 |
266 |
267 |
268 |
269 |
270 |
271 |
272 |
273 |
274 |
275 |
276 |
277 | 278 | ### Moar things to cover 279 | * Rust + Enums (Options) 280 | * Options 281 | * Error Handling 282 | * Results 283 | * Testing 284 | 285 |
286 |
287 |
288 |
289 |
290 |
291 |
292 |
293 |
294 |
295 |
296 |
297 |
298 |
299 |
300 |
301 | 302 | ### Enums 303 | Lets look at emuns in typescript, go, and rust. 304 | 305 | First typescript, lets program up a quick example. 306 | 307 |
308 |
309 |
310 |
311 |
312 |
313 |
314 |
315 |
316 |
317 |
318 |
319 |
320 |
321 |
322 |
323 | 324 | ### I should have typed something like this. 325 | 326 | ```typescript 327 | enum Thing { 328 | Foo = 1, // or give them no value and it will start at 0 and increment 329 | Bar = 2, 330 | Baz = 3, 331 | } 332 | ``` 333 | 334 |
335 |
336 |
337 |
338 |
339 |
340 |
341 |
342 |
343 |
344 |
345 |
346 |
347 |
348 |
349 |
350 | 351 | ### What about go? 352 | Onto another example! 353 | 354 |
355 |
356 |
357 |
358 |
359 |
360 |
361 |
362 |
363 |
364 |
365 |
366 |
367 |
368 |
369 |
370 | 371 | ### Code 372 | 373 | ```go 374 | type Foo = int 375 | 376 | const ( 377 | Thing Foo = iota 378 | Other 379 | That 380 | ) 381 | ``` 382 | 383 |
384 |
385 |
386 |
387 |
388 |
389 |
390 |
391 |
392 |
393 |
394 |
395 |
396 |
397 |
398 |
399 | 400 | ### Now Rust 401 | 402 |
403 |
404 |
405 |
406 |
407 |
408 |
409 |
410 |
411 |
412 |
413 |
414 |
415 |
416 |
417 |
418 | 419 | ### Or is this typescirpt? 420 | 421 | ```rust 422 | enum Thing { 423 | Foo, // or give them no value and it will start at 0 and increment 424 | Bar, 425 | Baz, 426 | } 427 | ``` 428 | 429 |
430 |
431 |
432 |
433 |
434 |
435 |
436 |
437 |
438 |
439 |
440 |
441 |
442 |
443 |
444 |
445 | 446 | ### Why go over enums... 447 | They are simple constructs. Well, they are simple in other languages. 448 | 449 | Lets dive more into them with rust. Let me show you how you can add types... 450 | 451 |
452 |
453 |
454 |
455 |
456 |
457 |
458 |
459 |
460 |
461 |
462 |
463 |
464 |
465 |
466 |
467 | 468 | ### What my code should of approximately looked like 469 | 470 | ```rust 471 | enum Option2 { 472 | Baz, 473 | Foo(isize), 474 | Bar(String), 475 | Fuzz(Vec), // string[], or a []string 476 | } 477 | 478 | fn main() { 479 | let opt2 = Option2::Foo(5); 480 | 481 | let mut opt22 = Option2::Fuzz(vec![]); 482 | 483 | if let Option2::Foo(x) = opt2 { 484 | let _ = x + 5; 485 | // x = 7; 486 | } 487 | 488 | if let Option2::Fuzz(vec) = &mut opt22 { 489 | vec.push(String::from("Hello, world!")); 490 | } 491 | 492 | match opt2 { 493 | Option2::Baz => todo!(), 494 | Option2::Foo(_) => todo!(), 495 | Option2::Bar(_) => todo!(), 496 | Option2::Fuzz(_) => todo!(), 497 | } 498 | } 499 | ``` 500 | 501 |
502 |
503 |
504 |
505 |
506 |
507 |
508 |
509 |
510 |
511 |
512 |
513 |
514 |
515 |
516 |
517 | 518 | ### What does this mean? 519 | This means that you can have an enum with many types, and these types can be 520 | generic. 521 | 522 | ```rust 523 | enum Foo { 524 | Bar(T) 525 | } 526 | ``` 527 | 528 |
529 |
530 |
531 |
532 |
533 |
534 |
535 |
536 |
537 |
538 |
539 |
540 |
541 |
542 |
543 |
544 | 545 | ### But how is this practically useful? 546 | 3 things. 547 | 548 | 1. lists with many types 549 | 1. Nullable 550 | 1. Error Handling 551 | 552 |
553 |
554 |
555 |
556 |
557 |
558 |
559 |
560 |
561 |
562 |
563 |
564 |
565 |
566 |
567 |
568 | 569 | ### Lets start with nullable and TypeScript 570 | 571 | I think we have all seen code like this 572 | 573 | ```typescript 574 | type Foo = { 575 | bar?: number; 576 | } 577 | 578 | function test(foo: Foo) { 579 | if (foo.bar) { // this is annoying, yes 580 | // undang 581 | } else { 582 | // dang 583 | } 584 | } 585 | ``` 586 | 587 |
588 |
589 |
590 |
591 |
592 |
593 |
594 |
595 |
596 |
597 |
598 |
599 |
600 |
601 |
602 |
603 | 604 | ### Let me show you nullables in Rust 605 | These are Options, they are enums, they have a generic. 606 | 607 | 608 | ```rust 609 | enum Option { 610 | None, 611 | Some(T) 612 | } 613 | ``` 614 | 615 | Lets see how you can play with these in Rust 616 | 617 |
618 |
619 |
620 |
621 |
622 |
623 |
624 |
625 |
626 |
627 |
628 |
629 |
630 |
631 |
632 |
633 | 634 | ### Potential code for options 635 | 636 | ```rust 637 | struct Foo { 638 | bar: Option 639 | } 640 | 641 | fn main() { 642 | let foo = Foo { 643 | bar: None 644 | }; 645 | 646 | let foo2 = Foo { 647 | bar: Some(2) 648 | }; 649 | 650 | if foo.bar.is_some() { 651 | let sum = foo.bar.unwrap() + 5; 652 | } 653 | 654 | foo.bar.unwrap_or(0); 655 | 656 | foo.bar.unwrap_or_else(|| { 657 | return 5; 658 | }); 659 | 660 | let out = foo.bar.map(|x| { 661 | return x + 5; 662 | }); 663 | } 664 | ``` 665 | 666 |
667 |
668 |
669 |
670 |
671 |
672 |
673 |
674 |
675 |
676 |
677 |
678 |
679 |
680 |
681 |
682 | 683 | ### Questions so far? 684 | 685 |
686 |
687 |
688 |
689 |
690 |
691 |
692 |
693 |
694 |
695 |
696 |
697 |
698 |
699 |
700 |
701 | 702 | ### Lets implement the Option enum! 703 | For fun lets try to implement `map` and `is_some` in rust on our "option" type. 704 | 705 |
706 |
707 |
708 |
709 |
710 |
711 |
712 |
713 |
714 |
715 |
716 |
717 |
718 |
719 |
720 |
721 | 722 | ### Results of this 723 | 724 | ```rust 725 | enum Option2 { 726 | None, 727 | Some(T) 728 | } 729 | 730 | impl Option2 { 731 | pub fn map(&self, f: fn(&T) -> T) -> Option2 { 732 | return match self { 733 | Option2::None => Option2::None, 734 | Option2::Some(v) => Option2::Some(f(v)), 735 | } 736 | } 737 | 738 | pub fn is_some(&self) -> bool { 739 | return match self { 740 | Option2::None => false, 741 | Option2::Some(_) => true, 742 | } 743 | } 744 | } 745 | 746 | fn main() { 747 | let opt = Some(5); 748 | let opt2 = Option2::Some(5); 749 | 750 | opt.map(|x| x + 5); 751 | let opt2 = opt2.map(|x| x + 5); 752 | 753 | if opt2.is_some() { 754 | 755 | } 756 | } 757 | ``` 758 | 759 |
760 |
761 |
762 |
763 |
764 |
765 |
766 |
767 |
768 |
769 |
770 |
771 |
772 |
773 |
774 |
775 | 776 | ### Questions? That was pretty radical section 777 | Hopefully you can see the incredible value of sumtypes. 778 | 779 | There is this concept in 1984 that peoples ability to think is directly tied 780 | with the language they communicate with. I think this is true in programming 781 | as well. This is one of the reasons learning a ton of languages is REALLY 782 | beneficial. 783 | 784 |
785 |
786 |
787 |
788 |
789 |
790 |
791 |
792 |
793 |
794 |
795 |
796 |
797 |
798 |
799 |
800 | 801 | ### What about error handling? 802 | 803 |
804 |
805 |
806 |
807 |
808 |
809 |
810 |
811 |
812 |
813 |
814 |
815 |
816 |
817 |
818 |
819 | 820 | ### JavaScript (TypeScript) 821 | "Exceptions as control flow" 822 | 823 | 2 types of errors that you will run across. 824 | 825 | 1. returned errors 826 | 1. thrown errors 827 | 828 | Pretty classic javascriptism -- conflation issues 829 | 830 | Let me program you a live example! 831 | 832 |
833 |
834 |
835 |
836 |
837 |
838 |
839 |
840 |
841 |
842 |
843 |
844 |
845 |
846 |
847 |
848 | 849 | ### Code 850 | 851 | ```typescript 852 | function foo() { 853 | throw new Error("Goodbye, World"); 854 | } 855 | 856 | try { 857 | foo(); 858 | } catch (e) { 859 | console.log("We had a problem, but we are ok", e); 860 | } 861 | 862 | console.log("great success()"); 863 | ``` 864 | 865 |
866 |
867 |
868 |
869 |
870 |
871 |
872 |
873 |
874 |
875 |
876 |
877 |
878 |
879 |
880 |
881 | 882 | In general TypeScript uses exceptions for control flow and with promises its a 883 | mix of value vs throwing due to `.catch`. 884 | 885 | not all errors can be caught and will just simply blow up somewhere... 886 | 887 |
888 |
889 |
890 |
891 |
892 |
893 |
894 |
895 |
896 |
897 |
898 |
899 |
900 |
901 |
902 |
903 | 904 | ### Lets look at Go 905 | We haven't done much of go, but it does differ here from typescript. 906 | 907 | This is one of the most fundamental arguments against and for go is its error 908 | handling. I will say that the error handling i find better than typescript but 909 | definitely more boilerplate to deal with it. 910 | 911 | The reason why i like it is because of control flow and where things can go 912 | wrong. 913 | 914 | #### Example time! 915 | Remember, errors are just values 916 | 917 | * create error 918 | * return error + struct 919 | 920 |
921 |
922 |
923 |
924 |
925 |
926 |
927 |
928 |
929 |
930 |
931 |
932 |
933 |
934 |
935 |
936 | 937 | ### The go code 938 | ```go 939 | package main 940 | 941 | import ( 942 | "errors" 943 | "fmt" 944 | ) 945 | 946 | func example() error { 947 | return fmt.Errorf("here is an error with a string"); 948 | } 949 | 950 | func otherExample() error { 951 | return errors.New("here is an error, but with errors") // approx same thing 952 | } 953 | 954 | 955 | // errors are pointers under the hood, so you can return the empty type 956 | func exampleNoError() error { 957 | return nil; 958 | } 959 | 960 | type Thing struct { } 961 | 962 | func exampleWithData(should bool) (*Thing, error) { 963 | if should { 964 | return &Thing{}, nil 965 | } 966 | return nil, fmt.Errorf("nice try, guy") 967 | } 968 | 969 | func main() { 970 | err := example(); 971 | if err != nil { 972 | // handle error 973 | } 974 | 975 | _, err = exampleWithData(true) 976 | if err != nil { 977 | // handle error 978 | } 979 | } 980 | ``` 981 | 982 |
983 |
984 |
985 |
986 |
987 |
988 |
989 |
990 |
991 |
992 |
993 |
994 |
995 |
996 |
997 |
998 | 999 | ### Rust 1000 | Remember those enums (sumtypes) and how I told you they handled errors? Well, 1001 | here is the `Result` type. 1002 | 1003 | ```rust 1004 | type Result { 1005 | Err(E), 1006 | Ok(V) 1007 | } 1008 | ``` 1009 | 1010 | Lets make some examples of how to use them! 1011 | 1012 |
1013 |
1014 |
1015 |
1016 |
1017 |
1018 |
1019 |
1020 |
1021 |
1022 |
1023 |
1024 |
1025 |
1026 |
1027 |
1028 | 1029 | ### The code I wrote on a sunday morning 1030 | I am sure there is a Johnny Cash reference somewhere around here. 1031 | 1032 | ```rust 1033 | fn error(num: i32) -> Result<(), usize> { 1034 | if num < 0 { 1035 | return Err((num * -1) as usize); 1036 | } 1037 | 1038 | return Ok(()); 1039 | } 1040 | 1041 | fn main() -> Result<(), usize> { 1042 | let res = error(5); 1043 | 1044 | if res.is_ok() { 1045 | //... 1046 | } 1047 | 1048 | match res { 1049 | Err(e) => // ... 1050 | Ok(v) => // ... 1051 | } 1052 | 1053 | let x = res.unwrap_or(()); 1054 | let x = res.expect("THIS BETTER EXIST"); 1055 | let x = res.unwrap(); // BAD 1056 | 1057 | let x = res?; 1058 | 1059 | return Ok(()); 1060 | } 1061 | ``` 1062 | 1063 |
1064 |
1065 |
1066 |
1067 |
1068 |
1069 |
1070 |
1071 |
1072 |
1073 |
1074 |
1075 |
1076 |
1077 |
1078 |
1079 | 1080 | ### Anyhow? 1081 | A nice library for writing great code with error handling is anyhow! Lets look 1082 | at it 1083 | 1084 |
1085 |
1086 |
1087 |
1088 |
1089 |
1090 |
1091 |
1092 |
1093 |
1094 |
1095 |
1096 |
1097 |
1098 |
1099 |
1100 | 1101 | ### Unit Testing! 1102 | 1103 | * TypeScript : Cries in Configuration 1104 | * GoLang : Meh 1105 | * Rust : oyes 1106 | 1107 |
1108 |
1109 |
1110 |
1111 |
1112 |
1113 |
1114 |
1115 |
1116 |
1117 |
1118 |
1119 |
1120 |
1121 |
1122 |
1123 | 1124 | ```bash 1125 | src/__tests__/test.ts 1126 | ``` 1127 | 1128 | ```typescript 1129 | test("foo", function() { 1130 | expect("foo").toEqual("foo"); 1131 | }); 1132 | ``` 1133 | 1134 | ```bash 1135 | yarn add jest ts-jest @types/jest 1136 | npx jest 1137 | ``` 1138 | 1139 |
1140 |
1141 |
1142 |
1143 |
1144 |
1145 |
1146 |
1147 |
1148 |
1149 |
1150 |
1151 |
1152 |
1153 |
1154 |
1155 | 1156 | ### Go version 1157 | there is some contention with how / where to put your tests. 1158 | 1159 | * test public interfaces only 1160 | * test within the package 1161 | 1162 | ``` 1163 | pkg/name/file.go 1164 | pkg/name/file_test.go 1165 | ``` 1166 | 1167 | ```go 1168 | package name_test 1169 | 1170 | import "testing" 1171 | 1172 | func TestThisFunc(t *testing.T) { 1173 | this := 5 1174 | if this != 7 { 1175 | t.Errorf("expected %v to equal 7", this) 1176 | } 1177 | } 1178 | ``` 1179 | 1180 | ```bash 1181 | go test ./... 1182 | go test ./path/to/package 1183 | ``` 1184 | 1185 |
1186 |
1187 |
1188 |
1189 |
1190 |
1191 |
1192 |
1193 |
1194 |
1195 |
1196 |
1197 |
1198 |
1199 |
1200 |
1201 | 1202 | ### Go does have an assertion library 1203 | But we will not be using it. We will just use what is built in during this 1204 | course. 1205 | 1206 |
1207 |
1208 |
1209 |
1210 |
1211 |
1212 |
1213 |
1214 |
1215 |
1216 |
1217 |
1218 |
1219 |
1220 |
1221 |
1222 | 1223 | ### Rust Version 1224 | Rust, of course, is the best 1225 | 1226 | * test in file 1227 | * One thing to be careful of is to what level private interfaces should be 1228 | tested. 1229 | 1230 |
1231 |
1232 |
1233 |
1234 |
1235 |
1236 |
1237 |
1238 |
1239 |
1240 |
1241 |
1242 |
1243 |
1244 |
1245 |
1246 | 1247 | ``` 1248 | ... // code ... 1249 | 1250 | #[cfg(test)] 1251 | mod test { 1252 | #[test] 1253 | fn this_test() { 1254 | assert_eq!(5, 7); 1255 | } 1256 | } 1257 | ``` 1258 | 1259 | ```bash 1260 | cargo test 1261 | cargo test path/to/file.rs 1262 | ``` 1263 | 1264 |
1265 |
1266 |
1267 |
1268 |
1269 |
1270 |
1271 |
1272 |
1273 |
1274 |
1275 |
1276 |
1277 |
1278 |
1279 |
1280 | 1281 | ### You will forget everything i just said 1282 | That is ok. The best way to make it set? Build it. 1283 | 1284 |
1285 |
1286 |
1287 |
1288 |
1289 |
1290 |
1291 |
1292 |
1293 |
1294 |
1295 |
1296 |
1297 |
1298 |
1299 |
1300 | 1301 | --------------------------------------------------------------------------------