aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.gitignore3
-rw-r--r--CONTRIBUTING.md223
-rw-r--r--LICENSE.md29
-rw-r--r--LICENSES/CC-BY-SA-4.0428
-rw-r--r--LICENSES/MIT21
-rw-r--r--PROGRESS.md542
-rw-r--r--README.md38
-rw-r--r--build.sh215
-rwxr-xr-xbuild/mo2037
-rw-r--r--build/templates/home.html12
-rw-r--r--build/templates/page.html12
-rw-r--r--build/templates/partials/.gitkeep0
-rw-r--r--build/templates/partials/footer.html16
-rw-r--r--build/templates/partials/head.html11
-rw-r--r--build/templates/partials/header.html6
-rw-r--r--linux-command-line/shell-basics/index.md327
16 files changed, 3920 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..c1c3231
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,3 @@
+.worknotes/
+output/
+
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
new file mode 100644
index 0000000..7173170
--- /dev/null
+++ b/CONTRIBUTING.md
@@ -0,0 +1,223 @@
+---
+title: contributing guide
+---
+
+# contributing to ubergeek
+
+ubergeek is a community study guide. Contributions are welcome — fixes,
+improvements, new exercises, better explanations, and sample projects.
+
+The primary repository lives on **sourcehut** and is mirrored to GitHub and
+GitLab. You can contribute from any platform.
+
+---
+
+## Ways to Contribute
+
+- **Fix errors** — typos, incorrect information, broken examples
+- **Improve explanations** — clearer wording, better analogies, added context
+- **Add exercises and projects** — practice problems, mini-projects, challenge sets
+- **Add sample code** — working examples in any of the covered languages
+- **Add diagrams and images** — ASCII art, schematics, architecture diagrams
+- **Review existing content** — flag sections that are confusing or incomplete
+- **Suggest new topics or modules** — open a discussion first
+
+---
+
+## Repository Structure
+
+```
+topic-name/
+├── index.md # Topic overview
+├── images/ # Diagrams, schematics, screenshots (.png, .svg, .jpg)
+├── samples/ # Runnable code and project files
+│ └── project-name/
+│ ├── src/
+│ ├── Makefile
+│ └── README.md
+└── module-name/
+ └── index.md # Module content
+```
+
+- Content goes in `index.md` files using CommonMark markdown
+- Images go in the topic-level `images/` directory
+- Sample code and project files go in the topic-level `samples/` directory
+- Every sample project should include a README explaining how to build and run it
+- Reference images using relative paths: `![alt](images/filename.png)`
+
+---
+
+## Content Style Guide
+
+### Tone
+- Direct and practical — teach by showing, not lecturing
+- Assume intelligence, not prior knowledge
+- Use "you" not "the student" or "one"
+
+### Structure
+- Every page starts with frontmatter (`title` required)
+- Open with a short paragraph explaining what and why
+- Use code blocks liberally — real, runnable examples
+- End modules with exercises (drill, challenges, mini-project)
+- End with key takeaways and a "Next" link
+
+### Code
+- All code must be correct and runnable
+- Include comments only where the logic is not obvious
+- Show output where it helps understanding
+- For multi-language topics, show C, Python, Rust, C++, and Bash where applicable
+
+### Software Installation
+- When a module requires extra software, link to the official site and include
+ installation steps for Arch Linux (primary), Debian/Ubuntu, and Fedora
+- Use system package managers where possible
+- Note any version requirements
+
+### Images
+- Prefer ASCII diagrams for simple cases
+- Store image files in `topic/images/` with descriptive names
+- Reference using relative paths: `![description](images/filename.png)`
+- Always include `alt` text
+
+### Naming
+- Directories: `kebab-case` (e.g., `text-processing`, `pointers-and-memory`)
+- Files: `kebab-case.md` for content, descriptive names for images and samples
+- No spaces, no uppercase in file/directory names
+
+---
+
+## Contributing via Sourcehut (primary)
+
+The canonical repository is on
+[sr.ht](https://git.sr.ht/~gumxcc/ubergeek). Contributions are accepted via
+email patches.
+
+### Setup
+
+```bash
+# Clone the repo
+git clone https://git.sr.ht/~gumxcc/ubergeek
+cd ubergeek
+
+# Configure git send-email (one-time setup)
+git config sendemail.to ~gumxcc/ubergeek@lists.sr.ht
+git config format.subjectPrefix "PATCH ubergeek"
+```
+
+### Submitting a Patch
+
+```bash
+# Create a branch and make your changes
+git checkout -b fix-typo-shell-basics
+# ... edit files ...
+git add -A
+git commit -m "fix: correct permissions example in shell basics"
+
+# Send patch via email
+git send-email --to=~gumxcc/ubergeek@lists.sr.ht HEAD~1
+```
+
+For multi-commit changes:
+
+```bash
+git send-email --to=~gumxcc/ubergeek@lists.sr.ht origin/main..HEAD
+```
+
+### Discussion
+
+Use the mailing list for questions, suggestions, and reviews:
+`~gumxcc/ubergeek@lists.sr.ht`
+
+See [git-send-email.io](https://git-send-email.io) for a tutorial on setting
+up `git send-email`.
+
+---
+
+## Contributing via GitHub / GitLab (mirrors)
+
+Pull requests (GitHub) and merge requests (GitLab) are both accepted.
+
+```bash
+# Fork the repo on GitHub or GitLab, then clone your fork
+git clone https://github.com/gumxcc/ubergeek.git # or gitlab.com
+cd ubergeek
+
+# Create a branch
+git checkout -b fix-typo-shell-basics
+
+# Make changes, commit, push
+git add -A
+git commit -m "fix: correct permissions example in shell basics"
+git push origin fix-typo-shell-basics
+
+# Open a pull/merge request from the web UI
+```
+
+### PR/MR Guidelines
+- Keep PRs focused — one fix or one module per PR
+- Write a clear description of what you changed and why
+- If adding a new module, open an issue first to discuss scope
+
+---
+
+## Sample Code Guidelines
+
+When adding to `samples/`:
+
+- **Must compile/run out of the box** — include a Makefile, requirements.txt,
+ Cargo.toml, or whatever the project needs
+- **Include a README.md** — what it does, how to build, how to run, expected output
+- **Keep it minimal** — demonstrate the concept, nothing more
+- **Pin dependencies** where practical — don't rely on "latest"
+- **Test on Linux** — that's the primary platform for this guide
+
+### KiCad / Hardware Projects
+
+- Include the full KiCad project directory (`.kicad_pro`, `.kicad_sch`, `.kicad_pcb`)
+- Add a README.md with a description and BOM
+- Export a schematic PDF and place it in `images/` for people without KiCad
+- Use KiCad's built-in libraries where possible
+
+---
+
+## Commit Messages
+
+```
+<type>: <short description>
+
+<optional body explaining why>
+```
+
+Types:
+- `content` — new or updated study material
+- `fix` — corrections to existing content
+- `exercises` — new or updated exercises
+- `samples` — new or updated sample code/projects
+- `images` — new or updated diagrams/images
+- `meta` — PROGRESS.md, CONTRIBUTING.md, index.md, study-plan.md
+- `infra` — CI, build scripts, tooling
+
+Examples:
+```
+content: add shell basics module for linux command line
+fix: correct voltage divider formula in electronics
+exercises: add pipeline challenges to text processing
+samples: add bare-metal blinky project for STM32F4
+```
+
+---
+
+## Review Process
+
+1. All contributions are reviewed before merging
+2. Content accuracy is the top priority
+3. Style and formatting can be fixed post-merge — don't let it block good content
+4. Disagreements are resolved by discussion, not authority
+
+---
+
+## License
+
+By contributing, you agree that your contributions will be licensed under
+CC BY-SA 4.0, the same license as the rest of the project. Code samples in
+`samples/` directories are additionally available under the MIT license.
diff --git a/LICENSE.md b/LICENSE.md
new file mode 100644
index 0000000..c78c9c0
--- /dev/null
+++ b/LICENSE.md
@@ -0,0 +1,29 @@
+# License
+
+This project uses a dual-license model.
+
+## Content — CC BY-SA 4.0
+
+All written content, documentation, exercises, images, and diagrams are
+licensed under the
+[Creative Commons Attribution-ShareAlike 4.0 International](LICENSES/CC-BY-SA-4.0)
+license.
+
+You are free to share and adapt the material for any purpose, including
+commercially, as long as you give credit and distribute derivative works under
+the same license.
+
+## Code Samples — MIT
+
+All code in `samples/` directories throughout the repository is licensed under
+the [MIT License](LICENSES/MIT).
+
+You are free to use, modify, and distribute the code with no copyleft
+restrictions.
+
+## Which license applies?
+
+| Path | License |
+|------|---------|
+| `*/samples/**` | MIT |
+| Everything else (`.md` files, images, diagrams) | CC BY-SA 4.0 |
diff --git a/LICENSES/CC-BY-SA-4.0 b/LICENSES/CC-BY-SA-4.0
new file mode 100644
index 0000000..2d58298
--- /dev/null
+++ b/LICENSES/CC-BY-SA-4.0
@@ -0,0 +1,428 @@
+Attribution-ShareAlike 4.0 International
+
+=======================================================================
+
+Creative Commons Corporation ("Creative Commons") is not a law firm and
+does not provide legal services or legal advice. Distribution of
+Creative Commons public licenses does not create a lawyer-client or
+other relationship. Creative Commons makes its licenses and related
+information available on an "as-is" basis. Creative Commons gives no
+warranties regarding its licenses, any material licensed under their
+terms and conditions, or any related information. Creative Commons
+disclaims all liability for damages resulting from their use to the
+fullest extent possible.
+
+Using Creative Commons Public Licenses
+
+Creative Commons public licenses provide a standard set of terms and
+conditions that creators and other rights holders may use to share
+original works of authorship and other material subject to copyright
+and certain other rights specified in the public license below. The
+following considerations are for informational purposes only, are not
+exhaustive, and do not form part of our licenses.
+
+ Considerations for licensors: Our public licenses are
+ intended for use by those authorized to give the public
+ permission to use material in ways otherwise restricted by
+ copyright and certain other rights. Our licenses are
+ irrevocable. Licensors should read and understand the terms
+ and conditions of the license they choose before applying it.
+ Licensors should also secure all rights necessary before
+ applying our licenses so that the public can reuse the
+ material as expected. Licensors should clearly mark any
+ material not subject to the license. This includes other CC-
+ licensed material, or material used under an exception or
+ limitation to copyright. More considerations for licensors:
+ wiki.creativecommons.org/Considerations_for_licensors
+
+ Considerations for the public: By using one of our public
+ licenses, a licensor grants the public permission to use the
+ licensed material under specified terms and conditions. If
+ the licensor's permission is not necessary for any reason--for
+ example, because of any applicable exception or limitation to
+ copyright--then that use is not regulated by the license. Our
+ licenses grant only permissions under copyright and certain
+ other rights that a licensor has authority to grant. Use of
+ the licensed material may still be restricted for other
+ reasons, including because others have copyright or other
+ rights in the material. A licensor may make special requests,
+ such as asking that all changes be marked or described.
+ Although not required by our licenses, you are encouraged to
+ respect those requests where reasonable. More considerations
+ for the public:
+ wiki.creativecommons.org/Considerations_for_licensees
+
+=======================================================================
+
+Creative Commons Attribution-ShareAlike 4.0 International Public
+License
+
+By exercising the Licensed Rights (defined below), You accept and agree
+to be bound by the terms and conditions of this Creative Commons
+Attribution-ShareAlike 4.0 International Public License ("Public
+License"). To the extent this Public License may be interpreted as a
+contract, You are granted the Licensed Rights in consideration of Your
+acceptance of these terms and conditions, and the Licensor grants You
+such rights in consideration of benefits the Licensor receives from
+making the Licensed Material available under these terms and
+conditions.
+
+
+Section 1 -- Definitions.
+
+ a. Adapted Material means material subject to Copyright and Similar
+ Rights that is derived from or based upon the Licensed Material
+ and in which the Licensed Material is translated, altered,
+ arranged, transformed, or otherwise modified in a manner requiring
+ permission under the Copyright and Similar Rights held by the
+ Licensor. For purposes of this Public License, where the Licensed
+ Material is a musical work, performance, or sound recording,
+ Adapted Material is always produced where the Licensed Material is
+ synched in timed relation with a moving image.
+
+ b. Adapter's License means the license You apply to Your Copyright
+ and Similar Rights in Your contributions to Adapted Material in
+ accordance with the terms and conditions of this Public License.
+
+ c. BY-SA Compatible License means a license listed at
+ creativecommons.org/compatiblelicenses, approved by Creative
+ Commons as essentially the equivalent of this Public License.
+
+ d. Copyright and Similar Rights means copyright and/or similar rights
+ closely related to copyright including, without limitation,
+ performance, broadcast, sound recording, and Sui Generis Database
+ Rights, without regard to how the rights are labeled or
+ categorized. For purposes of this Public License, the rights
+ specified in Section 2(b)(1)-(2) are not Copyright and Similar
+ Rights.
+
+ e. Effective Technological Measures means those measures that, in the
+ absence of proper authority, may not be circumvented under laws
+ fulfilling obligations under Article 11 of the WIPO Copyright
+ Treaty adopted on December 20, 1996, and/or similar international
+ agreements.
+
+ f. Exceptions and Limitations means fair use, fair dealing, and/or
+ any other exception or limitation to Copyright and Similar Rights
+ that applies to Your use of the Licensed Material.
+
+ g. License Elements means the license attributes listed in the name
+ of a Creative Commons Public License. The License Elements of this
+ Public License are Attribution and ShareAlike.
+
+ h. Licensed Material means the artistic or literary work, database,
+ or other material to which the Licensor applied this Public
+ License.
+
+ i. Licensed Rights means the rights granted to You subject to the
+ terms and conditions of this Public License, which are limited to
+ all Copyright and Similar Rights that apply to Your use of the
+ Licensed Material and that the Licensor has authority to license.
+
+ j. Licensor means the individual(s) or entity(ies) granting rights
+ under this Public License.
+
+ k. Share means to provide material to the public by any means or
+ process that requires permission under the Licensed Rights, such
+ as reproduction, public display, public performance, distribution,
+ dissemination, communication, or importation, and to make material
+ available to the public including in ways that members of the
+ public may access the material from a place and at a time
+ individually chosen by them.
+
+ l. Sui Generis Database Rights means rights other than copyright
+ resulting from Directive 96/9/EC of the European Parliament and of
+ the Council of 11 March 1996 on the legal protection of databases,
+ as amended and/or succeeded, as well as other essentially
+ equivalent rights anywhere in the world.
+
+ m. You means the individual or entity exercising the Licensed Rights
+ under this Public License. Your has a corresponding meaning.
+
+
+Section 2 -- Scope.
+
+ a. License grant.
+
+ 1. Subject to the terms and conditions of this Public License,
+ the Licensor hereby grants You a worldwide, royalty-free,
+ non-sublicensable, non-exclusive, irrevocable license to
+ exercise the Licensed Rights in the Licensed Material to:
+
+ a. reproduce and Share the Licensed Material, in whole or
+ in part; and
+
+ b. produce, reproduce, and Share Adapted Material.
+
+ 2. Exceptions and Limitations. For the avoidance of doubt, where
+ Exceptions and Limitations apply to Your use, this Public
+ License does not apply, and You do not need to comply with
+ its terms and conditions.
+
+ 3. Term. The term of this Public License is specified in Section
+ 6(a).
+
+ 4. Media and formats; technical modifications allowed. The
+ Licensor authorizes You to exercise the Licensed Rights in
+ all media and formats whether now known or hereafter created,
+ and to make technical modifications necessary to do so. The
+ Licensor waives and/or agrees not to assert any right or
+ authority to forbid You from making technical modifications
+ necessary to exercise the Licensed Rights, including
+ technical modifications necessary to circumvent Effective
+ Technological Measures. For purposes of this Public License,
+ simply making modifications authorized by this Section 2(a)
+ (4) never produces Adapted Material.
+
+ 5. Downstream recipients.
+
+ a. Offer from the Licensor -- Licensed Material. Every
+ recipient of the Licensed Material automatically
+ receives an offer from the Licensor to exercise the
+ Licensed Rights under the terms and conditions of this
+ Public License.
+
+ b. Additional offer from the Licensor -- Adapted Material.
+ Every recipient of Adapted Material from You
+ automatically receives an offer from the Licensor to
+ exercise the Licensed Rights in the Adapted Material
+ under the conditions of the Adapter's License You apply.
+
+ c. No downstream restrictions. You may not offer or impose
+ any additional or different terms or conditions on, or
+ apply any Effective Technological Measures to, the
+ Licensed Material if doing so restricts exercise of the
+ Licensed Rights by any recipient of the Licensed
+ Material.
+
+ 6. No endorsement. Nothing in this Public License constitutes or
+ may be construed as permission to assert or imply that You
+ are, or that Your use of the Licensed Material is, connected
+ with, or sponsored, endorsed, or granted official status by,
+ the Licensor or others designated to receive attribution as
+ provided in Section 3(a)(1)(A)(i).
+
+ b. Other rights.
+
+ 1. Moral rights, such as the right of integrity, are not
+ licensed under this Public License, nor are publicity,
+ privacy, and/or other similar personality rights; however, to
+ the extent possible, the Licensor waives and/or agrees not to
+ assert any such rights held by the Licensor to the limited
+ extent necessary to allow You to exercise the Licensed
+ Rights, but not otherwise.
+
+ 2. Patent and trademark rights are not licensed under this
+ Public License.
+
+ 3. To the extent possible, the Licensor waives any right to
+ collect royalties from You for the exercise of the Licensed
+ Rights, whether directly or through a collecting society
+ under any voluntary or waivable statutory or compulsory
+ licensing scheme. In all other cases the Licensor expressly
+ reserves any right to collect such royalties.
+
+
+Section 3 -- License Conditions.
+
+Your exercise of the Licensed Rights is expressly made subject to the
+following conditions.
+
+ a. Attribution.
+
+ 1. If You Share the Licensed Material (including in modified
+ form), You must:
+
+ a. retain the following if it is supplied by the Licensor
+ with the Licensed Material:
+
+ i. identification of the creator(s) of the Licensed
+ Material and any others designated to receive
+ attribution, in any reasonable manner requested by
+ the Licensor (including by pseudonym if
+ designated);
+
+ ii. a copyright notice;
+
+ iii. a notice that refers to this Public License;
+
+ iv. a notice that refers to the disclaimer of
+ warranties;
+
+ v. a URI or hyperlink to the Licensed Material to the
+ extent reasonably practicable;
+
+ b. indicate if You modified the Licensed Material and
+ retain an indication of any previous modifications; and
+
+ c. indicate the Licensed Material is licensed under this
+ Public License, and include the text of, or the URI or
+ hyperlink to, this Public License.
+
+ 2. You may satisfy the conditions in Section 3(a)(1) in any
+ reasonable manner based on the medium, means, and context in
+ which You Share the Licensed Material. For example, it may be
+ reasonable to satisfy the conditions by providing a URI or
+ hyperlink to a resource that includes the required
+ information.
+
+ 3. If requested by the Licensor, You must remove any of the
+ information required by Section 3(a)(1)(A) to the extent
+ reasonably practicable.
+
+ b. ShareAlike.
+
+ In addition to the conditions in Section 3(a), if You Share
+ Adapted Material You produce, the following conditions also apply.
+
+ 1. The Adapter's License You apply must be a Creative Commons
+ license with the same License Elements, this version or
+ later, or a BY-SA Compatible License.
+
+ 2. You must include the text of, or the URI or hyperlink to, the
+ Adapter's License You apply. You may satisfy this condition
+ in any reasonable manner based on the medium, means, and
+ context in which You Share Adapted Material.
+
+ 3. You may not offer or impose any additional or different terms
+ or conditions on, or apply any Effective Technological
+ Measures to, Adapted Material that restrict exercise of the
+ rights granted under the Adapter's License You apply.
+
+
+Section 4 -- Sui Generis Database Rights.
+
+Where the Licensed Rights include Sui Generis Database Rights that
+apply to Your use of the Licensed Material:
+
+ a. for the avoidance of doubt, Section 2(a)(1) grants You the right
+ to extract, reuse, reproduce, and Share all or a substantial
+ portion of the contents of the database;
+
+ b. if You include all or a substantial portion of the database
+ contents in a database in which You have Sui Generis Database
+ Rights, then the database in which You have Sui Generis Database
+ Rights (but not its individual contents) is Adapted Material,
+ including for purposes of Section 3(b); and
+
+ c. You must comply with the conditions in Section 3(a) if You Share
+ all or a substantial portion of the contents of the database.
+
+For the avoidance of doubt, this Section 4 supplements and does not
+replace Your obligations under this Public License where the Licensed
+Rights include other Copyright and Similar Rights.
+
+
+Section 5 -- Disclaimer of Warranties and Limitation of Liability.
+
+ a. UNLESS OTHERWISE SEPARATELY UNDERTAKEN BY THE LICENSOR, TO THE
+ EXTENT POSSIBLE, THE LICENSOR OFFERS THE LICENSED MATERIAL AS-IS
+ AND AS-AVAILABLE, AND MAKES NO REPRESENTATIONS OR WARRANTIES OF
+ ANY KIND CONCERNING THE LICENSED MATERIAL, WHETHER EXPRESS,
+ IMPLIED, STATUTORY, OR OTHER. THIS INCLUDES, WITHOUT LIMITATION,
+ WARRANTIES OF TITLE, MERCHANTABILITY, FITNESS FOR A PARTICULAR
+ PURPOSE, NON-INFRINGEMENT, ABSENCE OF LATENT OR OTHER DEFECTS,
+ ACCURACY, OR THE PRESENCE OR ABSENCE OF ERRORS, WHETHER OR NOT
+ KNOWN OR DISCOVERABLE. WHERE DISCLAIMERS OF WARRANTIES ARE NOT
+ ALLOWED IN FULL OR IN PART, THIS DISCLAIMER MAY NOT APPLY TO YOU.
+
+ b. TO THE EXTENT POSSIBLE, IN NO EVENT WILL THE LICENSOR BE LIABLE
+ TO YOU ON ANY LEGAL THEORY (INCLUDING, WITHOUT LIMITATION,
+ NEGLIGENCE) OR OTHERWISE FOR ANY DIRECT, SPECIAL, INDIRECT,
+ INCIDENTAL, CONSEQUENTIAL, PUNITIVE, EXEMPLARY, OR OTHER LOSSES,
+ COSTS, EXPENSES, OR DAMAGES ARISING OUT OF THIS PUBLIC LICENSE OR
+ USE OF THE LICENSED MATERIAL, EVEN IF THE LICENSOR HAS BEEN
+ ADVISED OF THE POSSIBILITY OF SUCH LOSSES, COSTS, EXPENSES, OR
+ DAMAGES. WHERE A LIMITATION OF LIABILITY IS NOT ALLOWED IN FULL OR
+ IN PART, THIS LIMITATION MAY NOT APPLY TO YOU.
+
+ c. The disclaimer of warranties and limitation of liability provided
+ above shall be interpreted in a manner that, to the extent
+ possible, most closely approximates an absolute disclaimer and
+ waiver of all liability.
+
+
+Section 6 -- Term and Termination.
+
+ a. This Public License applies for the term of the Copyright and
+ Similar Rights licensed here. However, if You fail to comply with
+ this Public License, then Your rights under this Public License
+ terminate automatically.
+
+ b. Where Your right to use the Licensed Material has terminated under
+ Section 6(a), it reinstates:
+
+ 1. automatically as of the date the violation is cured, provided
+ it is cured within 30 days of Your discovery of the
+ violation; or
+
+ 2. upon express reinstatement by the Licensor.
+
+ For the avoidance of doubt, this Section 6(b) does not affect any
+ right the Licensor may have to seek remedies for Your violations
+ of this Public License.
+
+ c. For the avoidance of doubt, the Licensor may also offer the
+ Licensed Material under separate terms or conditions or stop
+ distributing the Licensed Material at any time; however, doing so
+ will not terminate this Public License.
+
+ d. Sections 1, 5, 6, 7, and 8 survive termination of this Public
+ License.
+
+
+Section 7 -- Other Terms and Conditions.
+
+ a. The Licensor shall not be bound by any additional or different
+ terms or conditions communicated by You unless expressly agreed.
+
+ b. Any arrangements, understandings, or agreements regarding the
+ Licensed Material not stated herein are separate from and
+ independent of the terms and conditions of this Public License.
+
+
+Section 8 -- Interpretation.
+
+ a. For the avoidance of doubt, this Public License does not, and
+ shall not be interpreted to, reduce, limit, restrict, or impose
+ conditions on any use of the Licensed Material that could lawfully
+ be made without permission under this Public License.
+
+ b. To the extent possible, if any provision of this Public License is
+ deemed unenforceable, it shall be automatically reformed to the
+ minimum extent necessary to make it enforceable. If the provision
+ cannot be reformed, it shall be severed from this Public License
+ without affecting the enforceability of the remaining terms and
+ conditions.
+
+ c. No term or condition of this Public License will be waived and no
+ failure to comply consented to unless expressly agreed to by the
+ Licensor.
+
+ d. Nothing in this Public License constitutes or may be interpreted
+ as a limitation upon, or waiver of, any privileges and immunities
+ that apply to the Licensor or You, including from the legal
+ processes of any jurisdiction or authority.
+
+
+=======================================================================
+
+Creative Commons is not a party to its public
+licenses. Notwithstanding, Creative Commons may elect to apply one of
+its public licenses to material it publishes and in those instances
+will be considered the “Licensor.” The text of the Creative Commons
+public licenses is dedicated to the public domain under the CC0 Public
+Domain Dedication. Except for the limited purpose of indicating that
+material is shared under a Creative Commons public license or as
+otherwise permitted by the Creative Commons policies published at
+creativecommons.org/policies, Creative Commons does not authorize the
+use of the trademark "Creative Commons" or any other trademark or logo
+of Creative Commons without its prior written consent including,
+without limitation, in connection with any unauthorized modifications
+to any of its public licenses or any other arrangements,
+understandings, or agreements concerning use of licensed material. For
+the avoidance of doubt, this paragraph does not form part of the
+public licenses.
+
+Creative Commons may be contacted at creativecommons.org.
+
diff --git a/LICENSES/MIT b/LICENSES/MIT
new file mode 100644
index 0000000..dd99b59
--- /dev/null
+++ b/LICENSES/MIT
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2026 gumxcc and contributors
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/PROGRESS.md b/PROGRESS.md
new file mode 100644
index 0000000..abb0cfb
--- /dev/null
+++ b/PROGRESS.md
@@ -0,0 +1,542 @@
+---
+title: ubergeek study guide — progress tracker
+---
+
+# ubergeek study guide
+
+Master progress tracker and study plan. Single source of truth.
+
+**Legend:**
+- `[ ]` Module Name — not started
+- `[ ]` *wip* Module Name — in progress
+- `[x]` [Module Name](path/to/module/) — complete (linked)
+- *skip* — skippable for fast-track learners
+
+---
+
+## Study Path
+
+Phases are sequential but topics within a phase can be studied in parallel.
+Prerequisites are noted — you can jump ahead if you already know the prereqs.
+
+| Phase | Focus | Topics | Prereqs |
+|-------|-------|--------|---------|
+| 1 | Toolkit & Literacy | Command Line, Git, Technical Writing, Programming Fundamentals, C, Python, Bash, Math | None |
+| 2 | Core Theory | Computer Science, Electronics, Make & Dev Automation | Phase 1 |
+| 3 | Systems & Design | Operating Systems, PCB Design, Web Tech, Project Management | Phase 2 |
+| 4 | Security & Infra | Cyber Security, Ansible, Automation | Phases 2-3 |
+| 5 | Embedded World | Embedded Systems, PCB Prototyping, Digital Fabrication | Phases 2-3 |
+| 6 | Deep Systems | Kernel Development, Embedded OS/RTOS, IoT | Phases 3-5 |
+| 7 | Intelligence & Integration | Computer Vision/Edge AI, Robotics, Rust, C++, Advanced Languages | Phases 5-6 |
+| -- | Side Quest (anytime) | Global Geek Culture | None |
+
+**Fast-track:** If you have prior knowledge, check the prereqs list in each topic and skip what you know. Modules marked *skip* are optional/enrichment.
+
+---
+
+## Phase 1 — Toolkit & Literacy
+
+### 1.1 Linux Command Line Tools
+`path: linux-command-line/`
+> Prereqs: None
+
+- [ ] Shell Basics — navigation, file ops, permissions, pipes, redirection, man pages
+- [ ] File Management — find, locate, tar, rsync, ln, file, stat, du, df
+- [ ] Text Processing — grep, sed, awk, cut, sort, uniq, tr, wc, jq, yq
+- [ ] Process Management — ps, top/htop, kill, nice, nohup, jobs, bg/fg, screen/tmux
+- [ ] Networking — ssh, curl, wget, netstat/ss, ip, dig, nmap, nc, scp/sftp
+- [ ] System Administration — systemctl, journalctl, cron, users/groups, disk mgmt, mount
+- [ ] Advanced Shell — parameter expansion, process substitution, subshells, traps, here-docs
+- [ ] Power Tools — fzf, ripgrep, fd, bat, eza, delta, zoxide, direnv
+- [ ] *skip* Customization — shell config, aliases, functions, prompt, dotfiles management
+- [ ] Exercises & Projects — system monitor, backup script, log analyzer, file organizer
+
+### 1.2 Git
+`path: git/`
+> Prereqs: Command Line basics
+
+- [ ] Fundamentals — init, add, commit, status, log, diff, .gitignore
+- [ ] Branching & Merging — branches, merge, fast-forward, merge conflicts
+- [ ] Remote Collaboration — remotes, push, pull, fetch, clone, PRs, patches (email workflow)
+- [ ] Advanced Operations — rebase, cherry-pick, bisect, reflog, stash, worktrees
+- [ ] Internals — objects (blob/tree/commit/tag), refs, packfiles, how Git works
+- [ ] Workflows & Strategies — feature branches, Git Flow, trunk-based, monorepo, submodules
+- [ ] Hooks & Automation — pre-commit, commit-msg, CI integration, custom hooks
+- [ ] *skip* Sourcehut-Specific — sr.ht workflows, mailing list patches, builds.sr.ht
+- [ ] Exercises & Projects — contribute a patch, manage a multi-branch project, write custom hooks
+
+### 1.3 Technical Writing & Research Methodologies
+`path: technical-writing/`
+> Prereqs: None
+
+- [ ] Writing Fundamentals — clarity, structure, audience analysis, active voice, style guides
+- [ ] Technical Documentation — API docs, user guides, READMEs, changelogs, man pages
+- [ ] Research Methodology — literature review, hypothesis, experimental design, data collection
+- [ ] Data Presentation — tables, charts, statistical visualization, LaTeX/typst basics
+- [ ] *skip* Academic Writing — papers, citations, abstracts, peer review process
+- [ ] Documentation Tools — Markdown, AsciiDoc, Sphinx, Doxygen, mdBook
+- [ ] Exercises & Projects — write a project README, document an API, create a lab report
+
+### 1.4 Programming Fundamentals
+`path: programming/fundamentals/`
+> Prereqs: Command Line basics
+
+- [ ] Core Concepts — variables, types, expressions, control flow (all languages compared)
+- [ ] Functions & Modularity — functions, scope, recursion, modules/packages
+- [ ] Data Structures (language-level) — arrays, strings, hash maps, structs/classes
+- [ ] I/O & File Handling — stdin/stdout, file read/write, serialization (JSON, TOML, CSV)
+- [ ] Error Handling — exceptions, return codes, Result/Option types (cross-language comparison)
+- [ ] Memory Models — stack vs heap, value vs reference, ownership overview
+- [ ] Build & Run — compilers, interpreters, REPLs, build systems, package managers
+- [ ] Testing Basics — unit tests, assertions, test frameworks per language
+- [ ] Exercises & Projects — CLI calculator, file processor, multi-language Rosetta Stone set
+
+### 1.5 C
+`path: programming/c/`
+> Prereqs: Programming Fundamentals
+
+- [ ] C Basics — syntax, types, printf/scanf, compilation with gcc/clang
+- [ ] Pointers & Memory — pointer arithmetic, malloc/free, arrays vs pointers, void pointers
+- [ ] Strings & Arrays — C strings, buffer safety, multi-dimensional arrays
+- [ ] Structs & Unions — struct layout, padding/alignment, bit fields, tagged unions
+- [ ] Preprocessor — macros, conditional compilation, include guards, X-macros
+- [ ] Standard Library — stdio, stdlib, string.h, math.h, time.h, errno
+- [ ] File I/O & System Calls — fopen/fread, open/read/write, mmap, ioctl
+- [ ] Build Systems — Makefiles for C, static/shared libraries, linking, pkg-config
+- [ ] Debugging & Profiling — gdb, valgrind, sanitizers (ASan/UBSan), perf
+- [ ] Advanced C — function pointers, callbacks, opaque types, C11/C17/C23 features
+- [ ] Exercises & Projects — memory allocator, linked list library, simple shell, file parser
+
+### 1.6 Python
+`path: programming/python/`
+> Prereqs: Programming Fundamentals
+
+- [ ] Python Basics — syntax, types, REPL, scripts, pip, virtual environments (venv, uv)
+- [ ] Data Structures — lists, tuples, dicts, sets, comprehensions, slicing, unpacking
+- [ ] Functions & Modules — def, *args/**kwargs, lambda, imports, packages, \_\_name\_\_
+- [ ] OOP — classes, inheritance, dunder methods, properties, dataclasses, protocols
+- [ ] Error Handling — exceptions, try/except/finally, custom exceptions, context managers
+- [ ] File I/O & Data — file ops, pathlib, JSON, CSV, TOML, regex, struct/binary
+- [ ] Standard Library — os, sys, subprocess, collections, itertools, functools, logging, argparse
+- [ ] Testing — pytest, unittest, mocking, fixtures, parametrize, coverage
+- [ ] Scientific Stack Intro — NumPy basics, Matplotlib, pandas intro, Jupyter notebooks
+- [ ] Exercises & Projects — CLI tool, web scraper, data pipeline, test suite for a library
+
+### 1.7 Bash Scripting
+`path: programming/bash/`
+> Prereqs: Command Line Tools
+
+- [ ] Script Basics — shebang, variables, quoting, exit codes, set options (-euo pipefail)
+- [ ] Control Flow — if/elif/else, case, for, while, until, select
+- [ ] Functions — definition, local variables, return values, passing arguments
+- [ ] Text Processing in Scripts — grep/sed/awk integration, regex, here-docs/here-strings
+- [ ] Process & Job Control — subshells, command substitution, pipe chains, xargs, parallel
+- [ ] Advanced Patterns — signal handling (trap), getopts, associative arrays, nameref
+- [ ] Script Packaging — argument parsing, help messages, config files, logging, color output
+- [ ] Exercises & Projects — deployment script, system health checker, interactive menu tool
+
+### 1.8 Mathematics Foundations
+`path: mathematics/`
+> Prereqs: None (high school math assumed)
+
+- [ ] Logic & Proofs — propositional logic, predicate logic, proof techniques, set theory
+- [ ] Number Systems — integers, rationals, reals, complex numbers, modular arithmetic
+- [ ] Linear Algebra — vectors, matrices, systems of equations, determinants, eigenvalues
+- [ ] Calculus I — limits, derivatives, integrals, fundamental theorem
+- [ ] Calculus II — techniques of integration, sequences, series, Taylor/Maclaurin
+- [ ] Multivariable Calculus — partial derivatives, gradients, multiple integrals, vector fields
+- [ ] Differential Equations — first/second order ODEs, Laplace transforms, systems of ODEs
+- [ ] Probability & Statistics — combinatorics, probability theory, distributions, inference, Bayes
+- [ ] Discrete Mathematics — graph theory, combinatorics, recurrences, number theory basics
+- [ ] *skip* Numerical Methods — root finding, interpolation, numerical integration, optimization
+- [ ] *skip* Transform Methods — Fourier series/transform, Z-transform, DFT/FFT
+- [ ] Exercises & Projects — problem sets per module, computational exercises (Python/Julia)
+
+---
+
+## Phase 2 — Core Theory
+
+### 2.1 Computer Science
+`path: computer-science/`
+> Prereqs: Programming Fundamentals, Math (discrete math, logic)
+
+- [ ] Data Structures — arrays, linked lists, stacks, queues, trees, heaps, graphs, hash tables
+- [ ] Algorithms — sorting, searching, graph algorithms (BFS/DFS/Dijkstra), recursion
+- [ ] Algorithm Design — divide & conquer, dynamic programming, greedy, backtracking
+- [ ] Complexity Theory — Big-O/Omega/Theta, amortized analysis, P vs NP, reductions
+- [ ] Computer Architecture — CPU design, instruction sets, pipelining, caches, memory hierarchy
+- [ ] Networking — OSI model, TCP/IP, DNS, HTTP/HTTPS, sockets, routing
+- [ ] Databases — relational model, SQL, normalization, indexing, transactions, NoSQL overview
+- [ ] *skip* Compilers — lexing, parsing, ASTs, semantic analysis, code generation
+- [ ] *skip* Distributed Systems — CAP theorem, consensus, replication, consistency models
+- [ ] Exercises & Projects — implement core data structures (C + Python + Rust), solve algorithm sets
+
+### 2.2 Electronics
+`path: electronics/`
+> Prereqs: Math (calculus, linear algebra, complex numbers)
+
+- [ ] Circuit Fundamentals — voltage, current, resistance, power, Ohm's/Kirchhoff's laws
+- [ ] DC Circuit Analysis — node/mesh analysis, Thevenin/Norton, superposition
+- [ ] AC Circuits — phasors, impedance, resonance, frequency response, Bode plots
+- [ ] Passive Components — resistors, capacitors, inductors, transformers, practical specs
+- [ ] Diodes & Rectifiers — PN junction, diode types, rectifier circuits, voltage regulators
+- [ ] Transistors (BJT) — operation, biasing, small-signal models, amplifier configurations
+- [ ] Transistors (MOSFET) — operation, biasing, CMOS logic, switching applications
+- [ ] Op-Amps — ideal model, inverting/non-inverting, integrator/differentiator, active filters
+- [ ] Digital Electronics — logic gates, Boolean algebra, combinational circuits, sequential (flip-flops, counters)
+- [ ] Sensors & Measurement — common sensors (temp, light, force, IMU), signal conditioning, ADC/DAC
+- [ ] Power Electronics — linear regulators, switching converters (buck/boost), battery charging
+- [ ] *skip* RF Basics — transmission lines, impedance matching, modulation, antenna fundamentals
+- [ ] Exercises & Projects — voltage divider lab, amplifier design, logic circuit build, sensor interface
+
+### 2.3 Make & Dev Automation
+`path: make-and-automation/`
+> Prereqs: Command Line, Git, Programming (C or any compiled language)
+
+- [ ] Make Basics — targets, prerequisites, recipes, variables, automatic variables, phony targets
+- [ ] Advanced Make — pattern rules, functions, conditional directives, auto-dependencies, recursive vs non-recursive
+- [ ] CMake — CMakeLists.txt, targets, find_package, generator expressions, modern CMake idioms
+- [ ] *skip* Meson — build definitions, dependencies, cross-compilation, wraps
+- [ ] Task Runners — Justfile, Taskfile, npm scripts, Python invoke, cargo-make
+- [ ] Dev Containers — Dockerfiles for dev, devcontainer spec, reproducible environments
+- [ ] *skip* Documentation Generation — Doxygen, Sphinx autodoc, mdBook, man page generation
+- [ ] Exercises & Projects — multi-target C Makefile, CMake project with libraries, polyglot build system
+
+---
+
+## Phase 3 — Systems & Design
+
+### 3.1 Operating Systems
+`path: operating-systems/`
+> Prereqs: CS (architecture, data structures), C, Math (basic)
+
+- [ ] Introduction — OS role, history, types, system calls, kernel vs userspace
+- [ ] Process Management — processes, threads, context switching, scheduling algorithms
+- [ ] Synchronization — race conditions, mutexes, semaphores, deadlocks, condition variables
+- [ ] Memory Management — virtual memory, paging, segmentation, page replacement, TLB
+- [ ] File Systems — VFS, inodes, ext4, FAT, journaling, directory structures
+- [ ] I/O & Devices — device drivers model, block vs character, DMA, interrupt handling
+- [ ] IPC — pipes, shared memory, message queues, signals, sockets
+- [ ] Networking in OS — socket API, TCP/IP stack, network drivers, netfilter
+- [ ] Security & Protection — access control, capabilities, namespaces, cgroups, sandboxing
+- [ ] *skip* Virtualization — hypervisors (type 1/2), containers, paravirtualization, KVM
+- [ ] Exercises & Projects — system call tracer, simple scheduler simulation, xv6 labs
+
+### 3.2 PCB Design
+`path: pcb-design/`
+> Prereqs: Electronics (all fundamentals)
+
+- [ ] Fundamentals — schematic capture workflow, EDA tools overview (KiCad focus), libraries
+- [ ] Component Selection — reading datasheets, footprints, sourcing, lifecycle management
+- [ ] Schematic Design — hierarchical sheets, power symbols, net labels, design review checklist
+- [ ] PCB Layout Basics — board outline, stackup, component placement strategy, routing basics
+- [ ] Routing — trace width, via types, differential pairs, length matching, copper pours
+- [ ] Power Distribution — PDN design, decoupling strategy, thermal management, power planes
+- [ ] Signal Integrity — impedance control, crosstalk, EMI/EMC basics, ground planes
+- [ ] *skip* High-Speed Design — controlled impedance, return paths, high-speed routing rules
+- [ ] Design for Manufacturing — DFM/DFA rules, panelization, assembly drawings, BOM management
+- [ ] Exercises & Projects — LED breakout board, sensor board (2-layer), power supply board (4-layer)
+
+### 3.3 Web Technologies
+`path: web-technologies/`
+> Prereqs: CS (networking, databases), Programming (Python or JS)
+
+- [ ] HTML & CSS — semantic HTML5, CSS layout (flexbox, grid), responsive design, accessibility
+- [ ] JavaScript — ES6+, DOM manipulation, events, async/await, modules, fetch API
+- [ ] Backend Fundamentals — HTTP protocol, REST API design, authentication/authorization
+- [ ] Server-Side Development — Python (Flask/FastAPI), Node.js (Express), API development
+- [ ] Databases for Web — SQL (PostgreSQL), ORM concepts, migrations, Redis caching
+- [ ] *skip* Frontend Frameworks — component architecture, state management, SPA vs MPA concepts
+- [ ] DevOps for Web — Docker, reverse proxies (nginx), TLS, CI/CD, deployment strategies
+- [ ] Security — XSS, CSRF, CSP, SQL injection prevention, CORS, HTTPS
+- [ ] Exercises & Projects — static site, REST API with database, full-stack CRUD app
+
+### 3.4 Project Management
+`path: project-management/`
+> Prereqs: Technical Writing
+
+- [ ] Fundamentals — project lifecycle, scope, stakeholders, constraints triangle
+- [ ] Methodologies — Agile, Scrum, Kanban, Waterfall, hybrid approaches
+- [ ] Planning — work breakdown structure, estimation, scheduling, Gantt charts, risk management
+- [ ] Execution & Monitoring — KPIs, burndown charts, stand-ups, retrospectives
+- [ ] Tools & Systems — issue trackers, Kanban boards, time tracking, documentation wikis
+- [ ] *skip* Team Dynamics — communication, conflict resolution, remote collaboration
+- [ ] Engineering-Specific — hardware project management, firmware release cycles, versioning
+- [ ] Exercises & Projects — plan a personal project end-to-end, set up a Kanban workflow
+
+---
+
+## Phase 4 — Security & Infrastructure
+
+### 4.1 Cyber Security
+`path: cyber-security/`
+> Prereqs: CS (networking, OS concepts), Command Line, Programming
+
+- [ ] Fundamentals — CIA triad, threat modeling, attack surfaces, defense in depth
+- [ ] Network Security — firewalls, IDS/IPS, VPN, packet analysis, network scanning
+- [ ] System Hardening — access control, SELinux/AppArmor, audit logging, patching, CIS benchmarks
+- [ ] Cryptography — symmetric/asymmetric encryption, hashing, TLS/SSL, PKI, key management
+- [ ] Application Security — OWASP top 10, input validation, secure coding practices, code auditing
+- [ ] Embedded Security — secure boot, firmware encryption, hardware attacks, side-channel basics
+- [ ] *skip* Penetration Testing — methodology, recon, exploitation, post-exploitation, reporting
+- [ ] *skip* Incident Response — detection, containment, forensics, recovery, lessons learned
+- [ ] Exercises & Projects — harden a Linux server, set up TLS, CTF challenges, vulnerability assessment
+
+### 4.2 Ansible
+`path: ansible/`
+> Prereqs: Command Line (advanced), SSH, Linux administration
+
+- [ ] Introduction — IaC concepts, Ansible architecture, installation, configuration
+- [ ] Core Concepts — inventory, modules, ad-hoc commands, YAML syntax
+- [ ] Playbooks — plays, tasks, handlers, variables, facts, conditionals, loops
+- [ ] Roles & Organization — role structure, Galaxy, collections, reuse patterns, project layout
+- [ ] Advanced — Jinja2 templates, filters, lookups, custom modules, vault (secrets)
+- [ ] *skip* Cloud & Containers — cloud provisioning, Docker management, Kubernetes basics
+- [ ] Testing — Molecule, ansible-lint, CI integration, idempotency testing
+- [ ] Exercises & Projects — server setup playbook, multi-tier deployment, homelab automation
+
+### 4.3 Automation
+`path: automation/`
+> Prereqs: Command Line, Bash, Git, Make basics
+
+- [ ] Principles — DRY, idempotency, reproducibility, when to automate (ROI)
+- [ ] Shell Automation — cron, systemd timers, inotify/file watching, at
+- [ ] CI/CD — concepts, GitHub Actions, sourcehut builds, pipeline design, artifacts
+- [ ] Testing Automation — unit/integration/e2e strategies, fuzzing, property-based testing
+- [ ] Infrastructure Automation — Terraform concepts, Ansible integration, GitOps basics
+- [ ] *skip* Monitoring & Alerting — Prometheus, Grafana, alerting rules, log aggregation
+- [ ] Workflow Automation — pre-commit hooks, linters, formatters, changelog generation
+- [ ] Exercises & Projects — CI pipeline for a project, pre-commit setup, automated backup system
+
+---
+
+## Phase 5 — Embedded World
+
+### 5.1 Embedded Systems
+`path: embedded-systems/`
+> Prereqs: Electronics, C, OS concepts
+
+- [ ] Introduction — embedded vs general-purpose, architectures (ARM Cortex-M, RISC-V, AVR), dev boards
+- [ ] Microcontroller Fundamentals — CPU architecture, memory map, registers, clock system, startup code
+- [ ] GPIO & Basic I/O — digital I/O, pull-ups/downs, debouncing, LED/button interfacing
+- [ ] Interrupts — interrupt vectors, ISRs, priority, NVIC (ARM), critical sections
+- [ ] Timers & PWM — timer modes, input capture, output compare, PWM generation, frequency measurement
+- [ ] Serial Communication — UART (theory + implementation), SPI, I2C, protocol debugging
+- [ ] ADC & DAC — conversion principles, sampling, resolution, DMA-driven ADC, signal conditioning
+- [ ] DMA — DMA controller, channels, circular mode, memory-to-peripheral, performance gains
+- [ ] Memory — flash programming, linker scripts, bootloader concepts, EEPROM, external memory
+- [ ] Low Power Design — sleep modes, wakeup sources, power budgets, current measurement
+- [ ] Communication Protocols — CAN bus, Modbus, 1-Wire, wireless overview (BLE, WiFi, LoRa)
+- [ ] HAL vs Bare-Metal — vendor HALs (STM32 HAL/LL), CMSIS, register-level programming
+- [ ] Debugging & Tools — JTAG/SWD, OpenOCD, GDB for embedded, logic analyzer, oscilloscope
+- [ ] Exercises & Projects — blinky to bare-metal, UART logger, SPI sensor driver, motor controller
+
+### 5.2 PCB Prototyping
+`path: pcb-prototyping/`
+> Prereqs: PCB Design, Electronics
+
+- [ ] Breadboarding & Perfboard — prototyping techniques, Manhattan style, wire wrapping
+- [ ] PCB Milling — desktop CNC (3018, Bantam), FlatCAM, isolation routing, drill files
+- [ ] Chemical Etching — toner transfer, UV photoresist, etchant chemistry, safety
+- [ ] Professional Fabrication — Gerber/drill generation, fab house selection (JLCPCB, PCBWay), ordering
+- [ ] Assembly — hand soldering (through-hole + SMD), reflow (hot plate, oven), stencils
+- [ ] Testing & Debugging — continuity testing, power-on sequence, functional testing, rework
+- [ ] *skip* Rapid Iteration — version control for hardware, ECOs, change management
+- [ ] Exercises & Projects — etch a simple board, order and assemble a PCB, full prototype cycle
+
+### 5.3 Digital Fabrication
+`path: digital-fabrication/`
+> Prereqs: Electronics basics, CAD basics
+
+- [ ] Overview — fab labs, maker culture, digital fabrication workflow
+- [ ] 3D Printing — FDM, SLA/DLP, materials, slicing (Cura/PrusaSlicer), design for 3D printing
+- [ ] Laser Cutting — materials, kerf compensation, design for laser, power/speed settings, safety
+- [ ] *skip* CNC Machining — milling basics, G-code, CAM (FreeCAD Path), materials, work-holding
+- [ ] CAD for Fabrication — FreeCAD, OpenSCAD, parametric design, export formats (STL, DXF, STEP)
+- [ ] Enclosure Design — designing for electronics, standoffs, cable routing, thermal considerations
+- [ ] *skip* Mold Making & Casting — silicone molds, resin casting, urethane, finishing
+- [ ] Exercises & Projects — 3D print an enclosure, laser-cut a panel, design a complete housing
+
+---
+
+## Phase 6 — Deep Systems
+
+### 6.1 Kernel Development
+`path: kernel-development/`
+> Prereqs: OS, C (advanced), Computer Architecture
+
+- [ ] Introduction — kernel role, monolithic vs micro, Linux source tree navigation
+- [ ] Build & Boot — kernel configuration, cross-compiling, QEMU/VM testing, boot process
+- [ ] Kernel Modules — module structure, Makefiles, loading/unloading, module parameters, licensing
+- [ ] Character Devices — file_operations, major/minor numbers, ioctl, sysfs, procfs
+- [ ] Kernel Memory — kmalloc/kfree, vmalloc, slab allocator, page allocator, GFP flags
+- [ ] Concurrency — spinlocks, mutexes, RCU, atomic operations, per-CPU data, completion
+- [ ] Interrupts & Timers — top/bottom halves, tasklets, workqueues, hrtimers, softirqs
+- [ ] Device Model — platform devices, device tree, driver model, buses, probe/remove
+- [ ] *skip* Networking — net_device, socket buffers (skb), netfilter hooks, packet flow
+- [ ] *skip* File Systems — VFS interface, implementing a simple filesystem, block layer
+- [ ] Debugging — printk/dynamic debug, ftrace, kprobes, KGDB, crash dump analysis, KASAN
+- [ ] Exercises & Projects — hello-world module, char device driver, device tree overlay, simple driver
+
+### 6.2 Embedded OS & RTOS
+`path: embedded-os/`
+> Prereqs: Embedded Systems, OS, C
+
+- [ ] RTOS Concepts — real-time constraints, hard/soft RT, scheduling (rate monotonic, EDF), WCET
+- [ ] FreeRTOS — tasks, queues, semaphores/mutexes, timers, memory management, portability
+- [ ] Zephyr — architecture, devicetree, Kconfig, kernel primitives, networking stack, west tool
+- [ ] Embedded Linux — Buildroot, Yocto/OpenEmbedded, cross-toolchains, root filesystem, device tree
+- [ ] Bootloaders — U-Boot, MCUboot, boot stages, secure boot chain
+- [ ] Bare-Metal vs RTOS vs Linux — decision framework, resource constraints, trade-offs
+- [ ] Inter-Task Communication — message queues, event groups, mailboxes, shared memory, priority inversion
+- [ ] *skip* OTA Updates — firmware update strategies, A/B partitions, rollback, delta updates
+- [ ] Exercises & Projects — FreeRTOS multitask app, Zephyr sensor driver, Buildroot custom image
+
+### 6.3 Internet of Things
+`path: iot/`
+> Prereqs: Embedded Systems, Web Tech (backend), Networking
+
+- [ ] IoT Architecture — edge/fog/cloud, reference architectures, design patterns
+- [ ] Connectivity — WiFi, BLE, LoRa/LoRaWAN, Zigbee, Thread, protocol comparison
+- [ ] Messaging Protocols — MQTT (deep dive), CoAP, HTTP for IoT, WebSockets
+- [ ] IoT Platforms — Home Assistant, Node-RED, Thingsboard, AWS IoT Core overview
+- [ ] Data Pipeline — collection, time-series databases (InfluxDB), processing, dashboards (Grafana)
+- [ ] Edge Computing — local processing, gateway design, edge inference, offline operation
+- [ ] Security & Privacy — device identity, secure communication, firmware signing, threat landscape
+- [ ] *skip* Standards — Matter, Thread, OPC-UA, industrial IoT
+- [ ] Exercises & Projects — MQTT sensor network, LoRa node + gateway, home automation system
+
+---
+
+## Phase 7 — Intelligence & Integration
+
+### 7.1 Computer Vision & Edge AI
+`path: computer-vision/`
+> Prereqs: Math (linear algebra, calculus, probability), Python, Embedded basics
+
+- [ ] Image Fundamentals — digital images, color spaces, histograms, geometric transforms
+- [ ] Image Processing — convolution, filtering, morphology, edge detection, thresholding
+- [ ] Feature Detection — corners (Harris, Shi-Tomasi), descriptors (SIFT/ORB), feature matching
+- [ ] Classical CV — template matching, object detection (Haar/HOG), tracking, optical flow
+- [ ] Deep Learning Foundations — neural networks, backprop, CNNs, training loop, PyTorch/TensorFlow
+- [ ] Object Detection & Segmentation — YOLO, SSD, U-Net, Mask R-CNN, transfer learning
+- [ ] Model Optimization — quantization, pruning, knowledge distillation, ONNX, TFLite
+- [ ] Edge Deployment — Coral TPU, Jetson, OpenMV, RPi, NPU/VPU, inference frameworks
+- [ ] *skip* Advanced Topics — 3D vision, depth estimation, visual SLAM, generative models
+- [ ] Exercises & Projects — OpenCV pipeline, train a classifier, deploy on edge device, visual inspection
+
+### 7.2 Robotics
+`path: robotics/`
+> Prereqs: Math (linear algebra, calculus, diff eq), Electronics, Embedded, Programming (C++/Python)
+
+- [ ] Foundations — robot types, coordinate frames, homogeneous transforms, DH convention
+- [ ] Kinematics — forward/inverse kinematics, Jacobian, velocity kinematics, workspace analysis
+- [ ] Dynamics — Newton-Euler, Lagrangian mechanics, torque computation, gravity compensation
+- [ ] Actuators & Sensors — DC/stepper/servo motors, encoders, IMUs, LIDAR, depth cameras
+- [ ] Control Systems — PID control, state-space, trajectory generation, motion profiles
+- [ ] Motion Planning — path planning (A*, RRT, PRM), obstacle avoidance, configuration space
+- [ ] *skip* SLAM & Localization — EKF/particle filter, lidar SLAM, visual SLAM, sensor fusion
+- [ ] ROS 2 — architecture, nodes, topics, services, actions, launch files, URDF, Gazebo
+- [ ] *skip* Mobile Robots — wheeled (differential, omnidirectional), legged basics, drone basics
+- [ ] Exercises & Projects — PID line follower, robotic arm control, ROS 2 navigation stack
+
+### 7.3 Rust
+`path: programming/rust/`
+> Prereqs: Programming Fundamentals, C (recommended)
+
+- [ ] Getting Started — cargo, crate structure, hello world, rustup, editions
+- [ ] Ownership & Borrowing — ownership rules, references, lifetimes, borrow checker
+- [ ] Type System — structs, enums, pattern matching, generics, traits, trait objects
+- [ ] Error Handling — Result, Option, ? operator, custom errors, anyhow/thiserror
+- [ ] Collections & Iterators — Vec, HashMap, iterator adaptors, closures, functional patterns
+- [ ] Concurrency — threads, channels, Arc/Mutex, Send/Sync, async/await, tokio basics
+- [ ] Unsafe & FFI — unsafe blocks, raw pointers, extern "C", bindgen, calling C from Rust
+- [ ] Ecosystem — serde, clap, reqwest, embedded Rust (no_std), testing, benchmarking
+- [ ] Exercises & Projects — CLI tool, concurrent web scraper, embedded Rust blinky, C FFI wrapper
+
+### 7.4 C++
+`path: programming/cpp/`
+> Prereqs: C, Programming Fundamentals
+
+- [ ] C++ Basics — classes, constructors/destructors, namespaces, references, streams
+- [ ] OOP — inheritance, polymorphism, virtual functions, abstract classes, RTTI
+- [ ] Templates — function/class templates, template specialization, variadic templates, SFINAE basics
+- [ ] STL — containers, iterators, algorithms, string, functional
+- [ ] Modern C++ (17/20/23) — auto, structured bindings, ranges, concepts, modules, coroutines overview
+- [ ] Memory Management — RAII, smart pointers (unique/shared/weak), move semantics, rule of 5
+- [ ] Concurrency — std::thread, mutex, condition_variable, atomic, futures, async
+- [ ] *skip* Advanced — type erasure, CRTP, policy-based design, compile-time computation
+- [ ] Exercises & Projects — matrix library, concurrent data structure, embedded C++ on MCU
+
+### 7.5 Advanced Languages
+`path: programming/advanced-languages/`
+> Prereqs: Programming Fundamentals, specific domain knowledge per language
+
+- [ ] Python Advanced — decorators, generators, metaclasses, asyncio, C extensions, type hints
+- [ ] Lua — tables, metatables, coroutines, embedding in C, LuaJIT FFI, game scripting
+- [ ] Julia — multiple dispatch, type system, macros, performance, scientific computing, GPU
+- [ ] R — data frames, tidyverse, ggplot2, statistical modeling, R Markdown
+- [ ] Cross-Language — FFI patterns, polyglot projects, language interop, choosing the right tool
+- [ ] Exercises & Projects — Python C extension, Lua-embedded app, Julia scientific notebook, R analysis
+
+---
+
+## Side Quest — Anytime
+
+### S.1 Global Geek Culture
+`path: geek-culture/`
+> Prereqs: None — read alongside any phase
+
+- [ ] Hacker Culture Origins — MIT AI Lab, Tech Model Railroad Club, PDP-1, hacker ethic, "Hackers" (Levy)
+- [ ] Phone Phreaking & Early Hacking — Captain Crunch, blue boxes, 2600 magazine, WarGames, Legion of Doom
+- [ ] The Jargon File & Hacker Lore — Jargon File, INTERCAL, RFC humor, koans, AI koans, quines, IOCCC
+- [ ] Text File Underground — textfiles.com, e-zines (Phrack, LoD/H Technical Journal), BBS culture, ANSI art
+- [ ] The Geek Code & .sig Culture — Geek Code blocks, ASCII art sigs, fortune files, Plan files, finger protocol
+- [ ] Usenet & Early Internet Culture — flame wars, FAQs, killfiles, alt.* hierarchy, trolling as art, Eternal September
+- [ ] Cryptography Wars & Cypherpunks — cypherpunk mailing list, PGP, Clipper chip, Zimmermann, remailers
+- [ ] Demoscene — demo groups, size coding (4k/64k intros), Amiga/Atari scene, Revision/Assembly parties, pouet.net
+- [ ] Open Source Movement — GNU Manifesto, FSF, Cathedral vs Bazaar, Linux, copyleft vs permissive, forking culture
+- [ ] Warez & Underground Scenes — warez groups, NFOs, cracktros, courier culture, The Scene
+- [ ] Internet Mysteries & Challenges — Cicada 3301, Webdriver Torso, Markovian Parallax Denigrate, crypto puzzles, ARGs
+- [ ] MUDs, MOOs & Virtual Worlds — text adventures, LambdaMOO, online community origins, digital identity
+- [ ] Hacker Conferences & Community — DEF CON, CCC, 2600 meetings, hackerspaces, CTF culture
+- [ ] Maker & Hardware Hacking — hackerspaces, CCC hardware, Arduino/RPi, OSHW, right to repair, modding
+- [ ] *skip* Cyberpunk & Sci-Fi Canon — Gibson, Stephenson, Dick, Doctorow, technology predictions
+- [ ] *skip* Global Perspectives — CCC vs DEF CON culture, Shenzhen, Global South tech, internet freedom
+
+---
+
+## Summary
+
+| # | Topic | Modules | Status |
+|---|-------|---------|--------|
+| 1.1 | Linux Command Line | 10 | Not started |
+| 1.2 | Git | 9 | Not started |
+| 1.3 | Technical Writing | 7 | Not started |
+| 1.4 | Programming Fundamentals | 9 | Not started |
+| 1.5 | C | 11 | Not started |
+| 1.6 | Python | 10 | Not started |
+| 1.7 | Bash Scripting | 8 | Not started |
+| 1.8 | Mathematics | 12 | Not started |
+| 2.1 | Computer Science | 10 | Not started |
+| 2.2 | Electronics | 13 | Not started |
+| 2.3 | Make & Dev Automation | 8 | Not started |
+| 3.1 | Operating Systems | 11 | Not started |
+| 3.2 | PCB Design | 10 | Not started |
+| 3.3 | Web Technologies | 9 | Not started |
+| 3.4 | Project Management | 8 | Not started |
+| 4.1 | Cyber Security | 9 | Not started |
+| 4.2 | Ansible | 8 | Not started |
+| 4.3 | Automation | 8 | Not started |
+| 5.1 | Embedded Systems | 14 | Not started |
+| 5.2 | PCB Prototyping | 8 | Not started |
+| 5.3 | Digital Fabrication | 8 | Not started |
+| 6.1 | Kernel Development | 12 | Not started |
+| 6.2 | Embedded OS & RTOS | 9 | Not started |
+| 6.3 | IoT | 9 | Not started |
+| 7.1 | Computer Vision & Edge AI | 10 | Not started |
+| 7.2 | Robotics | 10 | Not started |
+| 7.3 | Rust | 9 | Not started |
+| 7.4 | C++ | 9 | Not started |
+| 7.5 | Advanced Languages | 6 | Not started |
+| S.1 | Geek Culture | 16 | Not started |
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..a25b9ca
--- /dev/null
+++ b/README.md
@@ -0,0 +1,38 @@
+# ubergeek
+
+A comprehensive, self-paced study guide covering electronics, embedded
+systems, programming, computer science, cybersecurity, and more. Built as a
+sourcehut wiki.
+
+## Topics
+
+30 topics across 7 phases — from command line basics to robotics and edge AI.
+See [PROGRESS.md](PROGRESS.md) for the full topic list and study roadmap.
+
+## Structure
+
+Each topic follows the same layout:
+
+```
+topic-name/
+├── index.md # overview and module list
+├── images/ # diagrams, schematics, screenshots
+├── samples/ # runnable code and project files
+└── module-name/
+ └── index.md # module content
+```
+
+## Links
+
+- **Sourcehut (primary):** https://git.sr.ht/~gumxcc/ubergeek
+- **Wiki:** https://man.sr.ht/~gumxcc/ubergeek/
+
+## Contributing
+
+See [CONTRIBUTING.md](CONTRIBUTING.md) for guidelines on submitting patches,
+pull requests, or merge requests.
+
+## License
+
+Content is [CC BY-SA 4.0](LICENSES/CC-BY-SA-4.0). Code samples are
+[MIT](LICENSES/MIT). See [LICENSE.md](LICENSE.md) for details.
diff --git a/build.sh b/build.sh
new file mode 100644
index 0000000..2973c2f
--- /dev/null
+++ b/build.sh
@@ -0,0 +1,215 @@
+#!/bin/bash
+# build.sh - ubergeek static site generator
+# Converts the wiki markdown structure into HTML using lowdown + mo.
+
+[[ "$(basename "${0}")" == "build.sh" ]] || { echo "Do not source build.sh"; exit 1; }
+
+set -euo pipefail
+
+SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
+if [[ ! -f "${SCRIPT_DIR}/build/mo" ]]; then
+ echo "[error] build/mo not found. Run: curl -sL https://raw.githubusercontent.com/tests-always-included/mo/master/mo -o build/mo && chmod +x build/mo" >&2
+ exit 1
+fi
+. "${SCRIPT_DIR}/build/mo"
+
+FORCE=false
+while [[ $# -gt 0 ]]; do
+ case "$1" in
+ -f|--force) FORCE=true ;;
+ *) echo "[error] Unknown option: $1" >&2; exit 1 ;;
+ esac
+ shift
+done
+
+TMPDIR_BUILD="$(mktemp -d)"
+cleanup() { rm -rf "${TMPDIR_BUILD}"; }
+trap cleanup EXIT INT TERM
+
+# ---------------------------------------------------------------------------
+# Helpers
+# ---------------------------------------------------------------------------
+
+get_frontmatter() {
+ local key="$1" file="$2"
+ lowdown -X "${key}" "${file}" 2>/dev/null || true
+}
+
+check_has_code() {
+ local html="$1"
+ if echo "${html}" | grep -qE '<pre><code|<code class='; then
+ HAS_CODE="true"
+ else
+ HAS_CODE=""
+ fi
+}
+
+render_page() {
+ local template="$1" outfile="$2"
+ mkdir -p "$(dirname "${outfile}")"
+ local tname="${template##build/templates/}"
+ cp "${template}" "build/templates/partials/${tname}"
+ (cd build/templates/partials && mo "${tname}") > "${outfile}"
+ rm -f "build/templates/partials/${tname}"
+}
+
+build_breadcrumbs() {
+ local path="$1"
+ BREADCRUMBS=""
+ local components="${path}"
+ while [[ "${components}" != "." ]]; do
+ BREADCRUMBS=" / <a href=\"/${components}\">$(basename "${components}")</a>${BREADCRUMBS}"
+ components="$(dirname "${components}")"
+ done
+}
+
+# ---------------------------------------------------------------------------
+# process_dir dir
+# Recursively renders topic and module pages, returns a markdown list
+# of direct children into ${TMPDIR_BUILD}/${dir//\//_}.list.md
+# ---------------------------------------------------------------------------
+process_dir() {
+ local dir="$1"
+ local list_file="${TMPDIR_BUILD}/${dir//\//_}.list.md"
+ : > "${list_file}"
+
+ local find_list="${TMPDIR_BUILD}/${dir//\//_}.find.list"
+ find "${dir}" -mindepth 1 -maxdepth 1 -not -name '.*' -print0 | sort -z > "${find_list}"
+
+ while IFS= read -r -d '' node; do
+ if [[ -d "${node}" ]]; then
+ local subdir_title
+ subdir_title="$(get_frontmatter title "${node}/index.md" 2>/dev/null || true)"
+ [[ -z "${subdir_title}" ]] && subdir_title="$(basename "${node}")"
+ printf -- '- [%s](/%s)\n' "${subdir_title}" "${node}" >> "${list_file}"
+ process_dir "${node}"
+
+ elif [[ "${node}" == *.md && "$(basename "${node}")" != "index.md" ]]; then
+ local slug="${node%.md}"
+ local title date lang
+ title="$(get_frontmatter title "${node}")"
+ [[ -z "${title}" ]] && title="$(basename "${slug}")"
+ date="$(get_frontmatter date "${node}")"
+ lang="$(get_frontmatter lang "${node}")"
+
+ local out_html="output/${slug}/index.html"
+ local content
+ content="$(lowdown "${node}")"
+ check_has_code "${content}"
+ build_breadcrumbs "${slug}"
+
+ export PAGE_TITLE="${title}"
+ export CONTENT="${content}"
+ export BREADCRUMBS
+ export HEADER_EXTRA="${date}"
+ export META_DATE="${date}"
+ export META_DESCRIPTION=""
+ export HAS_CODE
+ export LANG="${lang:-en}"
+ export DIR=""
+
+ render_page "build/templates/page.html" "${out_html}"
+ printf -- '- [%s](/%s)\n' "${title}" "${slug}" >> "${list_file}"
+ echo "[built] ${out_html}"
+ fi
+ done < "${find_list}"
+
+ # Build this directory's index page
+ local index_src="${dir}/index.md"
+ local index_md="${TMPDIR_BUILD}/${dir//\//_}.index.md"
+ local dir_title
+
+ if [[ -f "${index_src}" ]]; then
+ dir_title="$(get_frontmatter title "${index_src}")"
+ cp "${index_src}" "${index_md}"
+ else
+ dir_title="$(basename "${dir}")"
+ printf '# %s\n\n' "${dir_title}" > "${index_md}"
+ fi
+ printf '\n\n' >> "${index_md}"
+ cat "${list_file}" >> "${index_md}"
+
+ local out_html="output/${dir}/index.html"
+ local content
+ content="$(lowdown "${index_md}")"
+ check_has_code "${content}"
+ build_breadcrumbs "${dir}"
+
+ export PAGE_TITLE="${dir_title}"
+ export CONTENT="${content}"
+ export BREADCRUMBS
+ export HEADER_EXTRA=""
+ export META_DATE=""
+ export META_DESCRIPTION=""
+ export HAS_CODE
+ export LANG="en"
+ export DIR=""
+
+ render_page "build/templates/page.html" "${out_html}"
+ echo "[built] output/${dir}/index.html"
+}
+
+# ---------------------------------------------------------------------------
+# Main build
+# ---------------------------------------------------------------------------
+
+mkdir -p output
+
+# Discover top-level topic directories (exclude hidden, build, output)
+topics_list="${TMPDIR_BUILD}/topics.list.md"
+: > "${topics_list}"
+
+find_list="${TMPDIR_BUILD}/toplevel.find.list"
+find . -mindepth 1 -maxdepth 1 -type d \
+ -not -name '.*' -not -name 'build' -not -name 'output' \
+ -print0 | sort -z > "${find_list}"
+
+while IFS= read -r -d '' topic; do
+ topic="${topic#./}"
+ topic_title="$(get_frontmatter title "${topic}/index.md" 2>/dev/null || true)"
+ [[ -z "${topic_title}" ]] && topic_title="${topic}"
+ printf -- '- [%s](/%s)\n' "${topic_title}" "${topic}" >> "${topics_list}"
+ process_dir "${topic}"
+done < "${find_list}"
+
+# Home page — README.md + topic listing
+home_md="${TMPDIR_BUILD}/home.md"
+cp README.md "${home_md}"
+printf '\n\n## topics:\n\n' >> "${home_md}"
+cat "${topics_list}" >> "${home_md}"
+
+content="$(lowdown "${home_md}")"
+check_has_code "${content}"
+
+export PAGE_TITLE=""
+export CONTENT="${content}"
+export BREADCRUMBS=""
+export HEADER_EXTRA=""
+export META_DATE=""
+export META_DESCRIPTION=""
+export HAS_CODE
+export LANG="en"
+export DIR=""
+
+render_page "build/templates/home.html" "output/index.html"
+echo "[built] output/index.html"
+
+# License page
+if [[ -f "LICENSE.md" ]]; then
+ content="$(lowdown LICENSE.md)"
+ check_has_code "${content}"
+ export PAGE_TITLE="license"
+ export CONTENT="${content}"
+ export BREADCRUMBS=" / <a href=\"/license\">license</a>"
+ export HEADER_EXTRA=""
+ export META_DATE=""
+ export META_DESCRIPTION=""
+ export HAS_CODE
+ export LANG="en"
+ export DIR=""
+ render_page "build/templates/page.html" "output/license/index.html"
+ echo "[built] output/license/index.html"
+fi
+
+echo ""
+echo "[done] Build complete. Output in output/"
diff --git a/build/mo b/build/mo
new file mode 100755
index 0000000..8247a2d
--- /dev/null
+++ b/build/mo
@@ -0,0 +1,2037 @@
+#!/usr/bin/env bash
+#
+#/ Mo is a mustache template rendering software written in bash. It inserts
+#/ environment variables into templates.
+#/
+#/ Simply put, mo will change {{VARIABLE}} into the value of that
+#/ environment variable. You can use {{#VARIABLE}}content{{/VARIABLE}} to
+#/ conditionally display content or iterate over the values of an array.
+#/
+#/ Learn more about mustache templates at https://mustache.github.io/
+#/
+#/ Simple usage:
+#/
+#/ mo [OPTIONS] filenames...
+#/
+#/ Options:
+#/
+#/ --allow-function-arguments
+#/ Permit functions to be called with additional arguments. Otherwise,
+#/ the only way to get access to the arguments is to use the
+#/ MO_FUNCTION_ARGS environment variable.
+#/ -d, --debug
+#/ Enable debug logging to stderr.
+#/ -u, --fail-not-set
+#/ Fail upon expansion of an unset variable. Will silently ignore by
+#/ default. Alternately, set MO_FAIL_ON_UNSET to a non-empty value.
+#/ -x, --fail-on-function
+#/ Fail when a function returns a non-zero status code instead of
+#/ silently ignoring it. Alternately, set MO_FAIL_ON_FUNCTION to a
+#/ non-empty value.
+#/ -f, --fail-on-file
+#/ Fail when a file (from command-line or partial) does not exist.
+#/ Alternately, set MO_FAIL_ON_FILE to a non-empty value.
+#/ -e, --false
+#/ Treat the string "false" as empty for conditionals. Alternately,
+#/ set MO_FALSE_IS_EMPTY to a non-empty value.
+#/ -h, --help
+#/ This message.
+#/ -s=FILE, --source=FILE
+#/ Load FILE into the environment before processing templates.
+#/ Can be used multiple times. The file must be a valid shell script
+#/ and should only contain variable assignments.
+#/ -o=DELIM, --open=DELIM
+#/ Set the opening delimiter. Default is "{{".
+#/ -c=DELIM, --close=DELIM
+#/ Set the closing delimiter. Default is "}}".
+#/ -- Indicate the end of options. All arguments after this will be
+#/ treated as filenames only. Use when filenames may start with
+#/ hyphens.
+#/
+#/ Mo uses the following environment variables:
+#/
+#/ MO_ALLOW_FUNCTION_ARGUMENTS - When set to a non-empty value, this allows
+#/ functions referenced in templates to receive additional options and
+#/ arguments.
+#/ MO_CLOSE_DELIMITER - The string used when closing a tag. Defaults to "}}".
+#/ Used internally.
+#/ MO_CLOSE_DELIMITER_DEFAULT - The default value of MO_CLOSE_DELIMITER. Used
+#/ when resetting the close delimiter, such as when parsing a partial.
+#/ MO_CURRENT - Variable name to use for ".".
+#/ MO_DEBUG - When set to a non-empty value, additional debug information is
+#/ written to stderr.
+#/ MO_FUNCTION_ARGS - Arguments passed to the function.
+#/ MO_FAIL_ON_FILE - If a filename from the command-line is missing or a
+#/ partial does not exist, abort with an error.
+#/ MO_FAIL_ON_FUNCTION - If a function returns a non-zero status code, abort
+#/ with an error.
+#/ MO_FAIL_ON_UNSET - When set to a non-empty value, expansion of an unset env
+#/ variable will be aborted with an error.
+#/ MO_FALSE_IS_EMPTY - When set to a non-empty value, the string "false" will
+#/ be treated as an empty value for the purposes of conditionals.
+#/ MO_OPEN_DELIMITER - The string used when opening a tag. Defaults to "{{".
+#/ Used internally.
+#/ MO_OPEN_DELIMITER_DEFAULT - The default value of MO_OPEN_DELIMITER. Used
+#/ when resetting the open delimiter, such as when parsing a partial.
+#/ MO_ORIGINAL_COMMAND - Used to find the `mo` program in order to generate a
+#/ help message.
+#/ MO_PARSED - Content that has made it through the template engine.
+#/ MO_STANDALONE_CONTENT - The unparsed content that preceeded the current tag.
+#/ When a standalone tag is encountered, this is checked to see if it only
+#/ contains whitespace. If this and the whitespace condition after a tag is
+#/ met, then this will be reset to $'\n'.
+#/ MO_UNPARSED - Template content yet to make it through the parser.
+#/
+#/ Mo is under a MIT style licence with an additional non-advertising clause.
+#/ See LICENSE.md for the full text.
+#/
+#/ This is open source! Please feel free to contribute.
+#/
+#/ https://github.com/tests-always-included/mo
+
+#: Disable these warnings for the entire file
+#:
+#: VAR_NAME was modified in a subshell. That change might be lost.
+# shellcheck disable=SC2031
+#:
+#: Modification of VAR_NAME is local (to subshell caused by (..) group).
+# shellcheck disable=SC2030
+
+# Public: Template parser function. Writes templates to stdout.
+#
+# $0 - Name of the mo file, used for getting the help message.
+# $@ - Filenames to parse.
+#
+# Returns nothing.
+mo() (
+ local moSource moFiles moDoubleHyphens moParsed moContent
+
+ #: This function executes in a subshell; IFS is reset at the end.
+ IFS=$' \n\t'
+
+ #: Enable a strict mode. This is also reset at the end.
+ set -eEu -o pipefail
+ moFiles=()
+ moDoubleHyphens=false
+ MO_OPEN_DELIMITER_DEFAULT="{{"
+ MO_CLOSE_DELIMITER_DEFAULT="}}"
+ MO_FUNCTION_CACHE_HIT=()
+ MO_FUNCTION_CACHE_MISS=()
+
+ if [[ $# -gt 0 ]]; then
+ for arg in "$@"; do
+ if $moDoubleHyphens; then
+ #: After we encounter two hyphens together, all the rest
+ #: of the arguments are files.
+ moFiles=(${moFiles[@]+"${moFiles[@]}"} "$arg")
+ else
+ case "$arg" in
+ -h|--h|--he|--hel|--help|-\?)
+ mo::usage "$0"
+ exit 0
+ ;;
+
+ --allow-function-arguments)
+ MO_ALLOW_FUNCTION_ARGUMENTS=true
+ ;;
+
+ -u | --fail-not-set)
+ MO_FAIL_ON_UNSET=true
+ ;;
+
+ -x | --fail-on-function)
+ MO_FAIL_ON_FUNCTION=true
+ ;;
+
+ -p | --fail-on-file)
+ MO_FAIL_ON_FILE=true
+ ;;
+
+ -e | --false)
+ MO_FALSE_IS_EMPTY=true
+ ;;
+
+ -s=* | --source=*)
+ if [[ "$arg" == --source=* ]]; then
+ moSource="${arg#--source=}"
+ else
+ moSource="${arg#-s=}"
+ fi
+
+ if [[ -e "$moSource" ]]; then
+ # shellcheck disable=SC1090
+ . "$moSource"
+ else
+ echo "No such file: $moSource" >&2
+ exit 1
+ fi
+ ;;
+
+ -o=* | --open=*)
+ if [[ "$arg" == --open=* ]]; then
+ MO_OPEN_DELIMITER_DEFAULT="${arg#--open=}"
+ else
+ MO_OPEN_DELIMITER_DEFAULT="${arg#-o=}"
+ fi
+ ;;
+
+ -c=* | --close=*)
+ if [[ "$arg" == --close=* ]]; then
+ MO_CLOSE_DELIMITER_DEFAULT="${arg#--close=}"
+ else
+ MO_CLOSE_DELIMITER_DEFAULT="${arg#-c=}"
+ fi
+ ;;
+
+ -d | --debug)
+ MO_DEBUG=true
+ ;;
+
+ --)
+ #: Set a flag indicating we've encountered double hyphens
+ moDoubleHyphens=true
+ ;;
+
+ -*)
+ mo::error "Unknown option: $arg (See --help for options)"
+ ;;
+
+ *)
+ #: Every arg that is not a flag or a option should be a file
+ moFiles=(${moFiles[@]+"${moFiles[@]}"} "$arg")
+ ;;
+ esac
+ fi
+ done
+ fi
+
+ mo::debug "Debug enabled"
+ MO_OPEN_DELIMITER="$MO_OPEN_DELIMITER_DEFAULT"
+ MO_CLOSE_DELIMITER="$MO_CLOSE_DELIMITER_DEFAULT"
+ mo::content moContent ${moFiles[@]+"${moFiles[@]}"} || return 1
+ mo::parse moParsed "$moContent"
+ echo -n "$moParsed"
+)
+
+
+# Internal: Show a debug message
+#
+# $1 - The debug message to show
+#
+# Returns nothing.
+mo::debug() {
+ if [[ -n "${MO_DEBUG:-}" ]]; then
+ echo "DEBUG ${FUNCNAME[1]:-?} - $1" >&2
+ fi
+}
+
+
+# Internal: Show a debug message and internal state information
+#
+# No arguments
+#
+# Returns nothing.
+mo::debugShowState() {
+ if [[ -z "${MO_DEBUG:-}" ]]; then
+ return
+ fi
+
+ local moState moTemp moIndex moDots
+
+ mo::escape moTemp "$MO_OPEN_DELIMITER"
+ moState="open: $moTemp"
+ mo::escape moTemp "$MO_CLOSE_DELIMITER"
+ moState="$moState close: $moTemp"
+ mo::escape moTemp "$MO_STANDALONE_CONTENT"
+ moState="$moState standalone: $moTemp"
+ mo::escape moTemp "$MO_CURRENT"
+ moState="$moState current: $moTemp"
+ moIndex=$((${#MO_PARSED} - 20))
+ moDots=...
+
+ if [[ "$moIndex" -lt 0 ]]; then
+ moIndex=0
+ moDots=
+ fi
+
+ mo::escape moTemp "${MO_PARSED:$moIndex}"
+ moState="$moState parsed: $moDots$moTemp"
+
+ moDots=...
+
+ if [[ "${#MO_UNPARSED}" -le 20 ]]; then
+ moDots=
+ fi
+
+ mo::escape moTemp "${MO_UNPARSED:0:20}$moDots"
+ moState="$moState unparsed: $moTemp"
+
+ echo "DEBUG ${FUNCNAME[1]:-?} - $moState" >&2
+}
+
+# Internal: Show an error message and exit
+#
+# $1 - The error message to show
+# $2 - Error code
+#
+# Returns nothing. Exits the program.
+mo::error() {
+ echo "ERROR: $1" >&2
+ exit "${2:-1}"
+}
+
+
+# Internal: Show an error message with a snippet of context and exit
+#
+# $1 - The error message to show
+# $2 - The starting point
+# $3 - Error code
+#
+# Returns nothing. Exits the program.
+mo::errorNear() {
+ local moEscaped
+
+ mo::escape moEscaped "${2:0:40}"
+ echo "ERROR: $1" >&2
+ echo "ERROR STARTS NEAR: $moEscaped"
+ exit "${3:-1}"
+}
+
+
+# Internal: Displays the usage for mo. Pulls this from the file that
+# contained the `mo` function. Can only work when the right filename
+# comes is the one argument, and that only happens when `mo` is called
+# with `$0` set to this file.
+#
+# $1 - Filename that has the help message
+#
+# Returns nothing.
+mo::usage() {
+ while read -r line; do
+ if [[ "${line:0:2}" == "#/" ]]; then
+ echo "${line:3}"
+ fi
+ done < "$MO_ORIGINAL_COMMAND"
+ echo ""
+ echo "MO_VERSION=$MO_VERSION"
+}
+
+
+# Internal: Fetches the content to parse into MO_UNPARSED. Can be a list of
+# partials for files or the content from stdin.
+#
+# $1 - Destination variable name
+# $2-@ - File names (optional), read from stdin otherwise
+#
+# Returns nothing.
+mo::content() {
+ local moTarget moContent moFilename
+
+ moTarget=$1
+ shift
+ moContent=""
+
+ if [[ "${#@}" -gt 0 ]]; then
+ for moFilename in "$@"; do
+ mo::debug "Using template to load content from file: $moFilename"
+ #: This is so relative paths work from inside template files
+ moContent="$moContent$MO_OPEN_DELIMITER>$moFilename$MO_CLOSE_DELIMITER"
+ done
+ else
+ mo::debug "Will read content from stdin"
+ mo::contentFile moContent || return 1
+ fi
+
+ local "$moTarget" && mo::indirect "$moTarget" "$moContent"
+}
+
+
+# Internal: Read a file into MO_UNPARSED.
+#
+# $1 - Destination variable name.
+# $2 - Filename to load - if empty, defaults to /dev/stdin
+#
+# Returns nothing.
+mo::contentFile() {
+ local moFile moResult moContent
+
+ #: The subshell removes any trailing newlines. We forcibly add
+ #: a dot to the content to preserve all newlines. Reading from
+ #: stdin with a `read` loop does not work as expected, so `cat`
+ #: needs to stay.
+ moFile=${2:-/dev/stdin}
+
+ if [[ -e "$moFile" ]]; then
+ mo::debug "Loading content: $moFile"
+ moContent=$(
+ set +Ee
+ cat -- "$moFile"
+ moResult=$?
+ echo -n '.'
+ exit "$moResult"
+ ) || return 1
+ moContent=${moContent%.} #: Remove last dot
+ elif [[ -n "${MO_FAIL_ON_FILE-}" ]]; then
+ mo::error "No such file: $moFile"
+ else
+ mo::debug "File does not exist: $moFile"
+ moContent=""
+ fi
+
+ local "$1" && mo::indirect "$1" "$moContent"
+}
+
+
+# Internal: Send a variable up to the parent of the caller of this function.
+#
+# $1 - Variable name
+# $2 - Value
+#
+# Examples
+#
+# callFunc () {
+# local "$1" && mo::indirect "$1" "the value"
+# }
+# callFunc dest
+# echo "$dest" # writes "the value"
+#
+# Returns nothing.
+mo::indirect() {
+ unset -v "$1"
+ printf -v "$1" '%s' "$2"
+}
+
+
+# Internal: Send an array as a variable up to caller of a function
+#
+# $1 - Variable name
+# $2-@ - Array elements
+#
+# Examples
+#
+# callFunc () {
+# local myArray=(one two three)
+# local "$1" && mo::indirectArray "$1" "${myArray[@]}"
+# }
+# callFunc dest
+# echo "${dest[@]}" # writes "one two three"
+#
+# Returns nothing.
+mo::indirectArray() {
+ unset -v "$1"
+
+ #: IFS must be set to a string containing space or unset in order for
+ #: the array slicing to work regardless of the current IFS setting on
+ #: bash 3. This is detailed further at
+ #: https://github.com/fidian/gg-core/pull/7
+ eval "$(printf "IFS= %s=(\"\${@:2}\") IFS=%q" "$1" "$IFS")"
+}
+
+
+# Internal: Trim leading characters from MO_UNPARSED
+#
+# Returns nothing.
+mo::trimUnparsed() {
+ local moI moC
+
+ moI=0
+ moC=${MO_UNPARSED:0:1}
+
+ while [[ "$moC" == " " || "$moC" == $'\r' || "$moC" == $'\n' || "$moC" == $'\t' ]]; do
+ moI=$((moI + 1))
+ moC=${MO_UNPARSED:$moI:1}
+ done
+
+ if [[ "$moI" != 0 ]]; then
+ MO_UNPARSED=${MO_UNPARSED:$moI}
+ fi
+}
+
+
+# Internal: Remove whitespace and content after whitespace
+#
+# $1 - Name of the destination variable
+# $2 - The string to chomp
+#
+# Returns nothing.
+mo::chomp() {
+ local moTemp moR moN moT
+
+ moR=$'\r'
+ moN=$'\n'
+ moT=$'\t'
+ moTemp=${2%% *}
+ moTemp=${moTemp%%"$moR"*}
+ moTemp=${moTemp%%"$moN"*}
+ moTemp=${moTemp%%"$moT"*}
+
+ local "$1" && mo::indirect "$1" "$moTemp"
+}
+
+
+# Public: Parses text, interpolates mustache tags. Utilizes the current value
+# of MO_OPEN_DELIMITER, MO_CLOSE_DELIMITER, and MO_STANDALONE_CONTENT. Those
+# three variables shouldn't be changed by user-defined functions.
+#
+# $1 - Destination variable name - where to store the finished content
+# $2 - Content to parse
+# $3 - Preserve standalone status/content - truthy if not empty. When set to a
+# value, that becomes the standalone content value
+#
+# Returns nothing.
+mo::parse() {
+ local moOldParsed moOldStandaloneContent moOldUnparsed moResult
+
+ #: The standalone content is a trick to make the standalone tag detection
+ #: possible. When it's set to content with a newline and if the tag supports
+ #: it, the standalone content check happens. This check ensures only
+ #: whitespace is after the last newline up to the tag, and only whitespace
+ #: is after the tag up to the next newline. If that is the case, remove
+ #: whitespace and the trailing newline. By setting this to $'\n', we're
+ #: saying we are at the beginning of content.
+ mo::debug "Starting parse of ${#2} bytes"
+ moOldParsed=${MO_PARSED:-}
+ moOldUnparsed=${MO_UNPARSED:-}
+ MO_PARSED=""
+ MO_UNPARSED="$2"
+
+ if [[ -z "${3:-}" ]]; then
+ moOldStandaloneContent=${MO_STANDALONE_CONTENT:-}
+ MO_STANDALONE_CONTENT=$'\n'
+ else
+ MO_STANDALONE_CONTENT=$3
+ fi
+
+ MO_CURRENT=${MO_CURRENT:-}
+ mo::parseInternal
+ moResult="$MO_PARSED$MO_UNPARSED"
+ MO_PARSED=$moOldParsed
+ MO_UNPARSED=$moOldUnparsed
+
+ if [[ -z "${3:-}" ]]; then
+ MO_STANDALONE_CONTENT=$moOldStandaloneContent
+ fi
+
+ local "$1" && mo::indirect "$1" "$moResult"
+}
+
+
+# Internal: Parse MO_UNPARSED, writing content to MO_PARSED. Interpolates
+# mustache tags.
+#
+# No arguments
+#
+# Returns nothing.
+mo::parseInternal() {
+ local moChunk
+
+ mo::debug "Starting parse"
+
+ while [[ -n "$MO_UNPARSED" ]]; do
+ mo::debugShowState
+ moChunk=${MO_UNPARSED%%"$MO_OPEN_DELIMITER"*}
+ MO_PARSED="$MO_PARSED$moChunk"
+ MO_STANDALONE_CONTENT="$MO_STANDALONE_CONTENT$moChunk"
+ MO_UNPARSED=${MO_UNPARSED:${#moChunk}}
+
+ if [[ -n "$MO_UNPARSED" ]]; then
+ MO_UNPARSED=${MO_UNPARSED:${#MO_OPEN_DELIMITER}}
+ mo::trimUnparsed
+
+ case "$MO_UNPARSED" in
+ '#'*)
+ #: Loop, if/then, or pass content through function
+ mo::parseBlock false
+ ;;
+
+ '^'*)
+ #: Display section if named thing does not exist
+ mo::parseBlock true
+ ;;
+
+ '>'*)
+ #: Load partial - get name of file relative to cwd
+ mo::parsePartial
+ ;;
+
+ '/'*)
+ #: Closing tag
+ mo::errorNear "Unbalanced close tag" "$MO_UNPARSED"
+ ;;
+
+ '!'*)
+ #: Comment - ignore the tag content entirely
+ mo::parseComment
+ ;;
+
+ '='*)
+ #: Change delimiters
+ #: Any two non-whitespace sequences separated by whitespace.
+ mo::parseDelimiter
+ ;;
+
+ '&'*)
+ #: Unescaped - mo doesn't escape/unescape
+ MO_UNPARSED=${MO_UNPARSED#&}
+ mo::trimUnparsed
+ mo::parseValue
+ ;;
+
+ *)
+ #: Normal environment variable, string, subexpression,
+ #: current value, key, or function call
+ mo::parseValue
+ ;;
+ esac
+ fi
+ done
+}
+
+
+# Internal: Handle parsing a block
+#
+# $1 - Invert condition ("true" or "false")
+#
+# Returns nothing
+mo::parseBlock() {
+ local moInvertBlock moTokens moTokensString
+
+ moInvertBlock=$1
+ MO_UNPARSED=${MO_UNPARSED:1}
+ mo::tokenizeTagContents moTokens "$MO_CLOSE_DELIMITER"
+ MO_UNPARSED=${MO_UNPARSED#"$MO_CLOSE_DELIMITER"}
+ mo::tokensToString moTokensString "${moTokens[@]:1}"
+ mo::debug "Parsing block: $moTokensString"
+
+ if mo::standaloneCheck; then
+ mo::standaloneProcess
+ fi
+
+ if [[ "${moTokens[1]}" == "NAME" ]] && mo::isFunction "${moTokens[2]}"; then
+ mo::parseBlockFunction "$moInvertBlock" "$moTokensString" "${moTokens[@]:1}"
+ elif [[ "${moTokens[1]}" == "NAME" ]] && mo::isArray "${moTokens[2]}"; then
+ mo::parseBlockArray "$moInvertBlock" "$moTokensString" "${moTokens[@]:1}"
+ else
+ mo::parseBlockValue "$moInvertBlock" "$moTokensString" "${moTokens[@]:1}"
+ fi
+}
+
+
+# Internal: Handle parsing a block whose first argument is a function
+#
+# $1 - Invert condition ("true" or "false")
+# $2-@ - The parsed tokens from inside the block tags
+#
+# Returns nothing
+mo::parseBlockFunction() {
+ local moTarget moInvertBlock moTokens moTemp moUnparsed moTokensString
+
+ moInvertBlock=$1
+ moTokensString=$2
+ shift 2
+ moTokens=(${@+"$@"})
+ mo::debug "Parsing block function: $moTokensString"
+ mo::getContentUntilClose moTemp "$moTokensString"
+ #: Pass unparsed content to the function.
+ #: Keep the updated delimiters if they changed.
+
+ if [[ "$moInvertBlock" != "true" ]]; then
+ mo::evaluateFunction moResult "$moTemp" "${moTokens[@]:1}"
+ MO_PARSED="$MO_PARSED$moResult"
+ fi
+
+ mo::debug "Done parsing block function: $moTokensString"
+}
+
+
+# Internal: Handle parsing a block whose first argument is an array
+#
+# $1 - Invert condition ("true" or "false")
+# $2-@ - The parsed tokens from inside the block tags
+#
+# Returns nothing
+mo::parseBlockArray() {
+ local moInvertBlock moTokens moResult moArrayName moArrayIndexes moArrayIndex moTemp moUnparsed moOpenDelimiterBefore moCloseDelimiterBefore moOpenDelimiterAfter moCloseDelimiterAfter moParsed moTokensString moCurrent
+
+ moInvertBlock=$1
+ moTokensString=$2
+ shift 2
+ moTokens=(${@+"$@"})
+ mo::debug "Parsing block array: $moTokensString"
+ moOpenDelimiterBefore=$MO_OPEN_DELIMITER
+ moCloseDelimiterBefore=$MO_CLOSE_DELIMITER
+ mo::getContentUntilClose moTemp "$moTokensString"
+ moOpenDelimiterAfter=$MO_OPEN_DELIMITER
+ moCloseDelimiterAfter=$MO_CLOSE_DELIMITER
+ moArrayName=${moTokens[1]}
+ eval "moArrayIndexes=(\"\${!${moArrayName}[@]}\")"
+
+ if [[ "${#moArrayIndexes[@]}" -lt 1 ]]; then
+ #: No elements
+ if [[ "$moInvertBlock" == "true" ]]; then
+ #: Restore the delimiter before parsing
+ MO_OPEN_DELIMITER=$moOpenDelimiterBefore
+ MO_CLOSE_DELIMITER=$moCloseDelimiterBefore
+ moCurrent=$MO_CURRENT
+ MO_CURRENT=$moArrayName
+ mo::parse moParsed "$moTemp" "blockArrayInvert$MO_STANDALONE_CONTENT"
+ MO_CURRENT=$moCurrent
+ MO_PARSED="$MO_PARSED$moParsed"
+ fi
+ else
+ if [[ "$moInvertBlock" != "true" ]]; then
+ #: Process for each element in the array
+ moUnparsed=$MO_UNPARSED
+
+ for moArrayIndex in "${moArrayIndexes[@]}"; do
+ #: Restore the delimiter before parsing
+ MO_OPEN_DELIMITER=$moOpenDelimiterBefore
+ MO_CLOSE_DELIMITER=$moCloseDelimiterBefore
+ moCurrent=$MO_CURRENT
+ MO_CURRENT=$moArrayName.$moArrayIndex
+ mo::debug "Iterate over array using element: $MO_CURRENT"
+ mo::parse moParsed "$moTemp" "blockArray$MO_STANDALONE_CONTENT"
+ MO_CURRENT=$moCurrent
+ MO_PARSED="$MO_PARSED$moParsed"
+ done
+
+ MO_UNPARSED=$moUnparsed
+ fi
+ fi
+
+ MO_OPEN_DELIMITER=$moOpenDelimiterAfter
+ MO_CLOSE_DELIMITER=$moCloseDelimiterAfter
+ mo::debug "Done parsing block array: $moTokensString"
+}
+
+
+# Internal: Handle parsing a block whose first argument is a value
+#
+# $1 - Invert condition ("true" or "false")
+# $2-@ - The parsed tokens from inside the block tags
+#
+# Returns nothing
+mo::parseBlockValue() {
+ local moInvertBlock moTokens moResult moUnparsed moOpenDelimiterBefore moOpenDelimiterAfter moCloseDelimiterBefore moCloseDelimiterAfter moParsed moTemp moTokensString moCurrent
+
+ moInvertBlock=$1
+ moTokensString=$2
+ shift 2
+ moTokens=(${@+"$@"})
+ mo::debug "Parsing block value: $moTokensString"
+ moOpenDelimiterBefore=$MO_OPEN_DELIMITER
+ moCloseDelimiterBefore=$MO_CLOSE_DELIMITER
+ mo::getContentUntilClose moTemp "$moTokensString"
+ moOpenDelimiterAfter=$MO_OPEN_DELIMITER
+ moCloseDelimiterAfter=$MO_CLOSE_DELIMITER
+
+ #: Variable, value, or list of mixed things
+ mo::evaluateListOfSingles moResult "${moTokens[@]}"
+
+ if mo::isTruthy "$moResult" "$moInvertBlock"; then
+ mo::debug "Block is truthy: $moResult"
+ #: Restore the delimiter before parsing
+ MO_OPEN_DELIMITER=$moOpenDelimiterBefore
+ MO_CLOSE_DELIMITER=$moCloseDelimiterBefore
+ moCurrent=$MO_CURRENT
+ MO_CURRENT=${moTokens[1]}
+ mo::parse moParsed "$moTemp" "blockValue$MO_STANDALONE_CONTENT"
+ MO_PARSED="$MO_PARSED$moParsed"
+ MO_CURRENT=$moCurrent
+ fi
+
+ MO_OPEN_DELIMITER=$moOpenDelimiterAfter
+ MO_CLOSE_DELIMITER=$moCloseDelimiterAfter
+ mo::debug "Done parsing block value: $moTokensString"
+}
+
+
+# Internal: Handle parsing a partial
+#
+# No arguments.
+#
+# Indentation will be applied to the entire partial's contents before parsing.
+# This indentation is based on the whitespace that ends the previously parsed
+# content.
+#
+# Returns nothing
+mo::parsePartial() {
+ local moFilename moResult moIndentation moN moR moTemp moT
+
+ MO_UNPARSED=${MO_UNPARSED:1}
+ mo::trimUnparsed
+ mo::chomp moFilename "${MO_UNPARSED%%"$MO_CLOSE_DELIMITER"*}"
+ MO_UNPARSED="${MO_UNPARSED#*"$MO_CLOSE_DELIMITER"}"
+ moIndentation=""
+
+ if mo::standaloneCheck; then
+ moN=$'\n'
+ moR=$'\r'
+ moT=$'\t'
+ moIndentation="$moN${MO_PARSED//"$moR"/"$moN"}"
+ moIndentation=${moIndentation##*"$moN"}
+ moTemp=${moIndentation// }
+ moTemp=${moTemp//"$moT"}
+
+ if [[ -n "$moTemp" ]]; then
+ moIndentation=
+ fi
+
+ mo::debug "Adding indentation to partial: '$moIndentation'"
+ mo::standaloneProcess
+ fi
+
+ mo::debug "Parsing partial: $moFilename"
+
+ #: Execute in subshell to preserve current cwd and environment
+ moResult=$(
+ #: It would be nice to remove `dirname` and use a function instead,
+ #: but that is difficult when only given filenames.
+ cd "$(dirname -- "$moFilename")" || exit 1
+ echo "$(
+ local moPartialContent moPartialParsed
+
+ if ! mo::contentFile moPartialContent "${moFilename##*/}"; then
+ exit 1
+ fi
+
+ #: Reset delimiters before parsing
+ mo::indentLines moPartialContent "$moIndentation" "$moPartialContent"
+ MO_OPEN_DELIMITER="$MO_OPEN_DELIMITER_DEFAULT"
+ MO_CLOSE_DELIMITER="$MO_CLOSE_DELIMITER_DEFAULT"
+ mo::parse moPartialParsed "$moPartialContent"
+
+ #: Fix bash handling of subshells and keep trailing whitespace.
+ echo -n "$moPartialParsed."
+ )" || exit 1
+ ) || exit 1
+
+ if [[ -z "$moResult" ]]; then
+ mo::debug "Error detected when trying to read the file"
+ exit 1
+ fi
+
+ MO_PARSED="$MO_PARSED${moResult%.}"
+}
+
+
+# Internal: Handle parsing a comment
+#
+# No arguments.
+#
+# Returns nothing
+mo::parseComment() {
+ local moContent moContent
+
+ MO_UNPARSED=${MO_UNPARSED#*"$MO_CLOSE_DELIMITER"}
+ mo::debug "Parsing comment"
+
+ if mo::standaloneCheck; then
+ mo::standaloneProcess
+ fi
+}
+
+
+# Internal: Handle parsing the change of delimiters
+#
+# No arguments.
+#
+# Returns nothing
+mo::parseDelimiter() {
+ local moContent moOpen moClose
+
+ MO_UNPARSED=${MO_UNPARSED:1}
+ mo::trimUnparsed
+ mo::chomp moOpen "$MO_UNPARSED"
+ MO_UNPARSED=${MO_UNPARSED:${#moOpen}}
+ mo::trimUnparsed
+ mo::chomp moClose "${MO_UNPARSED%%="$MO_CLOSE_DELIMITER"*}"
+ MO_UNPARSED=${MO_UNPARSED#*="$MO_CLOSE_DELIMITER"}
+ mo::debug "Parsing delimiters: $moOpen $moClose"
+
+ if mo::standaloneCheck; then
+ mo::standaloneProcess
+ fi
+
+ MO_OPEN_DELIMITER="$moOpen"
+ MO_CLOSE_DELIMITER="$moClose"
+}
+
+
+# Internal: Handle parsing value or function call
+#
+# No arguments.
+#
+# Returns nothing
+mo::parseValue() {
+ local moUnparsedOriginal moTokens
+
+ moUnparsedOriginal=$MO_UNPARSED
+ mo::tokenizeTagContents moTokens "$MO_CLOSE_DELIMITER"
+ mo::evaluate moResult "${moTokens[@]:1}"
+ MO_PARSED="$MO_PARSED$moResult"
+
+ if [[ "${MO_UNPARSED:0:${#MO_CLOSE_DELIMITER}}" != "$MO_CLOSE_DELIMITER" ]]; then
+ mo::errorNear "Did not find closing tag" "$moUnparsedOriginal"
+ fi
+
+ if mo::standaloneCheck; then
+ mo::standaloneProcess
+ fi
+
+ MO_UNPARSED=${MO_UNPARSED:${#MO_CLOSE_DELIMITER}}
+}
+
+
+# Internal: Determine if the given name is a defined function.
+#
+# $1 - Function name to check
+#
+# Be extremely careful. Even if strict mode is enabled, it is not honored
+# in newer versions of Bash. Any errors that crop up here will not be
+# caught automatically.
+#
+# Examples
+#
+# moo () {
+# echo "This is a function"
+# }
+# if mo::isFunction moo; then
+# echo "moo is a defined function"
+# fi
+#
+# Returns 0 if the name is a function, 1 otherwise.
+mo::isFunction() {
+ local moFunctionName
+
+ # Need to test for the array length, otherwise Mac will report an
+ # unbound variable
+ if [[ "${#MO_FUNCTION_CACHE_HIT[@]}" -gt 0 ]]; then
+ for moFunctionName in "${MO_FUNCTION_CACHE_HIT[@]}"; do
+ if [[ "$moFunctionName" == "$1" ]]; then
+ return 0
+ fi
+ done
+ fi
+
+ if [[ "${#MO_FUNCTION_CACHE_MISS[@]}" -gt 0 ]]; then
+ for moFunctionName in "${MO_FUNCTION_CACHE_MISS[@]}"; do
+ if [[ "$moFunctionName" == "$1" ]]; then
+ return 1
+ fi
+ done
+ fi
+
+ if declare -F "$1" &> /dev/null; then
+ if [[ "${#MO_FUNCTION_CACHE_HIT[@]}" -gt 0 ]]; then
+ MO_FUNCTION_CACHE_HIT=( ${MO_FUNCTION_CACHE_HIT[@]+"${MO_FUNCTION_CACHE_HIT[@]}"} "$1" )
+ else
+ MO_FUNCTION_CACHE_HIT=( "$1" )
+ fi
+
+ return 0
+ fi
+
+ if [[ "${#MO_FUNCTION_CACHE_MISS[@]}" -gt 0 ]]; then
+ MO_FUNCTION_CACHE_MISS=( ${MO_FUNCTION_CACHE_MISS[@]+"${MO_FUNCTION_CACHE_MISS[@]}"} "$1" )
+ else
+ MO_FUNCTION_CACHE_MISS=( "$1" )
+ fi
+
+ return 1
+}
+
+
+# Internal: Determine if a given environment variable exists and if it is
+# an array.
+#
+# $1 - Name of environment variable
+#
+# Be extremely careful. Even if strict mode is enabled, it is not honored
+# in newer versions of Bash. Any errors that crop up here will not be
+# caught automatically.
+#
+# Examples
+#
+# var=(abc)
+# if moIsArray var; then
+# echo "This is an array"
+# echo "Make sure you don't accidentally use \$var"
+# fi
+#
+# Returns 0 if the name is not empty, 1 otherwise.
+mo::isArray() {
+ #: Namespace this variable so we don't conflict with what we're testing.
+ local moTestResult
+
+ moTestResult=$(declare -p "$1" 2>/dev/null) || return 1
+ [[ "${moTestResult:0:10}" == "declare -a" ]] && return 0
+ [[ "${moTestResult:0:10}" == "declare -A" ]] && return 0
+
+ return 1
+}
+
+
+# Internal: Determine if an array index exists.
+#
+# $1 - Variable name to check
+# $2 - The index to check
+#
+# Has to check if the variable is an array and if the index is valid for that
+# type of array.
+#
+# Returns true (0) if everything was ok, 1 if there's any condition that fails.
+mo::isArrayIndexValid() {
+ local moDeclare moTest
+
+ moDeclare=$(declare -p "$1")
+ moTest=""
+
+ if [[ "${moDeclare:0:10}" == "declare -a" ]]; then
+ #: Numerically indexed array - must check if the index looks like a
+ #: number because using a string to index a numerically indexed array
+ #: will appear like it worked.
+ if [[ "$2" == "0" ]] || [[ "$2" =~ ^[1-9][0-9]*$ ]]; then
+ #: Index looks like a number
+ eval "moTest=\"\${$1[$2]+ok}\""
+ fi
+ elif [[ "${moDeclare:0:10}" == "declare -A" ]]; then
+ #: Associative array
+ eval "moTest=\"\${$1[$2]+ok}\""
+ fi
+
+ if [[ -n "$moTest" ]]; then
+ return 0;
+ fi
+
+ return 1
+}
+
+
+# Internal: Determine if a variable is assigned, even if it is assigned an empty
+# value.
+#
+# $1 - Variable name to check.
+#
+# Can not use logic like this in case invalid variable names are passed.
+# [[ "${!1-a}" == "${!1-b}" ]]
+#
+# Using logic like this gives false positives. Also, this is not supported on
+# Bash 3.2 and the script parsing will error before any commands are executed.
+# [[ -v "$a" ]]
+#
+# Declaring a variable is not the same as assigning the variable.
+# export x
+# declare -p x # Output: declare -x x
+# # Bash 3.2 returns error code 1 and outputs:
+# # bash: declare: x: not found
+# export y=""
+# declare -p y # Output: declare -x y=""
+# unset z
+# declare -p z # Error code 1 and output: bash: declare: z: not found
+#
+# Returns true (0) if the variable is set, 1 if the variable is unset.
+MO_VAR_TEST="ok"
+
+# This must not use `[[` because otherwise Bash 3.2 will stop execution and
+# always write to stderr without the ability to capture it.
+if test -v "MO_VAR_TEST" &> /dev/null; then
+ mo::debug "Using declare -p and [[ -v ]] for variable checks"
+ # More recent Bash
+ mo::isVarSet() {
+ # Do not convert this to [[, otherwise Bash 3.2 will fail to parse the
+ # script.
+ if declare -p "$1" &> /dev/null && test -v "$1"; then
+ return 0
+ fi
+
+ return 1
+ }
+else
+ mo::debug "Using declare -p for variable checks"
+ # Bash 3.2
+ mo::isVarSet() {
+ # If the variable is exported and not assigned, declare -p will error.
+ if declare -p "$1" &> /dev/null; then
+ return 0
+ fi
+
+ return 1
+ }
+fi
+unset MO_VAR_TEST
+
+
+# Internal: Determine if a value is considered truthy.
+#
+# $1 - The value to test
+# $2 - Invert the value, either "true" or "false"
+#
+# Returns true (0) if truthy, 1 otherwise.
+mo::isTruthy() {
+ local moTruthy
+
+ moTruthy=true
+
+ if [[ -z "${1-}" ]]; then
+ moTruthy=false
+ elif [[ -n "${MO_FALSE_IS_EMPTY-}" ]] && [[ "${1-}" == "false" ]]; then
+ moTruthy=false
+ fi
+
+ #: XOR the results
+ #: moTruthy inverse desiredResult
+ #: true false true
+ #: true true false
+ #: false false false
+ #: false true true
+ if [[ "$moTruthy" == "$2" ]]; then
+ mo::debug "Value is falsy, test result: $moTruthy inverse: $2"
+ return 1
+ fi
+
+ mo::debug "Value is truthy, test result: $moTruthy inverse: $2"
+ return 0
+}
+
+
+# Internal: Convert token list to values
+#
+# $1 - Destination variable name
+# $2-@ - Tokens to convert
+#
+# Sample call:
+#
+# mo::evaluate dest NAME username VALUE abc123 PAREN 2
+#
+# Returns nothing.
+mo::evaluate() {
+ local moTarget moStack moValue moType moIndex moCombined moResult
+
+ moTarget=$1
+ shift
+
+ #: Phase 1 - remove all command tokens (PAREN, BRACE)
+ moStack=()
+
+ while [[ $# -gt 0 ]]; do
+ case "$1" in
+ PAREN|BRACE)
+ moType=$1
+ moValue=$2
+ mo::debug "Combining $moValue tokens"
+ moIndex=$((${#moStack[@]} - (2 * moValue)))
+ mo::evaluateListOfSingles moCombined "${moStack[@]:$moIndex}"
+
+ if [[ "$moType" == "PAREN" ]]; then
+ moStack=("${moStack[@]:0:$moIndex}" NAME "$moCombined")
+ else
+ moStack=("${moStack[@]:0:$moIndex}" VALUE "$moCombined")
+ fi
+ ;;
+
+ *)
+ moStack=(${moStack[@]+"${moStack[@]}"} "$1" "$2")
+ ;;
+ esac
+
+ shift 2
+ done
+
+ #: Phase 2 - check if this is a function or if we should just concatenate values
+ if [[ "${moStack[0]:-}" == "NAME" ]] && mo::isFunction "${moStack[1]}"; then
+ #: Special case - if the first argument is a function, then the rest are
+ #: passed to the function.
+ mo::debug "Evaluating function: ${moStack[1]}"
+ mo::evaluateFunction moResult "" "${moStack[@]:1}"
+ else
+ #: Concatenate
+ mo::debug "Concatenating ${#moStack[@]} stack items"
+ mo::evaluateListOfSingles moResult ${moStack[@]+"${moStack[@]}"}
+ fi
+
+ local "$moTarget" && mo::indirect "$moTarget" "$moResult"
+}
+
+
+# Internal: Convert an argument list to individual values.
+#
+# $1 - Destination variable name
+# $2-@ - A list of argument types and argument name/value.
+#
+# This assumes each value is separate from the rest. In contrast, mo::evaluate
+# will pass all arguments to a function if the first value is a function.
+#
+# Sample call:
+#
+# mo::evaluateListOfSingles dest NAME username VALUE abc123
+#
+# Returns nothing.
+mo::evaluateListOfSingles() {
+ local moResult moTarget moTemp
+
+ moTarget=$1
+ shift
+ moResult=""
+
+ while [[ $# -gt 1 ]]; do
+ mo::evaluateSingle moTemp "$1" "$2"
+ moResult="$moResult$moTemp"
+ shift 2
+ done
+
+ mo::debug "Evaluated list of singles: $moResult"
+
+ local "$moTarget" && mo::indirect "$moTarget" "$moResult"
+}
+
+
+# Internal: Evaluate a single argument
+#
+# $1 - Name of variable for result
+# $2 - Type of argument, either NAME or VALUE
+# $3 - Argument
+#
+# Returns nothing
+mo::evaluateSingle() {
+ local moResult moType moArg
+
+ moType=$2
+ moArg=$3
+ mo::debug "Evaluating $moType: $moArg ($MO_CURRENT)"
+
+ if [[ "$moType" == "VALUE" ]]; then
+ moResult=$moArg
+ elif [[ "$moArg" == "." ]]; then
+ mo::evaluateVariable moResult ""
+ elif [[ "$moArg" == "@key" ]]; then
+ mo::evaluateKey moResult
+ elif mo::isFunction "$moArg"; then
+ mo::evaluateFunction moResult "" "$moArg"
+ else
+ mo::evaluateVariable moResult "$moArg"
+ fi
+
+ local "$1" && mo::indirect "$1" "$moResult"
+}
+
+
+# Internal: Return the value for @key based on current's name
+#
+# $1 - Name of variable for result
+#
+# Returns nothing
+mo::evaluateKey() {
+ local moResult
+
+ if [[ "$MO_CURRENT" == *.* ]]; then
+ moResult="${MO_CURRENT#*.}"
+ else
+ moResult="${MO_CURRENT}"
+ fi
+
+ local "$1" && mo::indirect "$1" "$moResult"
+}
+
+
+# Internal: Handle a variable name
+#
+# $1 - Destination variable name
+# $2 - Variable name
+#
+# Returns nothing.
+mo::evaluateVariable() {
+ local moResult moArg moNameParts
+
+ moArg=$2
+ moResult=""
+ mo::findVariableName moNameParts "$moArg"
+ mo::debug "Evaluate variable ($moArg, $MO_CURRENT): ${moNameParts[*]}"
+
+ if [[ -z "${moNameParts[1]}" ]]; then
+ if mo::isArray "${moNameParts[0]}"; then
+ eval mo::join moResult "," "\${${moNameParts[0]}[@]}"
+ else
+ if mo::isVarSet "${moNameParts[0]}"; then
+ moResult=${moNameParts[0]}
+ moResult="${!moResult}"
+ elif [[ -n "${MO_FAIL_ON_UNSET-}" ]]; then
+ mo::error "Environment variable not set: ${moNameParts[0]}"
+ fi
+ fi
+ else
+ if mo::isArray "${moNameParts[0]}"; then
+ eval "set +u;moResult=\"\${${moNameParts[0]}[${moNameParts[1]%%.*}]}\""
+ else
+ mo::error "Unable to index a scalar as an array: $moArg"
+ fi
+ fi
+
+ local "$1" && mo::indirect "$1" "$moResult"
+}
+
+
+# Internal: Find the name of a variable to use
+#
+# $1 - Destination variable name, receives an array
+# $2 - Variable name from the template
+#
+# The array contains the following values
+# [0] - Variable name
+# [1] - Array index, or empty string
+#
+# Example variables
+# a="a"
+# b="b"
+# c=("c.0" "c.1")
+# d=([b]="d.b" [d]="d.d")
+#
+# Given these inputs (function input, current value), produce these outputs
+# a c => a
+# a c.0 => a
+# b d => d.b
+# b d.d => d.b
+# a d => d.a
+# a d.d => d.a
+# c.0 d => c.0
+# d.b d => d.b
+# '' c => c
+# '' c.0 => c.0
+# Returns nothing.
+mo::findVariableName() {
+ local moVar moNameParts moResultBase moResultIndex moCurrent
+
+ moVar=$2
+ moResultBase=$moVar
+ moResultIndex=""
+
+ if [[ -z "$moVar" ]]; then
+ moResultBase=${MO_CURRENT%%.*}
+
+ if [[ "$MO_CURRENT" == *.* ]]; then
+ moResultIndex=${MO_CURRENT#*.}
+ fi
+ elif [[ "$moVar" == *.* ]]; then
+ mo::debug "Find variable name; name has dot: $moVar"
+ moResultBase=${moVar%%.*}
+ moResultIndex=${moVar#*.}
+ elif [[ -n "$MO_CURRENT" ]]; then
+ moCurrent=${MO_CURRENT%%.*}
+ mo::debug "Find variable name; look in array: $moCurrent"
+
+ if mo::isArrayIndexValid "$moCurrent" "$moVar"; then
+ moResultBase=$moCurrent
+ moResultIndex=$moVar
+ fi
+ fi
+
+ local "$1" && mo::indirectArray "$1" "$moResultBase" "$moResultIndex"
+}
+
+
+# Internal: Join / implode an array
+#
+# $1 - Variable name to receive the joined content
+# $2 - Joiner
+# $3-@ - Elements to join
+#
+# Returns nothing.
+mo::join() {
+ local joiner part result target
+
+ target=$1
+ joiner=$2
+ result=$3
+ shift 3
+
+ for part in "$@"; do
+ result="$result$joiner$part"
+ done
+
+ local "$target" && mo::indirect "$target" "$result"
+}
+
+
+# Internal: Call a function.
+#
+# $1 - Variable for output
+# $2 - Content to pass
+# $3 - Function to call
+# $4-@ - Additional arguments as list of type, value/name
+#
+# Returns nothing.
+mo::evaluateFunction() {
+ local moArgs moContent moFunctionResult moTarget moFunction moTemp moFunctionCall
+
+ moTarget=$1
+ moContent=$2
+ moFunction=$3
+ shift 3
+ moArgs=()
+
+ while [[ $# -gt 1 ]]; do
+ mo::evaluateSingle moTemp "$1" "$2"
+ moArgs=(${moArgs[@]+"${moArgs[@]}"} "$moTemp")
+ shift 2
+ done
+
+ mo::escape moFunctionCall "$moFunction"
+
+ if [[ -n "${MO_ALLOW_FUNCTION_ARGUMENTS-}" ]]; then
+ mo::debug "Function arguments are allowed"
+
+ if [[ ${#moArgs[@]} -gt 0 ]]; then
+ for moTemp in "${moArgs[@]}"; do
+ mo::escape moTemp "$moTemp"
+ moFunctionCall="$moFunctionCall $moTemp"
+ done
+ fi
+ fi
+
+ mo::debug "Calling function: $moFunctionCall"
+
+ #: Call the function in a subshell for safety. Employ the trick to preserve
+ #: whitespace at the end of the output.
+ moContent=$(
+ export MO_FUNCTION_ARGS=(${moArgs[@]+"${moArgs[@]}"})
+ echo -n "$moContent" | eval "$moFunctionCall ; moFunctionResult=\$? ; echo -n '.' ; exit \"\$moFunctionResult\""
+ ) || {
+ moFunctionResult=$?
+ if [[ -n "${MO_FAIL_ON_FUNCTION-}" && "$moFunctionResult" != 0 ]]; then
+ mo::error "Function failed with status code $moFunctionResult: $moFunctionCall" "$moFunctionResult"
+ fi
+ }
+
+ local "$moTarget" && mo::indirect "$moTarget" "${moContent%.}"
+}
+
+
+# Internal: Check if a tag appears to have only whitespace before it and after
+# it on a line. There must be a new line before and there must be a newline
+# after or the end of a string
+#
+# No arguments.
+#
+# Returns 0 if this is a standalone tag, 1 otherwise.
+mo::standaloneCheck() {
+ local moContent moN moR moT
+
+ moN=$'\n'
+ moR=$'\r'
+ moT=$'\t'
+
+ #: Check the content before
+ moContent=${MO_STANDALONE_CONTENT//"$moR"/"$moN"}
+
+ #: By default, signal to the next check that this one failed
+ MO_STANDALONE_CONTENT=""
+
+ if [[ "$moContent" != *"$moN"* ]]; then
+ mo::debug "Not a standalone tag - no newline before"
+
+ return 1
+ fi
+
+ moContent=${moContent##*"$moN"}
+ moContent=${moContent//"$moT"/}
+ moContent=${moContent// /}
+
+ if [[ -n "$moContent" ]]; then
+ mo::debug "Not a standalone tag - non-whitespace detected before tag"
+
+ return 1
+ fi
+
+ #: Check the content after
+ moContent=${MO_UNPARSED//"$moR"/"$moN"}
+ moContent=${moContent%%"$moN"*}
+ moContent=${moContent//"$moT"/}
+ moContent=${moContent// /}
+
+ if [[ -n "$moContent" ]]; then
+ mo::debug "Not a standalone tag - non-whitespace detected after tag"
+
+ return 1
+ fi
+
+ #: Signal to the next check that this tag removed content
+ MO_STANDALONE_CONTENT=$'\n'
+
+ return 0
+}
+
+
+# Internal: Process content before and after a tag. Remove prior whitespace up
+# to the previous newline. Remove following whitespace up to and including the
+# next newline.
+#
+# No arguments.
+#
+# Returns nothing.
+mo::standaloneProcess() {
+ local moI moTemp
+
+ mo::debug "Standalone tag - processing content before and after tag"
+ moI=$((${#MO_PARSED} - 1))
+ mo::debug "zero done ${#MO_PARSED}"
+ mo::escape moTemp "$MO_PARSED"
+ mo::debug "$moTemp"
+
+ # Mac appears to allow getting characters from before the start of the string
+ while [[ "$moI" -ge 0 ]] && [[ "${MO_PARSED:$moI:1}" == " " || "${MO_PARSED:$moI:1}" == $'\t' ]]; do
+ moI=$((moI - 1))
+ done
+
+ if [[ $((moI + 1)) != "${#MO_PARSED}" ]]; then
+ MO_PARSED="${MO_PARSED:0:${moI}+1}"
+ fi
+
+ moI=0
+
+ while [[ "${MO_UNPARSED:${moI}:1}" == " " || "${MO_UNPARSED:${moI}:1}" == $'\t' ]]; do
+ moI=$((moI + 1))
+ done
+
+ if [[ "${MO_UNPARSED:${moI}:1}" == $'\r' ]]; then
+ moI=$((moI + 1))
+ fi
+
+ if [[ "${MO_UNPARSED:${moI}:1}" == $'\n' ]]; then
+ moI=$((moI + 1))
+ fi
+
+ if [[ "$moI" != 0 ]]; then
+ MO_UNPARSED=${MO_UNPARSED:${moI}}
+ fi
+}
+
+
+# Internal: Apply indentation before any line that has content in MO_UNPARSED.
+#
+# $1 - Destination variable name.
+# $2 - The indentation string.
+# $3 - The content that needs the indentation string prepended on each line.
+#
+# Returns nothing.
+mo::indentLines() {
+ local moContent moIndentation moResult moN moR moChunk
+
+ moIndentation=$2
+ moContent=$3
+
+ if [[ -z "$moIndentation" ]]; then
+ mo::debug "Not applying indentation, empty indentation"
+
+ local "$1" && mo::indirect "$1" "$moContent"
+ return
+ fi
+
+ if [[ -z "$moContent" ]]; then
+ mo::debug "Not applying indentation, empty contents"
+
+ local "$1" && mo::indirect "$1" "$moContent"
+ return
+ fi
+
+ moResult=
+ moN=$'\n'
+ moR=$'\r'
+
+ mo::debug "Applying indentation: '${moIndentation}'"
+
+ while [[ -n "$moContent" ]]; do
+ moChunk=${moContent%%"$moN"*}
+ moChunk=${moChunk%%"$moR"*}
+ moContent=${moContent:${#moChunk}}
+
+ if [[ -n "$moChunk" ]]; then
+ moResult="$moResult$moIndentation$moChunk"
+ fi
+
+ moResult="$moResult${moContent:0:1}"
+ moContent=${moContent:1}
+ done
+
+ local "$1" && mo::indirect "$1" "$moResult"
+}
+
+
+# Internal: Escape a value
+#
+# $1 - Destination variable name
+# $2 - Value to escape
+#
+# Returns nothing
+mo::escape() {
+ local moResult
+
+ moResult=$2
+ moResult=$(declare -p moResult)
+ moResult=${moResult#*=}
+
+ local "$1" && mo::indirect "$1" "$moResult"
+}
+
+
+# Internal: Get the content up to the end of the block by minimally parsing and
+# balancing blocks. Returns the content before the end tag to the caller and
+# removes the content + the end tag from MO_UNPARSED. This can change the
+# delimiters, adjusting MO_OPEN_DELIMITER and MO_CLOSE_DELIMITER.
+#
+# $1 - Destination variable name
+# $2 - Token string to match for a closing tag
+#
+# Returns nothing.
+mo::getContentUntilClose() {
+ local moChunk moResult moTemp moTokensString moTokens moTarget moTagStack moResultTemp
+
+ moTarget=$1
+ moTagStack=("$2")
+ mo::debug "Get content until close tag: ${moTagStack[0]}"
+ moResult=""
+
+ while [[ -n "$MO_UNPARSED" ]] && [[ "${#moTagStack[@]}" -gt 0 ]]; do
+ moChunk=${MO_UNPARSED%%"$MO_OPEN_DELIMITER"*}
+ moResult="$moResult$moChunk"
+ MO_UNPARSED=${MO_UNPARSED:${#moChunk}}
+
+ if [[ -n "$MO_UNPARSED" ]]; then
+ moResultTemp="$MO_OPEN_DELIMITER"
+ MO_UNPARSED=${MO_UNPARSED:${#MO_OPEN_DELIMITER}}
+ mo::getContentTrim moTemp
+ moResultTemp="$moResultTemp$moTemp"
+ mo::debug "First character within tag: ${MO_UNPARSED:0:1}"
+
+ case "$MO_UNPARSED" in
+ '#'*)
+ #: Increase block
+ moResultTemp="$moResultTemp${MO_UNPARSED:0:1}"
+ MO_UNPARSED=${MO_UNPARSED:1}
+ mo::getContentTrim moTemp
+ mo::getContentWithinTag moTemp "$MO_CLOSE_DELIMITER"
+ moResultTemp="$moResultTemp${moTemp[0]}"
+ moTagStack=("${moTemp[1]}" "${moTagStack[@]}")
+ ;;
+
+ '^'*)
+ #: Increase block
+ moResultTemp="$moResultTemp${MO_UNPARSED:0:1}"
+ MO_UNPARSED=${MO_UNPARSED:1}
+ mo::getContentTrim moTemp
+ mo::getContentWithinTag moTemp "$MO_CLOSE_DELIMITER"
+ moResultTemp="$moResultTemp${moTemp[0]}"
+ moTagStack=("${moTemp[1]}" "${moTagStack[@]}")
+ ;;
+
+ '>'*)
+ #: Partial - ignore
+ moResultTemp="$moResultTemp${MO_UNPARSED:0:1}"
+ MO_UNPARSED=${MO_UNPARSED:1}
+ mo::getContentTrim moTemp
+ mo::getContentWithinTag moTemp "$MO_CLOSE_DELIMITER"
+ moResultTemp="$moResultTemp${moTemp[0]}"
+ ;;
+
+ '/'*)
+ #: Decrease block
+ moResultTemp="$moResultTemp${MO_UNPARSED:0:1}"
+ MO_UNPARSED=${MO_UNPARSED:1}
+ mo::getContentTrim moTemp
+ mo::getContentWithinTag moTemp "$MO_CLOSE_DELIMITER"
+
+ if [[ "${moTagStack[0]}" == "${moTemp[1]}" ]]; then
+ moResultTemp="$moResultTemp${moTemp[0]}"
+ moTagStack=("${moTagStack[@]:1}")
+
+ if [[ "${#moTagStack[@]}" -eq 0 ]]; then
+ #: Erase all portions of the close tag
+ moResultTemp=""
+ fi
+ else
+ mo::errorNear "Unbalanced closing tag, expected: ${moTagStack[0]}" "${moTemp[0]}${MO_UNPARSED}"
+ fi
+ ;;
+
+ '!'*)
+ #: Comment - ignore
+ mo::getContentComment moTemp
+ moResultTemp="$moResultTemp$moTemp"
+ ;;
+
+ '='*)
+ #: Change delimiters
+ mo::getContentDelimiter moTemp
+ moResultTemp="$moResultTemp$moTemp"
+ ;;
+
+ '&'*)
+ #: Unescaped - bypass one then ignore
+ moResultTemp="$moResultTemp${MO_UNPARSED:0:1}"
+ MO_UNPARSED=${MO_UNPARSED:1}
+ mo::getContentTrim moTemp
+ moResultTemp="$moResultTemp$moTemp"
+ mo::getContentWithinTag moTemp "$MO_CLOSE_DELIMITER"
+ moResultTemp="$moResultTemp${moTemp[0]}"
+ ;;
+
+ *)
+ #: Normal variable - ignore
+ mo::getContentWithinTag moTemp "$MO_CLOSE_DELIMITER"
+ moResultTemp="$moResultTemp${moTemp[0]}"
+ ;;
+ esac
+
+ moResult="$moResult$moResultTemp"
+ fi
+ done
+
+ MO_STANDALONE_CONTENT="$MO_STANDALONE_CONTENT$moResult"
+
+ if mo::standaloneCheck; then
+ moResultTemp=$MO_PARSED
+ MO_PARSED=$moResult
+ mo::standaloneProcess
+ moResult=$MO_PARSED
+ MO_PARSED=$moResultTemp
+ fi
+
+ local "$moTarget" && mo::indirect "$moTarget" "$moResult"
+}
+
+
+# Internal: Convert a list of tokens to a string
+#
+# $1 - Destination variable for the string
+# $2-$@ - Token list
+#
+# Returns nothing.
+mo::tokensToString() {
+ local moTarget moString moTokens
+
+ moTarget=$1
+ shift 1
+ moTokens=("$@")
+ moString=$(declare -p moTokens)
+ moString=${moString#*=}
+
+ local "$moTarget" && mo::indirect "$moTarget" "$moString"
+}
+
+
+# Internal: Trims content from MO_UNPARSED, returns trimmed content.
+#
+# $1 - Destination variable
+#
+# Returns nothing.
+mo::getContentTrim() {
+ local moChar moResult
+
+ moChar=${MO_UNPARSED:0:1}
+ moResult=""
+
+ while [[ "$moChar" == " " ]] || [[ "$moChar" == $'\r' ]] || [[ "$moChar" == $'\t' ]] || [[ "$moChar" == $'\n' ]]; do
+ moResult="$moResult$moChar"
+ MO_UNPARSED=${MO_UNPARSED:1}
+ moChar=${MO_UNPARSED:0:1}
+ done
+
+ local "$1" && mo::indirect "$1" "$moResult"
+}
+
+
+# Get the content up to and including a close tag
+#
+# $1 - Destination variable
+#
+# Returns nothing.
+mo::getContentComment() {
+ local moResult
+
+ mo::debug "Getting content for comment"
+ moResult=${MO_UNPARSED%%"$MO_CLOSE_DELIMITER"*}
+ MO_UNPARSED=${MO_UNPARSED:${#moResult}}
+
+ if [[ "$MO_UNPARSED" == "$MO_CLOSE_DELIMITER"* ]]; then
+ moResult="$moResult$MO_CLOSE_DELIMITER"
+ MO_UNPARSED=${MO_UNPARSED#"$MO_CLOSE_DELIMITER"}
+ fi
+
+ local "$1" && mo::indirect "$1" "$moResult"
+}
+
+
+# Get the content up to and including a close tag. First two non-whitespace
+# tokens become the new open and close tag.
+#
+# $1 - Destination variable
+#
+# Returns nothing.
+mo::getContentDelimiter() {
+ local moResult moTemp moOpen moClose
+
+ mo::debug "Getting content for delimiter"
+ moResult=""
+ mo::getContentTrim moTemp
+ moResult="$moResult$moTemp"
+ mo::chomp moOpen "$MO_UNPARSED"
+ MO_UNPARSED="${MO_UNPARSED:${#moOpen}}"
+ moResult="$moResult$moOpen"
+ mo::getContentTrim moTemp
+ moResult="$moResult$moTemp"
+ mo::chomp moClose "${MO_UNPARSED%%="$MO_CLOSE_DELIMITER"*}"
+ MO_UNPARSED="${MO_UNPARSED:${#moClose}}"
+ moResult="$moResult$moClose"
+ mo::getContentTrim moTemp
+ moResult="$moResult$moTemp"
+ MO_OPEN_DELIMITER="$moOpen"
+ MO_CLOSE_DELIMITER="$moClose"
+
+ local "$1" && mo::indirect "$1" "$moResult"
+}
+
+
+# Get the content up to and including a close tag. First two non-whitespace
+# tokens become the new open and close tag.
+#
+# $1 - Destination variable, an array
+# $2 - Terminator string
+#
+# The array contents:
+# [0] The raw content within the tag
+# [1] The parsed tokens as a single string
+#
+# Returns nothing.
+mo::getContentWithinTag() {
+ local moUnparsed moTokens
+
+ moUnparsed=${MO_UNPARSED}
+ mo::tokenizeTagContents moTokens "$MO_CLOSE_DELIMITER"
+ MO_UNPARSED=${MO_UNPARSED#"$MO_CLOSE_DELIMITER"}
+ mo::tokensToString moTokensString "${moTokens[@]:1}"
+ moParsed=${moUnparsed:0:$((${#moUnparsed} - ${#MO_UNPARSED}))}
+
+ local "$1" && mo::indirectArray "$1" "$moParsed" "$moTokensString"
+}
+
+
+# Internal: Parse MO_UNPARSED and retrieve the content within the tag
+# delimiters. Converts everything into an array of string values.
+#
+# $1 - Destination variable for the array of contents.
+# $2 - Stop processing when this content is found.
+#
+# The list of tokens are in RPN form. The first item in the resulting array is
+# the number of actual tokens (after combining command tokens) in the list.
+#
+# Given: a 'bc' "de\"\n" (f {g 'h'})
+# Result: ([0]=4 [1]=NAME [2]=a [3]=VALUE [4]=bc [5]=VALUE [6]=$'de\"\n'
+# [7]=NAME [8]=f [9]=NAME [10]=g [11]=VALUE [12]=h
+# [13]=BRACE [14]=2 [15]=PAREN [16]=2
+#
+# Returns nothing
+mo::tokenizeTagContents() {
+ local moResult moTerminator moTemp moUnparsedOriginal moTokenCount
+
+ moTerminator=$2
+ moResult=()
+ moUnparsedOriginal=$MO_UNPARSED
+ moTokenCount=0
+ mo::debug "Tokenizing tag contents until terminator: $moTerminator"
+
+ while true; do
+ mo::trimUnparsed
+
+ case "$MO_UNPARSED" in
+ "")
+ mo::errorNear "Did not find matching terminator: $moTerminator" "$moUnparsedOriginal"
+ ;;
+
+ "$moTerminator"*)
+ mo::debug "Found terminator"
+ local "$1" && mo::indirectArray "$1" "$moTokenCount" ${moResult[@]+"${moResult[@]}"}
+ return
+ ;;
+
+ '('*)
+ #: Do not tokenize the open paren - treat this as RPL
+ MO_UNPARSED=${MO_UNPARSED:1}
+ mo::tokenizeTagContents moTemp ')'
+ moResult=(${moResult[@]+"${moResult[@]}"} "${moTemp[@]:1}" PAREN "${moTemp[0]}")
+ MO_UNPARSED=${MO_UNPARSED:1}
+ ;;
+
+ '{'*)
+ #: Do not tokenize the open brace - treat this as RPL
+ MO_UNPARSED=${MO_UNPARSED:1}
+ mo::tokenizeTagContents moTemp '}'
+ moResult=(${moResult[@]+"${moResult[@]}"} "${moTemp[@]:1}" BRACE "${moTemp[0]}")
+ MO_UNPARSED=${MO_UNPARSED:1}
+ ;;
+
+ ')'* | '}'*)
+ mo::errorNear "Unbalanced closing parenthesis or brace" "$MO_UNPARSED"
+ ;;
+
+ "'"*)
+ mo::tokenizeTagContentsSingleQuote moTemp
+ moResult=(${moResult[@]+"${moResult[@]}"} "${moTemp[@]}")
+ ;;
+
+ '"'*)
+ mo::tokenizeTagContentsDoubleQuote moTemp
+ moResult=(${moResult[@]+"${moResult[@]}"} "${moTemp[@]}")
+ ;;
+
+ *)
+ mo::tokenizeTagContentsName moTemp
+ moResult=(${moResult[@]+"${moResult[@]}"} "${moTemp[@]}")
+ ;;
+ esac
+
+ mo::debug "Got chunk: ${moTemp[0]} ${moTemp[1]}"
+ moTokenCount=$((moTokenCount + 1))
+ done
+}
+
+
+# Internal: Get the contents of a variable name.
+#
+# $1 - Destination variable name for the token list (array of strings)
+#
+# Returns nothing
+mo::tokenizeTagContentsName() {
+ local moTemp
+
+ mo::chomp moTemp "${MO_UNPARSED%%"$MO_CLOSE_DELIMITER"*}"
+ moTemp=${moTemp%%(*}
+ moTemp=${moTemp%%)*}
+ moTemp=${moTemp%%\{*}
+ moTemp=${moTemp%%\}*}
+ MO_UNPARSED=${MO_UNPARSED:${#moTemp}}
+ mo::trimUnparsed
+ mo::debug "Parsed default token: $moTemp"
+
+ local "$1" && mo::indirectArray "$1" "NAME" "$moTemp"
+}
+
+
+# Internal: Get the contents of a tag in double quotes. Parses the backslash
+# sequences.
+#
+# $1 - Destination variable name for the token list (array of strings)
+#
+# Returns nothing.
+mo::tokenizeTagContentsDoubleQuote() {
+ local moResult moUnparsedOriginal
+
+ moUnparsedOriginal=$MO_UNPARSED
+ MO_UNPARSED=${MO_UNPARSED:1}
+ moResult=
+ mo::debug "Getting double quoted tag contents"
+
+ while true; do
+ if [[ -z "$MO_UNPARSED" ]]; then
+ mo::errorNear "Unbalanced double quote" "$moUnparsedOriginal"
+ fi
+
+ case "$MO_UNPARSED" in
+ '"'*)
+ MO_UNPARSED=${MO_UNPARSED:1}
+ local "$1" && mo::indirectArray "$1" "VALUE" "$moResult"
+ return
+ ;;
+
+ \\b*)
+ moResult="$moResult"$'\b'
+ MO_UNPARSED=${MO_UNPARSED:2}
+ ;;
+
+ \\e*)
+ #: Note, \e is ESC, but in Bash $'\E' is ESC.
+ moResult="$moResult"$'\E'
+ MO_UNPARSED=${MO_UNPARSED:2}
+ ;;
+
+ \\f*)
+ moResult="$moResult"$'\f'
+ MO_UNPARSED=${MO_UNPARSED:2}
+ ;;
+
+ \\n*)
+ moResult="$moResult"$'\n'
+ MO_UNPARSED=${MO_UNPARSED:2}
+ ;;
+
+ \\r*)
+ moResult="$moResult"$'\r'
+ MO_UNPARSED=${MO_UNPARSED:2}
+ ;;
+
+ \\t*)
+ moResult="$moResult"$'\t'
+ MO_UNPARSED=${MO_UNPARSED:2}
+ ;;
+
+ \\v*)
+ moResult="$moResult"$'\v'
+ MO_UNPARSED=${MO_UNPARSED:2}
+ ;;
+
+ \\*)
+ moResult="$moResult${MO_UNPARSED:1:1}"
+ MO_UNPARSED=${MO_UNPARSED:2}
+ ;;
+
+ *)
+ moResult="$moResult${MO_UNPARSED:0:1}"
+ MO_UNPARSED=${MO_UNPARSED:1}
+ ;;
+ esac
+ done
+}
+
+
+# Internal: Get the contents of a tag in single quotes. Only gets the raw
+# value.
+#
+# $1 - Destination variable name for the token list (array of strings)
+#
+# Returns nothing.
+mo::tokenizeTagContentsSingleQuote() {
+ local moResult moUnparsedOriginal
+
+ moUnparsedOriginal=$MO_UNPARSED
+ MO_UNPARSED=${MO_UNPARSED:1}
+ moResult=
+ mo::debug "Getting single quoted tag contents"
+
+ while true; do
+ if [[ -z "$MO_UNPARSED" ]]; then
+ mo::errorNear "Unbalanced single quote" "$moUnparsedOriginal"
+ fi
+
+ case "$MO_UNPARSED" in
+ "'"*)
+ MO_UNPARSED=${MO_UNPARSED:1}
+ local "$1" && mo::indirectArray "$1" VALUE "$moResult"
+ return
+ ;;
+
+ *)
+ moResult="$moResult${MO_UNPARSED:0:1}"
+ MO_UNPARSED=${MO_UNPARSED:1}
+ ;;
+ esac
+ done
+}
+
+
+# Save the original command's path for usage later
+MO_ORIGINAL_COMMAND="$(cd "${BASH_SOURCE[0]%/*}" || exit 1; pwd)/${BASH_SOURCE[0]##*/}"
+MO_VERSION="3.1.0"
+
+# If sourced, load all functions.
+# If executed, perform the actions as expected.
+if [[ "$0" == "${BASH_SOURCE[0]}" ]] || [[ -z "${BASH_SOURCE[0]}" ]]; then
+ mo "$@"
+fi
diff --git a/build/templates/home.html b/build/templates/home.html
new file mode 100644
index 0000000..e0b7521
--- /dev/null
+++ b/build/templates/home.html
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<html lang="en">
+ <head>
+ {{> head.html}}
+ <title>ubergeek</title>
+ </head>
+ <body>
+ {{> header.html}}
+ <main>{{{CONTENT}}}</main>
+ {{> footer.html}}
+ </body>
+</html>
diff --git a/build/templates/page.html b/build/templates/page.html
new file mode 100644
index 0000000..502dd58
--- /dev/null
+++ b/build/templates/page.html
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<html lang="{{LANG}}">
+ <head>
+ {{> head.html}}
+ <title>ubergeek{{#PAGE_TITLE}}: {{PAGE_TITLE}}{{/PAGE_TITLE}}</title>
+ </head>
+ <body{{#DIR}} dir="{{DIR}}"{{/DIR}}>
+ {{> header.html}}
+ <main>{{{CONTENT}}}</main>
+ {{> footer.html}}
+ </body>
+</html>
diff --git a/build/templates/partials/.gitkeep b/build/templates/partials/.gitkeep
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/build/templates/partials/.gitkeep
diff --git a/build/templates/partials/footer.html b/build/templates/partials/footer.html
new file mode 100644
index 0000000..6dca778
--- /dev/null
+++ b/build/templates/partials/footer.html
@@ -0,0 +1,16 @@
+<footer>
+ <hr />
+ <a href="https://git.gumx.cc">git</a> /
+ <a href="https://mail.gumx.cc">mail</a> /
+ <a href="https://irc.gumx.cc">irc</a> /
+ <a href="https://vpn.gumx.cc">vpn</a> /
+ <a href="https://pgp.gumx.cc">pgp</a> /
+ <a href="https://wk.fo">wk.fo</a>
+ <br />
+ <a href="https://webring.gumx.cc/gumx.cc/previous">&larr; previous site</a> /
+ <a href="https://webring.gumx.cc/">webring</a> /
+ <a href="https://webring.gumx.cc/gumx.cc/next">next site &rarr;</a>
+ <br />
+ <a href="https://git.gumx.cc/ubergeek">source</a> /
+ <a href="/license">license</a>
+</footer>
diff --git a/build/templates/partials/head.html b/build/templates/partials/head.html
new file mode 100644
index 0000000..f9f143e
--- /dev/null
+++ b/build/templates/partials/head.html
@@ -0,0 +1,11 @@
+ <meta charset="utf-8" />
+ <meta name="viewport" content="width=device-width,initial-scale=1" />
+ <meta name="author" content="Ahmed (gumx) Alaa" />
+ <meta name="copyright" content="CC BY-SA 4.0" />
+ {{#META_DATE}}<meta name="date" content="{{META_DATE}}" />{{/META_DATE}}
+ {{#META_DESCRIPTION}}<meta name="description" content="{{META_DESCRIPTION}}" />{{/META_DESCRIPTION}}
+ <link rel="stylesheet" href="/styles/main.css" />
+ {{#HAS_CODE}}<link rel="stylesheet" href="/styles/highlight.min.css" />
+ <script src="/scripts/highlight.min.js"></script>
+ <script>hljs.highlightAll();</script>{{/HAS_CODE}}
+ <link href="data:," rel="icon">
diff --git a/build/templates/partials/header.html b/build/templates/partials/header.html
new file mode 100644
index 0000000..cbe3936
--- /dev/null
+++ b/build/templates/partials/header.html
@@ -0,0 +1,6 @@
+ <header>
+ <nav>
+ <strong><a href="/">ubergeek</a>{{BREADCRUMBS}}</strong>
+ {{#HEADER_EXTRA}}<br /><em>{{HEADER_EXTRA}}</em>{{/HEADER_EXTRA}}
+ </nav>
+ </header>
diff --git a/linux-command-line/shell-basics/index.md b/linux-command-line/shell-basics/index.md
new file mode 100644
index 0000000..c0cf75b
--- /dev/null
+++ b/linux-command-line/shell-basics/index.md
@@ -0,0 +1,327 @@
+---
+title: Shell Basics
+---
+
+# Shell Basics
+
+The shell is your primary interface to a Linux system. Everything — file management, process control, system administration — flows through it. Before graphical tools, before IDEs, there was the shell. Mastering it is non-negotiable.
+
+**Prerequisites:** None
+**Estimated time:** 4-6 hours of practice
+
+---
+
+## What is a Shell?
+
+A shell is a program that reads commands from your input and executes them. It's both a **command interpreter** (runs programs) and a **programming language** (scripts).
+
+Common shells:
+- **bash** — Bourne Again Shell, the default on most Linux systems
+- **zsh** — Extended bash with better completion and prompting
+- **sh** — POSIX shell, the lowest common denominator
+- **fish** — Friendly interactive shell, not POSIX-compatible
+
+When you open a terminal, you're running a **terminal emulator** (alacritty, kitty, xterm) which hosts a **shell process**. The terminal draws pixels; the shell interprets commands.
+
+```bash
+# Check your current shell
+echo $SHELL
+
+# List available shells
+cat /etc/shells
+
+# The shell prompt typically shows:
+# username@hostname:current_directory$
+```
+
+> **Key distinction:** The terminal and the shell are different things. You can run bash inside alacritty, or inside a Linux TTY, or over SSH. The shell doesn't care what's displaying its output.
+
+---
+
+## Navigating the Filesystem
+
+Linux has a single filesystem tree rooted at `/`. Everything is a file — devices, processes, sockets.
+
+### Essential Navigation Commands
+
+```bash
+pwd # Print working directory — where you are
+ls # List files in current directory
+ls -la # Long format, show hidden files (dotfiles)
+ls -lh # Human-readable sizes (K, M, G)
+cd /etc # Change to /etc
+cd ~ # Change to home directory (also just: cd)
+cd - # Change to previous directory
+cd .. # Go up one level
+```
+
+### The Filesystem Hierarchy
+
+| Path | Purpose |
+|------|---------|
+| `/` | Root of everything |
+| `/home` | User home directories |
+| `/etc` | System configuration files |
+| `/var` | Variable data (logs, databases, mail) |
+| `/tmp` | Temporary files (cleared on reboot) |
+| `/usr` | User programs and data (read-only) |
+| `/usr/bin` | Most user commands |
+| `/usr/local` | Locally installed software |
+| `/dev` | Device files |
+| `/proc` | Process information pseudo-filesystem |
+| `/sys` | Kernel/device information pseudo-filesystem |
+| `/opt` | Optional/third-party software |
+
+```bash
+# The filesystem is a tree. Explore it:
+ls /
+ls /etc
+ls /dev
+
+# Everything is a file:
+file /dev/null # character special device
+file /etc/hostname # ASCII text
+file /usr/bin/ls # ELF 64-bit executable
+```
+
+---
+
+## File Operations
+
+### Creating, Copying, Moving, Deleting
+
+```bash
+# Create
+touch newfile.txt # Create empty file (or update timestamp)
+mkdir mydir # Create directory
+mkdir -p path/to/nested/dir # Create nested directories
+
+# Copy
+cp file.txt backup.txt # Copy file
+cp -r mydir/ mydir_backup/ # Copy directory recursively
+
+# Move / Rename
+mv old.txt new.txt # Rename
+mv file.txt ~/Documents/ # Move to another directory
+
+# Delete
+rm file.txt # Remove file (no trash, no undo)
+rm -r mydir/ # Remove directory recursively
+rm -ri mydir/ # Interactive — asks before each deletion
+rmdir emptydir/ # Remove only if empty
+```
+
+> **Warning:** `rm` is permanent. There is no trash can. `rm -rf /` will destroy your entire system if you have the permissions. Always double-check your path before pressing Enter.
+
+### Viewing Files
+
+```bash
+cat file.txt # Print entire file to stdout
+less file.txt # Paginated viewer (q to quit, / to search)
+head -n 20 file.txt # First 20 lines
+tail -n 20 file.txt # Last 20 lines
+tail -f /var/log/syslog # Follow a file in real-time (great for logs)
+wc -l file.txt # Count lines
+```
+
+`less` is your friend. Use it for anything longer than a screenful. Key bindings inside `less`:
+- `Space` / `b` — page down / up
+- `/pattern` — search forward
+- `n` / `N` — next / previous search match
+- `g` / `G` — go to top / bottom
+- `q` — quit
+
+---
+
+## Permissions
+
+Every file has an **owner**, a **group**, and a set of **permissions** (read, write, execute) for each of: owner, group, others.
+
+```bash
+ls -l file.txt
+# -rw-r--r-- 1 ahmed users 1234 Mar 31 10:00 file.txt
+# │├──┤├──┤├──┤
+# │ │ │ └── others: read only
+# │ │ └────── group: read only
+# │ └─────────── owner: read + write
+# └───────────── file type: - = regular, d = directory, l = symlink
+```
+
+### chmod — Change Permissions
+
+```bash
+# Symbolic notation
+chmod u+x script.sh # Add execute for owner
+chmod go-w file.txt # Remove write for group and others
+chmod a+r file.txt # Add read for all
+
+# Octal notation (most common)
+chmod 755 script.sh # rwxr-xr-x — owner full, others read+execute
+chmod 644 file.txt # rw-r--r-- — owner read+write, others read
+chmod 600 secret.key # rw------- — owner only
+```
+
+**Octal cheat sheet:** r=4, w=2, x=1. Add them up per group.
+- `7` = rwx (4+2+1)
+- `6` = rw- (4+2)
+- `5` = r-x (4+1)
+- `4` = r-- (4)
+- `0` = --- (0)
+
+### chown — Change Ownership
+
+```bash
+chown ahmed file.txt # Change owner
+chown ahmed:users file.txt # Change owner and group
+chown -R ahmed:users mydir/ # Recursive
+```
+
+### Directories and Execute Permission
+
+The execute bit on directories means something different: it grants **access** (the ability to `cd` into it and access files within). A directory with `r--` lets you list filenames but not read the files. A directory with `--x` lets you access files by name but not list them.
+
+---
+
+## Pipes and Redirection
+
+This is where the shell gets powerful. The Unix philosophy: small programs that do one thing well, connected together.
+
+### Redirection
+
+Every process has three standard streams:
+- **stdin** (0) — input
+- **stdout** (1) — normal output
+- **stderr** (2) — error output
+
+```bash
+# Redirect stdout to a file
+ls > filelist.txt # Overwrite
+ls >> filelist.txt # Append
+
+# Redirect stderr
+gcc broken.c 2> errors.txt # Errors go to file
+gcc broken.c 2>&1 # Stderr merges into stdout
+
+# Redirect both
+command > output.txt 2>&1 # Both to same file
+command &> output.txt # Bash shorthand for the above
+
+# Discard output
+command > /dev/null 2>&1 # Silence everything
+
+# Read stdin from a file
+sort < unsorted.txt
+```
+
+### Pipes
+
+Pipes connect stdout of one command to stdin of the next.
+
+```bash
+# Find the 10 largest files in /var/log
+du -sh /var/log/* 2>/dev/null | sort -rh | head -10
+
+# Count unique IP addresses in a log
+cat access.log | awk '{print $1}' | sort | uniq -c | sort -rn | head -20
+
+# Find running processes matching a pattern
+ps aux | grep nginx | grep -v grep
+
+# Chain as many as you need
+cat /etc/passwd | cut -d: -f1 | sort | head -5
+```
+
+> **Useless use of cat:** `cat file | grep pattern` can be written as `grep pattern file`. But in pipelines with many stages, starting with `cat` can be clearer. Don't stress about it early on — clarity matters more than micro-optimization.
+
+### Command Substitution
+
+Use the output of one command inside another:
+
+```bash
+# Using $() — preferred
+echo "Today is $(date +%Y-%m-%d)"
+files=$(ls *.txt)
+
+# Using backticks — older style, harder to nest
+echo "Today is `date +%Y-%m-%d`"
+```
+
+---
+
+## Wildcards (Globbing)
+
+The shell expands patterns before passing them to commands:
+
+```bash
+* # Match any characters (except leading dot)
+? # Match exactly one character
+[abc] # Match a, b, or c
+[0-9] # Match any digit
+[!abc] # Match anything except a, b, c
+** # Match directories recursively (bash: shopt -s globstar)
+
+# Examples
+ls *.txt # All .txt files
+ls file?.log # file1.log, fileA.log, etc.
+ls *.{jpg,png} # All .jpg and .png files (brace expansion, not globbing)
+rm /tmp/test_* # All files starting with test_ in /tmp
+```
+
+---
+
+## Getting Help
+
+```bash
+man ls # Manual page for ls (press q to quit)
+man -k "copy files" # Search manual descriptions
+ls --help # Quick help (most GNU tools)
+type ls # Is it a builtin, alias, or binary?
+which python # Full path of a command
+whatis ls # One-line description
+apropos network # Search man pages by keyword
+```
+
+**Reading man pages:** The synopsis section uses conventions:
+- `[optional]` — square brackets mean optional
+- `REQUIRED` — caps or no brackets means required
+- `...` — can be repeated
+- `a | b` — choose one
+
+---
+
+## Exercises
+
+### Drill Exercises
+
+1. Navigate to `/etc` and list all files starting with `host`. What do they contain?
+2. Create a directory structure: `~/practice/project/{src,docs,tests}` in one command
+3. Create a file, set its permissions to 750, then explain who can do what with it
+4. Use `ls -la /` and identify which entries are regular files, directories, and symlinks
+5. Redirect the output of `ls /nonexistent` to `/dev/null` while keeping error messages visible
+
+### Pipeline Challenges
+
+6. Count how many lines in `/etc/passwd` contain the string `nologin`
+7. List the 5 largest files in `/usr/bin` by size (hint: `ls -lS` or `du`)
+8. Extract just the usernames from `/etc/passwd` (field 1, colon-separated) and sort them
+9. Find all environment variables that contain the string `PATH` (hint: `env | grep`)
+10. Get the total line count of all `.conf` files in `/etc/` (hint: `wc -l`, `cat`)
+
+### Mini-Project
+
+**Build a quick reference card:** Create a file `~/cheatsheet.md` that contains the 20 commands you found most useful from this section, organized by category, with a one-line description and example for each. This is your cheatsheet — you'll extend it throughout the course.
+
+---
+
+## Key Takeaways
+
+- The shell is a command interpreter and a programming environment
+- Everything in Linux is a file, organized in a single tree from `/`
+- Permissions are the foundation of Linux security — understand `rwx` and octal
+- Pipes and redirection are the core of composing commands
+- `man` and `--help` are always available — read them first, search the web second
+- Practice in a real terminal. Reading about commands is not the same as using them.
+
+---
+
+**Next:** [File Management](../file-management/) — find, locate, tar, rsync, and more