diff --git a/.env b/.env new file mode 100644 index 0000000..5cbca00 --- /dev/null +++ b/.env @@ -0,0 +1 @@ +MDBOOK_VERSION=0.4.6 diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml deleted file mode 100644 index 08cd9ac..0000000 --- a/.github/FUNDING.yml +++ /dev/null @@ -1,5 +0,0 @@ -# These are supported funding model platforms - -ko_fi: dudochkin -liberapay: dudochkin -open_collective: dudochkin \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md deleted file mode 100644 index 8084fb1..0000000 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ /dev/null @@ -1,18 +0,0 @@ ---- -name: Bug report -about: About unexpected behaviors -title: "[BUG] " -labels: bug -assignees: '' - ---- - -**Describe the bug** -Describe what is expected, what you actually get. -It would be nice to have screenshot or result image uploaded - -**To Reproduce** -Some minimal reproduce code is highly recommended - -**Version Information** -Please give us what version you are using. If you are pulling `Ruex` directly from git repo, please mention this as well diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md deleted file mode 100644 index 462cb65..0000000 --- a/.github/ISSUE_TEMPLATE/feature_request.md +++ /dev/null @@ -1,17 +0,0 @@ ---- -name: Feature request -about: Suggest an idea to Ruex maintainers -title: "[Feature Request]" -labels: feature request -assignees: '' - ---- - -### What is the feature ? -*Detailed feature descrption* - -### (Optional) Why this feature is useful and how people would use the feature ? -*Explain why this feature is important* - -### (Optional) Additional Information -*More details are appreciated:)* diff --git a/.github/ISSUE_TEMPLATE/general-questions.md b/.github/ISSUE_TEMPLATE/general-questions.md deleted file mode 100644 index 96d4e89..0000000 --- a/.github/ISSUE_TEMPLATE/general-questions.md +++ /dev/null @@ -1,10 +0,0 @@ ---- -name: General Questions -about: Any other issues -title: '' -labels: '' -assignees: '' - ---- - - diff --git a/.github/codecov.yml b/.github/codecov.yml deleted file mode 100644 index f2cf6e4..0000000 --- a/.github/codecov.yml +++ /dev/null @@ -1,9 +0,0 @@ -comment: - layout: "diff, flags, files" - require_changes: true - -coverage: - status: - project: - default: - informational: true diff --git a/.github/workflows/audit.yaml b/.github/workflows/audit.yaml deleted file mode 100644 index e0c79d5..0000000 --- a/.github/workflows/audit.yaml +++ /dev/null @@ -1,17 +0,0 @@ -name: Security audit -on: - pull_request: - push: - branches: - - master - schedule: - - cron: '0 0 * * 0' - -jobs: - security_audit: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - - uses: actions-rs/audit-check@v1 - with: - token: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/coverage.yaml b/.github/workflows/coverage.yaml deleted file mode 100644 index b9cf34a..0000000 --- a/.github/workflows/coverage.yaml +++ /dev/null @@ -1,49 +0,0 @@ -name: Coverage - -#on: -# push: -# branches: -# - main -# pull_request: - -on: [push, pull_request] - -env: - CARGO_TERM_COLOR: always - RUST_BACKTRACE: full - -jobs: - coverage: - name: Coverage - runs-on: ubuntu-latest - steps: - - name: Checkout repository - uses: actions/checkout@v2 - - - name: Install Rust - uses: actions-rs/toolchain@v1 - with: - toolchain: nightly - profile: minimal - default: true - - - name: Restore cache - uses: Swatinem/rust-cache@v1 - - - name: Run cargo-tarpaulin - uses: actions-rs/tarpaulin@v0.1 - with: - args: '--all-features --run-types Doctests,Tests' - timeout: 120 - - - name: Upload to codecov.io - uses: codecov/codecov-action@239febf655bba88b16ff5dea1d3135ea8663a1f9 - with: - token: ${{ secrets.CODECOV_TOKEN }} - - - name: Archive code coverage results - uses: actions/upload-artifact@v2 - with: - name: code-coverage-report - path: cobertura.xml - retention-days: 30 diff --git a/.github/workflows/documentation.yml b/.github/workflows/documentation.yml new file mode 100644 index 0000000..18e8e9f --- /dev/null +++ b/.github/workflows/documentation.yml @@ -0,0 +1,30 @@ +# This is a basic workflow to help you get started with Actions + +# On every push this script is executed + +name: Documentation + +on: + push: + branches: + - docs + +jobs: + deploy: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + + - name: Setup mdBook + uses: peaceiris/actions-mdbook@v1 + with: + mdbook-version: '0.4.6' + # mdbook-version: 'latest' + + - run: mdbook build + + - name: Deploy + uses: peaceiris/actions-gh-pages@v3 + with: + github_token: ${{ secrets.TOKEN }} + publish_dir: ./book diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml deleted file mode 100644 index 6893c69..0000000 --- a/.github/workflows/tests.yml +++ /dev/null @@ -1,27 +0,0 @@ -name: Tests - -on: [push, pull_request] - -jobs: - build: - - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v1 - - name: Build - run: cargo build --verbose - - name: Run tests - run: cargo test --verbose - - clippy_check: - - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v1 - - run: rustup component add clippy - - uses: actions-rs/clippy-check@v1 - with: - token: ${{ secrets.GITHUB_TOKEN }} - args: --all-features diff --git a/.gitignore b/.gitignore index 9574792..3597896 100644 --- a/.gitignore +++ b/.gitignore @@ -1,11 +1,5 @@ -# Generated by Cargo -# will have compiled files and executables -/target/ - -# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries -# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html -Cargo.lock - -# These are backup files generated by rustfmt -**/*.rs.bk -/refs \ No newline at end of file +.idea +.vscode +.DS_Store +/public/ +/book/ diff --git a/.markdownlint.yaml b/.markdownlint.yaml new file mode 100644 index 0000000..ae89eab --- /dev/null +++ b/.markdownlint.yaml @@ -0,0 +1,21 @@ +--- +# Use `#` for headers +MD003: + style: atx + +# Set maximum line length +MD013: + line_length: 80 + +# Use `---` for horizontal rule +MD035: + style: --- + +# Use ``` for code blocks +MD046: + style: fenced +MD048: + style: backtick + +# See https://github.com/DavidAnson/markdownlint/blob/main/doc/Rules.md for +# additional info diff --git a/CHANGELOG.md b/CHANGELOG.md deleted file mode 100644 index 1d6474d..0000000 --- a/CHANGELOG.md +++ /dev/null @@ -1,24 +0,0 @@ -# Changelog - -## Package vX.X.X (YYYY-MM-DD) - -### Improved - -- A here your changes - -### Added - -- A here your changes - -### Fixed - -- A here your changes - -### Improvement - -- A here your changes - -### Removed - -- A here your changes - diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md deleted file mode 100644 index 8dea5d4..0000000 --- a/CODE_OF_CONDUCT.md +++ /dev/null @@ -1,129 +0,0 @@ -# Contributor Covenant Code of Conduct - -## Our Pledge - -We as members, contributors, and leaders pledge to make participation in our -community a harassment-free experience for everyone, regardless of age, body -size, visible or invisible disability, ethnicity, sex characteristics, gender -identity and expression, level of experience, education, socio-economic status, -nationality, personal appearance, race, religion, or sexual identity -and orientation. - -We pledge to act and interact in ways that contribute to an open, welcoming, -diverse, inclusive, and healthy community. - -## Our Standards - -Examples of behavior that contributes to a positive environment for our -community include: - -* Demonstrating empathy and kindness toward other people -* Being respectful of differing opinions, viewpoints, and experiences -* Giving and gracefully accepting constructive feedback -* Accepting responsibility and apologizing to those affected by our mistakes, - and learning from the experience -* Focusing on what is best not just for us as individuals, but for the - overall community -* Accepting and using the preferred gender pronouns of all people who have specified them involved in the project. - -Examples of unacceptable behavior include: - -* The use of sexualized language or imagery, and sexual attention or - advances of any kind -* Trolling, insulting or derogatory comments, and personal or political attacks -* Public or private harassment -* Publishing others' private information, such as a physical or email - address, without their explicit permission -* Other conduct which could reasonably be considered inappropriate in a - professional setting - -## Enforcement Responsibilities - -Community leaders are responsible for clarifying and enforcing our standards of -acceptable behavior and will take appropriate and fair corrective action in -response to any behavior that they deem inappropriate, threatening, offensive, -or harmful. - -Community leaders have the right and responsibility to remove, edit, or reject -comments, commits, code, wiki edits, issues, and other contributions that are -not aligned to this Code of Conduct, and will communicate reasons for moderation -decisions when appropriate. - -## Scope - -This Code of Conduct applies within all community spaces, and also applies when -an individual is officially representing the community in public spaces. -Examples of representing our community include using an official e-mail address, -posting via an official social media account, or acting as an appointed -representative at an online or offline event. - -## Enforcement - -Instances of abusive, harassing, or otherwise unacceptable behavior may be -reported to the community leaders responsible for enforcement by emailing -`dudochkin.victor@gmail.com`. -All complaints will be reviewed and investigated promptly and fairly. - -All community leaders are obligated to respect the privacy and security of the -reporter of any incident. - -## Enforcement Guidelines - -Community leaders will follow these Community Impact Guidelines in determining -the consequences for any action they deem in violation of this Code of Conduct: - -### 1. Correction - -**Community Impact**: Use of inappropriate language or other behavior deemed -unprofessional or unwelcome in the community. - -**Consequence**: A private, written warning from community leaders, providing -clarity around the nature of the violation and an explanation of why the -behavior was inappropriate. A public apology may be requested. - -### 2. Warning - -**Community Impact**: A violation through a single incident or series -of actions. - -**Consequence**: A warning with consequences for continued behavior. No -interaction with the people involved, including unsolicited interaction with -those enforcing the Code of Conduct, for a specified period of time. This -includes avoiding interactions in community spaces as well as external channels -like social media. Violating these terms may lead to a temporary or -permanent ban. - -### 3. Temporary Ban - -**Community Impact**: A serious violation of community standards, including -sustained inappropriate behavior. - -**Consequence**: A temporary ban from any sort of interaction or public -communication with the community for a specified period of time. No public or -private interaction with the people involved, including unsolicited interaction -with those enforcing the Code of Conduct, is allowed during this period. -Violating these terms may lead to a permanent ban. - -### 4. Permanent Ban - -**Community Impact**: Demonstrating a pattern of violation of community -standards, including sustained inappropriate behavior, harassment of an -individual, or aggression toward or disparagement of classes of individuals. - -**Consequence**: A permanent ban from any sort of public interaction within -the community. - -## Attribution - -This Code of Conduct is adapted from the [Contributor Covenant][homepage], -version 2.0, available at -https://www.contributor-covenant.org/version/2/0/code_of_conduct.html. - -Community Impact Guidelines were inspired by [Mozilla's code of conduct -enforcement ladder](https://github.com/mozilla/diversity). - -[homepage]: https://www.contributor-covenant.org - -For answers to common questions about this code of conduct, see the FAQ at -https://www.contributor-covenant.org/faq. Translations are available at -https://www.contributor-covenant.org/translations. \ No newline at end of file diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index ea2f6ad..469d94a 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,186 +1,111 @@ -## Contributing to Angular Rust +# Contributing -This describes how developers may contribute to Angular Rust. +## Introduction -## Mission +This book is a catalogue of Rust programming techniques, (anti-)patterns, +idioms and other explanations. It is a compilation of collective (sometimes +implicit) knowledge as well as experiences that have emerged through +collaborative work. -Angular Rust's mission is to provide a batteries-included framework for making large scale web and desktop application development as efficient and maintainable as possible. +The patterns described here are __not rules__, but should be taken as +guidelines for writing idiomatic code in Rust. We are collecting Rust patterns +in this book so people can learn the tradeoffs between Rust idioms and use them +properly in their own code. -The design should be configurable and modular so that it can grow with the developer. However, it should provide a wonderful un-boxing experience and default configuration that can woo new developers and make simple web and desktop apps straight forward. The framework should have an opinion about how to do all of the common tasks in web and desktop development to reduce unnecessary cognitive load. +If you want to be part of this effort here are some ways you can participate: -Perhaps most important of all, Angular Rust should be a joy to use. We want to reduce the time spent on tedious boilerplate functionality and increase the time -available for creating polished solutions for your application's target users. +## Discussion board -## How to Contribute +If you have a question or an idea regarding certain content but you want to +have feedback of fellow community members and you think it may not be +appropriate to file an issue open a discussion in our [discussion board](https://github.com/rust-unofficial/patterns/discussions). -### Join the Community +## Writing a new article -The first step to improving Angular Rust is to join the community and help grow it! You can find the community on: +Before writing a new article please check in one of the following resources if +there is an existing discussion or if someone is already working on that topic: - [![](https://img.shields.io/badge/Facebook-1877F2?style=for-the-badge&logo=facebook&logoColor=white)](https://www.facebook.com/groups/angular.rust) - [![](https://img.shields.io/badge/Stack_Overflow-FE7A16?style=for-the-badge&logo=stack-overflow&logoColor=white)](https://stackoverflow.com/questions/tagged/angular-rust) - [![](https://img.shields.io/badge/YouTube-FF0000?style=for-the-badge&logo=youtube&logoColor=white)](https://www.youtube.com/channel/UCBJTkSl_JWShuolUy4JksTQ) - [![](https://img.shields.io/badge/Medium-12100E?style=for-the-badge&logo=medium&logoColor=white)](https://medium.com/@angular.rust) - [![](https://img.shields.io/gitter/room/angular_rust/angular_rust?style=for-the-badge)](https://gitter.im/angular_rust/community) +- [Umbrella issue](https://github.com/rust-unofficial/patterns/issues/116), +- [All issues](https://github.com/rust-unofficial/patterns/issues), +- [Pull Requests](https://github.com/rust-unofficial/patterns/pulls) -We believe the wider community can create better code. The first tool for improving the community is to tell the developers about the project by giving it a star. More stars - more members. +If you don't find an issue regarding your topic and you are sure it is not more +feasible to open a thread in the [discussion board](https://github.com/rust-unofficial/patterns/discussions) +please open a new issue, so we can discuss about the ideas and future content +of the article together and maybe give some feedback/input on it. - ![Star a repo](https://dudochkin-victor.github.io/assets/star-me-wide.svg) +When writing a new article it's recommended to copy the [pattern template](https://github.com/rust-unofficial/patterns/blob/master/template.md) +into the appropriate directory and start editing it. You may not want to fill +out every section and remove it or you might want to add extra sections. -Once you've joined, there are many ways to contribute to Angular Rust: +Consider writing your article in a way that has a low barrier of entry so also +[Rustlings](https://github.com/rust-lang/rustlings) can follow and understand +the thought process behind it. So we can encourage people to use these patterns +early on. -* Report bugs (via GitHub) -* Answer questions of other community members (via Gitter or GitHub Discussions) -* Give feedback on new feature discussions (via GitHub and Gitter) -* Propose your own ideas (via Gitter or GitHub) +We encourage you to write idiomatic Rust code that builds in the [playground](https://play.rust-lang.org/). -### How Angular Rust is Developed +If you use links to blogposts or in general content that is not to be sure +existing in a few years (e.g. pdfs) please take a snapshot with the +[Wayback Machine](https://web.archive.org/) and use the link to that snapshot +in your article. -We have begun to formalize the development process by adopting pragmatic practices such as: +Don't forget to add your new article to the `SUMMARY.md` to let it be rendered +to the book. -* Developing on the `develop` branch -* Merging `develop` branch to `main` branch in 6 week iterations -* Tagging releases with MAJOR.MINOR syntax (e.g. v0.8) -** We may also tag MAJOR.MINOR.HOTFIX releases as needed (e.g. v0.8.1) to address urgent bugs. Such releases will not introduce or change functionality -* Managing bugs, enhancements, features and release milestones via GitHub's Issue Tracker -* Using feature branches to create pull requests -* Discussing new features **before** hacking away at it +Please make `Draft Pull requests` early so we can follow your progress and can +give early feedback (see the following section). +## Style guide -## Dive into code +In order to have a consistent style across the book, we suggest to: -### Fork this repository +- Follow the official Rust book's [style guide](https://github.com/rust-lang/book/blob/master/style-guide.md). +- Follow [RFC 1574](https://github.com/rust-lang/rfcs/blob/master/text/1574-more-api-documentation-conventions.md#appendix-a-full-conventions-text). + Tl;dr: + - Prefer full types name. For example `Option` instead of `Option`. + - Prefer line comments (`//`) over block comments (`/* */`) where applicable. -fork this repository +## Check the article locally -Fork this repository by clicking on the fork button on the top of this page. -This will create a copy of this repository in your account. +Before submitting the PR launch the commands `mdbook build` to make sure that +the book builds and `mdbook test` to make sure that code examples are correct. -
+### Markdown lint -### Clone the repository +To make sure the files comply with our Markdown style we use [markdownlint-cli](https://github.com/igorshubovych/markdownlint-cli). +To spare you some manual work to get through the CI test you can use the +following commands to automatically fix most of the emerging problems when +writing Markdown files. -clone this repository +- Install: -Now clone the forked repository to your machine. Go to your GitHub account, open the forked repository, click on the code button and then click the _copy to clipboard_ icon. + ```sh + npm install -g markdownlint-cli + ``` -Open a terminal and run the following git command: +- Check all markdown files: + - unix: `markdownlint '**/*.md'` + - windows: `markdownlint **/*.md` -``` -git clone "url you just copied" -``` +- Automatically fix basic errors: + - unix: `markdownlint -f '**/*.md'` + - windows: `markdownlint -f **/*.md` -where "url you just copied" (without the quotation marks) is the url to this repository (your fork of this project). See the previous steps to obtain the url. +## Creating a Pull Request -> use SSH tab to copy proper URL +"Release early and often!" also applies to pull requests! -copy URL to clipboard +Once your article has some visible work, create a `[WIP]` draft pull request +and give it a description of what you did or want to do. Early reviews of the +community are not meant as an offense but to give feedback. -For example: +A good principle: "Work together, share ideas, teach others." -``` -git clone git@github.com:$USER/ruex.git -``` +### Important Note -where `$USER` is your GitHub username. Here you're copying the contents of the `ruex` repository on GitHub to your computer. +Please **don't force push** commits in your branch, in order to keep commit +history and make it easier for us to see changes between reviews. - -
- -### Create a branch - -Change to the repository directory on your computer (if you are not already there): - -``` -cd ruex -``` - -Now create a branch using the `git checkout` command: - -``` -git checkout -b origin/develop -``` -replacing `` with the adequate name of the feature you will develop. - -### Make necessary changes and commit those changes - -Now that you've properly installed and forked Angular Rust, you are ready to start coding (assuming you have a validated your ideas with other community members)! - -### Format Your Code - -Remember to run `cargo fmt` before committing your changes. -Many Go developers opt to have their editor run `cargo fmt` automatically when saving Go files. - -Additionally, follow the [core Rust style conventions](https://rustc-dev-guide.rust-lang.org/conventions.html) to have your pull requests accepted. - -### Write Tests (and Benchmarks for Bonus Points) - -Significant new features require tests. Besides unit tests, it is also possible to test a feature by exercising it in one of the sample apps and verifying its -operation using that app's test suite. This has the added benefit of providing example code for developers to refer to. - -Benchmarks are helpful but not required. - -### Run the Tests - -Typically running the main set of unit tests will be sufficient: - -``` -$ cargo test -``` -### Document Your Feature - -Due to the wide audience and shared nature of Angular Rust, documentation is an essential addition to your new code. **Pull requests risk not being accepted** until proper documentation is created to detail how to make use of new functionality. - -### Add yourself to the list of contributors - -Open `CONTRIBUTORS.md` file in a text editor, add your name to it. Don't add it at the beginning or end of the file. Put it anywhere in between. Now, save the file. - -git status - -If you go to the project directory and execute the command `git status`, you'll see there are changes. - -Add those changes to the branch you just created using the `git add` command: - -``` -git add . -``` - -Now commit those changes using the `git commit` command: - -``` -git commit -m "$COMMENT" -``` - -replacing `$COMMENT` with appropriate description of your changes. - -### Push changes to GitHub - -Push your changes using the command `git push`: - -``` -git push origin -``` - -replacing `` with the name of the branch you created earlier. - -### Submit your changes for review - -Once you've done all of the above & pushed your changes to your fork, you can create a pull request for review and acceptance. - -If you go to your repository on GitHub, you'll see a `Compare & pull request` button. Click on that button. - -create a pull request - -Now submit the pull request. -Do not forget to set develop branch for your pull request. - -submit pull request - - -## Where to go from here? - -Congrats! You just completed the standard _fork -> clone -> edit -> pull request_ workflow that you'll encounter often as a contributor! - -You can check more details in our **[detailed contribution guide](https://angular-rust.github.io/contributing/)**. - -You could join our gitter team in case you need any help or have any questions. [Join gitter team](https://gitter.im/angular_rust/community). +Make sure to `Allow edits of maintainers` (under the text box) in the PR so +people can actually collaborate on things or fix smaller issues themselves. diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md deleted file mode 100644 index cb853e1..0000000 --- a/CONTRIBUTORS.md +++ /dev/null @@ -1,10 +0,0 @@ -Ruex contributors (sorted alphabetically) -============================================ - -* **[Victor Dudochkin](https://github.com/dudochkin.victor)** - - * Core development - - - -**[Full contributors list](https://github.com/angular-rust/ruex/contributors).** \ No newline at end of file diff --git a/Cargo.toml b/Cargo.toml deleted file mode 100644 index 7bb2fed..0000000 --- a/Cargo.toml +++ /dev/null @@ -1,21 +0,0 @@ -[package] -name = "ruex" -version = "0.1.3" -authors = ["Victor Dudochkin "] -readme = "README.md" -homepage = "https://angular-rust.github.io/ruex" -repository = "https://github.com/angular-rust/ruex" -documentation = "https://docs.rs/ruex" -description = "Generic State Management framework" -keywords = ["mvc", "redux", "state-management", "time-travel", "event-bus"] - -edition = "2018" -license = "MPL-2.0" - -[badges] -maintenance = { status = "actively-developed" } - -[dependencies] -log = "0.4" -serde = { version = "1.0", features = ["derive"] } -once_cell = "1.7.2" diff --git a/README.md b/README.md index 1756acb..8ced0f4 100644 --- a/README.md +++ b/README.md @@ -1,171 +1 @@ -
- -[![](https://dudochkin-victor.github.io/assets/ruex/logo-wide.svg)](#top) -# Ruex - -[![API Docs][docrs-badge]][docrs-url] -[![Crates.io][crates-badge]][crates-url] -[![Code coverage][codecov-badge]][codecov-url] -[![Tests][tests-badge]][tests-url] -[![MPL-2.0 licensed][license-badge]][license-url] -[![Gitter chat][gitter-badge]][gitter-url] -[![loc][loc-badge]][loc-url] -
- -[docrs-badge]: https://img.shields.io/docsrs/ruex?style=flat-square -[docrs-url]: https://docs.rs/ruex/ -[crates-badge]: https://img.shields.io/crates/v/ruex.svg?style=flat-square -[crates-url]: https://crates.io/crates/ruex -[license-badge]: https://img.shields.io/badge/license-MPL--2.0-blue.svg?style=flat-square -[license-url]: https://github.com/angular-rust/ruex/blob/master/LICENSE -[gitter-badge]: https://img.shields.io/gitter/room/angular_rust/community.svg?style=flat-square -[gitter-url]: https://gitter.im/angular_rust/community -[tests-badge]: https://img.shields.io/github/workflow/status/angular-rust/ruex/Tests?label=tests&logo=github&style=flat-square -[tests-url]: https://github.com/angular-rust/ruex/actions/workflows/tests.yml -[codecov-badge]: https://img.shields.io/codecov/c/github/angular-rust/ruex?logo=codecov&style=flat-square&token=L7KV27OLY0 -[codecov-url]: https://codecov.io/gh/angular-rust/ruex -[loc-badge]: https://img.shields.io/tokei/lines/github/angular-rust/ruex?style=flat-square -[loc-url]: https://github.com/angular-rust/ruex - -Design pattern framework on top of PureMVC. - -The PureMVC framework has a very narrow goal. That is to help you -separate your application’s coding interests into three discrete tiers: -[Model][2], [View][3] and [Controller][1]. - -This separation of interests, and the tightness and direction of the -couplings used to make them work together is of paramount -importance in the building of scalable and maintainable applications. - -In this implementation of the classic MVC Design meta-pattern, these -three tiers of the application are governed by three Singletons (a class -where only one instance may be created) called simply [Model][2], [View][3] -and [Controller][1]. Together, they are referred to as the ‘Core actors’. - -A fourth Singleton, the [Facade][4] simplifies development by providing a -single interface for communication with the Core actors. - -## Model & Proxies - -The [Model][2] simply caches named references to Proxies. Proxy code -manipulates the data model, communicating with remote services if -need be to persist or retrieve it. - -This results in portable Model tier code. - -## View & Mediators - -The View primarily caches named references to [Mediators][7]. [Mediator][7] -code stewards View Components, adding event listeners, sending -and receiving notifications to and from the rest of the system on -their behalf and directly manipulating their state. - -This separates the View definition from the logic that controls it. - -## Controller & Commands - -The [Controller][1] maintains named mappings to Command classes, -which are stateless, and only created when needed. - -[Commands][9] may retrieve and interact with Proxies, send -Notifications, execute other [Commands][9], and are often used to -orchestrate complex or system-wide activities such as application -startup and shutdown. They are the home of your application’s -Business Logic. - -## Facade & Core - -The [Facade][4], another Singleton, initializes the Core actors ([Model][2], -[View][3] and [Controller][1]), and provides a single place to access all of -their public methods. - -By extending the [Facade][4], your application gets all the benefits of -Core actors without having to import and work with them directly. -You will implement a concrete [Facade][4] for your application only once -and it is simply done. - -[Proxies][6], [Mediators][7] and [Commands][9] may then use your application’s -concrete [Facade][4] in order to access and communicate with each -other. - -## Observers & Notifications - -PureMVC applications may run in environments without access to -Event and EventDispatcher classes, so the framework -implements an [Observer][8] notification scheme for communication -between the Core MVC actors and other parts of the system in a -loosely-coupled way. - -You need not be concerned about the details of the PureMVC -[Observer][8]/[Notification][5] implementation; it is internal to the -framework. You will use a simple method to send [Notifications][5] from -[Proxies][6], [Mediators][7], [Commands][9] and the Facade itself that doesn’t -even require you to create a [Notification][5] instance. - -[1]: https://docs.rs/ruex/latest/ruex/prelude/trait.Controller.html -[2]: https://docs.rs/ruex/latest/ruex/prelude/trait.Model.html -[3]: https://docs.rs/ruex/latest/ruex/prelude/trait.View.html -[4]: https://docs.rs/ruex/latest/ruex/prelude/trait.Facade.html -[5]: https://docs.rs/ruex/latest/ruex/prelude/trait.Notification.html -[6]: https://docs.rs/ruex/latest/ruex/prelude/trait.Proxy.html -[7]: https://docs.rs/ruex/latest/ruex/prelude/trait.Mediator.html -[8]: https://docs.rs/ruex/latest/ruex/prelude/trait.Observer.html -[9]: https://docs.rs/ruex/latest/ruex/prelude/trait.Command.html - - -## Quick Start - -Install Ruex: - - cargo add ruex - -## Learn More - -* [Manual, Docs, etc](https://angular-rust.github.io/) -* [Samples](https://github.com/angular-rust/ux-samples) -* [Apps using Angular Rust](https://github.com/angular-rust/ruex/wiki/Apps-in-the-Wild) -* [Articles Featuring Angular Rust](https://github.com/angular-rust/ruex/wiki/Articles) -* [The Catalog of Design Patterns](https://refactoring.guru/design-patterns/catalog) -* [Design patterns card](http://www.mcdonaldland.info/files/designpatterns/designpatternscard.pdf) - -## Community - - [![](https://img.shields.io/badge/Facebook-1877F2?style=for-the-badge&logo=facebook&logoColor=white)](https://www.facebook.com/groups/angular.rust) - [![](https://img.shields.io/badge/Stack_Overflow-FE7A16?style=for-the-badge&logo=stack-overflow&logoColor=white)](https://stackoverflow.com/questions/tagged/angular-rust) - [![](https://img.shields.io/badge/YouTube-FF0000?style=for-the-badge&logo=youtube&logoColor=white)](https://www.youtube.com/channel/UCBJTkSl_JWShuolUy4JksTQ) - [![](https://img.shields.io/badge/Medium-12100E?style=for-the-badge&logo=medium&logoColor=white)](https://medium.com/@angular.rust) - [![](https://img.shields.io/gitter/room/angular_rust/angular_rust?style=for-the-badge)](https://gitter.im/angular_rust/community) - - -## Contributing - -We believe the wider community can create better code. The first tool for improving the community is to tell the developers about the project by giving it a star. More stars - more members. - - ![Star a repo](https://dudochkin-victor.github.io/assets/star-me-wide.svg) - -Angular Rust is a community effort and we welcome all kinds of contributions, big or small, from developers of all backgrounds. We want the Angular Rust community to be a fun and friendly place, so please review our [Code of Conduct](CODE_OF_CONDUCT.md) to learn what behavior will not be tolerated. - -### New to Angular Rust? - -Start learning about the framework by helping us improve our [documentation](https://angular-rust.github.io/). Pull requests which improve test coverage are also very welcome. - -### Looking for inspiration? - -Check out the community curated list of awesome things related to Angular Rust / WebAssembly at [awesome-angular-rust](https://github.com/angular-rust/awesome-angular-rust). - -### Confused about something? - -Feel free to drop into our [Gitter chatroom](https://gitter.im/angular_rust/community) or open a [new "Question" issue](https://github.com/angular-rust/ruex/issues/new/choose) to get help from contributors. Often questions lead to improvements to the ergonomics of the framework, better documentation, and even new features! - -### Ready to dive into the code? - -After reviewing the [Contributing Code Guidelines](CONTRIBUTING.md), check out the ["Good First Issues"](https://github.com/angular-rust/ruex/issues?q=is%3Aopen+is%3Aissue+label%3A%22good+first+issue%22) (they are eager for attention!). Once you find one that interests you, feel free to assign yourself to an issue and don't hesitate to reach out for guidance, the issues vary in complexity. - -### Let's help each other! - -Come help us on the [issues that matter that the most](https://github.com/angular-rust/ruex/labels/%3Adollar%3A%20Funded%20on%20Issuehunt) and receive a small cash reward for your troubles. We use [Issuehunt](https://issuehunt.io/r/angular-rust/ruex/) to fund issues from our Open Collective funds. If you really care about an issue, you can choose to add funds yourself! - -### Found a bug? - -Please [report all bugs!](https://github.com/angular-rust/ruex/issues/new/choose) We are happy to help support developers fix the bugs they find if they are interested and have the time. - +# ruex \ No newline at end of file diff --git a/README1.md b/README1.md new file mode 100644 index 0000000..76c5b8e --- /dev/null +++ b/README1.md @@ -0,0 +1,38 @@ +# Rust Design Patterns + +An open source book about design patterns and idioms in the Rust programming +language that you can read [here](https://rust-unofficial.github.io/patterns/). + +## Contributing + +You are missing content in this repository that can be helpful for others and +you are eager to explain it? Awesome! We are always happy about new contributions +(e.g. elaboration or corrections on certain topics) to this project. + +You can check the [Umbrella issue](https://github.com/rust-unofficial/patterns/issues/116) +for all the patterns, anti-patterns, and idioms that could be added. + +We suggest reading our [Contribution guide](./CONTRIBUTING.md) to get more information +on how contributing to this repository works. + +## Building with mdbook + +This book is built with [mdbook](https://rust-lang.github.io/mdBook/). You can +install it by running `cargo install mdbook`. + +If you want to build it locally you can run one of these two commands in the root +directory of the repository: + +- `mdbook build` + + Builds static html pages as output and place them in the `/book` directory by + default. + +- `mdbook serve` + + Serves the book at `http://localhost:3000` (port is changeable, take a look at + the terminal output to be sure) and reloads the browser when a change occurs. + +## License + +This content of this repository is licensed under **MPL-2.0**; see [LICENSE](./LICENSE). diff --git a/SECURITY.md b/SECURITY.md deleted file mode 100644 index 4ec86f9..0000000 --- a/SECURITY.md +++ /dev/null @@ -1,13 +0,0 @@ -# Security Policy - -If you believe you have found a security vulnerability in Angular Rust, we encourage you to let us know right away. We will investigate all legitimate reports and do our best to quickly fix the problem. - -## Reporting a Vulnerability - -To report a security vulnerability, please create a Github issue [here](https://github.com/angular-rust/ruex/issues/new). - -If you can, please include the following details: -* An MCVE (minimum complete verifiable example) – this is a short code snippet which demonstrates the error in the -the simplest possible (or just a simple) way. -* Which versions of Angular Rust the vulnerability is present in -* What effects the vulnerability has and how serious the vulnerability is \ No newline at end of file diff --git a/SUMMARY1.md b/SUMMARY1.md new file mode 100644 index 0000000..9928c74 --- /dev/null +++ b/SUMMARY1.md @@ -0,0 +1,47 @@ +# Summary + +- [Introduction](./intro.md) +- [Idioms](./idioms/index.md) + - [Use borrowed types for arguments](./idioms/coercion-arguments.md) + - [Concatenating Strings with `format!`](./idioms/concat-format.md) + - [Constructor](./idioms/ctor.md) + - [The `Default` Trait](./idioms/default.md) + - [Collections Are Smart Pointers](./idioms/deref.md) + - [Finalisation in Destructors](./idioms/dtor-finally.md) + - [`mem::{take(_), replace(_)}`](./idioms/mem-replace.md) + - [On-Stack Dynamic Dispatch](./idioms/on-stack-dyn-dispatch.md) + - [Foreign function interface usage](./idioms/ffi-intro.md) + - [Idiomatic Errors](./idioms/ffi-errors.md) + - [Accepting Strings](./idioms/ffi-accepting-strings.md) + - [Passing Strings](./idioms/ffi-passing-strings.md) + - [Iterating over an `Option`](./idioms/option-iter.md) + - [Pass Variables to Closure](./idioms/pass-var-to-closure.md) + - [Privacy For Extensibility](./idioms/priv-extend.md) + - [Easy doc initialization](./idioms/rustdoc-init.md) + - [Temporary mutability](./idioms/temporary-mutability.md) + +- [Design Patterns](./patterns/index.md) + - [Builder](./patterns/builder.md) + - [Compose Structs](./patterns/compose-structs.md) + - [Entry API](./patterns/entry.md) + - [Foreign function interface usage](./patterns/ffi-intro.md) + - [Object-Based APIs](./patterns/ffi-export.md) + - [Type Consolidation into Wrappers](./patterns/ffi-wrappers.md) + - [Fold](./patterns/fold.md) + - [Interpreter](./patterns/interpreter.md) + - [Newtype](./patterns/newtype.md) + - [RAII Guards](./patterns/RAII.md) + - [Prefer Small Crates](./patterns/small-crates.md) + - [Strategy](./patterns/strategy.md) + - [Contain unsafety in small modules](./patterns/unsafe-mods.md) + - [Visitor](./patterns/visitor.md) + +- [Anti-patterns](./anti_patterns/index.md) + - [`#[deny(warnings)]`](./anti_patterns/deny-warnings.md) + - [Deref Polymorphism](./anti_patterns/deref.md) + +- [Functional Programming](./functional/index.md) + - [Programming paradigms](./functional/paradigms.md) + +- [Additional Resources](./additional_resources/index.md) + - [Design principles](./additional_resources/design-principles.md) diff --git a/TODO.md b/TODO.md deleted file mode 100644 index 320ec15..0000000 --- a/TODO.md +++ /dev/null @@ -1,64 +0,0 @@ -TODO -==== - -## Basics - -- [ ] Add basic Design Pattern info -- [ ] Add complete list of patterns on side - -## Types of Patterns - -- [ ] Structural ([Refactoring.Guru](https://refactoring.guru/design-patterns/structural-patterns)) -- [ ] Behavioral ([Refactoring.Guru](https://refactoring.guru/design-patterns/behavioral-patterns)) -- [ ] Creational ([Refactoring.Guru](https://refactoring.guru/design-patterns/creational-patterns)) - -## Design Patterns - -- [ ] Abstract Factory -- [ ] Factory Method -- [ ] Prototype -- [ ] Adapter -- [ ] Bridge -- [ ] Composite -- [ ] Decorator -- [ ] Flyweight -- [ ] Chain of Responsibility -- [ ] Interpreter -- [ ] Iterator -- [ ] Memento -- [ ] Strategy -- [ ] Template Method -- [ ] Visitor - -## Todo - -Template name | Type -:-----------------------|:---------- -Abstract Factory | Creational -Adapter | Structural -Bridge | Structural -Chain of Responsibility | Behavioral -Composite | Structural -Decorator | Structural -Factory Method | Creational -Flyweight | Structural -Interpreter | Behavioral -Iterator | Behavioral -Memento | Behavioral -Prototype | Creational -State | Behavioral -Strategy | Behavioral -Template Method | Behavioral -Visitor | Behavioral - -## Pattern List - -Pattern name | Type -:-----------------------------------------------------------------|:---------- -[Builder](src/foundation/patterns/builder.rs) | Creational -[Command](src/foundation/patterns/command/simple_command.rs) | Behavioral -[Facade](src/foundation/patterns/facade.rs) | Structural -[Mediator](src/foundation/patterns/mediator/mediator.rs) | Behavioral -[Proxy](src/foundation/patterns/proxy/proxy.rs) | Structural -[Observer](src/foundation/patterns/observer/observer.rs) | Behavioral -[Singleton](src/prelude/singleton.rs) | Creational \ No newline at end of file diff --git a/book.toml b/book.toml new file mode 100644 index 0000000..f70d734 --- /dev/null +++ b/book.toml @@ -0,0 +1,10 @@ +[book] +title = "Ruex" +authors = ["Victor Dudochkin"] +language = "en" +multilingual = false +src = "src" + +[output.html] +theme = "src/theme" +site-url = "/ruex/" \ No newline at end of file diff --git a/book.toml1 b/book.toml1 new file mode 100644 index 0000000..c43d672 --- /dev/null +++ b/book.toml1 @@ -0,0 +1,20 @@ +[book] +title = "Rust Design Patterns" +authors = ["the rust-unofficial authors"] +description = "A catalogue of Rust design patterns, anti-patterns and idioms" +language = "en" +multilingual = false +src = "." + +[build] +create-missing = false + +[rust] +edition = "2018" + +[output.html] +default-theme = "rust" +git-repository-url = "https://github.com/rust-unofficial/patterns" +git-repository-icon = "fa-github" + +# [output.linkcheck] # enable the "mdbook-linkcheck" renderer, disabled due to gh-actions diff --git a/intro.md b/intro.md new file mode 100644 index 0000000..8486841 --- /dev/null +++ b/intro.md @@ -0,0 +1,33 @@ +# Introduction + +## Participation + +If you are interested in contributing to this book, check out the +[contribution guidelines](https://github.com/rust-unofficial/patterns/blob/master/CONTRIBUTING.md). + +## Design patterns + +When developing programs, we have to solve many problems. +A program can be viewed as a solution to a problem. +It can also be viewed as a collection of solutions to many different problems. +All of these solutions work together to solve a bigger problem. + +## Design patterns in Rust + +There are many problems that share the same form. +Due to the fact that Rust is not object-oriented design patterns vary with +respect to other object-oriented programming languages. +While the details are different, since they have the same form they can be +solved using the same fundamental methods: + +- [Design patterns](./patterns/index.md) are methods to solve common problems + when writing software. +- [Anti-patterns](./anti_patterns/index.md) are methods to solve these same + common problems. However, while design patterns give us benefits, + anti-patterns create more problems. +- [Idioms](./idioms/index.md) are guidelines to follow when coding. + They are social norms of the community. + You can break them, but if you do you should have a good reason for it. + +TODO: Mention why Rust is a bit special - functional elements, type system, +borrow checker diff --git a/macro/Cargo.toml b/macro/Cargo.toml deleted file mode 100644 index 751cf67..0000000 --- a/macro/Cargo.toml +++ /dev/null @@ -1,14 +0,0 @@ -[package] -name = "ruex-macro" -version = "0.1.0" -authors = ["Victor Dudochkin "] -edition = "2018" - -[lib] -proc-macro = true - -[dependencies] -proc-macro2 = "1.0" -syn = { version = "1.0", features = ["full"]} -quote = "1.0" -proc-macro-error = "1.0" diff --git a/macro/examples/aop.rs b/macro/examples/aop.rs deleted file mode 100644 index c5d46e6..0000000 --- a/macro/examples/aop.rs +++ /dev/null @@ -1,58 +0,0 @@ -use ruex_macro::*; - -// #[Aspect { -// advice: "aspects::TestAspect", -// before: "before(val,val2)", -// after: "after()" -// }] -// fn test_aop(&self, val: i32) -> Result { -// let joint_point = move || { -// println!("Closure {:?} with {}", self, val) -// }; - -// joint_point(); - -// Ok(String::from("Good")) -// } - -#[derive(Debug)] -enum AppErr { - WrongParam, -} - -mod aspects { - use super::AppErr; - - struct TestAspect; - - impl TestAspect { - fn before(val: i32) -> Result<(), AppErr> { - println!("called before"); - Ok(()) - } - - fn after(val: Result) -> Result { - println!("called after"); - Ok(String::from("Here")) - } - } -} - -#[derive(Debug)] -struct AopExample; - -impl AopExample { - #[Aspect { - advice: "aspects::TestAspect", - before: "before(val)", - after: "after()" - }] - fn test_aop(&self, val: i32) -> Result { - Ok(String::from("Good")) - } -} - -fn main() { - let ex = AopExample; - println!("HERE {:?}", ex.test_aop(10)); -} \ No newline at end of file diff --git a/macro/src/lib.rs b/macro/src/lib.rs deleted file mode 100644 index 20ab885..0000000 --- a/macro/src/lib.rs +++ /dev/null @@ -1,68 +0,0 @@ -#![allow(unused_imports, unused_variables, dead_code, non_snake_case)] - -use proc_macro::TokenStream; -use quote::quote; -use syn::{ - parse::{Parse, ParseStream, Result}, - parse_macro_input, - punctuated::Punctuated, - DeriveInput, Ident, ItemFn, Token, -}; - -struct AdviceField { - member: Ident, - // colon: Token![:], - value: syn::LitStr, -} - -impl Parse for AdviceField { - fn parse(input: ParseStream) -> Result { - let member: Ident = input.parse()?; - let colon_token: Token![:] = input.parse()?; - // let value: Expr = input.parse()?; - let value: syn::LitStr = input.parse()?; - - Ok(AdviceField { - member, - // colon_token, - value, - }) - } -} - -struct Args { - // advice: Ident, - // before: Ident, - // after: Ident, -} - -impl Parse for Args { - fn parse(input: ParseStream) -> Result { - let vars = Punctuated::::parse_terminated(input)?; - let idents: Vec = vars.into_iter().collect(); - // dbg!(idents); - // todo!("GOOD") - Ok(Args { - // vars: vars.into_iter().collect(), - }) - } -} - -#[proc_macro_attribute] -pub fn Aspect(attr: TokenStream, item: TokenStream) -> TokenStream { - // println!("attr: \"{}\"", attr.to_string()); - let attr = attr.clone(); - let attr = syn::parse_macro_input!(attr as Args); - - // println!("item: \"{}\"", item.to_string()); - - item -} - -#[cfg(test)] -mod tests { - #[test] - fn it_works() { - assert_eq!(2 + 2, 4); - } -} diff --git a/src/2013-01-01-using-a-design-pattern.md b/src/2013-01-01-using-a-design-pattern.md new file mode 100644 index 0000000..7b2daf7 --- /dev/null +++ b/src/2013-01-01-using-a-design-pattern.md @@ -0,0 +1,137 @@ +--- +layout: default +title: How do you use a Design Pattern? +category: info +description: Explanation of how to appropriately apply a Design Pattern. +--- + +{{ page.title }} +================ + +First it is very important to understand [what a design pattern is][what]. + +After understanding what it is and why you would want to use a design pattern, +the steps of using a design pattern can be summarized as below: + +1. Identity a Problem +2. Find a Design Pattern +3. Understand the Design Pattern +4. Look at the Example +5. Fit the Pattern +6. Implement the Pattern + +## Identity a Problem + +It is very easy to design software in an ad-hoc fashion by adding functions and +classes whenever the need arises. The problem with this is that it doesn't work +well for large projects or when multiple develop on the same project. + +The way to remedy this is to think about how you want to do something before you +actually do it. By doing this, you can look at the trade-offs and shortcomings +whenever you make a decision about the structure of your code. + +A few examples of the things developers do when they come across a problem and +don't design a solution for it: + +1. I need code to do X. I'll add it to this object. Oh, this code also needs to + be in this object? I'll just copy and paste it. +2. I need to reuse code across different objects. I'll just make a dummy object + that only contains that method. + +By designing instead of coding blindly, the following problems might arise +in comparison: + +1. If I were to use inheritance here to solve X problem, what side effects would + it have when this code needs to be extended for Y? +2. If I split up the classes in this way, it will solve X. Will that make it + easier to understand if problem Y arises? + +The second set of questions accurately looks at the side-effects of every action +in a code base. + +Out of the six steps, this might be the most difficult one to master. It +requires the ability to examine decisions and being able to see the trade-offs +when designing software. + +## Find a Design Pattern + +If the problem has been accurately understood, the next thing to do is to find a +Design Pattern that will either solve the problem or lessen the impact of it. + +This requires knowing some design patterns or at least knowing the categories +that they fall in. When you know that the problem you are solving deals with +object instances and instantiating them effectively, it would help to look at +Creational design patterns. + +For the complete list of design patterns that this website has, view the +[homepage][home]. + +## Understand the Design Pattern + +Once you've selected the design pattern that will help you. It is well worth +your time to understand it fully. + +Each design pattern on this site has eight parts: + +1. A definition +2. List of alternative names (if any) +3. A diagram showing a [UML][uml] representation of the pattern +4. A common problem the pattern solves +5. A wrong solution +6. A correct solution using the pattern +7. Consequences of using the pattern +8. Example code + +After understanding it, if it doesn't actually solve your problem, you may have +to go to the previous step of finding one that might solve it. + +Have a look at the [Adapter Design Pattern][adapter] to see what this looks +like. + +## Look at the Example + +The example will provide a self contained use case of the pattern in action. It +will show how to structure the classes/interfaces and it will show the +functions/methods that will need to interact together. + +The example can be also used as a template for what you wish to do. The next +step will look what might happen if your problem requires a lot more effort to +solve. You may wish to just use it as a reference and make further +modifications based on your specific use case. + +## Fit the Pattern + +The problem that you have might not fit the given problem exactly. For whatever +the reason may be, it may be required to make certain changes to either your +structure or to the design pattern. + +It may be the case that you are already using another design pattern as well. +You may have to make them work together and do other things to improve the +design of your software. + +## Implement the Pattern + +The implementation of a design pattern will vary greatly depending on the +language, package layout, and other factors of the software environment. + +However, the core idea does not change. An example of this would be implementing +the Observer pattern in Rust as compared to Java. + +In Java, you could use polymorphism or an interface when creating your observer +pattern. + +In Rust, this changes a bit. Rust doesn't have inheritance or polymorphism. It +does have traits which is very similar to an interface. A Rust programmer +wouldn't be able to use polymorphism in this case. + +## Final Thoughts + +By using a design pattern, the quality of your code and software can be greatly +increased. It's important to realize the benefit that they can provide and what +problems they may solve. + +[home]: / +[what]: /rust-design-patterns/what/ +[observer]: /rust-design-patterns/observer/ +[adapter]: /rust-design-patterns/adapter/ +[uml]: http://en.wikipedia.org/wiki/Unified_Modeling_Language diff --git a/src/2013-01-01-what-is-a-design-pattern.md b/src/2013-01-01-what-is-a-design-pattern.md new file mode 100644 index 0000000..6221f14 --- /dev/null +++ b/src/2013-01-01-what-is-a-design-pattern.md @@ -0,0 +1,108 @@ +--- +layout: default +title: What is a Design Pattern? +category: info +description: Explanation of what a Design Pattern actually is. +--- + +{{ page.title }} +================ + +A design pattern is a way to organize and structure software. While the idea +isn't quite intuitive unless you have a basic understanding of building +software, let's look at it from a neutral perspective using an analogy. + +## Cathedral Analogy + +Imagine you live in the 12th century and are appointed to lead the construction +of a cathedral. Your name may or may not be [Tom Builder][pillars]. + +This might seem like a daunting task if you don't have much experience with +building cathedrals. To give you a foundation (ha, pun) so that you can start +designing it, you want to study other famous cathedrals. + +As you view more and more cathedrals and talk to various other cathedral +builders, you start to notice certain "patterns" among the great structures. For +example, you may notice that certain vaulting requires more robust support. You +might notice that certain materials work better for certain parts of the +cathedral because it is lighter or possibly sturdier than others. + +In order to help future cathedral builders, you want to compile all that you've +learned into a single volume to help other builders. You want the information +to be as general as possible such that it can help the most number of people as +they design and build. + +This is exactly what a design pattern is. The fact that it has to do with +architecture instead of software engineering doesn't matter. The goal of a +design pattern is always to provide a common solution to a certain problem when +designing something. + +## Software as the Cathedral + +This isn't the first time that software has been likened to building a +cathedral; Eric S. Raymond also used the comparison for open source software in +his seminal essay, [The Cathedral and the Bazaar][esr]. + +The idea is still the same; when designing software there are times when +programming it a certain way will bring far more benefits than the easier +approach or vice versa. + +The hard part is understanding the benefits and shortcomings of various design +patterns and recognizing when to use them. + +## Why would you use/not use a Design Pattern? + +A design pattern can improve various aspects of software engineering. Whether it +has to do with the immediate improvement of speed to develop, or the future of +the code base in ways you might not expect. + +When a design pattern isn't appropriately selected, it can have negative side +effects on software development. It's important to choose design patterns based +on the problem they are trying to solve. + +Here are a few important measurements that design patterns can greatly change. + +### Development Speed + +A design pattern can hinder the speed of development if a sophisticated design +isn't needed. + +Conversely, using a known design pattern can speed up development because it +alleviates the hassle of having to think about how all your classes will +interact and work together. + +### Readability + +Readability in programming is a measurement of how easy it is to look at a piece +(however big) of code and understand what it is doing. + +By taking software and changing it so that it fits well-known problems, it gives +people that aren't familiar with your code base (new developers, managers, +maintainers, etc.) an easier starting point and an easier time when trying to +understand the code. + +Sometimes one of the hardest parts about understanding a code base is just +understanding why things are arranged the way they are. The more you can do to +put things in a logical and well-thought out manner, the less a person reading +the code will have to do to understand it. Design patterns give a tried and tested +format as guidelines for doing this. + +### Maintainability + +Another measurement that is closely tied to readability, is a way of looking at +how easy it is to add features and fix bugs in an existing project or code base. + +While all programmers are wishful thinkers in that we hope to write code that +will solve the problem once and never be modified, that is rarely the case. + +There are various design patterns (such as the [Strategy][strategy]) + +### Simplicity + +There is no denying that a simple approach can sometimes be the best for the +given project. By using a design pattern, you risk adding more classes and +obfuscating the code. + +[strategy]: / +[pillars]: http://en.wikipedia.org/wiki/The_Pillars_of_the_Earth +[esr]: http://www.catb.org/~esr/writings/cathedral-bazaar/cathedral-bazaar/ diff --git a/src/2013-06-11-abstract-factory.md b/src/2013-06-11-abstract-factory.md new file mode 100644 index 0000000..9cdde83 --- /dev/null +++ b/src/2013-06-11-abstract-factory.md @@ -0,0 +1,206 @@ +--- +layout: default +title: Abstract Factory +category: creational +description: Abstract factory design pattern in the Rust programming language with example. +css: + - /css/code.css + - /lib/lightbox/css/lightbox.css +js: + - //ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js + - /lib/lightbox/js/lightbox.js +--- + +{{ page.title }} +================ + +Also Known As: **Kit** + +## Definition + +Provide an interface for creating families of related or dependent objects +without specifying their concrete classes. + +## Diagram + + + +## Problem + +There are times when one is programming on a project and there are a group of +objects that have something in common. Sometimes the project will also have a +set of these groups and need to use them interchangeably. + +The problem is when we want to use these groups of objects interchangeably yet +creating the objects becomes a hassle. + +## Wrong Solution + +One way to handle this is to just make multiple `if` statements in the code for +when objects are created. If the system is in this state, create this object of +group A, if the system is in this state, create this object of group B and so +on. + +## Correct Solution + +The right way to take care of this is to use the Abstract Factory pattern. +Instead of making giant `if` statements, we want to program to an interface +instead and use the power of object-oriented programming. + +## Example + +Apple and Google both make tablets and phones. Each company has a different way +of creating their products and obviously have different factories where they +create them. + +The thing is that a website that sells Apple and Google devices doesn't care +about how the factory makes the products. Instead it cares about how to ask the +factory for a phone/tablet when it needs to. All it cares about is that each +factory works the same way. In programmer terminology, each factory has the same +*interface*. + +### Example Diagram + + + +### Example Code + +View [abstract_factory.rs][github] on GitHub + +{% highlight rust %} + +/* + * Abstract Factory Design Pattern + * http://joshldavis.com/rust-design-patterns/abstract-factory/ + */ + +/* + * Core Trait that defines a Phone + */ +trait Phone { + fn call(&self); +} + +/* + * Core Trait that defines a Tablet + */ +trait Tablet { + fn play_games(&self); +} + +/* + * Core Trait that defines a Factory + */ +trait Factory { + fn new_phone(&self) -> P; + fn new_tablet(&self) -> T; +} + + +/* + * Define our Apple products. Normally these structs would contain a lot more + * data. + */ +struct iPhone; + +impl Phone for iPhone { + fn call(&self) { + println("Look! I'm calling on an iPhone!"); + } +} + +struct iPad; + +impl Tablet for iPad { + fn play_games(&self) { + println("Just playing some games on my iPad."); + } +} + +/* + * Create AppleFactory and implement it for our Apple devices + */ +struct AppleFactory; + +impl Factory for AppleFactory { + fn new_phone(&self) -> iPhone { + return iPhone; + } + + fn new_tablet(&self) -> iPad { + return iPad; + } +} + +/* + * Define our Google products. Like with Apple's products, these are + * simplified. + */ + +struct Nexus4; + +impl Phone for Nexus4 { + fn call(&self) { + println("Look! I'm calling on a Nexus 4!"); + } +} + +struct Nexus10; + +impl Tablet for Nexus10 { + fn play_games(&self) { + println("Just playing some games on my Nexus 10."); + } +} + +/* + * Create GoogleFactory and implement it for our Google devices + */ +struct GoogleFactory; + +impl Factory for GoogleFactory { + fn new_phone(&self) -> Nexus4 { + return Nexus4; + } + + fn new_tablet(&self) -> Nexus10 { + return Nexus10; + } +} + + +fn main() { + // Create our two different factories + let apple = AppleFactory; + let google = GoogleFactory; + + // Both factories use the same interface, so let's just use them + + // Test out creating phones + let phone = apple.new_phone(); + phone.call(); + + let phone = google.new_phone(); + phone.call(); + + // Test out creating tablets + let tablet = apple.new_tablet(); + tablet.play_games(); + + let tablet = google.new_tablet(); + tablet.play_games(); +} + +{% endhighlight %} + +[github]: https://github.com/jdavis/rust-design-patterns/blob/master/patterns/abstract_factory.rs diff --git a/src/2013-06-11-adapter.md b/src/2013-06-11-adapter.md new file mode 100644 index 0000000..3e3d82f --- /dev/null +++ b/src/2013-06-11-adapter.md @@ -0,0 +1,245 @@ +--- +layout: default +title: Adapter +category: structural +description: The Adapter design pattern in the Rust programming language with example. +css: + - /css/code.css + - /lib/lightbox/css/lightbox.css +js: + - //ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js + - /lib/lightbox/js/lightbox.js +--- + +{{ page.title }} +================ + +Also Known As: **Wrapper** + +## Definition + +Convert the interface of a class into another interface clients expect. +Adapter lets classes work together that couldn't otherwise because of +incompatible interfaces. + +## Diagram + + + +## Problem + +Often systems of code need to interact. If this is designed from the start then +there is no problem. One system defines an abstraction and the other system +adds implementation for that interface. + +The problem arises when the system that uses the abstraction changes its +requirements. If the implementation changes, then the interface is now broken. + +## Wrong Solution + +One way to fix this is to just rewrite the system the interface to still match +the new implementation. This is *can* work in some cases but depending on the +size of the system it might not be feasible. + +## Correct Solution + +The correct solution using the Adapter pattern would be to just make a middle +layer that **adapts** the interface the first system expects into the new +implementation. + +This mitigates the need to rewrite both systems yet allows flexibility in case +systems continue to change. + +## Example + +Back in the 60s there were tons of Astronatus working for NASA. At the +time, all they needed to learn was how to fly NASA's ships. When ever NASA +came out with a new ship, it was similar to the olds ones. This worked for +decades. All was well in the galaxy. + +However in 2010, NASA shut down the Space Shuttle program. The Astronauts +were out of jobs and started to look for new ones. Once the Astronauts realized +that their space suits made for poor work attire on Earth, they knew they had to +revisit the stars. So they went to the next best company to work for, SpaceX. +When they got to SpaceX they realized that technology had changed quite a bit +since the 60s. SpaceX realized this as well, so to compensate for the Astronauts +they came up with an idea to use the Adapter pattern. + +The smart SpaceX engineers made a new class that just 'Adapted' the old +interface of the NASA Space Shuttles to SpaceX's new rocket ships. The +Astronauts now had no problem flying SpaceX's ships through the galaxy. + +Together the Astronauts and SpaceX were able to settle the galaxy far +and wide. + +### Example Diagram + + + +### Example Code + +View [adapter.rs][github] on GitHub + +{% highlight rust %} + +/* + * Core Trait that defines a basic Rocket Ship + */ +trait RocketShip { + fn turn_on(&self); + fn turn_off(&self); + fn blast_off(&self); + fn fly(&self); +} + +/* + * Basic struct for a NASA Ship + */ +struct NASAShip; + +/* + * Implement RocketShip trait to add functionality to NASAShip + */ +impl RocketShip for NASAShip { + fn turn_on(&self) { + println("NASA Ship is turning on.") + } + + fn turn_off(&self) { + println("NASA Ship is turning off.") + } + + fn blast_off(&self) { + println("NASA Ship is blasting off.") + } + + fn fly(&self) { + println("NASA Ship is flying away.") + } +} + +/* + * Uh oh, here is our problem. It's the amazingly advanced SpaceX ship that our + * astronaut doesn't know how to pilot. + */ +trait SpaceXShip { + fn ignition(&self); + fn on(&self); + fn off(&self); + fn launch(&self); + fn fly(&self); +} + +/* + * Basic struct for a SpaceX Dragon rocket ship + */ +struct SpaceXDragon; + +/* + * Implement the SpaceX trait to add functionality to the Space X Dragon + */ +impl SpaceXShip for SpaceXDragon { + fn ignition(&self) { + println("Turning Dragon's ignition.") + } + + fn on(&self) { + println("Turning on the Dragon.") + } + + fn off(&self) { + println("Turing off the Dragon.") + } + + fn launch(&self) { + println("Launching the Dragon") + } + + fn fly(&self) { + println("The Dragon is flying away.") + } +} + +/* + * Uh oh, the new SpaceXDragon doesn't implement the RocketShip interface. We + * need to create an adapter that does. + */ + +/* + * Adapter to adapt anything that implements SpaceXShip to the RocketShip trait + */ +struct SpaceXAdapter { + ship: SpaceXDragon +} + +/* + * SpaceX Adapter that adds RocketShip traits to any SpaceXShip + */ +impl RocketShip for SpaceXAdapter { + fn turn_on(&self) { + self.ship.ignition(); + self.ship.on(); + } + + fn turn_off(&self) { + self.ship.off(); + } + + fn blast_off(&self) { + self.ship.launch(); + } + + fn fly(&self) { + self.ship.fly(); + } +} + +/* + * Basic function to pilot ships that implement the RocketShip trait + */ +fn pilot(ship: &S) { + ship.turn_on(); + ship.blast_off(); + ship.fly(); + ship.turn_off(); + print("\n"); +} + +fn main() { + // Create a new NASAShip + let saturn5 = NASAShip; + + // Let's fly our NASAShip + println("Piloting the Saturn 5."); + pilot(&saturn5); + + // Create a Dragon + let dragon = SpaceXDragon; + + // Uh oh, our pilot function doesn't recognize this ship... + // pilot(&dragon); <-- Gives a compile time error. + + // Let's Adapt our SpaceXDragon ship + let dragon_adapter = SpaceXAdapter { + ship: dragon + }; + + // Now we can pilot the Dragon! + println("Piloting the Dragon Adapter."); + pilot(&dragon_adapter); +} + +{% endhighlight %} + +[github]: https://github.com/jdavis/rust-design-patterns/blob/master/patterns/adapter.rs diff --git a/src/2013-06-11-chain-of-responsibility.md b/src/2013-06-11-chain-of-responsibility.md new file mode 100644 index 0000000..e563b6b --- /dev/null +++ b/src/2013-06-11-chain-of-responsibility.md @@ -0,0 +1,63 @@ +--- +layout: default +title: Chain of Responsibility +category: behavioral +description: The Chain of Responsibility design pattern in the Rust programming language with an example. +css: + - /css/code.css + - /lib/lightbox/css/lightbox.css +js: + - //ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js + - /lib/lightbox/js/lightbox.js +--- + +{{ page.title }} +================ + +## Definition + +Avoid coupling the sender of a request to its receiver by giving more than one +object a chance to handle the request. Chain the receiving objects and pass the +request along the chain until an object handles it. + +## Diagram + + + +## Problem + +TODO + +## Wrong Solution + +TODO + +## Correct Solution + +TODO + +## Example + +TODO + +### Example Diagram + +TODO + +### Example Code + +View [chain_of_responsibility.rs][github] on GitHub + +{% highlight rust %} + +// TODO + +{% endhighlight %} + +[github]: https://github.com/jdavis/rust-design-patterns/blob/master/patterns/chain_of_responsibility.rs diff --git a/src/SUMMARY.md b/src/SUMMARY.md new file mode 100644 index 0000000..7390c82 --- /dev/null +++ b/src/SUMMARY.md @@ -0,0 +1,3 @@ +# Summary + +- [Chapter 1](./chapter_1.md) diff --git a/src/additional_resources/design-principles.md b/src/additional_resources/design-principles.md new file mode 100644 index 0000000..b764d36 --- /dev/null +++ b/src/additional_resources/design-principles.md @@ -0,0 +1,95 @@ +# Design principles + +## A brief overview over common design principles + +--- + +## [SOLID](https://en.wikipedia.org/wiki/SOLID) + +- [Single Responsibility Principle (SRP)](https://en.wikipedia.org/wiki/Single-responsibility_principle): + A class should only have a single responsibility, that is, only changes to + one part of the software's specification should be able to affect the + specification of the class. +- [Open/Closed Principle (OCP)](https://en.wikipedia.org/wiki/Open%E2%80%93closed_principle): + "Software entities ... should be open for extension, but closed for + modification." +- [Liskov Substitution Principle (LSP)](https://en.wikipedia.org/wiki/Liskov_substitution_principle): + "Objects in a program should be replaceable with instances of their subtypes + without altering the correctness of that program." +- [Interface Segregation Principle (ISP)](https://en.wikipedia.org/wiki/Interface_segregation_principle): + "Many client-specific interfaces are better than one general-purpose + interface." +- [Dependency Inversion Principle (DIP)](https://en.wikipedia.org/wiki/Dependency_inversion_principle): + One should "depend upon abstractions, [not] concretions." + +## [DRY (Don’t Repeat Yourself)](https://en.wikipedia.org/wiki/Don%27t_repeat_yourself) + +"Every piece of knowledge must have a single, unambiguous, authoritative +representation within a system" + +## [KISS principle](https://en.wikipedia.org/wiki/KISS_principle) + +most systems work best if they are kept simple rather than made complicated; +therefore, simplicity should be a key goal in design, and unnecessary +complexity should be avoided + +## [Law of Demeter (LoD)](https://en.wikipedia.org/wiki/Law_of_Demeter) + +a given object should assume as little as possible about the structure or +properties of anything else (including its subcomponents), in accordance with +the principle of "information hiding" + +## [Design by contract (DbC)](https://en.wikipedia.org/wiki/Design_by_contract) + +software designers should define formal, precise and verifiable interface +specifications for software components, which extend the ordinary definition of +abstract data types with preconditions, postconditions and invariants + +## [Encapsulation](https://en.wikipedia.org/wiki/Encapsulation_(computer_programming)) + +bundling of data with the methods that operate on that data, or the restricting +of direct access to some of an object's components. Encapsulation is used to +hide the values or state of a structured data object inside a class, preventing +unauthorized parties' direct access to them. + +## [Command-Query-Separation(CQS)](https://en.wikipedia.org/wiki/Command%E2%80%93query_separation) + +“Functions should not produce abstract side effects...only commands +(procedures) will be permitted to produce side effects.” - Bertrand Meyer: +Object Oriented Software Construction + +## [Principle of least astonishment (POLA)](https://en.wikipedia.org/wiki/Principle_of_least_astonishment) + +a component of a system should behave in a way that most users will expect it +to behave. The behavior should not astonish or surprise users + +## Linguistic-Modular-Units + +“Modules must correspond to syntactic units in the language used.” - Bertrand +Meyer: Object Oriented Software Construction + +## Self-Documentation + +“The designer of a module should strive to make all information about the +module part of the module itself.” - Bertrand Meyer: Object Oriented Software +Construction + +## Uniform-Access + +“All services offered by a module should be available through a uniform +notation, which does not betray whether they are implemented through storage or +through computation.” - Bertrand Meyer: Object Oriented Software Construction + +## Single-Choice + +“Whenever a software system must support a set of alternatives, one and only +one module in the system should know their exhaustive list.” - Bertrand Meyer: +Object Oriented Software Construction + +## Persistence-Closure + +“Whenever a storage mechanism stores an object, it must store with it the +dependents of that object. Whenever a retrieval mechanism retrieves a +previously stored object, it must also retrieve any dependent of that object +that has not yet been retrieved.” - Bertrand Meyer: Object Oriented Software +Construction diff --git a/src/additional_resources/index.md b/src/additional_resources/index.md new file mode 100644 index 0000000..2902293 --- /dev/null +++ b/src/additional_resources/index.md @@ -0,0 +1,16 @@ +# Additional resources + +A collection of complementary helpful content + +## Talks + +- [Design Patterns in Rust](https://www.youtube.com/watch?v=Pm_oO0N5B9k) by + Nicholas Cameron at the PDRust (2016) +- [Writing Idiomatic Libraries in Rust](https://www.youtube.com/watch?v=0zOg8_B71gE) + by Pascal Hertleif at RustFest (2017) +- [Rust Programming Techniques](https://www.youtube.com/watch?v=vqavdUGKeb4) by + Nicholas Cameron at LinuxConfAu (2018) + +## Books (Online) + +- [The Rust API Guidelines](https://rust-lang.github.io/api-guidelines) diff --git a/src/anti_patterns/deny-warnings.md b/src/anti_patterns/deny-warnings.md new file mode 100644 index 0000000..c4efd72 --- /dev/null +++ b/src/anti_patterns/deny-warnings.md @@ -0,0 +1,108 @@ +# `#![deny(warnings)]` + +## Description + +A well-intentioned crate author wants to ensure their code builds without +warnings. So they annotate their crate root with the following: + +## Example + +```rust +#![deny(warnings)] + +// All is well. +``` + +## Advantages + +It is short and will stop the build if anything is amiss. + +## Drawbacks + +By disallowing the compiler to build with warnings, a crate author opts out of +Rust's famed stability. Sometimes new features or old misfeatures need a change +in how things are done, thus lints are written that `warn` for a certain grace +period before being turned to `deny`. + +For example, it was discovered that a type could have two `impl`s with the same +method. This was deemed a bad idea, but in order to make the transition smooth, +the `overlapping-inherent-impls` lint was introduced to give a warning to those +stumbling on this fact, before it becomes a hard error in a future release. + +Also sometimes APIs get deprecated, so their use will emit a warning where +before there was none. + +All this conspires to potentially break the build whenever something changes. + +Furthermore, crates that supply additional lints (e.g. [rust-clippy]) can no +longer be used unless the annotation is removed. This is mitigated with +[--cap-lints]. The `--cap-lints=warn` command line argument, turns all `deny` +lint errors into warnings. But be aware that `forbid` lints are stronger than +`deny` hence the 'forbid' level cannot be overridden to be anything lower than +an error. As a result `forbid` lints will still stop compilation. + +## Alternatives + +There are two ways of tackling this problem: First, we can decouple the build +setting from the code, and second, we can name the lints we want to deny +explicitly. + +The following command line will build with all warnings set to `deny`: + +```RUSTFLAGS="-D warnings" cargo build``` + +This can be done by any individual developer (or be set in a CI tool like +Travis, but remember that this may break the build when something changes) +without requiring a change to the code. + +Alternatively, we can specify the lints that we want to `deny` in the code. +Here is a list of warning lints that is (hopefully) safe to deny (as of Rustc 1.48.0): + +```rust,ignore +#[deny(bad-style, + const-err, + dead-code, + improper-ctypes, + non-shorthand-field-patterns, + no-mangle-generic-items, + overflowing-literals, + path-statements , + patterns-in-fns-without-body, + private-in-public, + unconditional-recursion, + unused, + unused-allocation, + unused-comparisons, + unused-parens, + while-true)] +``` + +In addition, the following `allow`ed lints may be a good idea to `deny`: + +```rust,ignore +#[deny(missing-debug-implementations, + missing-docs, + trivial-casts, + trivial-numeric-casts, + unused-extern-crates, + unused-import-braces, + unused-qualifications, + unused-results)] +``` + +Some may also want to add `missing-copy-implementations` to their list. + +Note that we explicitly did not add the `deprecated` lint, as it is fairly +certain that there will be more deprecated APIs in the future. + +## See also + +- [A collection of all clippy lints](https://rust-lang.github.io/rust-clippy/master) +- [deprecate attribute] documentation +- Type `rustc -W help` for a list of lints on your system. Also type +`rustc --help` for a general list of options +- [rust-clippy] is a collection of lints for better Rust code + +[rust-clippy]: https://github.com/Manishearth/rust-clippy +[deprecate attribute]: https://doc.rust-lang.org/reference/attributes.html#deprecation +[--cap-lints]: https://doc.rust-lang.org/rustc/lints/levels.html#capping-lints diff --git a/src/anti_patterns/deref.md b/src/anti_patterns/deref.md new file mode 100644 index 0000000..3c5d8a7 --- /dev/null +++ b/src/anti_patterns/deref.md @@ -0,0 +1,129 @@ +# `Deref` polymorphism + +## Description + +Abuse the `Deref` trait to emulate inheritance between structs, and thus reuse +methods. + +## Example + +Sometimes we want to emulate the following common pattern from OO languages such +as Java: + +```java +class Foo { + void m() { ... } +} + +class Bar extends Foo {} + +public static void main(String[] args) { + Bar b = new Bar(); + b.m(); +} +``` + +We can use the deref polymorphism anti-pattern to do so: + +```rust,ignore +use std::ops::Deref; + +struct Foo {} + +impl Foo { + fn m(&self) { + //.. + } + +} + +struct Bar { + f: Foo +} + +impl Deref for Bar { + type Target = Foo; + fn deref(&self) -> &Foo { + &self.f + } +} + +fn main() { + let b = Bar { Foo {} }; + b.m(); +} +``` + +There is no struct inheritance in Rust. Instead we use composition and include +an instance of `Foo` in `Bar` (since the field is a value, it is stored inline, +so if there were fields, they would have the same layout in memory as the Java +version (probably, you should use `#[repr(C)]` if you want to be sure)). + +In order to make the method call work we implement `Deref` for `Bar` with `Foo` +as the target (returning the embedded `Foo` field). That means that when we +dereference a `Bar` (for example, using `*`) then we will get a `Foo`. That is +pretty weird. Dereferencing usually gives a `T` from a reference to `T`, here we +have two unrelated types. However, since the dot operator does implicit +dereferencing, it means that the method call will search for methods on `Foo` as +well as `Bar`. + +## Advantages + +You save a little boilerplate, e.g., + +```rust,ignore +impl Bar { + fn m(&self) { + self.f.m() + } +} +``` + +## Disadvantages + +Most importantly this is a surprising idiom - future programmers reading this in +code will not expect this to happen. That's because we are abusing the `Deref` +trait rather than using it as intended (and documented, etc.). It's also because +the mechanism here is completely implicit. + +This pattern does not introduce subtyping between `Foo` and `Bar` like +inheritance in Java or C++ does. Furthermore, traits implemented by `Foo` are +not automatically implemented for `Bar`, so this pattern interacts badly with +bounds checking and thus generic programming. + +Using this pattern gives subtly different semantics from most OO languages with +regards to `self`. Usually it remains a reference to the sub-class, with this +pattern it will be the 'class' where the method is defined. + +Finally, this pattern only supports single inheritance, and has no notion of +interfaces, class-based privacy, or other inheritance-related features. So, it +gives an experience that will be subtly surprising to programmers used to Java +inheritance, etc. + +## Discussion + +There is no one good alternative. Depending on the exact circumstances it might +be better to re-implement using traits or to write out the facade methods to +dispatch to `Foo` manually. We do intend to add a mechanism for inheritance +similar to this to Rust, but it is likely to be some time before it reaches +stable Rust. See these [blog](http://aturon.github.io/blog/2015/09/18/reuse/) +[posts](http://smallcultfollowing.com/babysteps/blog/2015/10/08/virtual-structs-part-4-extended-enums-and-thin-traits/) +and this [RFC issue](https://github.com/rust-lang/rfcs/issues/349) for more details. + +The `Deref` trait is designed for the implementation of custom pointer types. +The intention is that it will take a pointer-to-`T` to a `T`, not convert +between different types. It is a shame that this isn't (probably cannot be) +enforced by the trait definition. + +Rust tries to strike a careful balance between explicit and implicit mechanisms, +favouring explicit conversions between types. Automatic dereferencing in the dot +operator is a case where the ergonomics strongly favour an implicit mechanism, +but the intention is that this is limited to degrees of indirection, not +conversion between arbitrary types. + +## See also + +- [Collections are smart pointers idiom](../idioms/deref.md). +- Delegation crates for less boilerplate like [delegate](https://crates.io/crates/delegate) + or [ambassador](https://crates.io/crates/ambassador) +- [Documentation for `Deref` trait](https://doc.rust-lang.org/std/ops/trait.Deref.html). diff --git a/src/anti_patterns/index.md b/src/anti_patterns/index.md new file mode 100644 index 0000000..84107e0 --- /dev/null +++ b/src/anti_patterns/index.md @@ -0,0 +1,8 @@ +# Anti-patterns + +An [anti-pattern](https://en.wikipedia.org/wiki/Anti-pattern) is a solution to +a "recurring problem that is usually ineffective and risks being highly +counterproductive". Just as valuable as knowing how to solve a problem, is +knowing how _not_ to solve it. Anti-patterns give us great counter-examples to +consider relative to design patterns. Anti-patterns are not confined to code. +For example, a process can be an anti-pattern, too. diff --git a/src/chapter_1.md b/src/chapter_1.md new file mode 100644 index 0000000..b743fda --- /dev/null +++ b/src/chapter_1.md @@ -0,0 +1 @@ +# Chapter 1 diff --git a/src/event_bus.rs b/src/event_bus.rs deleted file mode 100644 index c9b358d..0000000 --- a/src/event_bus.rs +++ /dev/null @@ -1,76 +0,0 @@ -// use serde::{Deserialize, Serialize}; -// use std::collections::HashSet; -// use yew::worker::*; - -// #[derive(Serialize, Deserialize, Debug)] -// pub enum Request { -// EventBusMsg(String), -// } - -// pub struct EventBus { -// link: AgentLink, -// subscribers: HashSet, -// } - -// impl Agent for EventBus { -// type Reach = Context; -// type Message = (); -// type Input = Request; -// type Output = String; - -// fn create(link: AgentLink) -> Self { -// Self { -// link, -// subscribers: HashSet::new(), -// } -// } - -// fn update(&mut self, _msg: Self::Message) {} - -// fn handle_input(&mut self, msg: Self::Input, _id: HandlerId) { -// match msg { -// Request::EventBusMsg(s) => { -// for sub in self.subscribers.iter() { -// self.link.respond(*sub, s.clone()); -// } -// } -// } -// } - -// fn connected(&mut self, id: HandlerId) { -// self.subscribers.insert(id); -// } - -// fn disconnected(&mut self, id: HandlerId) { -// self.subscribers.remove(&id); -// } -// } - - -// pub struct Model; - -// impl Component for Model { -// type Message = (); -// type Properties = (); - -// fn create(_props: Self::Properties, _link: ComponentLink) -> Self { -// Self -// } - -// fn change(&mut self, _msg: Self::Properties) -> ShouldRender { -// false -// } - -// fn update(&mut self, _props: Self::Message) -> ShouldRender { -// unimplemented!() -// } - -// fn view(&self) -> Html { -// html! { -// <> -// -// -// -// } -// } -// } \ No newline at end of file diff --git a/src/foundation/mod.rs b/src/foundation/mod.rs deleted file mode 100644 index 54b20ad..0000000 --- a/src/foundation/mod.rs +++ /dev/null @@ -1,72 +0,0 @@ -//! PureMVC and other design patterns -//! -//! ## Model & Proxies -//! -//! The [Model][2] simply caches named references to Proxies. Proxy code -//! manipulates the data model, communicating with remote services if -//! need be to persist or retrieve it. -//! -//! This results in portable Model tier code. -//! -//! ## View & Mediators -//! -//! The View primarily caches named references to [Mediators][7]. [Mediator][7] -//! code stewards View Components, adding event listeners, sending -//! and receiving notifications to and from the rest of the system on -//! their behalf and directly manipulating their state. -//! -//! This separates the View definition from the logic that controls it. -//! -//! ## Controller & Commands -//! -//! The [Controller][1] maintains named mappings to Command classes, -//! which are stateless, and only created when needed. -//! -//! [Commands][9] may retrieve and interact with Proxies, send -//! Notifications, execute other [Commands][9], and are often used to -//! orchestrate complex or system-wide activities such as application -//! startup and shutdown. They are the home of your application’s -//! Business Logic. -//! -//! ## Facade & Core -//! -//! The [Facade][4], another Singleton, initializes the Core actors ([Model][2], -//! [View][3] and [Controller][1]), and provides a single place to access all of -//! their public methods. -//! -//! By extending the [Facade][4], your application gets all the benefits of -//! Core actors without having to import and work with them directly. -//! You will implement a concrete [Facade][4] for your application only once -//! and it is simply done. -//! -//! [Proxies][6], [Mediators][7] and [Commands][9] may then use your application’s -//! concrete [Facade][4] in order to access and communicate with each -//! other. -//! -//! ## Observers & Notifications -//! -//! PureMVC applications may run in environments without access to -//! Event and EventDispatcher classes, so the framework -//! implements an [Observer][8] notification scheme for communication -//! between the Core MVC actors and other parts of the system in a -//! loosely-coupled way. -//! -//! You need not be concerned about the details of the PureMVC -//! [Observer][8]/[Notification][5] implementation; it is internal to the -//! framework. You will use a simple method to send [Notifications][5] from -//! [Proxies][6], [Mediators][7], [Commands][9] and the Facade itself that doesn’t -//! even require you to create a [Notification][5] instance. -//! -//! What next: [Catalog of patterns..][patterns] -//! -//! [1]: crate::prelude::Controller -//! [2]: crate::prelude::Model -//! [3]: crate::prelude::View -//! [4]: crate::prelude::Facade -//! [5]: crate::prelude::Notification -//! [6]: crate::prelude::Proxy -//! [7]: crate::prelude::Mediator -//! [8]: crate::prelude::Observer -//! [9]: crate::prelude::Command -//! -pub mod patterns; \ No newline at end of file diff --git a/src/foundation/patterns/builder.rs b/src/foundation/patterns/builder.rs deleted file mode 100644 index 0123dd3..0000000 --- a/src/foundation/patterns/builder.rs +++ /dev/null @@ -1,90 +0,0 @@ -//! Builder Design Pattern -//! -//! Builder pattern builds a complex object using simple objects and using a step by step approach. -//! This type of design pattern comes under creational pattern as this pattern provides one of the best ways to create an object. -//! -//! It is used to construct a complex object step by step and the final step will return the object. -//! The process of constructing an object should be generic so that it can be used to -//! create different representations of the same object. - -use std::cell::RefCell; - -use crate::prelude::{Getter, SetterMut, With, WithBuilder}; - -/// Promote the Getter trait. -impl Getter for Target -where - From: AsRef, -{ - fn get(from: &From) -> &Target { - from.as_ref() - } -} - -/// Builder pattern implementation. -pub struct Builder { - /// Contains the builder context - pub inner: RefCell>, -} - -impl Default for Builder { - fn default() -> Self { - Self { - inner: RefCell::new(Some(Default::default())), - } - } -} - -impl Builder { - /// Finally creates the entity - pub fn build(&self) -> Option { - self.inner.borrow_mut().take() - } -} - -/// Promote With trait for all objects which implement Setter trait. -/// -/// Current version of Rust is not supported trait exclusion from trait bounds. -/// -/// So if you need IMP'ish version of With, you should to implement it yourself -/// something like a: -/// `impl With for Builder<Window>` -/// -impl<Param, Target> With<Param> for Builder<Target> -where - Target: Default + SetterMut<Param>, -{ - fn with(self, param: Param) -> Self { - self.inner - .borrow_mut() - .as_mut() - .map(|val| SetterMut::<Param>::set(val, param)); - self - } -} - -/// Promote the WithBuilder trait for all builders which able -/// to configure specific parameter types. -impl<Target, Param> WithBuilder<Param> for Target -where - Builder<Target>: With<Param>, - Target: Default, -{ - fn with(param: Param) -> Builder<Self> { - Builder::<Self>::default().with(param) - } -} - -// /// Simple method to generate builder of object. -// pub trait Construction<T> { -// fn construct() -> Builder<T>; -// } - -// impl<T> Construction<T> for T -// where -// T: Default + Clone, -// { -// fn construct() -> Builder<T> { -// Builder::<T>::default() -// } -// } diff --git a/src/foundation/patterns/command/macro_command.rs b/src/foundation/patterns/command/macro_command.rs deleted file mode 100644 index e8c44fc..0000000 --- a/src/foundation/patterns/command/macro_command.rs +++ /dev/null @@ -1,89 +0,0 @@ -use std::{cell::RefCell, fmt, rc::Rc}; - -use crate::{ - foundation::patterns::facade::BaseFacade, - prelude::{Command, Facade, Interest, Notification, Notifier, Singleton}, -}; - -/// A base [Command] implementation that executes other _Commands_. -/// -/// A [MacroCommand] maintains an list of -/// [Command] Class references called _SubCommands_. -/// -/// When [execute](Command::execute) is called, the [MacroCommand] -/// instantiates and calls [execute](Command::execute) on each of its _SubCommands_ turn. -/// Each _SubCommand_ will be passed a reference to the original -/// [Notification] that was passed to the [MacroCommand]'s -/// [execute](Command::execute) method. - -pub struct MacroCommand<Body> -where - Body: fmt::Debug + 'static, -{ - sub_commands: RefCell<Vec<Box<dyn Command<Body>>>>, -} - -impl<Body> MacroCommand<Body> -where - Body: fmt::Debug + 'static, -{ - /// Constructor. - /// - pub fn new() -> Self { - Self { - sub_commands: RefCell::new(Vec::new()), - } - } - - /// Add a `SubCommand`. - /// - /// The `SubCommands` will be called in First In/First Out (FIFO) - /// order. - /// - /// Note that `SubCommand`s may be any [Command] implementor, - /// [MacroCommand]'s or [SimpleCommand]'s are both acceptable. - /// - /// [SimpleCommand]: super::SimpleCommand - pub fn add_sub_command(&mut self, command: Box<dyn Command<Body>>) { - let mut sub_commands = self.sub_commands.borrow_mut(); - - sub_commands.push(command); - } -} - -impl<Body> Command<Body> for MacroCommand<Body> -where - Body: fmt::Debug + 'static, -{ - /// Execute this [MacroCommand]'s `SubCommands`. - /// - /// The `SubCommands` will be called in First In/First Out (FIFO) - /// order. - fn execute(&self, notification: Rc<dyn Notification<Body>>) { - let mut sub_commands = self.sub_commands.borrow_mut(); - while let Some(command) = sub_commands.pop() { - command.execute(notification.clone()); - } - } -} - -impl<Body> Notifier<Body> for MacroCommand<Body> -where - Body: fmt::Debug + 'static, -{ - fn send(&self, interest: Interest, body: Option<Body>) { - log::error!("You should implement yourself MacroCommand"); - BaseFacade::<Body>::global().send(interest, body); - } -} - -impl<Body> fmt::Debug for MacroCommand<Body> -where - Body: fmt::Debug + 'static, -{ - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("MacroCommand") - .field("sub_commands", &self.sub_commands) - .finish() - } -} diff --git a/src/foundation/patterns/command/mod.rs b/src/foundation/patterns/command/mod.rs deleted file mode 100644 index 4e0d3b7..0000000 --- a/src/foundation/patterns/command/mod.rs +++ /dev/null @@ -1,40 +0,0 @@ -//! Commands -//! -//! The concrete Facade generally initializes the Controller with the set of -//! Notification to Command mappings needed at startup. -//! -//! For each mapping, the Controller registers itself as an Observer for the -//! given Notification. When notified, the Controller instantiates the -//! appropriate Command. Finally, the Controller calls the Command’s -//! execute method, passing in the Notification. -//! -//! Commands are stateless; they are created when needed and are -//! intended to go away when they have been executed. For this reason, it -//! is important not to instantiate or store references to Commands in -//! long-living objects. -//! -//! ## Use of Macro and Simple Commands -//! -//! Commands, like all PureMVC framework classes, implement an -//! interface, namely ICommand. PureMVC includes two ICommand -//! implementations that you may easily extend. -//! -//! The SimpleCommand class merely has an execute method which -//! accepts an INotification instance. Insert your code in the execute -//! method and that’s it. -//! -//! The MacroCommand class allows you to execute multiple sub- -//! commands sequentially, each being created and passed a reference -//! to the original Notification. -//! -//! MacroCommand calls its initializeMacroCommand method from -//! within its constructor. You override this method in your subclasses -//! to call the addSubCommand method once for each Command to be -//! added. You may add any combination of SimpleCommands or -//! MacroCommands. - -mod macro_command; -pub use self::macro_command::*; - -mod simple_command; -pub use self::simple_command::*; diff --git a/src/foundation/patterns/command/simple_command.rs b/src/foundation/patterns/command/simple_command.rs deleted file mode 100644 index 5ec0e0e..0000000 --- a/src/foundation/patterns/command/simple_command.rs +++ /dev/null @@ -1,39 +0,0 @@ -use std::{fmt, rc::Rc}; - -use crate::{ - foundation::patterns::facade::BaseFacade, - prelude::{Command, Facade, Interest, Notification, Notifier, Singleton}, -}; - -/// A reference [Command] implementation. -pub struct SimpleCommand {} - -impl SimpleCommand {} - -impl<Body> Command<Body> for SimpleCommand -where - Body: fmt::Debug + 'static, -{ - /// Fulfill the use-case initiated by the given [Notification]. - /// - /// In the Command Pattern, an application use-case typically - /// begins with some user action, which results in an [Notification] being broadcast, which - /// is handled by business logic in the [execute](Command::execute) method of an [Command]. - fn execute(&self, _notification: Rc<dyn Notification<Body>>) {} -} - -impl<Body> Notifier<Body> for SimpleCommand -where - Body: fmt::Debug + 'static, -{ - fn send(&self, interest: Interest, body: Option<Body>) { - log::error!("You should implement yourself SimpleCommand"); - BaseFacade::<Body>::global().send(interest, body); - } -} - -impl fmt::Debug for SimpleCommand { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("SimpleCommand").finish() - } -} diff --git a/src/foundation/patterns/default/controller.rs b/src/foundation/patterns/default/controller.rs deleted file mode 100644 index 54d7af8..0000000 --- a/src/foundation/patterns/default/controller.rs +++ /dev/null @@ -1,161 +0,0 @@ -use std::{cell::RefCell, collections::HashMap, fmt, rc::Rc}; - -use crate::prelude::*; - -/// A Singleton [Controller] implementation. -/// -/// In PureMVC, the [Controller] class follows the -/// 'Command and Controller' strategy, and assumes these responsibilities: -/// -/// - Remembering which [Command]'s are intended to handle which [Notification]'s. -/// - Registering itself as an [Observer] with the [View] for each [Notification] -/// that it has an [Command] mapping for. -/// - Creating a new instance of the proper [Command] to handle a given [Notification] -/// when notified by the [View]. -/// - Calling the [Command]'s [execute] method, passing in the [Notification]. -/// -/// -/// Your application must register [Command]'s with the [Controller]. -/// The simplest way is to subclass [Facade], -/// and use its [register_command] method to add your registrations. -/// -/// [execute]: Command::execute -/// [register_command]: Controller::register_command - -pub struct BaseController<Body> -where - Body: fmt::Debug + 'static, -{ - // Mapping of Notification names to Command Class references - // Mayby use IntMap for performance - command_map: RefCell<HashMap<Interest, Rc<dyn Command<Body>>>>, - - notify_context: Rc<BaseNotifyContext>, -} - -unsafe impl<Body> std::marker::Send for BaseController<Body> where Body: fmt::Debug + 'static {} -unsafe impl<Body> std::marker::Sync for BaseController<Body> where Body: fmt::Debug + 'static {} - -impl<Body> BaseController<Body> -where - Body: fmt::Debug + 'static, -{ - /// Create instance of BaseController. - /// - /// Actually, you have to reimplement the [Controller] for your purposes with Singleton pattern. - /// - /// This [View] implementation is keept here in educational purposes only. - /// - pub fn new() -> Self { - Self { - command_map: RefCell::new(HashMap::new()), - notify_context: Rc::new(BaseNotifyContext {}), - } - } - - /// Reprecent controller as [NotifyContext] - pub fn as_context(&self) -> Rc<dyn NotifyContext> { - self.notify_context.clone() - } -} - -impl<Body> Singleton for BaseController<Body> -where - Body: fmt::Debug + 'static, -{ - /// [Controller] Singleton Factory method - /// - /// It is not possible to implement Singleton with generics. - /// So you should implement it in your final code only. - /// - /// Error: use of generic parameter from outer function - /// - fn global() -> &'static Self { - // static BASE_CONTROLLER_INSTANCE: OnceCell<BaseFacade<Body>> = OnceCell::new(); - // BASE_CONTROLLER_INSTANCE.get_or_init(Self::new) - todo!("you have to reimplement the controller for your purposes") - } -} - -impl<Body> Controller<Body> for BaseController<Body> -where - Body: fmt::Debug + 'static, -{ - fn execute_command(&self, notification: Rc<dyn Notification<Body>>) { - let command_map = self.command_map.borrow(); - - log::info!("Execute Command [BaseController] {:?}", notification); - - command_map.get(¬ification.interest()).map(|command| { - log::info!("Command [BaseController] {:?} for {:?}", command, notification); - command.execute(notification) - }); - } - - fn has_command(&self, interest: &Interest) -> bool { - let command_map = self.command_map.borrow(); - - command_map.contains_key(interest) - } - - fn register_command(&self, interest: Interest, command: Rc<dyn Command<Body>>) { - log::info!("Register Command [BaseController] {:?}", interest); - { - // this code should be uncommented in your final version - // check the [Facade]::register_command - // if !self.has_command(interest) { - // View::instance().register_observer( - // interest, - // Box::new(Observer::new(Box::new(|notification| self.execute_command(notification)), self.as_context())), - // ); - // } - } - - self.command_map.borrow_mut().insert(interest, command); - } - - fn remove_command(&self, interest: &Interest) { - // if the Command is registered... - if self.has_command(interest) { - { - // this code should be uncommented in your final version - // check the [Facade]::remove_command - // // remove the observer - // View::instance().remove_observer(interest, &self.as_context()); - } - self.command_map.borrow_mut().remove(interest); - } - } -} - -impl<Body> fmt::Debug for BaseController<Body> -where - Body: fmt::Debug + 'static, -{ - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("Controller") - // .field("x", &self.x) - .finish() - } -} - -#[derive(Clone, Copy)] -struct BaseNotifyContext; - -impl NotifyContext for BaseNotifyContext { - fn id(&self) -> u64 { - 0x01 - } -} - -impl NotifyContext for Rc<BaseNotifyContext> { - fn id(&self) -> u64 { - 0x01 - } -} - -impl fmt::Debug for BaseNotifyContext { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("BaseNotifyContext").field("id", &self.id()).finish() - } -} diff --git a/src/foundation/patterns/default/mod.rs b/src/foundation/patterns/default/mod.rs deleted file mode 100644 index 2891c2b..0000000 --- a/src/foundation/patterns/default/mod.rs +++ /dev/null @@ -1,37 +0,0 @@ -//! [Controller][1], [Model][2], [View][3] base implementation -//! -//! The PureMVC framework has a very narrow goal. That is to help you -//! separate your application’s coding concerns into three discrete tiers; -//! [Model][2], [View][3] and [Controller][1]. -//! -//! In this implementation of the classic MVC design meta-pattern, the -//! application tiers are represented by three Singletons (a class where -//! only one instance may be created). -//! -//! A fourth Singleton, the Facade, simplifies development by providing a -//! single interface for communications throughout the application. -//! -//! - The [Model][2] caches named references to [Proxies][6], which expose an API for -//! manipulating the Data Model (including data retrieved from remote services). -//! - The [View][3] primarily caches named references to [Mediator][4]'s, which adapt and -//! steward the [View][3] Components that make up the user interface. -//! - The [Controller][1] maintains named mappings to [Command][5] classes, which are -//! stateless, and only created when needed. -//! - The Facade initializes and caches the Core actors ([Model][2], [View][3] and -//! [Controller][1]), and provides a single place to access all of their public methods. -//! -//! [1]: crate::prelude::Controller -//! [2]: crate::prelude::Model -//! [3]: crate::prelude::View -//! [4]: crate::prelude::Mediator -//! [5]: crate::prelude::Command -//! [6]: crate::prelude::Proxy -//! -mod controller; -pub use self::controller::*; - -mod model; -pub use self::model::*; - -mod view; -pub use self::view::*; diff --git a/src/foundation/patterns/default/model.rs b/src/foundation/patterns/default/model.rs deleted file mode 100644 index 2ee5847..0000000 --- a/src/foundation/patterns/default/model.rs +++ /dev/null @@ -1,107 +0,0 @@ -use once_cell::sync::OnceCell; -use std::{ - any::{Any, TypeId}, - cell::RefCell, - collections::BTreeMap, - rc::Rc, -}; - -use crate::prelude::{Model, Proxy, Singleton}; - -/// A Singleton [Model] implementation. -/// -/// In PureMVC, the [Model] class provides access to model objects (Proxies) by named lookup. -/// -/// The [Model] assumes these responsibilities: -/// -/// - Maintain a cache of [Proxy] instances. -/// - Provide methods for registering, retrieving, and removing [Proxy] instances. -/// -/// -/// Your application must register [Proxy] instances with the [Model]. Typically, you use an -/// [Command] to create and register [Proxy] instances once the [Facade] has initialized the Core -/// actors. -/// -/// [Command]: crate::prelude::Command -/// [Facade]: crate::prelude::Facade -pub struct BaseModel { - // Mapping of proxy types to [Proxy] instances - storages: RefCell<BTreeMap<TypeId, Rc<dyn Any>>>, -} - -unsafe impl std::marker::Send for BaseModel {} -unsafe impl std::marker::Sync for BaseModel {} - -impl BaseModel { - /// Create instance of BaseModel. - /// - /// This [Model] implementation is a Singleton, so you should not call the constructor - /// directly, but instead call the static Singleton Factory method [global()][1] - /// - /// [1]: Singleton::global - pub fn new() -> Self { - Self { - storages: RefCell::new(BTreeMap::new()), - } - } -} - -impl Singleton for BaseModel { - /// Model Singleton Factory method - /// - fn global() -> &'static Self { - static BASE_MODEL_INSTANCE: OnceCell<BaseModel> = OnceCell::new(); - BASE_MODEL_INSTANCE.get_or_init(Self::new) - } -} - -impl Model for BaseModel { - fn has_proxy<P: Proxy>(&self) -> bool { - let type_id = TypeId::of::<P>(); - self.storages.borrow().contains_key(&type_id) - } - - fn register_proxy<P: Proxy>(&self, proxy: Rc<P>) { - let type_id = TypeId::of::<P>(); - - log::info!("Register Proxy [BaseModel] {:?}", proxy); - - self.storages.borrow_mut().insert(type_id, proxy.clone()); - - proxy.on_register(); - } - - fn remove_proxy<P: Proxy>(&self) -> Option<Rc<P>> { - let type_id = TypeId::of::<P>(); - - self.storages - .borrow_mut() - .remove(&type_id) - .map(|proxy| match proxy.downcast::<P>() { - Ok(proxy) => { - proxy.on_remove(); - proxy - } - Err(_) => { - panic!("Something wrong with proxy storage"); - } - }) - } - - fn retrieve_proxy<P: Proxy>(&self) -> Option<Rc<P>> { - // log::info!("Retrieve Proxy [BaseModel]"); - - let type_id = TypeId::of::<P>(); - - match self.storages.borrow().get(&type_id) { - Some(item) => match item.clone().downcast::<P>() { - Ok(proxy) => Some(proxy), - Err(_) => { - log::error!("Something wrong with proxy storage"); - None - } - }, - None => None, - } - } -} diff --git a/src/foundation/patterns/default/view.rs b/src/foundation/patterns/default/view.rs deleted file mode 100644 index 39fbac0..0000000 --- a/src/foundation/patterns/default/view.rs +++ /dev/null @@ -1,233 +0,0 @@ -use std::{ - any::{Any, TypeId}, - cell::RefCell, - collections::{BTreeMap, HashMap}, - fmt, - rc::Rc, -}; - -use crate::{ - foundation::patterns::observer::BaseObserver, - prelude::{Interest, Mediator, MediatorRegistry, Notification, NotifyContext, Observer, Singleton, View}, -}; - -/// A Singleton [View] implementation. -/// -/// In PureMVC, the [View] class assumes these responsibilities: -/// -/// - Maintain a cache of [Mediator] instances. -/// - Provide methods for registering, retrieving, and removing [Mediator]'s. -/// - Managing the observer lists for each [Notification] in the application. -/// - Providing a method for attaching [Observer]'s to an [Notification]'s observer list. -/// - Providing a method for broadcasting an [Notification]. -/// - Notifying the [Observer]'s of a given [Notification] when it broadcast. -/// - -pub struct BaseView<Body> -where - Body: fmt::Debug + 'static, -{ - // Mapping of Mediator types to Mediator instances - mediator_map: RefCell<BTreeMap<TypeId, Rc<dyn Any>>>, - - // Mapping of Notification names to Observer lists - observer_map: RefCell<HashMap<Interest, Vec<Rc<dyn Observer<Body>>>>>, -} - -unsafe impl<Body> std::marker::Send for BaseView<Body> where Body: fmt::Debug + 'static {} -unsafe impl<Body> std::marker::Sync for BaseView<Body> where Body: fmt::Debug + 'static {} - -impl<Body> BaseView<Body> -where - Body: fmt::Debug + 'static, -{ - /// Create instance of BaseView. - /// - /// Actually, you have to reimplement the [View] for your purposes with Singleton pattern. - /// - /// This [View] implementation is keept here in educational purposes only. - /// - pub fn new() -> Self { - Self { - mediator_map: RefCell::new(BTreeMap::new()), - observer_map: RefCell::new(HashMap::new()), - } - } -} - -impl<Body> Singleton for BaseView<Body> -where - Body: fmt::Debug + 'static, -{ - /// View Singleton Factory method - /// - /// It is not possible to implement Singleton with generics. - /// So you should implement it in your final code only. - /// - /// Error: use of generic parameter from outer function - /// - fn global() -> &'static Self { - // static BASE_VIEW_INSTANCE: OnceCell<BaseFacade<Body>> = OnceCell::new(); - // BASE_VIEW_INSTANCE.get_or_init(Self::new) - todo!("you have to reimplement the view for your purposes") - } -} - -impl<Body> View<Body> for BaseView<Body> -where - Body: fmt::Debug + 'static, -{ - fn notify(&self, note: Rc<dyn Notification<Body>>) { - // Copy observers from reference array to working array, - // since the reference array may change during the notification loop - // and prevent double borrow )) - let observers = { - self.observer_map - .borrow() - .get(¬e.interest()) - .map(|observers| observers.clone()) - }; - - if let Some(observers) = observers { - for observer in observers.iter() { - log::info!("Notify observer {:?} for {:?}", observer, note.interest()); - observer.notify(note.clone()); - } - } - } - - fn register_observer(&self, interest: Interest, observer: Rc<dyn Observer<Body>>) { - // log::info!("Register Observer [BaseView] {:?}", interest); - let mut observer_map = self.observer_map.borrow_mut(); - - if !observer_map.contains_key(&interest) { - observer_map.insert(interest.clone(), Vec::new()); - } - - observer_map - .get_mut(&interest) - .map(|observers| observers.push(observer)); - } - - // It private so its fun - fn remove_observer(&self, interest: &Interest, context: &Rc<dyn NotifyContext>) { - let mut observer_map = self.observer_map.borrow_mut(); - - // the observer list for the notification under inspection - observer_map.remove(interest).as_mut().map(|observers| { - // find the observer for the notify_context - for (idx, observer) in observers.iter().enumerate() { - if observer.compare_context(context) == true { - // there can only be one Observer for a given notify_context - // in any given Observer list, so remove it and break - observers.remove(idx); - break; - } - } - }); - } -} - -impl<Body> MediatorRegistry<Body> for BaseView<Body> -where - Body: fmt::Debug + 'static, -{ - fn register_mediator<M: Mediator<Body>>(&self, mediator: Rc<M>) { - log::info!("Register Mediator [BaseView] {:?}", mediator); - - let mut mediator_map = self.mediator_map.borrow_mut(); - - let type_id = TypeId::of::<M>(); - // do not allow re-registration (you must to removeMediator fist) - if mediator_map.contains_key(&type_id) { - return; - } - - // Register the Mediator for retrieval by name - mediator_map.insert(type_id, mediator.clone()); - - // Get Notification interests, if any. - let interests = mediator.list_notification_interests(); - if interests.len() > 0 { - let mediator = mediator.clone(); - let context = mediator.clone(); - // Create Observer - let observer = Rc::new(BaseObserver::new( - Box::new(move |notification| { - log::info!("Observer notify {:?}", notification); - mediator.handle_notification(notification.clone()) - }), - context, - )); - - // Register Mediator as Observer for its list of Notification interests - for interest in interests.iter() { - self.register_observer(interest.clone(), observer.clone()); - } - } - - mediator.on_register(); - } - - fn retrieve_mediator<M: Mediator<Body>>(&self) -> Option<Rc<M>> { - let type_id = TypeId::of::<M>(); - - match self.mediator_map.borrow().get(&type_id) { - Some(item) => match item.clone().downcast::<M>() { - Ok(mediator) => Some(mediator.clone()), - Err(_) => { - log::error!("Something wrong with proxy storage"); - None - } - }, - None => None, - } - } - - fn remove_mediator<M: Mediator<Body>>(&self) -> Option<Rc<M>> { - // remove the mediator from the map - let type_id = TypeId::of::<M>(); - - self.mediator_map.borrow_mut().remove(&type_id).map(|mediator| { - match mediator.downcast::<M>() { - Ok(mediator) => { - // for every notification this mediator is interested in... - let interests = mediator.list_notification_interests(); - for interest in interests.iter() { - // remove the observer linking the mediator - // to the notification interest - - let mut observer_map = self.observer_map.borrow_mut(); - - let context = mediator.id(); - - // the observer list for the notification under inspection - observer_map.remove(interest).as_mut().map(|observers| { - // find the observer for the notify_context - for (idx, observer) in observers.iter().enumerate() { - if observer.context().id() == context { - // there can only be one Observer for a given notify_context - // in any given Observer list, so remove it and break - observers.remove(idx); - break; - } - } - }); - } - - // alert the mediator that it has been removed - mediator.on_remove(); - mediator - } - Err(_) => { - panic!("Something wrong with mediator storage"); - } - } - }) - } - - fn has_mediator<M: Mediator<Body>>(&self) -> bool { - let type_id = TypeId::of::<M>(); - self.mediator_map.borrow().contains_key(&type_id) - } -} diff --git a/src/foundation/patterns/facade/facade.rs b/src/foundation/patterns/facade/facade.rs deleted file mode 100644 index c7a0b8c..0000000 --- a/src/foundation/patterns/facade/facade.rs +++ /dev/null @@ -1,193 +0,0 @@ -// use once_cell::sync::OnceCell; -use std::{fmt::Debug, rc::Rc}; - -use crate::{ - foundation::patterns::{ - default::{BaseController, BaseModel, BaseView}, - observer::{BaseNotification, BaseObserver}, - }, - prelude::*, -}; - -/// A base Singleton [Facade] implementation. -/// -/// In PureMVC, the [Facade] class assumes these -/// responsibilities: -/// -/// - Initializing the [Model], [View] and [Controller] Singletons. -/// - Providing all the methods defined by the [Model], [View], & [Controller] interfaces. -/// - Providing a single point of contact to the application for registering [Command]'s and notifying [Observer]'s -/// - -pub struct BaseFacade<Body> -where - Body: Debug + 'static, -{ - // Private references to Model, View and Controller - controller: BaseController<Body>, - view: BaseView<Body>, -} - -impl<Body> BaseFacade<Body> -where - Body: Debug + 'static, -{ - /// Create instance of BaseFacade. - /// - /// Actually, you have to reimplement the [Facade] for your purposes with Singleton pattern. - /// - /// This [Facade] implementation is keept here in educational purposes only. - - pub fn new() -> Self { - Self { - controller: BaseController::new(), - view: BaseView::new(), - } - } -} - -impl<Body> Singleton for BaseFacade<Body> -where - Body: Debug + 'static, -{ - /// Facade Singleton Factory method - /// - /// It is not possible to implement Singleton with generics. - /// So you should implement it in your final code only. - /// - /// Error: use of generic parameter from outer function - /// - fn global() -> &'static Self { - // static BASE_FACADE_INSTANCE: OnceCell<BaseFacade<Body>> = OnceCell::new(); - // BASE_FACADE_INSTANCE.get_or_init(Self::new) - todo!("you have to reimplement the facade for your purposes") - } -} - -impl<Body> Facade<Body> for BaseFacade<Body> -where - Body: Debug + 'static, -{ - fn has_command(&self, interest: &Interest) -> bool { - self.controller.has_command(interest) - } - - fn register_command(&self, interest: Interest, command: Rc<dyn Command<Body>>) { - { - // this code should be located in [Controller] in your final version - // check the [BaseController]::register_command - - if !self.has_command(&interest) { - self.view.register_observer( - interest, - Rc::new(BaseObserver::new( - Box::new(|notification| { - log::error!("You should implement yourself Facade"); - BaseFacade::<Body>::global() - .controller - .execute_command(notification); - }), - self.controller.as_context(), - )), - ); - } - } - - self.controller.register_command(interest, command); - } - - fn remove_command(&self, interest: &Interest) { - if self.has_command(interest) { - { - // this code should be located in [Controller] in your final version - // check the [BaseController]::remove_command - - // remove the observer - self.view - .remove_observer(interest, &self.controller.as_context()); - } - - self.controller.remove_command(interest); - } - } - - fn send(&self, interest: Interest, body: Option<Body>) { - self.notify(Rc::new(BaseNotification::new(interest, body))); - } -} - -impl<Body> Model for BaseFacade<Body> -where - Body: Debug + 'static, -{ - /// Check if a [Proxy] is registered - fn has_proxy<P: Proxy>(&self) -> bool { - BaseModel::global().has_proxy::<P>() - } - - /// Register an [Proxy] with the [Model] by name. - fn register_proxy<P: Proxy>(&self, proxy: Rc<P>) { - BaseModel::global().register_proxy(proxy); - } - - /// Remove an [Proxy] instance from the [Model] by name. - fn remove_proxy<P: Proxy>(&self) -> Option<Rc<P>> { - BaseModel::global().remove_proxy::<P>() - } - - /// Retrieve a [Proxy] from the [Model] by name. - fn retrieve_proxy<P: Proxy>(&self) -> Option<Rc<P>> { - BaseModel::global().retrieve_proxy::<P>() - } -} - -impl<Body> View<Body> for BaseFacade<Body> -where - Body: Debug + 'static, -{ - fn register_observer(&self, interest: Interest, observer: Rc<dyn Observer<Body>>) { - self.view.register_observer(interest, observer); - } - - fn remove_observer(&self, interest: &Interest, notify_context: &Rc<dyn NotifyContext>) { - self.view.remove_observer(interest, notify_context); - } - - /// Notify the [Observer]'s for a particular [Notification]. - /// - /// All previously attached [Observer]'s for this [Notification]'s - /// list are notified and are passed a reference to the [Notification] in - /// the order in which they were registered. - /// - /// NOTE: Use this method only if you are sending custom Notifications. Otherwise - /// use the sendNotification method which does not require you to create the - /// Notification instance. - fn notify(&self, note: Rc<dyn Notification<Body>>) { - self.view.notify(note); - } -} - -impl<Body> MediatorRegistry<Body> for BaseFacade<Body> -where - Body: Debug + 'static, -{ - /// Register an [Mediator] instance with the [View]. - fn register_mediator<M: Mediator<Body>>(&self, mediator: Rc<M>) { - self.view.register_mediator(mediator); - } - - /// Retrieve an [Mediator] instance from the [View]. - fn retrieve_mediator<M: Mediator<Body>>(&self) -> Option<Rc<M>> { - self.view.retrieve_mediator::<M>() - } - - /// Remove a [Mediator] instance from the [View]. - fn remove_mediator<M: Mediator<Body>>(&self) -> Option<Rc<M>> { - self.view.remove_mediator::<M>() - } - - /// Check if a [Mediator] is registered or not - fn has_mediator<M: Mediator<Body>>(&self) -> bool { - self.view.has_mediator::<M>() - } -} diff --git a/src/foundation/patterns/facade/mod.rs b/src/foundation/patterns/facade/mod.rs deleted file mode 100644 index e830b5b..0000000 --- a/src/foundation/patterns/facade/mod.rs +++ /dev/null @@ -1,45 +0,0 @@ -//! The base Facade implementation -//! -//! The three Core actors of the MVC meta-pattern are represented in -//! PureMVC by the Model, View and Controller classes. To simplify the -//! process of application development, PureMVC employs the Facade -//! pattern. -//! -//! The Facade brokers your requests to the Model, View and Controller, -//! so that your code does not need import those classes and you do not -//! need to work with them individually. The Facade class automatically -//! instantiates the Core MVC Singletons in its constructor. -//! -//! Typically, the framework Facade will be sub-classed in your application -//! and used to initialize the Controller with Command mappings. -//! Preparation of the Model and View are then orchestrated by -//! Commands executed by the Controller. -//! -//! ## What is a Concrete Facade? -//! Though the Core actors are complete, usable implementations, the -//! Facade provides an implementation that should be considered -//! abstract, in that you never instantiate it directly. -//! -//! Instead, you subclass the framework Facade and add or override -//! some of its methods to make it useful in your application. -//! -//! This concrete Facade is then used to access and notify the -//! Commands, Mediators and Proxies that do the actual work of the -//! system. By convention, it is named ‘ApplicationFacade’, but you -//! may call it whatever you like. -//! -//! Generally, your application’s View hierarchy (display components) -//! will be created by whatever process your platform normally -//! employs. In Flex, an MXML application instantiates all its children or -//! a Flash movie creates all the objects on its Stage. Once the -//! application’s View hierarchy has been built, the PureMVC apparatus -//! is started and the Model and View regions are prepared for use. -//! -//! Your concrete Facade is also used to facilitate the startup process in -//! a way that keeps the main application code from knowing much -//! about the PureMVC apparatus to which it will be connected. The -//! application merely passes a reference to itself to a ‘startup’ method -//! on your concrete Facade’s Singleton instance. - -mod facade; -pub use self::facade::*; diff --git a/src/foundation/patterns/fsm/fsm.rs b/src/foundation/patterns/fsm/fsm.rs deleted file mode 100644 index 31ac6ce..0000000 --- a/src/foundation/patterns/fsm/fsm.rs +++ /dev/null @@ -1,129 +0,0 @@ -use std::{any::TypeId, cell::RefCell, collections::HashMap, rc::Rc}; - -use super::{integrations::FsmIntegration, State, StateDef, Transitions, Typed}; - -/// Represents Fsm properties -#[derive(Default)] -pub struct FsmProps<T> -where - T: FsmIntegration<T>, -{ - states: HashMap<TypeId, Rc<StateDef<T>>>, - current_state: Option<Rc<StateDef<T>>>, -} - -/// Represent finite state machine -#[derive(Default, Clone)] -pub struct Fsm<T> -where - T: FsmIntegration<T>, -{ - integration: T, - props: Rc<RefCell<FsmProps<T>>>, -} - -impl<T> Fsm<T> -where - T: FsmIntegration<T>, -{ - // Require #![feature(const_type_id)] - // pub const TYPE_ID: TypeId = TypeId::of::<Self>(); - - /// Create new finite state machine - pub fn new(integration: T) -> Self { - Self { - integration, - props: Rc::new(RefCell::new(FsmProps { - states: HashMap::new(), - current_state: None, - })), - } - } - - /// Triggers a state change. Transition will only happen if stateClass is in the list - /// of transitions for the currentState. - /// - state - The struct of the state to change to. - pub fn goto(&self, state: impl State<T> + 'static) -> bool { - // should be private i think - let state_type_id = state.type_id(); - - // detect transition exists - let allowed = { - let props = self.props.borrow(); - props - .current_state - .as_ref() - .map(|current_state| { - current_state - .transitions - .iter() - .position(|item| item.type_id() == state.type_id()) - .unwrap_or_default() - }) - .map(|_| true) - .unwrap_or_default() - }; - - let mut props = self.props.borrow_mut(); - - let current_state = props.current_state.clone(); - - match props.states.get_mut(&state_type_id) { - Some(new_state) => { - // State transition - if current_state.is_some() { - // transition allowed - if allowed { - // make transition - self.integration.transition(new_state.clone(), current_state); - - props.current_state = Some(new_state.clone()); - log::warn!("Handle integration"); - - return true; - } - - log::warn!("No transition defined from {} to {:?}", self.current_state_name(), state); - } else { - // Initial state transition - self.integration.transition(new_state.clone(), None); - props.current_state = Some(new_state.clone()); - return true; - } - } - None => panic!("Attempting to transtion to {:?}, but state has not been added.", state), - } - - false - } - - /// Add a state with transitions to FSM. - /// - /// Passing the struct instead of a string reference for convinience. - /// While setting the state will be about 4X slower, and getting about 10X, it should not be a - /// concern unless you are going to switch state thousands of times per second. - pub fn add(&self, state: impl State<T> + 'static, transitions: Transitions<T>) { - let state_def = StateDef::new(state, transitions); - let state_type_id = state_def.type_id(); - - let mut props = self.props.borrow_mut(); - if props.states.contains_key(&state_type_id) { - // TODO: Rather not have this as a runtime error, should be a macro for that. - // panic!("Trying to add {:?} several times. Only add states once!", state_def); - unimplemented!() - } - - props.states.insert(state_type_id, Rc::new(state_def)); - } - - /// Retrieve current state - pub fn current_state_name(&self) -> String { - // let props = self.props.borrow(); - // props - // .current_state - // .as_ref() - // .map(|x| format!("{:?}", x)) - // .unwrap_or_default() - unimplemented!() - } -} diff --git a/src/foundation/patterns/fsm/fsm_controller.rs b/src/foundation/patterns/fsm/fsm_controller.rs deleted file mode 100644 index bbcdc6f..0000000 --- a/src/foundation/patterns/fsm/fsm_controller.rs +++ /dev/null @@ -1,51 +0,0 @@ -use super::{integrations::FsmIntegration, Fsm, State}; -// use Timer; - -/// Controller to inject into application -/// -/// Injecting FSM directly to would give access to add method, but adding states should be done during initial -/// configuration. So provide access to FsmController instead to restrict usage to only the functionality needed after -/// startup. -pub struct FsmController<T> -where - T: FsmIntegration<T>, -{ - fsm: Fsm<T>, -} - -impl<T> FsmController<T> -where - T: FsmIntegration<T>, -{ - /// Create new FsmController - pub fn new(fsm: Fsm<T>) -> Self { - Self { fsm } - } - - // wait: Option<bool> = true - /// Goto to the state - pub fn goto( - &self, - state: impl State<T> + 'static, - guard: Option<Box<dyn Fn() -> bool>>, - wait: Option<bool>, - ) -> bool { - if let Some(guard) = guard { - let allowed = guard(); - if !allowed && !wait.unwrap_or(true) { - return false; - } else if !allowed { - // FIXME: should called next tick async fashion - self.goto(state, Some(guard), None); - return true; - } - } - - self.fsm.goto(state) - } - - /// Retrieve current state - pub fn current_state_name(&self) -> String { - self.fsm.current_state_name() - } -} diff --git a/src/foundation/patterns/fsm/integrations.rs b/src/foundation/patterns/fsm/integrations.rs deleted file mode 100644 index 49f886b..0000000 --- a/src/foundation/patterns/fsm/integrations.rs +++ /dev/null @@ -1,34 +0,0 @@ -use std::rc::Rc; - -use super::StateDef; - -// pub mod command; - -// pub mod injector; - -/// Defines finite state machine integration functionality -pub trait FsmIntegration<T: FsmIntegration<T>>: Clone { - /// Makes a transition from one state to another - fn transition(&self, new_state: Rc<StateDef<T>>, old_state: Option<Rc<StateDef<T>>>) -> bool; -} - -/// Represents callback integration -#[derive(Default, Debug, Clone)] -pub struct CallbackIntegration; - -impl CallbackIntegration { - /// Create new callback integration - pub fn new() {} -} - -impl FsmIntegration<Self> for CallbackIntegration { - fn transition(&self, new_state: Rc<StateDef<Self>>, old_state: Option<Rc<StateDef<Self>>>) -> bool { - if let Some(ref old_state) = old_state { - old_state.state.exit(self) - } - - new_state.state.enter(self); - - true - } -} diff --git a/src/foundation/patterns/fsm/mod.rs b/src/foundation/patterns/fsm/mod.rs deleted file mode 100644 index f1d4eef..0000000 --- a/src/foundation/patterns/fsm/mod.rs +++ /dev/null @@ -1,235 +0,0 @@ -//! Finite State Machines -//! -//! A state machine is a behavior model. It consists of a finite number of states and is therefore also -//! called finite-state machine (FSM). Based on the current state and a given input the machine performs -//! state transitions and produces outputs. There are basic types like Mealy and Moore machines and more -//! complex types like Harel and UML statecharts. -//! -//! The basic building blocks of a state machine are states and transitions. A state is a situation of a system -//! depending on previous inputs and causes a reaction on following inputs. One state is marked as the initial state; -//! this is where the execution of the machine starts. A state transition defines for which input a state is changed -//! from one to another. Depending on the state machine type, states and/or transitions produce outputs. -//! -//! Consider the simple state machine above. It consists of two states, *Off* and *On*. *On* is the initial state here; -//! it is activated when the state machine is executed. The arrows between the states denote the possible state transitions. -//! They define for which input a state change occurs. Here, the active state is changed from *On* to *Off* for the -//! input *buttonpressed*, and back again to *On* for the same input. -//! -//! > **Please note:** In automata theory an automaton reacts on inputs and produces outputs. There, the terms input and -//! output are usually used for symbols which belong to an alphabet. Modern state machines use an extended definition -//! of inputs and outputs. Inputs can be events like a button click or a time trigger while outputs are actions like -//! an operation call or a variable assignment. -//! -//! In the following, we will extend the simple switch example to explain the differences between Mealy and Moore machines -//! as well as Harel statecharts and UML state machines. - -use std::any::TypeId; - -mod fsm; -pub use fsm::*; - -mod fsm_controller; -pub use fsm_controller::*; - -mod integrations; -pub use integrations::*; - -mod state_def; -pub use state_def::*; - -///! Defines tipe_id functionality -pub trait Typed { - ///! Retrieve TypeId - fn type_id(&self) -> TypeId; -} - -///! Defines Factory Method functionality -pub trait FactoryMethod<T> { - ///! Create instance from Factory - fn create(&self) -> T; -} - -///! State's holder -pub type Transitions<T> = Vec<Box<dyn State<T>>>; - -// todo should contain PartialEq -// pub fn contains(&self, x: &T) -> bool -// or fn any<F>(&mut self, f: F) -> bool - -///! Defines State functionality for finite state machine -#[allow(unused_variables)] -pub trait State<T>: std::fmt::Debug + Typed -where - T: FsmIntegration<T>, -{ - ///! Enter to state - fn enter(&self, target: &T) {} - - ///! Exit from state - fn exit(&self, target: &T) {} -} - -#[cfg(test)] -mod tests { - use std::{any::TypeId, rc::Rc}; - - use super::{ - integrations::{CallbackIntegration, FsmIntegration}, - Fsm, FsmController, State, StateDef, Typed, - }; - - // pub entered: bool, - #[derive(Default, Debug)] - struct MockCallbackState; - - impl State<CallbackIntegration> for MockCallbackState { - fn enter(&self, _target: &CallbackIntegration) { - //! self.entered = true; - } - - fn exit(&self, _target: &CallbackIntegration) {} - } - - impl Typed for MockCallbackState { - fn type_id(&self) -> TypeId { - TypeId::of::<Self>() - } - } - - // A unit struct - // pub entered: bool, - #[derive(Default, Debug)] - struct MockInjectorStateB; - - impl State<MockIntegration> for MockInjectorStateB { - fn enter(&self, _target: &MockIntegration) { - //! self.entered = true; - } - - fn exit(&self, _target: &MockIntegration) { - //! self.entered = false; - } - } - - impl Typed for MockInjectorStateB { - fn type_id(&self) -> TypeId { - TypeId::of::<Self>() - } - } - - // pub entered: bool, - #[derive(Default, Debug)] - struct MockInjectorState; - - impl State<MockIntegration> for MockInjectorState { - fn enter(&self, _target: &MockIntegration) { - //! self.entered = true; - } - - fn exit(&self, _target: &MockIntegration) { - //! self.entered = false; - } - } - - impl Typed for MockInjectorState { - fn type_id(&self) -> TypeId { - TypeId::of::<Self>() - } - } - - #[derive(Default, Debug, Clone)] - struct MockIntegration; - - impl FsmIntegration<Self> for MockIntegration { - fn transition(&self, new_state: Rc<StateDef<Self>>, old_state: Option<Rc<StateDef<Self>>>) -> bool { - if let Some(ref old_state) = old_state { - old_state.state.exit(self); - } - - new_state.state.enter(self); - - true - } - } - - // should enter initial state - #[test] - fn should_enter_initial_state() { - let fsm = Fsm::new(MockIntegration::default()); - - // Seems controller should work with Rc<FSM> - let controller = FsmController::new(fsm.clone()); - - fsm.add(MockInjectorState, vec![]); - - controller.goto(MockInjectorState, None, None); - // MockInjectorState entered should be true; - // assert_eq!(2 + 2, 4); - } - - // should not allow entering state if transition not added - #[test] - fn should_not_allow_entering_state_if_transition_not_added() { - let fsm = Fsm::new(MockIntegration::default()); - - let _controller = FsmController::new(fsm.clone()); - - fsm.add(MockInjectorState, vec![]); - - // controller.goto(MockInjectorStateB) - // Attempting to transtion to MockInjectorStateB, but state has not been added - } - - // should only allow adding state once - #[test] - fn should_only_allow_adding_state_once() { - let fsm = Fsm::new(MockIntegration::default()); - - let _controller = FsmController::new(fsm.clone()); - fsm.add(MockInjectorState, vec![]); - - // fsm.add.bind(MockInjectorState, vec![]) - // Trying to add MockInjectorState several times. Only add states once! - } - - // should not allow entering state if transition not defined - #[test] - fn should_not_allow_entering_state_if_transition_not_defined() { - let fsm = Fsm::new(MockIntegration::default()); - - let controller = FsmController::new(fsm.clone()); - - fsm.add(MockInjectorState, vec![]); - fsm.add(MockInjectorStateB, vec![]); - - controller.goto(MockInjectorState, None, None); - controller.goto(MockInjectorStateB, None, None); - // MockInjectorStateB entered should be false; - } - - // should enter defined transition - #[test] - fn should_enter_defined_transition() { - let fsm = Fsm::new(MockIntegration::default()); - let controller = FsmController::new(fsm.clone()); - fsm.add(MockInjectorState, vec![Box::new(MockInjectorStateB::default())]); - fsm.add(MockInjectorStateB, vec![]); - - controller.goto(MockInjectorState, None, None); - controller.goto(MockInjectorStateB, None, None); - // MockInjectorStateB entered should be true - // MockInjectorState entered should be false - } - - // should call enter on states when using callback integration - #[test] - fn should_call_enter_on_states_when_using_callback_integration() { - let fsm = Fsm::new(CallbackIntegration::default()); - - let controller = FsmController::new(fsm.clone()); - fsm.add(MockCallbackState, vec![]); - - controller.goto(MockCallbackState, None, None); - // MockCallbackState entered should be true - } -} diff --git a/src/foundation/patterns/fsm/state_def.rs b/src/foundation/patterns/fsm/state_def.rs deleted file mode 100644 index 4f20285..0000000 --- a/src/foundation/patterns/fsm/state_def.rs +++ /dev/null @@ -1,37 +0,0 @@ -use std::any::TypeId; - -use super::{FsmIntegration, State, Transitions, Typed}; - -/// Represents the fsm state definition -#[derive(Debug)] -pub struct StateDef<T> -where - T: FsmIntegration<T>, -{ - /// Represens the state - pub state: Box<dyn State<T>>, - /// Represens the state transitions - pub transitions: Transitions<T>, -} - -impl<T> StateDef<T> -where - T: FsmIntegration<T>, -{ - /// Create new state definition - pub fn new(state: impl State<T> + 'static, transitions: Transitions<T>) -> Self { - Self { - state: Box::new(state), - transitions, - } - } -} - -impl<T> Typed for StateDef<T> -where - T: FsmIntegration<T>, -{ - fn type_id(&self) -> TypeId { - self.state.type_id() - } -} diff --git a/src/foundation/patterns/mediator/mediator.rs b/src/foundation/patterns/mediator/mediator.rs deleted file mode 100644 index 40f51f5..0000000 --- a/src/foundation/patterns/mediator/mediator.rs +++ /dev/null @@ -1,79 +0,0 @@ -use std::{fmt, rc::Rc}; - -use crate::{ - foundation::patterns::facade::BaseFacade, - prelude::{Facade, Interest, Mediator, Notification, Notifier, NotifyContext, Singleton, View}, -}; - -/// A base [Mediator] implementation. - -pub struct BaseMediator<Body> { - // The view component - view_component: Option<Rc<dyn View<Body>>>, -} - -impl<Body> BaseMediator<Body> { - /// The name of the [Mediator]. - /// - /// Typically, a [Mediator] will be written to serve - /// one specific control or group controls and so, - /// will not have a need to be dynamically named. - - /// Constructor. - pub fn new(view_component: Option<Rc<dyn View<Body>>>) -> Self { - Self { view_component } - } -} - -impl<Body> Mediator<Body> for BaseMediator<Body> -where - Body: fmt::Debug + 'static, -{ - fn view_component(&self) -> Option<Rc<dyn View<Body>>> { - self.view_component.as_ref().map(|c| c.clone()) - } - - fn handle_notification(&self, _notification: Rc<dyn Notification<Body>>) {} - - fn list_notification_interests(&self) -> &[Interest] { - &[] - } - - fn on_register(&self) {} - - fn on_remove(&self) {} - - fn set_view_component(&mut self, view_component: Option<Rc<dyn View<Body>>>) { - self.view_component = view_component; - } -} - -impl<Body> NotifyContext for BaseMediator<Body> -where - Body: fmt::Debug + 'static, -{ - fn id(&self) -> u64 { - 0x01 - } -} - -impl<Body> Notifier<Body> for BaseMediator<Body> -where - Body: fmt::Debug + 'static, -{ - fn send(&self, interest: Interest, body: Option<Body>) { - log::error!("You should implement yourself Mediator"); - BaseFacade::<Body>::global().send(interest, body); - } -} - -impl<Body> fmt::Debug for BaseMediator<Body> -where - Body: fmt::Debug + 'static, -{ - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("EmployeesMediator") - // .field("x", &self.x) - .finish() - } -} diff --git a/src/foundation/patterns/mediator/mod.rs b/src/foundation/patterns/mediator/mod.rs deleted file mode 100644 index 4dd8c71..0000000 --- a/src/foundation/patterns/mediator/mod.rs +++ /dev/null @@ -1,49 +0,0 @@ -//! The base Proxy implementation -//! -//! A Mediator class is used to mediate the user's interaction with one or -//! more of the application's View Components and the rest of the PureMVC application. -//! -//! In a application, a Mediator typically places event listeners -//! on its View Component to handle user gestures and requests for data -//! from the Component. It sends and receives Notifications to -//! communicate with the rest of the application. -//! -//! ## Responsibilities of the Concrete Mediator -//! -//! The Flash, Flex and AIR frameworks provide a vast array of richly- -//! interactive UI components. You may extend these or write your own -//! in ActionScript to provide endless possibilities for presenting the -//! data model to the user and allowing them to interact with it. -//! -//! In the not so distant future, there will be other platforms running -//! ActionScript. And the framework has been ported and demonstrated -//! on other platforms already including Silverlight and J2ME, further -//! widening the horizons for RIA development with this technology. -//! -//! A goal of the PureMVC framework is to be neutral to the -//! technologies being used at the boundaries of the application and -//! provide simple idioms for adapting whatever UI component or Data -//! structure/service you might find yourself concerned with at the -//! moment. -//! -//! To the PureMVC-based application, a View Component is any UI -//! component, regardless of what framework it is provided by or how -//! many sub-components it may contain. A View Component should -//! encapsulate as much of its own state and operation as possible, -//! exposing a simple API of events, methods and properties. -//! -//! A concrete Mediator helps us adapt one or more View Components -//! to the application by holding the only references to those -//! components and interacting with the API they expose. -//! -//! The responsibilities for the Mediator are primarily handling Events -//! dispatched from the View Component and relevant Notifications -//! sent from the rest of the system. -//! -//! Since Mediators will also frequently interact with Proxies, it is -//! common for a Mediator to retrieve and maintain a local reference to -//! frequently accessed Proxies in its constructor. This reduces -//! repetitive retrieveProxy calls to obtain the same reference. - -mod mediator; -pub use self::mediator::*; diff --git a/src/foundation/patterns/mod.rs b/src/foundation/patterns/mod.rs deleted file mode 100644 index a222283..0000000 --- a/src/foundation/patterns/mod.rs +++ /dev/null @@ -1,17 +0,0 @@ -//! Catalog of patterns - -pub mod command; - -pub mod default; - -pub mod facade; - -pub mod fsm; - -pub mod mediator; - -pub mod observer; - -pub mod proxy; - -pub mod builder; \ No newline at end of file diff --git a/src/foundation/patterns/observer/mod.rs b/src/foundation/patterns/observer/mod.rs deleted file mode 100644 index da5d890..0000000 --- a/src/foundation/patterns/observer/mod.rs +++ /dev/null @@ -1,30 +0,0 @@ -//! Observers & Notifications -//! -//! PureMVC applications may run in environments without access to -//! Flash’s Event and EventDispatcher classes, so the framework -//! implements an Observer notification scheme for communication -//! between the Core MVC actors and other parts of the system in a -//! loosely-coupled way. -//! -//! You need not be concerned about the details of the PureMVC -//! Observer/Notification implementation; it is internal to the -//! framework. You will use a simple method to send Notifications from -//! Proxies, Mediators, Commands and the Facade itself that doesn’t -//! even require you to create a Notification instance. -//! -//! ## Notifications Can Be Used to Trigger Command Execution -//! -//! Commands are mapped to Notification names in your concrete -//! Facade, and are automatically executed by the Controller when -//! their mapped Notifications are sent. Commands typically -//! orchestrate complex interaction between the interests of the View -//! and Model while knowing as little about each as possible. - -mod notification; -pub use self::notification::*; - -mod notifier; -pub use self::notifier::*; - -mod observer; -pub use self::observer::*; diff --git a/src/foundation/patterns/observer/notification.rs b/src/foundation/patterns/observer/notification.rs deleted file mode 100644 index ca994e8..0000000 --- a/src/foundation/patterns/observer/notification.rs +++ /dev/null @@ -1,81 +0,0 @@ -use std::fmt; - -use crate::prelude::{Interest, Notification}; - -/// A base [Notification] implementation. -/// -/// PureMVC does not rely upon underlying event models such -/// as the one provided with Flash. -/// -/// The Observer Pattern as implemented within PureMVC exists -/// to support event-driven communication between the -/// application and the actors of the MVC triad. -/// -/// Notifications are not meant to be a replacement for Events. -/// Generally, [Mediator] implementors place event listeners on their view components, which they -/// then handle in the usual way. This may lead to the broadcast of [Notification]'s to -/// trigger [Command]'s or to communicate with other [Mediator]'s. [Proxy] and [Command] -/// instances communicate with each other and [Mediator]'s by broadcasting [Notification]'s. -/// -/// A key difference between native event's and PureMVC -/// [Notification]'s is that event's follow the -/// 'Chain of Responsibility' pattern, 'bubbling' up the display hierarchy -/// until some parent component handles the event, while -/// PureMVC [Notification]'s follow a 'Publish/Subscribe' -/// pattern. PureMVC classes need not be related to each other in a -/// parent/child relationship in order to communicate with one another -/// using [Notification]'s. -/// -/// [Command]: crate::prelude::Command -/// [Proxy]: crate::prelude::Proxy -/// [Mediator]: crate::prelude::Mediator - -pub struct BaseNotification<Body> -where - Body: fmt::Debug + 'static, -{ - // the type of the notification instance - interest: Interest, - - // the body of the notification instance - body: Option<Body>, -} - -impl<Body> BaseNotification<Body> -where - Body: fmt::Debug + 'static, -{ - /// Constructor. - pub fn new(interest: Interest, body: Option<Body>) -> Self { - Self { interest, body } - } -} - -impl<Body> Notification<Body> for BaseNotification<Body> -where - Body: fmt::Debug + 'static, -{ - fn body(&self) -> Option<&Body> { - self.body.as_ref() - } - - fn interest(&self) -> Interest { - self.interest - } - - fn set_body(&mut self, body: Option<Body>) { - self.body = body; - } -} - -impl<Body> fmt::Debug for BaseNotification<Body> -where - Body: fmt::Debug + 'static, -{ - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("BaseNotification") - .field("interest", &self.interest) - .field("body", &self.body) - .finish() - } -} diff --git a/src/foundation/patterns/observer/notifier.rs b/src/foundation/patterns/observer/notifier.rs deleted file mode 100644 index d90d8ae..0000000 --- a/src/foundation/patterns/observer/notifier.rs +++ /dev/null @@ -1,48 +0,0 @@ -use std::fmt::Debug; - -use crate::{ - foundation::patterns::facade::BaseFacade, - prelude::{Facade, Interest, Notifier, Singleton}, -}; - -/// A Base [Notifier] implementation. -/// -/// [MacroCommand], [Command], [Mediator] and [Proxy] -/// all have a need to send [Notification]'s. -/// -/// The [Notifier] interface provides a common method called -/// [send](Notifier::send) that relieves implementation code of -/// the necessity to actually construct [Notification]'s. -/// -/// The [Notifier] class, which all of the above mentioned classes -/// extend, provides an initialized reference to the [Facade] -/// Singleton, which is required for the convienience method -/// for sending [Notification]'s, but also eases implementation as these -/// classes have frequent [Facade] interactions and usually require -/// access to the facade anyway. -/// -/// [MacroCommand]: crate::foundation::patterns::command::MacroCommand -/// [Notification]: crate::prelude::Notification -/// [Command]: crate::prelude::Command -/// [Mediator]: crate::prelude::Mediator -/// [Proxy]: crate::prelude::Proxy -/// [Facade]: crate::prelude::Facade - -pub struct BaseNotifier; - -impl BaseNotifier { - /// Create new BaseNotifier - pub fn new() -> Self { - Self {} - } -} - -impl<Body> Notifier<Body> for BaseNotifier -where - Body: Debug + 'static, -{ - fn send(&self, interest: Interest, body: Option<Body>) { - log::error!("You should implement yourself Notifier"); - BaseFacade::<Body>::global().send(interest, body); - } -} diff --git a/src/foundation/patterns/observer/observer.rs b/src/foundation/patterns/observer/observer.rs deleted file mode 100644 index 06fc4c1..0000000 --- a/src/foundation/patterns/observer/observer.rs +++ /dev/null @@ -1,78 +0,0 @@ -use std::{fmt, rc::Rc}; - -use crate::prelude::{Notification, NotifyContext, Observer}; - -/// A base [Observer] implementation. -/// -/// An [Observer] is an object that encapsulates information -/// about an interested object with a method that should -/// be called when a particular [Notification] is broadcast. -/// -/// In PureMVC, the [Observer] class assumes these responsibilities: -/// -/// - Encapsulate the notification (callback) method of the interested object. -/// - Encapsulate the notification context (this) of the interested object. -/// - Provide methods for setting the notification method and context. -/// - Provide a method for notifying the interested object. -/// - -pub struct BaseObserver<Body> -where - Body: fmt::Debug + 'static, -{ - notify: Box<dyn Fn(Rc<dyn Notification<Body>>)>, - context: Rc<dyn NotifyContext>, -} - -impl<Body> BaseObserver<Body> -where - Body: fmt::Debug + 'static, -{ - /// Constructor. - /// - /// The notification method on the interested object should take - /// one parameter of type [Notification] - pub fn new(notify: Box<dyn Fn(Rc<dyn Notification<Body>>)>, context: Rc<dyn NotifyContext>) -> Self { - Self { notify, context } - } - - // Get the notification method. - fn method(&self) -> &impl Fn(Rc<dyn Notification<Body>>) { - &self.notify - } -} - -impl<Body> Observer<Body> for BaseObserver<Body> -where - Body: fmt::Debug + 'static, -{ - // Get the notification context. - fn context(&self) -> &Rc<dyn NotifyContext> { - &self.context - } - - fn compare_context(&self, object: &Rc<dyn NotifyContext>) -> bool { - object.id() == self.context.id() - } - - fn notify(&self, notification: Rc<dyn Notification<Body>>) { - self.method()(notification); - } - - fn set_context(&mut self, context: Rc<dyn NotifyContext>) { - self.context = context; - } - - fn set_method(&mut self, notify: Box<dyn Fn(Rc<dyn Notification<Body>>)>) { - self.notify = notify; - } -} - -impl<Body> fmt::Debug for BaseObserver<Body> -where - Body: fmt::Debug + 'static, -{ - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("BaseObserver").field("context", &self.context).finish() - } -} diff --git a/src/foundation/patterns/proxy/mod.rs b/src/foundation/patterns/proxy/mod.rs deleted file mode 100644 index 8c5bef1..0000000 --- a/src/foundation/patterns/proxy/mod.rs +++ /dev/null @@ -1,60 +0,0 @@ -//! The base Proxy implementation -//! -//! Generally speaking, the Proxy pattern is used to provide a placeholder -//! for an object in order to control access to it. In a PureMVC-based -//! application, the Proxy class is used specifically to manage a portion of -//! the application's data model. -//! -//! A Proxy might manage access to a locally created data structure of -//! arbitrary complexity. This is the Proxy’s Data Object. -//! -//! In this case, idioms for interacting with it probably involve -//! synchronous setting and getting of its data. It may expose all or part -//! of its Data Object’s properties and methods, or a reference to the Data -//! Object itself. When exposing methods for updating the data, it may -//! also send Notifications to the rest of the system that the data has -//! changed. -//! -//! A Remote Proxy might be used to encapsulate interaction with a -//! remote service to save or retrieve a piece of data. The Proxy can -//! maintain the object that communicates with the remote service, and -//! control access to the data sent and received from the service. -//! -//! In such a case, one might set data or call a method of the Proxy and -//! await an asynchronous Notification, sent by the Proxy when the -//! service has received the data from the remote endpoint. -//! -//! ## Responsibilities of the Concrete Proxy -//! -//! The concrete Proxy allows us to encapsulate a piece of the data -//! model, wherever it comes from and whatever its type, by managing -//! the Data Object and the application’s access to it. -//! -//! The Proxy implementation class that comes with PureMVC is a -//! simple data carrier object that can be registered with the Model. -//! -//! Though it is completely usable in this form, you will usually subclass -//! Proxy and add functionality specific to the particular Proxy. -//! Common variations on the Proxy pattern include: -//! -//! - Remote Proxy, where the data managed by the -//! concrete Proxy is in a remote location and will be -//! accessed via a service of some sort. -//! -//! - Proxy and Delegate, where access to a service object -//! needs to be shared between multiple Proxies. The -//! Delegate class maintains the service object and -//! controls access to it, ensuring that responses are -//! properly routed to their requestors. -//! -//! - Protection Proxy, used when objects need to have -//! different access rights. -//! o Virtual Proxy, which creates expensive objects on -//! demand. -//! -//! - Smart Proxy, loads data object into memory on first -//! access, performs reference counting, allows locking -//! of object to ensure no other object can change it. - -mod proxy; -pub use self::proxy::*; diff --git a/src/foundation/patterns/proxy/proxy.rs b/src/foundation/patterns/proxy/proxy.rs deleted file mode 100644 index 60e3a83..0000000 --- a/src/foundation/patterns/proxy/proxy.rs +++ /dev/null @@ -1,71 +0,0 @@ -use std::fmt; - -use crate::{ - foundation::patterns::facade::BaseFacade, - prelude::{Facade, Interest, Notifier, Proxy, Singleton}, -}; - -/// A base [Proxy] implementation. -/// -/// In PureMVC, [Proxy] classes are used to manage parts of the -/// application's data model. -/// -/// A [Proxy] might simply manage a reference to a local data object, -/// in which case interacting with it might involve setting and -/// getting of its data in synchronous fashion. -/// -/// [Proxy] classes are also used to encapsulate the application's -/// interaction with remote services to save or retrieve data, in which case, -/// we adopt an asyncronous idiom; setting data (or calling a method) on the -/// [Proxy] and listening for a [Notification] to be sent -/// when the [Proxy] has retrieved the data from the service. -/// -/// [Notification]: crate::prelude::Notification - -pub struct BaseProxy<Body> -where - Body: fmt::Debug + 'static, -{ - /// Represens data object - pub data: Option<Body>, -} - -impl<Body> BaseProxy<Body> -where - Body: fmt::Debug + 'static, -{ - /// Constructor - pub fn new(data: Option<Body>) -> Self { - Self { data } - } -} - -impl<Body> Proxy for BaseProxy<Body> -where - Body: fmt::Debug + 'static, -{ - fn on_register(&self) {} - - fn on_remove(&self) {} -} - -impl<Body> Notifier<Body> for BaseProxy<Body> -where - Body: fmt::Debug + 'static, -{ - fn send(&self, interest: Interest, body: Option<Body>) { - log::error!("You should implement yourself Proxy"); - BaseFacade::<Body>::global().send(interest, body); - } -} - -impl<Body> fmt::Debug for BaseProxy<Body> -where - Body: fmt::Debug + 'static, -{ - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("Proxy<Body>") - // .field("x", &self.x) - .finish() - } -} diff --git a/src/functional/index.md b/src/functional/index.md new file mode 100644 index 0000000..b29e9ca --- /dev/null +++ b/src/functional/index.md @@ -0,0 +1,10 @@ +# Functional Usage of Rust + +Rust is an imperative language, but it follows many +[functional programming](https://en.wikipedia.org/wiki/Functional_programming) paradigms. + +> In computer science, *functional programming* is a programming paradigm where +> programs are constructed by applying and composing functions. +> It is a declarative programming paradigm in which function definitions are +> trees of expressions that each return a value, rather than a sequence of +> imperative statements which change the state of the program. diff --git a/src/functional/paradigms.md b/src/functional/paradigms.md new file mode 100644 index 0000000..9fc3b99 --- /dev/null +++ b/src/functional/paradigms.md @@ -0,0 +1,69 @@ +# Programming paradigms + +One of the biggest hurdles to understanding functional programs when coming +from an imperative background is the shift in thinking. Imperative programs +describe __how__ to do something, whereas declarative programs describe +__what__ to do. Let's sum the numbers from 1 to 10 to show this. + +## Imperative + +```rust +let mut sum = 0; +for i in 1..11 { + sum += i; +} +println!("{}", sum); +``` + +With imperative programs, we have to play compiler to see what is happening. +Here, we start with a `sum` of `0`. +Next, we iterate through the range from 1 to 10. +Each time through the loop, we add the corresponding value in the range. +Then we print it out. + +| `i` | `sum` | +|:---:|:-----:| +| 1 | 1 | +| 2 | 3 | +| 3 | 6 | +| 4 | 10 | +| 5 | 15 | +| 6 | 21 | +| 7 | 28 | +| 8 | 36 | +| 9 | 45 | +| 10 | 55 | + +This is how most of us start out programming. We learn that a program is a set +of steps. + +## Declarative + +```rust +println!("{}", (1..11).fold(0, |a, b| a + b)); +``` + +Whoa! This is really different! What's going on here? +Remember that with declarative programs we are describing __what__ to do, +rather than __how__ to do it. `fold` is a function that [composes](https://en.wikipedia.org/wiki/Function_composition) +functions. The name is a convention from Haskell. + +Here, we are composing functions of addition (this closure: `|a, b| a + b`) +with a range from 1 to 10. The `0` is the starting point, so `a` is `0` at +first. `b` is the first element of the range, `1`. `0 + 1 = 1` is the result. +So now we `fold` again, with `a = 1`, `b = 2` and so `1 + 2 = 3` is the next +result. This process continues until we get to the last element in the range, +`10`. + +| `a` | `b` | result | +|:---:|:---:|:------:| +| 0 | 1 | 1 | +| 1 | 2 | 3 | +| 3 | 3 | 6 | +| 6 | 4 | 10 | +| 10 | 5 | 15 | +| 15 | 6 | 21 | +| 21 | 7 | 28 | +| 28 | 8 | 36 | +| 36 | 9 | 45 | +| 45 | 10 | 55 | diff --git a/src/idioms/coercion-arguments.md b/src/idioms/coercion-arguments.md new file mode 100644 index 0000000..89e29f8 --- /dev/null +++ b/src/idioms/coercion-arguments.md @@ -0,0 +1,139 @@ +# Use borrowed types for arguments + +## Description + +Using a target of a deref coercion can increase the flexibility of your code +when you are deciding which argument type to use for a function argument. +In this way, the function will accept more input types. + +This is not limited to slice-able or fat pointer types. +In fact you should always prefer using the __borrowed type__ over +__borrowing the owned type__. +Such as `&str` over `&String`, `&[T]` over `&Vec<T>`, or `&T` over `&Box<T>`. + +Using borrowed types you can avoid layers of indirection for those instances +where the owned type already provides a layer of indirection. For instance, a +`String` has a layer of indirection, so a `&String` will have two layers of +indrection. We can avoid this by using `&str` instead, and letting `&String` +coerce to a `&str` whenever the function is invoked. + +## Example + +For this example, we will illustrate some differences for using `&String` as a +function argument versus using a `&str`, but the ideas apply as well to using +`&Vec<T>` versus using a `&[T]` or using a `&T` versus a `&Box<T>`. + +Consider an example where we wish to determine if a word contains three +consecutive vowels. We don't need to own the string to determine this, so we +will take a reference. + +The code might look something like this: + +```rust +fn three_vowels(word: &String) -> bool { + let mut vowel_count = 0; + for c in word.chars() { + match c { + 'a' | 'e' | 'i' | 'o' | 'u' => { + vowel_count += 1; + if vowel_count >= 3 { + return true + } + } + _ => vowel_count = 0 + } + } + false +} + +fn main() { + let ferris = "Ferris".to_string(); + let curious = "Curious".to_string(); + println!("{}: {}", ferris, three_vowels(&ferris)); + println!("{}: {}", curious, three_vowels(&curious)); + + // This works fine, but the following two lines would fail: + // println!("Ferris: {}", three_vowels("Ferris")); + // println!("Curious: {}", three_vowels("Curious")); + +} +``` + +This works fine because we are passing a `&String` type as a parameter. +If we comment in the last two lines this example fails because a `&str` type +will not coerce to a `&String` type. We can fix this by simply modifying the +type for our argument. + +For instance, if we change our function declaration to: + +```rust, ignore +fn three_vowels(word: &str) -> bool { +``` + +then both versions will compile and print the same output. + +```bash +Ferris: false +Curious: true +``` + +But wait, that's not all! There is more to this story. +It's likely that you may say to yourself: that doesn't matter, I will never be +using a `&'static str` as an input anyways (as we did when we used `"Ferris"`). +Even ignoring this special example, you may still find that using `&str` will +give you more flexibility than using a `&String`. + +Let's now take an example where someone gives us a sentence, and we want to +determine if any of the words in the sentence has a word that contains three +consecutive vowels. +We probably should make use of the function we have already defined and simply +feed in each word from the sentence. + +An example of this could look like this: + +```rust +fn three_vowels(word: &str) -> bool { + let mut vowel_count = 0; + for c in word.chars() { + match c { + 'a' | 'e' | 'i' | 'o' | 'u' => { + vowel_count += 1; + if vowel_count >= 3 { + return true + } + } + _ => vowel_count = 0 + } + } + false +} + +fn main() { + let sentence_string = + "Once upon a time, there was a friendly curious crab named Ferris".to_string(); + for word in sentence_string.split(' ') { + if three_vowels(word) { + println!("{} has three consecutive vowels!", word); + } + } +} +``` + +Running this example using our function declared with an argument type `&str` +will yield + +```bash +curious has three consecutive vowels! +``` + +However, this example will not run when our function is declared with an +argument type `&String`. This is because string slices are a `&str` and not a +`&String` which would require an allocation to be converted to `&String` which +is not implicit, whereas converting from `String` to `&str` is cheap and implicit. + +## See also + +- [Rust Language Reference on Type Coercions](https://doc.rust-lang.org/reference/type-coercions.html) +- For more discussion on how to handle `String` and `&str` see + [this blog series (2015)](https://web.archive.org/web/20201112023149/https://hermanradtke.com/2015/05/03/string-vs-str-in-rust-functions.html) + by Herman J. Radtke III diff --git a/src/idioms/concat-format.md b/src/idioms/concat-format.md new file mode 100644 index 0000000..372d86e --- /dev/null +++ b/src/idioms/concat-format.md @@ -0,0 +1,33 @@ +# Concatenating strings with `format!` + +## Description + +It is possible to build up strings using the `push` and `push_str` methods on a +mutable `String`, or using its `+` operator. However, it is often more +convenient to use `format!`, especially where there is a mix of literal and +non-literal strings. + +## Example + +```rust +fn say_hello(name: &str) -> String { + // We could construct the result string manually. + // let mut result = "Hello ".to_owned(); + // result.push_str(name); + // result.push('!'); + // result + + // But using format! is better. + format!("Hello {}!", name) +} +``` + +## Advantages + +Using `format!` is usually the most succinct and readable way to combine strings. + +## Disadvantages + +It is usually not the most efficient way to combine strings - a series of `push` +operations on a mutable string is usually the most efficient (especially if the +string has been pre-allocated to the expected size). diff --git a/src/idioms/ctor.md b/src/idioms/ctor.md new file mode 100644 index 0000000..aef2aae --- /dev/null +++ b/src/idioms/ctor.md @@ -0,0 +1,36 @@ +# Constructors + +## Description + +Rust does not have constructors as a language construct. Instead, the +convention is to use a static `new` method to create an object. + +## Example + +```rust,ignore +// A Rust vector, see liballoc/vec.rs +pub struct Vec<T> { + buf: RawVec<T>, + len: usize, +} + +impl<T> Vec<T> { + // Constructs a new, empty `Vec<T>`. + // Note this is a static method - no self. + // This constructor doesn't take any arguments, but some might in order to + // properly initialise an object + pub fn new() -> Vec<T> { + // Create a new Vec with fields properly initialised. + Vec { + // Note that here we are calling RawVec's constructor. + buf: RawVec::new(), + len: 0, + } + } +} +``` + +## See also + +The [builder pattern](../patterns/builder.md) for constructing objects where +there are multiple configurations. diff --git a/src/idioms/default.md b/src/idioms/default.md new file mode 100644 index 0000000..4717a02 --- /dev/null +++ b/src/idioms/default.md @@ -0,0 +1,62 @@ +# The `Default` Trait + +## Description + +Many types in Rust have a [constructor]. However, this is *specific* to the +type; Rust cannot abstract over "everything that has a `new()` method". To +allow this, the [`Default`] trait was conceived, which can be used with +containers and other generic types (e.g. see [`Option::unwrap_or_default()`]). +Notably, some containers already implement it where applicable. + +Not only do one-element containers like `Cow`, `Box` or `Arc` implement +`Default` for contained `Default` types, one can automatically +`#[derive(Default)]` for structs whose fields all implement it, so the more +types implement `Default`, the more useful it becomes. + +On the other hand, constructors can take multiple arguments, while the +`default()` method does not. There can even be multiple constructors with +different names, but there can only be one `Default` implementation per type. + +## Example + +```rust +use std::{path::PathBuf, time::Duration}; + +// note that we can simply auto-derive Default here. +#[derive(Default, Debug)] +struct MyConfiguration { + // Option defaults to None + output: Option<PathBuf>, + // Vecs default to empty vector + search_path: Vec<PathBuf>, + // Duration defaults to zero time + timeout: Duration, + // bool defaults to false + check: bool, +} + +impl MyConfiguration { + // add setters here +} + +fn main() { + // construct a new instance with default values + let mut conf = MyConfiguration::default(); + // do something with conf here + conf.check = true; + println!("conf = {:#?}", conf); +} +``` + +## See also + +- The [constructor] idiom is another way to generate instances that may or may +not be "default" +- The [`Default`] documentation (scroll down for the list of implementors) +- [`Option::unwrap_or_default()`] +- [`derive(new)`] + +[constructor]: ctor.md +[`Default`]: https://doc.rust-lang.org/stable/std/default/trait.Default.html +[`Option::unwrap_or_default()`]: https://doc.rust-lang.org/stable/std/option/enum.Option.html#method.unwrap_or_default +[`derive(new)`]: https://crates.io/crates/derive-new/ diff --git a/src/idioms/deref.md b/src/idioms/deref.md new file mode 100644 index 0000000..1815301 --- /dev/null +++ b/src/idioms/deref.md @@ -0,0 +1,79 @@ +# Collections are smart pointers + +## Description + +Use the `Deref` trait to treat collections like smart pointers, offering owning +and borrowed views of data. + +## Example + +```rust,ignore +use std::ops::Deref; + +struct Vec<T> { + data: T, + //.. +} + +impl<T> Deref for Vec<T> { + type Target = [T]; + + fn deref(&self) -> &[T] { + //.. + } +} +``` + +A `Vec<T>` is an owning collection of `T`s, a slice (`&[T]`) is a borrowed +collection of `T`s. Implementing `Deref` for `Vec` allows implicit dereferencing +from `&Vec<T>` to `&[T]` and includes the relationship in auto-derefencing +searches. Most methods you might expect to be implemented for `Vec`s are instead +implemented for slices. + +See also `String` and `&str`. + +## Motivation + +Ownership and borrowing are key aspects of the Rust language. Data structures +must account for these semantics properly in order to give a good user +experience. When implementing a data structure which owns its data, offering a +borrowed view of that data allows for more flexible APIs. + +## Advantages + +Most methods can be implemented only for the borrowed view, they are then +implicitly available for the owning view. + +Gives clients a choice between borrowing or taking ownership of data. + +## Disadvantages + +Methods and traits only available via dereferencing are not taken into account +when bounds checking, so generic programming with data structures using this +pattern can get complex (see the `Borrow` and `AsRef` traits, etc.). + +## Discussion + +Smart pointers and collections are analogous: a smart pointer points to a single +object, whereas a collection points to many objects. From the point of view of +the type system there is little difference between the two. A collection owns +its data if the only way to access each datum is via the collection and the +collection is responsible for deleting the data (even in cases of shared +ownership, some kind of borrowed view may be appropriate). If a collection owns +its data, it is usually useful to provide a view of the data as borrowed so that +it can be multiply referenced. + +Most smart pointers (e.g., `Foo<T>`) implement `Deref<Target=T>`. However, +collections will usually dereference to a custom type. `[T]` and `str` have some +language support, but in the general case, this is not necessary. `Foo<T>` can +implement `Deref<Target=Bar<T>>` where `Bar` is a dynamically sized type and +`&Bar<T>` is a borrowed view of the data in `Foo<T>`. + +Commonly, ordered collections will implement `Index` for `Range`s to provide +slicing syntax. The target will be the borrowed view. + +## See also + +[Deref polymorphism anti-pattern](../anti_patterns/deref.md). + +[Documentation for `Deref` trait](https://doc.rust-lang.org/std/ops/trait.Deref.html). diff --git a/src/idioms/dtor-finally.md b/src/idioms/dtor-finally.md new file mode 100644 index 0000000..9c6b037 --- /dev/null +++ b/src/idioms/dtor-finally.md @@ -0,0 +1,90 @@ +# Finalisation in destructors + +## Description + +Rust does not provide the equivalent to `finally` blocks - code that will be +executed no matter how a function is exited. Instead an object's destructor can +be used to run code that must be run before exit. + +## Example + +```rust,ignore +fn bar() -> Result<(), ()> { + // These don't need to be defined inside the function. + struct Foo; + + // Implement a destructor for Foo. + impl Drop for Foo { + fn drop(&mut self) { + println!("exit"); + } + } + + // The dtor of _exit will run however the function `bar` is exited. + let _exit = Foo; + // Implicit return with `?` operator. + baz()?; + // Normal return. + Ok(()) +} +``` + +## Motivation + +If a function has multiple return points, then executing code on exit becomes +difficult and repetitive (and thus bug-prone). This is especially the case where +return is implicit due to a macro. A common case is the `?` operator which +returns if the result is an `Err`, but continues if it is `Ok`. `?` is used as +an exception handling mechanism, but unlike Java (which has `finally`), there is +no way to schedule code to run in both the normal and exceptional cases. +Panicking will also exit a function early. + +## Advantages + +Code in destructors will (nearly) always be run - copes with panics, early +returns, etc. + +## Disadvantages + +It is not guaranteed that destructors will run. For example, if there is an +infinite loop in a function or if running a function crashes before exit. +Destructors are also not run in the case of a panic in an already panicking +thread. Therefore destructors cannot be relied on as finalisers where it is +absolutely essential that finalisation happens. + +This pattern introduces some hard to notice, implicit code. Reading a function +gives no clear indication of destructors to be run on exit. This can make +debugging tricky. + +Requiring an object and `Drop` impl just for finalisation is heavy on boilerplate. + +## Discussion + +There is some subtlety about how exactly to store the object used as a +finaliser. It must be kept alive until the end of the function and must then be +destroyed. The object must always be a value or uniquely owned pointer (e.g., +`Box<Foo>`). If a shared pointer (such as `Rc`) is used, then the finaliser can +be kept alive beyond the lifetime of the function. For similar reasons, the +finaliser should not be moved or returned. + +The finaliser must be assigned into a variable, otherwise it will be destroyed +immediately, rather than when it goes out of scope. The variable name must start +with `_` if the variable is only used as a finaliser, otherwise the compiler +will warn that the finaliser is never used. However, do not call the variable +`_` with no suffix - in that case it will be destroyed immediately. + +In Rust, destructors are run when an object goes out of scope. This happens +whether we reach the end of block, there is an early return, or the program +panics. When panicking, Rust unwinds the stack running destructors for each +object in each stack frame. So, destructors get called even if the panic happens +in a function being called. + +If a destructor panics while unwinding, there is no good action to take, so Rust +aborts the thread immediately, without running further destructors. This means +that destructors are not absolutely guaranteed to run. It also means that you +must take extra care in your destructors not to panic, since it could leave +resources in an unexpected state. + +## See also + +[RAII](../patterns/RAII.md). diff --git a/src/idioms/ffi-accepting-strings.md b/src/idioms/ffi-accepting-strings.md new file mode 100644 index 0000000..98f364d --- /dev/null +++ b/src/idioms/ffi-accepting-strings.md @@ -0,0 +1,125 @@ +# Accepting Strings + +## Description + +When accepting strings via FFI through pointers, there are two principles that +should be followed: + +1. Keep foreign strings "borrowed", rather than copying them directly. +2. Minimize `unsafe` code during the conversion. + +## Motivation + +Rust has built-in support for C-style strings with its `CString` and `CStr` +types. However, there are different approaches one can take with strings that +are being accepted from a foreign caller of a Rust function. + +The best practice is simple: use `CStr` in such a way as to minimize unsafe +code, and create a borrowed slice. If an owned String is needed, call +`to_string()` on the string slice. + +## Code Example + +```rust,ignore +pub mod unsafe_module { + + // other module content + + #[no_mangle] + pub extern "C" fn mylib_log(msg: *const libc::c_char, level: libc::c_int) { + let level: crate::LogLevel = match level { /* ... */ }; + + let msg_str: &str = unsafe { + // SAFETY: accessing raw pointers expected to live for the call, + // and creating a shared reference that does not outlive the current + // stack frame. + match std::ffi::CStr::from_ptr(msg).to_str() { + Ok(s) => s, + Err(e) => { + crate::log_error("FFI string conversion failed"); + return; + } + } + }; + + crate::log(msg_str, level); + } +} +``` + +## Advantages + +The example is is written to ensure that: + +1. The `unsafe` block is as small as possible. +2. The pointer with an "untracked" lifetime becomes a "tracked" shared + reference + +Consider an alternative, where the string is actually copied: + +```rust,ignore +pub mod unsafe_module { + + // other module content + + pub extern "C" fn mylib_log(msg: *const libc::c_char, level: libc::c_int) { + // DO NOT USE THIS CODE. + // IT IS UGLY, VERBOSE, AND CONTAINS A SUBTLE BUG. + + let level: crate::LogLevel = match level { /* ... */ }; + + let msg_len = unsafe { /* SAFETY: strlen is what it is, I guess? */ + libc::strlen(msg) + }; + + let mut msg_data = Vec::with_capacity(msg_len + 1); + + let msg_cstr: std::ffi::CString = unsafe { + // SAFETY: copying from a foreign pointer expected to live + // for the entire stack frame into owned memory + std::ptr::copy_nonoverlapping(msg, msg_data.as_mut(), msg_len); + + msg_data.set_len(msg_len + 1); + + std::ffi::CString::from_vec_with_nul(msg_data).unwrap() + } + + let msg_str: String = unsafe { + match msg_cstr.into_string() { + Ok(s) => s, + Err(e) => { + crate::log_error("FFI string conversion failed"); + return; + } + } + }; + + crate::log(&msg_str, level); + } +} +``` + +This code in inferior to the original in two respects: + +1. There is much more `unsafe` code, and more importantly, more invariants it + must uphold. +2. Due to the extensive arithmetic required, there is a bug in this version + that cases Rust `undefined behaviour`. + +The bug here is a simple mistake in pointer arithmetic: the string was copied, +all `msg_len` bytes of it. However, the `NUL` terminator at the end was not. + +The Vector then had its size *set* to the length of the *zero padded string* -- +rather than *resized* to it, which could have added a zero at the end. +As a result, the last byte in the Vector is uninitialized memory. +When the `CString` is created at the bottom of the block, its read of the +Vector will cause `undefined behaviour`! + +Like many such issues, this would be difficult issue to track down. +Sometimes it would panic because the string was not `UTF-8`, sometimes it would +put a weird character at the end of the string, sometimes it would just +completely crash. + +## Disadvantages + +None? diff --git a/src/idioms/ffi-errors.md b/src/idioms/ffi-errors.md new file mode 100644 index 0000000..59d0c91 --- /dev/null +++ b/src/idioms/ffi-errors.md @@ -0,0 +1,139 @@ +# Error Handling in FFI + +## Description + +In foreign languages like C, errors are represented by return codes. +However, Rust's type system allows much more rich error information to be +captured a propogated through a full type. + +This best practice shows different kinds of error codes, and how to expose them +in a usable way: + +1. Flat Enums should be converted to integers and returned as codes. +2. Structured Enums should be converted to an integer code with a string error + message for detail. +3. Custom Error Types should become "transparent", with a C representation. + +## Code Example + +### Flat Enums + +```rust,ignore +enum DatabaseError { + IsReadOnly = 1, // user attempted a write operation + IOError = 2, // user should read the C errno() for what it was + FileCorrupted = 3, // user should run a repair tool to recover it +} + +impl From<DatabaseError> for libc::c_int { + fn from(e: DatabaseError) -> libc::c_int { + (e as i8).into() + } +} +``` + +### Structured Enums + +```rust,ignore +pub mod errors { + enum DatabaseError { + IsReadOnly, + IOError(std::io::Error), + FileCorrupted(String), // message describing the issue + } + + impl From<DatabaseError> for libc::c_int { + fn from(e: DatabaseError) -> libc::c_int { + match e { + DatabaseError::IsReadOnly => 1, + DatabaseError::IOError(_) => 2, + DatabaseError::FileCorrupted(_) => 3, + } + } + } +} + +pub mod c_api { + use super::errors::DatabaseError; + + #[no_mangle] + pub extern "C" fn db_error_description( + e: *const DatabaseError + ) -> *mut libc::c_char { + + let error: &DatabaseError = unsafe { + // SAFETY: pointer lifetime is greater than the current stack frame + &*e + }; + + let error_str: String = match error { + DatabaseError::IsReadOnly => { + format!("cannot write to read-only database"); + } + DatabaseError::IOError(e) => { + format!("I/O Error: {}", e); + } + DatabaseError::FileCorrupted(s) => { + format!("File corrupted, run repair: {}", &s); + } + }; + + let c_error = unsafe { + // SAFETY: copying error_str to an allocated buffer with a NUL + // character at the end + let mut malloc: *mut u8 = libc::malloc(error_str.len() + 1) as *mut _; + + if malloc.is_null() { + return std::ptr::null_mut(); + } + + let src = error_str.as_bytes().as_ptr(); + + std::ptr::copy_nonoverlapping(src, malloc, error_str.len()); + + std::ptr::write(malloc.add(error_str.len()), 0); + + malloc as *mut libc::c_char + }; + + c_error + } +} +``` + +### Custom Error Types + +```rust,ignore +struct ParseError { + expected: char, + line: u32, + ch: u16 +} + +impl ParseError { /* ... */ } + +/* Create a second version which is exposed as a C structure */ +#[repr(C)] +pub struct parse_error { + pub expected: libc::c_char, + pub line: u32, + pub ch: u16 +} + +impl From<ParseError> for parse_error { + fn from(e: ParseError) -> parse_error { + let ParseError { expected, line, ch } = e; + parse_error { expected, line, ch } + } +} +``` + +## Advantages + +This ensures that the foreign language has clear access to error information +while not compromising the Rust code's API at all. + +## Disadvantages + +It's a lot of typing, and some types may not be able to be converted easily +to C. diff --git a/src/idioms/ffi-intro.md b/src/idioms/ffi-intro.md new file mode 100644 index 0000000..26fc542 --- /dev/null +++ b/src/idioms/ffi-intro.md @@ -0,0 +1,14 @@ +# FFI Idioms + +Writing FFI code is an entire course in itself. +However, there are several idioms here that can act as pointers, and avoid +traps for inexperienced users of `unsafe` Rust. + +This section contains idioms that may be useful when doing FFI. + +1. [Idiomatic Errors](./ffi-errors.md) - Error handling with integer codes and + sentinel return values (such as `NULL` pointers) + +2. [Accepting Strings](./ffi-accepting-strings.md) with minimal unsafe code + +3. [Passing Strings](./ffi-passing-strings.md) to FFI functions diff --git a/src/idioms/ffi-passing-strings.md b/src/idioms/ffi-passing-strings.md new file mode 100644 index 0000000..18de5c8 --- /dev/null +++ b/src/idioms/ffi-passing-strings.md @@ -0,0 +1,105 @@ +# Passing Strings + +## Description + +When passing strings to FFI functions, there are four principles that should be +followed: + +1. Make the lifetime of owned strings as long as possible. +2. Minimize `unsafe` code during the conversion. +3. If the C code can modify the string data, use `Vec` instead of `CString`. +4. Unless the Foreign Function API requires it, the ownership of the string + should not transfer to the callee. + +## Motivation + +Rust has built-in support for C-style strings with its `CString` and `CStr` +types. However, there are different approaches one can take with strings that +are being sent to a foreign function call from a Rust function. + +The best practice is simple: use `CString` in such a way as to minimize +`unsafe` code. However, a secondary caveat is that +*the object must live long enough*, meaning the lifetime should be maximized. +In addition, the documentation explains that "round-tripping" a `CString` after +modification is UB, so additional work is necessary in that case. + +## Code Example + +```rust,ignore +pub mod unsafe_module { + + // other module content + + extern "C" { + fn seterr(message: *const libc::c_char); + fn geterr(buffer: *mut libc::c_char, size: libc::c_int) -> libc::c_int; + } + + fn report_error_to_ffi<S: Into<String>>( + err: S + ) -> Result<(), std::ffi::NulError>{ + let c_err = std::ffi::CString::new(err.into())?; + + unsafe { + // SAFETY: calling an FFI whose documentation says the pointer is + // const, so no modification should occur + seterr(c_err.as_ptr()); + } + + Ok(()) + // The lifetime of c_err continues until here + } + + fn get_error_from_ffi() -> Result<String, std::ffi::IntoStringError> { + let mut buffer = vec![0u8; 1024]; + unsafe { + // SAFETY: calling an FFI whose documentation implies + // that the input need only live as long as the call + let written: usize = geterr(buffer.as_mut_ptr(), 1023).into(); + + buffer.truncate(written + 1); + } + + std::ffi::CString::new(buffer).unwrap().into_string() + } +} +``` + +## Advantages + +The example is written in a way to ensure that: + +1. The `unsafe` block is as small as possible. +2. The `CString` lives long enough. +3. Errors with typecasts are always propagated when possible. + +A common mistake (so common it's in the documentation) is to not use the +variable in the first block: + +```rust,ignore +pub mod unsafe_module { + + // other module content + + fn report_error<S: Into<String>>(err: S) -> Result<(), std::ffi::NulError> { + unsafe { + // SAFETY: whoops, this contains a dangling pointer! + seterr(std::ffi::CString::new(err.into())?.as_ptr()); + } + Ok(()) + } +} +``` + +This code will result in a dangling pointer, because the lifetime of the +`CString` is not extended by the pointer creation, unlike if a reference were +created. + +Another issue frequently raised is that the initialization of a 1k vector of +zeroes is "slow". However, recent versions of Rust actually optimize that +particular macro to a call to `zmalloc`, meaning it is as fast as the operating +system's ability to return zeroed memory (which is quite fast). + +## Disadvantages + +None? diff --git a/src/idioms/index.md b/src/idioms/index.md new file mode 100644 index 0000000..e03fcfa --- /dev/null +++ b/src/idioms/index.md @@ -0,0 +1,18 @@ +# Idioms + +[Idioms](https://en.wikipedia.org/wiki/Programming_idiom) are commonly used +styles and patterns largely agreed upon by a community. They are guidelines. +Writing idiomatic code allows other developers to understand what is happening +because they are familiar with the form that it has. + +The computer understands the machine code that is generated by the compiler. +The language is therefore mostly beneficial to the developer. +So, since we have this abstraction layer, why not put it to good use and make +it simple? + +Remember the [KISS principle](https://en.wikipedia.org/wiki/KISS_principle): +"Keep It Simple, Stupid". It claims that "most systems work best if they are +kept simple rather than made complicated; therefore, simplicity should be a key +goal in design, and unnecessary complexity should be avoided". + +> Code is there for humans, not computers, to understand. diff --git a/src/idioms/mem-replace.md b/src/idioms/mem-replace.md new file mode 100644 index 0000000..49d6891 --- /dev/null +++ b/src/idioms/mem-replace.md @@ -0,0 +1,122 @@ +# `mem::{take(_), replace(_)}` to keep owned values in changed enums + +## Description + +Say we have a `&mut MyEnum` which has (at least) two variants, +`A { name: String, x: u8 }` and `B { name: String }`. Now we want to change +`MyEnum::A` to a `B` if `x` is zero, while keeping `MyEnum::B` intact. + +We can do this without cloning the `name`. + +## Example + +```rust +use std::mem; + +enum MyEnum { + A { name: String, x: u8 }, + B { name: String } +} + +fn a_to_b(e: &mut MyEnum) { + + // we mutably borrow `e` here. This precludes us from changing it directly + // as in `*e = ...`, because the borrow checker won't allow it. Therefore + // the assignment to `e` must be outside the `if let` clause. + *e = if let MyEnum::A { ref mut name, x: 0 } = *e { + + // this takes out our `name` and put in an empty String instead + // (note that empty strings don't allocate). + // Then, construct the new enum variant (which will + // be assigned to `*e`, because it is the result of the `if let` expression). + MyEnum::B { name: mem::take(name) } + + // In all other cases, we return immediately, thus skipping the assignment + } else { return } +} +``` + +This also works with more variants: + +```rust +use std::mem; + +enum MultiVariateEnum { + A { name: String }, + B { name: String }, + C, + D +} + +fn swizzle(e: &mut MultiVariateEnum) { + use MultiVariateEnum::*; + *e = match *e { + // Ownership rules do not allow taking `name` by value, but we cannot + // take the value out of a mutable reference, unless we replace it: + A { ref mut name } => B { name: mem::take(name) }, + B { ref mut name } => A { name: mem::take(name) }, + C => D, + D => C + } +} +``` + +## Motivation + +When working with enums, we may want to change an enum value in place, perhaps +to another variant. This is usually done in two phases to keep the borrow +checker happy. In the first phase, we observe the existing value and look at +its parts to decide what to do next. In the second phase we may conditionally +change the value (as in the example above). + +The borrow checker won't allow us to take out `name` of the enum (because +*something* must be there. We could of course `.clone()` name and put the clone +into our `MyEnum::B`, but that would be an instance of the [Clone to satisfy +the borrow checker] antipattern. Anyway, we can avoid the extra allocation by +changing `e` with only a mutable borrow. + +`mem::take` lets us swap out the value, replacing it with it's default value, +and returning the previous value. For `String`, the default value is an empty +`String`, which does not need to allocate. As a result, we get the original +`name` *as an owned value*. We can then wrap this in another enum. + +__NOTE:__ `mem::replace` is very similar, but allows us to specify what to +replace the value with. An equivalent to our `mem::take` line would be +`mem::replace(name, String::new())`. + +Note, however, that if we are using an `Option` and want to replace its +value with a `None`, `Option`’s `take()` method provides a shorter and +more idiomatic alternative. + +## Advantages + +Look ma, no allocation! Also you may feel like Indiana Jones while doing it. + +## Disadvantages + +This gets a bit wordy. Getting it wrong repeatedly will make you hate the +borrow checker. The compiler may fail to optimize away the double store, +resulting in reduced performance as opposed to what you'd do in unsafe +languages. + +Furthermore, the type you are taking needs to implement the [`Default` +trait](./default.md). However, if the type you're working with doesn't +implement this, you can instead use `mem::replace`. + +## Discussion + +This pattern is only of interest in Rust. In GC'd languages, you'd take the +reference to the value by default (and the GC would keep track of refs), and in +other low-level languages like C you'd simply alias the pointer and fix things +later. + +However, in Rust, we have to do a little more work to do this. An owned value +may only have one owner, so to take it out, we need to put something back in – +like Indiana Jones, replacing the artifact with a bag of sand. + +## See also + +This gets rid of the [Clone to satisfy the borrow checker] antipattern in a +specific case. + +[Clone to satisfy the borrow checker](TODO: Hinges on PR #23) diff --git a/src/idioms/on-stack-dyn-dispatch.md b/src/idioms/on-stack-dyn-dispatch.md new file mode 100644 index 0000000..c076458 --- /dev/null +++ b/src/idioms/on-stack-dyn-dispatch.md @@ -0,0 +1,92 @@ +# On-Stack Dynamic Dispatch + +## Description + +We can dynamically dispatch over multiple values, however, to do so, we need +to declare multiple variables to bind differently-typed objects. To extend the +lifetime as necessary, we can use deferred conditional initialization, as seen +below: + +## Example + +```rust +use std::io; +use std::fs; + +# fn main() -> Result<(), Box<dyn std::error::Error>> { +# let arg = "-"; + +// These must live longer than `readable`, and thus are declared first: +let (mut stdin_read, mut file_read); + +// We need to ascribe the type to get dynamic dispatch. +let readable: &mut dyn io::Read = if arg == "-" { + stdin_read = io::stdin(); + &mut stdin_read +} else { + file_read = fs::File::open(arg)?; + &mut file_read +}; + +// Read from `readable` here. + +# Ok(()) +# } +``` + +## Motivation + +Rust monomorphises code by default. This means a copy of the code will be +generated for each type it is used with and optimized independently. While this +allows for very fast code on the hot path, it also bloats the code in places +where performance is not of the essence, thus costing compile time and cache +usage. + +Luckily, Rust allows us to use dynamic dispatch, but we have to explicitly ask +for it. + +## Advantages + +We do not need to allocate anything on the heap. Neither do we need to +initialize something we won't use later, nor do we need to monomorphize the +whole code that follows to work with both `File` or `Stdin`. + +## Disadvantages + +The code needs more moving parts than the `Box`-based version: + +```rust,ignore +// We still need to ascribe the type for dynamic dispatch. +let readable: Box<dyn io::Read> = if arg == "-" { + Box::new(io::stdin()) +} else { + Box::new(fs::File::open(arg)?) +}; +// Read from `readable` here. +``` + +## Discussion + +Rust newcomers will usually learn that Rust requires all variables to be +initialized *before use*, so it's easy to overlook the fact that *unused* +variables may well be uninitialized. Rust works quite hard to ensure that this +works out fine and only the initialized values are dropped at the end of their +scope. + +The example meets all the constraints Rust places on us: + +* All variables are initialized before using (in this case borrowing) them +* Each variable only holds values of a single type. In our example, `stdin` is +of type `Stdin`, `file` is of type `File` and `readable` is of type `&mut dyn +Read` +* Each borrowed value outlives all the references borrowed from it + +## See also + +* [Finalisation in destructors](dtor-finally.md) and +[RAII guards](../patterns/RAII.md) can benefit from tight control over lifetimes. +* For conditionally filled `Option<&T>`s of (mutable) references, one can +initialize an `Option<T>` directly and use its [`.as_ref()`] method to get an +optional reference. + +[`.as_ref()`]: https://doc.rust-lang.org/std/option/enum.Option.html#method.as_ref diff --git a/src/idioms/option-iter.md b/src/idioms/option-iter.md new file mode 100644 index 0000000..e1c74bd --- /dev/null +++ b/src/idioms/option-iter.md @@ -0,0 +1,59 @@ +# Iterating over an `Option` + +## Description + +`Option` can be viewed as a container that contains either zero or one +elements. In particular, it implements the `IntoIterator` trait, and as such +can be used with generic code that needs such a type. + +## Examples + +Since `Option` implements `IntoIterator`, it can be used as an argument to +[`.extend()`](https://doc.rust-lang.org/std/iter/trait.Extend.html#tymethod.extend): + +```rust +let turing = Some("Turing"); +let mut logicians = vec!["Curry", "Kleene", "Markov"]; + +logicians.extend(turing); + +// equivalent to +if let Some(turing_inner) = turing { + logicians.push(turing_inner); +} +``` + +If you need to tack an `Option` to the end of an existing iterator, you can +pass it to [`.chain()`](https://doc.rust-lang.org/std/iter/trait.Iterator.html#method.chain): + +```rust +let turing = Some("Turing"); +let logicians = vec!["Curry", "Kleene", "Markov"]; + +for logician in logicians.iter().chain(turing.iter()) { + println!("{} is a logician", logician); +} +``` + +Note that if the `Option` is always `Some`, then it is more idiomatic to use +[`std::iter::once`](https://doc.rust-lang.org/std/iter/fn.once.html) on the +element instead. + +Also, since `Option` implements `IntoIterator`, it's possible to iterate over +it using a `for` loop. This is equivalent to matching it with `if let Some(..)`, +and in most cases you should prefer the latter. + +## See also + +* [`std::iter::once`](https://doc.rust-lang.org/std/iter/fn.once.html) is an +iterator which yields exactly one element. It's a more readable alternative to +`Some(foo).into_iter()`. + +* [`Iterator::filter_map`](https://doc.rust-lang.org/std/iter/trait.Iterator.html#method.filter_map) + is a version of [`Iterator::flat_map`](https://doc.rust-lang.org/std/iter/trait.Iterator.html#method.flat_map), + specialized to mapping functions which return `Option`. + +* The [`ref_slice`](https://crates.io/crates/ref_slice) crate provides functions + for converting an `Option` to a zero- or one-element slice. + +* [Documentation for `Option<T>`](https://doc.rust-lang.org/std/option/enum.Option.html) diff --git a/src/idioms/pass-var-to-closure.md b/src/idioms/pass-var-to-closure.md new file mode 100644 index 0000000..81e5b51 --- /dev/null +++ b/src/idioms/pass-var-to-closure.md @@ -0,0 +1,59 @@ +# Pass variables to closure + +## Description + +By default, closures capture their environment by borrowing. Or you can use +`move`-closure to move whole environment. However, often you want to move just +some variables to closure, give it copy of some data, pass it by reference, or +perform some other transformation. + +Use variable rebinding in separate scope for that. + +## Example + +Use + +```rust +use std::rc::Rc; + +let num1 = Rc::new(1); +let num2 = Rc::new(2); +let num3 = Rc::new(3); +let closure = { + // `num1` is moved + let num2 = num2.clone(); // `num2` is cloned + let num3 = num3.as_ref(); // `num3` is borrowed + move || { + *num1 + *num2 + *num3; + } +}; +``` + +instead of + +```rust +use std::rc::Rc; + +let num1 = Rc::new(1); +let num2 = Rc::new(2); +let num3 = Rc::new(3); + +let num2_cloned = num2.clone(); +let num3_borrowed = num3.as_ref(); +let closure = move || { + *num1 + *num2_cloned + *num3_borrowed; +}; +``` + +## Advantages + +Copied data are grouped together with closure definition, so their purpose is +more clear and they will be dropped immediately even if they are not consumed +by closure. + +Closure uses same variable names as surrounding code whether data are copied or +moved. + +## Disadvantages + +Additional indentation of closure body. diff --git a/src/idioms/priv-extend.md b/src/idioms/priv-extend.md new file mode 100644 index 0000000..d0c115b --- /dev/null +++ b/src/idioms/priv-extend.md @@ -0,0 +1,46 @@ +# Privacy for extensibility + +## Description + +Use a private field to ensure that a struct is extensible without breaking +stability guarantees. + +## Example + +```rust,ignore +mod a { + // Public struct. + pub struct S { + pub foo: i32, + // Private field. + bar: i32, + } +} + +fn main(s: a::S) { + // Because S::bar is private, it cannot be named here and we must use `..` + // in the pattern. + let a::S { foo: _, ..} = s; +} + +``` + +## Discussion + +Adding a field to a struct is a mostly backwards compatible change. +However, if a client uses a pattern to deconstruct a struct instance, they +might name all the fields in the struct and adding a new one would break that +pattern. The client could name some of the fields and use `..` in the pattern, +in which case adding another field is backwards compatible. Making at least one +of the struct's fields private forces clients to use the latter form of patterns, +ensuring that the struct is future-proof. + +The downside of this approach is that you might need to add an otherwise unneeded +field to the struct. You can use the `()` type so that there is no runtime overhead +and prepend `_` to the field name to avoid the unused field warning. + +If Rust allowed private variants of enums, we could use the same trick to make +adding a variant to an enum backwards compatible. The problem there is exhaustive +match expressions. A private variant would force clients to have a `_` wildcard +pattern. A common way to implement this instead is using the [`#[non_exhaustive]`](<https://doc.rust-lang.org/reference/attributes/type_system.html>) +attribute. diff --git a/src/idioms/rustdoc-init.md b/src/idioms/rustdoc-init.md new file mode 100644 index 0000000..63f7d1a --- /dev/null +++ b/src/idioms/rustdoc-init.md @@ -0,0 +1,94 @@ +# Easy doc initialization + +## Description + +If a struct takes significant effort to initialize, when writing docs, it can be +quicker to wrap your example with a helper function which takes the struct as an +argument. + +## Motivation + +Sometimes there is a struct with multiple or complicated parameters and several +methods. Each of these methods should have examples. + +For example: + +```rust,ignore +struct Connection { + name: String, + stream: TcpStream, +} + +impl Connection { + /// Sends a request over the connection. + /// + /// # Example + /// ```no_run + /// # // Boilerplate are required to get an example working. + /// # let stream = TcpStream::connect("127.0.0.1:34254"); + /// # let connection = Connection { name: "foo".to_owned(), stream }; + /// # let request = Request::new("RequestId", RequestType::Get, "payload"); + /// let response = connection.send_request(request); + /// assert!(response.is_ok()); + /// ``` + fn send_request(&self, request: Request) -> Result<Status, SendErr> { + // ... + } + + /// Oh no, all that boilerplate needs to be repeated here! + fn check_status(&self) -> Status { + // ... + } +} +``` + +## Example + +Instead of typing all of this boiler plate to create an `Connection` and +`Request` it is easier to just create a wrapping helper function which takes +them as arguments: + +```rust,ignore +struct Connection { + name: String, + stream: TcpStream, +} + +impl Connection { + /// Sends a request over the connection. + /// + /// # Example + /// ``` + /// # fn call_send(connection: Connection, request: Request) { + /// let response = connection.send_request(request); + /// assert!(response.is_ok()); + /// # } + /// ``` + fn send_request(&self, request: Request) { + // ... + } +} +``` + +**Note** in the above example the line `assert!(response.is_ok());` will not +actually run while testing because it is inside of a function which is never +invoked. + +## Advantages + +This is much more concise and avoids repetitive code in examples. + +## Disadvantages + +As example is in a function, the code will not be tested. Though it still will +checked to make sure it compiles when running a `cargo test`. So this pattern is +most useful when need `no_run`. With this, you do not need to add `no_run`. + +## Discussion + +If assertions are not required this pattern works well. + +If they are, an alternative can be to create a public method to create a helper +instance which is annotated with `#[doc(hidden)]` (so that users won't see it). +Then this method can be called inside of rustdoc because it is part of the +crate's public API. diff --git a/src/idioms/temporary-mutability.md b/src/idioms/temporary-mutability.md new file mode 100644 index 0000000..c6d51b3 --- /dev/null +++ b/src/idioms/temporary-mutability.md @@ -0,0 +1,45 @@ +# Temporary mutability + +## Description + +Often it is necessary to prepare and process some data, but after that data are +only inspected and never modified. The intention can be made explicit by redefining +the mutable variable as immutable. + +It can be done either by processing data within nested block or by redefining +variable. + +## Example + +Say, vector must be sorted before usage. + +Using nested block: + +```rust,ignore +let data = { + let mut data = get_vec(); + data.sort(); + data +}; + +// Here `data` is immutable. +``` + +Using variable rebinding: + +```rust,ignore +let mut data = get_vec(); +data.sort(); +let data = data; + +// Here `data` is immutable. +``` + +## Advantages + +Compiler ensures that you don't accidentally mutate data after some point. + +## Disadvantages + +Nested block requires additional indentation of block body. +One more line to return data from block or redefine variable. diff --git a/src/img/abstract-factory-example.png b/src/img/abstract-factory-example.png new file mode 100644 index 0000000..6b3a69a Binary files /dev/null and b/src/img/abstract-factory-example.png differ diff --git a/src/img/abstract-factory-structure.png b/src/img/abstract-factory-structure.png new file mode 100644 index 0000000..c9f8a2b Binary files /dev/null and b/src/img/abstract-factory-structure.png differ diff --git a/src/img/adapter-example.png b/src/img/adapter-example.png new file mode 100644 index 0000000..01594cc Binary files /dev/null and b/src/img/adapter-example.png differ diff --git a/src/img/adapter-structure.png b/src/img/adapter-structure.png new file mode 100644 index 0000000..9085236 Binary files /dev/null and b/src/img/adapter-structure.png differ diff --git a/src/img/chain-of-responsibility-structure.png b/src/img/chain-of-responsibility-structure.png new file mode 100644 index 0000000..1150fe8 Binary files /dev/null and b/src/img/chain-of-responsibility-structure.png differ diff --git a/src/img/rust-logo.png b/src/img/rust-logo.png new file mode 100644 index 0000000..a59dfe3 Binary files /dev/null and b/src/img/rust-logo.png differ diff --git a/src/lib.rs b/src/lib.rs deleted file mode 100644 index 2300b36..0000000 --- a/src/lib.rs +++ /dev/null @@ -1,34 +0,0 @@ -#![doc(html_logo_url = "https://dudochkin-victor.github.io/assets/ruex/logo.svg")] - -#![warn(missing_docs)] - -//! Design pattern framework on top of PureMVC. -//! -//! The PureMVC framework has a very narrow goal. That is to help you -//! separate your application’s coding interests into three discrete tiers: -//! [Model][2], [View][3] and [Controller][1]. -//! -//! This separation of interests, and the tightness and direction of the -//! couplings used to make them work together is of paramount -//! importance in the building of scalable and maintainable applications. -//! -//! In this implementation of the classic MVC Design meta-pattern, these -//! three tiers of the application are governed by three Singletons (a class -//! where only one instance may be created) called simply [Model][2], [View][3] -//! and [Controller][1]. Together, they are referred to as the ‘Core actors’. -//! -//! A fourth Singleton, the [Facade][4] simplifies development by providing a -//! single interface for communication with the Core actors. -//! -//! [Read more..][foundation] -//! -//! ![PureMVC Diagram](https://raw.githubusercontent.com/wiki/ohyo-io/wampire/images/pure-mvc.svg) -//! -//! [1]: crate::prelude::Controller -//! [2]: crate::prelude::Model -//! [3]: crate::prelude::View -//! [4]: crate::prelude::Facade -//! -pub mod foundation; - -pub mod prelude; diff --git a/src/patterns/RAII.md b/src/patterns/RAII.md new file mode 100644 index 0000000..d7b6dbf --- /dev/null +++ b/src/patterns/RAII.md @@ -0,0 +1,121 @@ +# RAII with guards + +## Description + +[RAII][wikipedia] stands for "Resource Acquisition is Initialisation" which is a +terrible name. The essence of the pattern is that resource initialisation is done +in the constructor of an object and finalisation in the destructor. This pattern +is extended in Rust by using an RAII object as a guard of some resource and relying +on the type system to ensure that access is always mediated by the guard object. + +## Example + +Mutex guards are the classic example of this pattern from the std library (this +is a simplified version of the real implementation): + +```rust,ignore +use std::ops::Deref; + +struct Foo {} + +struct Mutex<T> { + // We keep a reference to our data: T here. + //.. +} + +struct MutexGuard<'a, T: 'a> { + data: &'a T, + //.. +} + +// Locking the mutex is explicit. +impl<T> Mutex<T> { + fn lock(&self) -> MutexGuard<T> { + // Lock the underlying OS mutex. + //.. + + // MutexGuard keeps a reference to self + MutexGuard { + data: self, + //.. + } + } +} + +// Destructor for unlocking the mutex. +impl<'a, T> Drop for MutexGuard<'a, T> { + fn drop(&mut self) { + // Unlock the underlying OS mutex. + //.. + } +} + +// Implementing Deref means we can treat MutexGuard like a pointer to T. +impl<'a, T> Deref for MutexGuard<'a, T> { + type Target = T; + + fn deref(&self) -> &T { + self.data + } +} + +fn baz(x: Mutex<Foo>) { + let xx = x.lock(); + xx.foo(); // foo is a method on Foo. + // The borrow checker ensures we can't store a reference to the underlying + // Foo which will outlive the guard xx. + + // x is unlocked when we exit this function and xx's destructor is executed. +} +``` + +## Motivation + +Where a resource must be finalised after use, RAII can be used to do this +finalisation. If it is an error to access that resource after finalisation, then +this pattern can be used to prevent such errors. + +## Advantages + +Prevents errors where a resource is not finalised and where a resource is used +after finalisation. + +## Discussion + +RAII is a useful pattern for ensuring resources are properly deallocated or +finalised. We can make use of the borrow checker in Rust to statically prevent +errors stemming from using resources after finalisation takes place. + +The core aim of the borrow checker is to ensure that references to data do not +outlive that data. The RAII guard pattern works because the guard object +contains a reference to the underlying resource and only exposes such +references. Rust ensures that the guard cannot outlive the underlying resource +and that references to the resource mediated by the guard cannot outlive the +guard. To see how this works it is helpful to examine the signature of `deref` +without lifetime elision: + +```rust,ignore +fn deref<'a>(&'a self) -> &'a T { + //.. +} +``` + +The returned reference to the resource has the same lifetime as `self` (`'a`). +The borrow checker therefore ensures that the lifetime of the reference to `T` +is shorter than the lifetime of `self`. + +Note that implementing `Deref` is not a core part of this pattern, it only makes +using the guard object more ergonomic. Implementing a `get` method on the guard +works just as well. + +## See also + +[Finalisation in destructors idiom](../idioms/dtor-finally.md) + +RAII is a common pattern in C++: [cppreference.com](http://en.cppreference.com/w/cpp/language/raii), +[wikipedia][wikipedia]. + +[wikipedia]: https://en.wikipedia.org/wiki/Resource_Acquisition_Is_Initialization + +[Style guide entry](https://doc.rust-lang.org/1.0.0/style/ownership/raii.html) +(currently just a placeholder). diff --git a/src/patterns/builder.md b/src/patterns/builder.md new file mode 100644 index 0000000..ab6a498 --- /dev/null +++ b/src/patterns/builder.md @@ -0,0 +1,108 @@ +# Builder + +## Description + +Construct an object with calls to a builder helper. + +## Example + +```rust +#[derive(Debug, PartialEq)] +pub struct Foo { + // Lots of complicated fields. + bar: String, +} + +pub struct FooBuilder { + // Probably lots of optional fields. + bar: String, +} + +impl FooBuilder { + pub fn new(/* ... */) -> FooBuilder { + // Set the minimally required fields of Foo. + FooBuilder { + bar: String::from("X"), + } + } + + pub fn name(mut self, bar: String) -> FooBuilder { + // Set the name on the builder itself, and return the builder by value. + self.bar = bar; + self + } + + // If we can get away with not consuming the Builder here, that is an + // advantage. It means we can use the FooBuilder as a template for constructing + // many Foos. + pub fn build(self) -> Foo { + // Create a Foo from the FooBuilder, applying all settings in FooBuilder + // to Foo. + Foo { bar: self.bar } + } +} + +#[test] +fn builder_test() { + let foo = Foo { + bar: String::from("Y"), + }; + let foo_from_builder: Foo = FooBuilder::new().name(String::from("Y")).build(); + assert_eq!(foo, foo_from_builder); +} +``` + +## Motivation + +Useful when you would otherwise require many different constructors or where +construction has side effects. + +## Advantages + +Separates methods for building from other methods. + +Prevents proliferation of constructors + +Can be used for one-liner initialisation as well as more complex construction. + +## Disadvantages + +More complex than creating a struct object directly, or a simple constructor +function. + +## Discussion + +This pattern is seen more frequently in Rust (and for simpler objects) than in +many other languages because Rust lacks overloading. Since you can only have a +single method with a given name, having multiple constructors is less nice in +Rust than in C++, Java, or others. + +This pattern is often used where the builder object is useful in its own right, +rather than being just a builder. For example, see +[`std::process::Command`](https://doc.rust-lang.org/std/process/struct.Command.html) +is a builder for [`Child`](https://doc.rust-lang.org/std/process/struct.Child.html) +(a process). In these cases, the `T` and `TBuilder` pattern +of naming is not used. + +The example takes and returns the builder by value. It is often more ergonomic +(and more efficient) to take and return the builder as a mutable reference. The +borrow checker makes this work naturally. This approach has the advantage that +one can write code like + +```rust,ignore +let mut fb = FooBuilder::new(); +fb.a(); +fb.b(); +let f = fb.build(); +``` + +as well as the `FooBuilder::new().a().b().build()` style. + +## See also + +- [Description in the style guide](https://web.archive.org/web/20210104103100/https://doc.rust-lang.org/1.12.0/style/ownership/builders.html) +- [derive_builder](https://crates.io/crates/derive_builder), a crate for automatically + implementing this pattern while avoiding the boilerplate. +- [Constructor pattern](../idioms/ctor.md) for when construction is simpler. +- [Builder pattern (wikipedia)](https://en.wikipedia.org/wiki/Builder_pattern) +- [Construction of complex values](https://web.archive.org/web/20210104103000/https://rust-lang.github.io/api-guidelines/type-safety.html#c-builder) diff --git a/src/patterns/compose-structs.md b/src/patterns/compose-structs.md new file mode 100644 index 0000000..013f6cb --- /dev/null +++ b/src/patterns/compose-structs.md @@ -0,0 +1,99 @@ +# Compose structs together for better borrowing + +TODO - this is not a very snappy name + +## Description + +Sometimes a large struct will cause issues with the borrow checker - although +fields can be borrowed independently, sometimes the whole struct ends up being +used at once, preventing other uses. A solution might be to decompose the struct +into several smaller structs. Then compose these together into the original +struct. Then each struct can be borrowed separately and have more flexible +behaviour. + +This will often lead to a better design in other ways: applying this design +pattern often reveals smaller units of functionality. + +## Example + +Here is a contrived example of where the borrow checker foils us in our plan to +use a struct: + +```rust +struct A { + f1: u32, + f2: u32, + f3: u32, +} + +fn foo(a: &mut A) -> &u32 { &a.f2 } +fn bar(a: &mut A) -> u32 { a.f1 + a.f3 } + +fn baz(a: &mut A) { + // The later usage of x causes a to be borrowed for the rest of the function. + let x = foo(a); + // Borrow checker error: + // let y = bar(a); // ~ ERROR: cannot borrow `*a` as mutable more than once + // at a time + println!("{}", x); +} +``` + +We can apply this design pattern and refactor `A` into two smaller structs, thus +solving the borrow checking issue: + +```rust +// A is now composed of two structs - B and C. +struct A { + b: B, + c: C, +} +struct B { + f2: u32, +} +struct C { + f1: u32, + f3: u32, +} + +// These functions take a B or C, rather than A. +fn foo(b: &mut B) -> &u32 { &b.f2 } +fn bar(c: &mut C) -> u32 { c.f1 + c.f3 } + +fn baz(a: &mut A) { + let x = foo(&mut a.b); + // Now it's OK! + let y = bar(&mut a.c); + println!("{}", x); +} +``` + +## Motivation + +TODO Why and where you should use the pattern + +## Advantages + +Lets you work around limitations in the borrow checker. + +Often produces a better design. + +## Disadvantages + +Leads to more verbose code. + +Sometimes, the smaller structs are not good abstractions, and so we end up with +a worse design. That is probably a 'code smell', indicating that the program +should be refactored in some way. + +## Discussion + +This pattern is not required in languages that don't have a borrow checker, so +in that sense is unique to Rust. However, making smaller units of functionality +often leads to cleaner code: a widely acknowledged principle of software +engineering, independent of the language. + +This pattern relies on Rust's borrow checker to be able to borrow fields +independently of each other. In the example, the borrow checker knows that `a.b` +and `a.c` are distinct and can be borrowed independently, it does not try to +borrow all of `a`, which would make this pattern useless. diff --git a/src/patterns/entry.md b/src/patterns/entry.md new file mode 100644 index 0000000..b06a07e --- /dev/null +++ b/src/patterns/entry.md @@ -0,0 +1,35 @@ +# Entry API + +## Description + +A short, prose description of the pattern. + +## Example + +```rust +// An example of the pattern in action, should be mostly code, commented +// liberally. +``` + +## Motivation + +Why and where you should use the pattern + +## Advantages + +Good things about this pattern. + +## Disadvantages + +Bad things about this pattern. Possible contraindications. + +## Discussion + +TODO vs insert_or_update etc. + +## See also + +[RFC](https://github.com/rust-lang/rfcs/blob/master/text/0216-collection-views.md) +[RFC](https://github.com/rust-lang/rfcs/blob/8e2d3a3341da533f846f61f10335b72c9a9f4740/text/0921-entry_v3.md) + +[Hashmap::entry docs](https://doc.rust-lang.org/std/collections/struct.HashMap.html#method.entry) diff --git a/src/patterns/ffi-export.md b/src/patterns/ffi-export.md new file mode 100644 index 0000000..6bb719e --- /dev/null +++ b/src/patterns/ffi-export.md @@ -0,0 +1,260 @@ +# Object-Based APIs + +## Description + +When designing APIs in Rust which are exposed to other languages, there are some +important design principles which are contrary to normal Rust API design: + +1. All Encapsulated types should be *owned* by Rust, *managed* by the user, + and *opaque*. +2. All Transactional data types should be *owned* by the user, and *transparent*. +3. All library behavior should be functions acting upon Encapsulated types. +4. All library behavior should be encapsulated into types not based on structure, + but *provenance/lifetime*. + +## Motivation + +Rust has built-in FFI support to other languages. +It does this by providing a way for crate authors to provide C-compatible APIs +through different ABIs (though that is unimportant to this practice). + +Well-designed Rust FFI follows C API design principles, while compromising the +design in Rust as little as possible. There are three goals with any foreign API: + +1. Make it easy to use in the target language. +2. Avoid the API dictating internal unsafety on the Rust side as much as possible. +3. Keep the potential for memory unsafety and Rust `undefined behaviour` as small + as possible. + +Rust code must trust the memory safety of the foreign language beyond a certain +point. However, every bit of `unsafe` code on the Rust side is an opportunity for +bugs, or to exacerbate `undefined behaviour`. + +For example, if a pointer provenance is wrong, that may be a segfault due to +invalid memory access. But if it is manipulated by unsafe code, it could become +full-blown heap corruption. + +The Object-Based API design allows for writing shims that have good memory safety +characteristics, and a clean boundary of what is safe and what is `unsafe`. + +## Code Example + +The POSIX standard defines the API to access an on-file database, known as [DBM](https://web.archive.org/web/20210105035602/https://www.mankier.com/0p/ndbm.h). +It is an excellent example of an "object-based" API. + +Here is the definition in C, which hopefully should be easy to read for those +involved in FFI. The commentary below should help explaining it for those who +miss the subtleties. + +```C +struct DBM; +typedef struct { void *dptr, size_t dsize } datum; + +int dbm_clearerr(DBM *); +void dbm_close(DBM *); +int dbm_delete(DBM *, datum); +int dbm_error(DBM *); +datum dbm_fetch(DBM *, datum); +datum dbm_firstkey(DBM *); +datum dbm_nextkey(DBM *); +DBM *dbm_open(const char *, int, mode_t); +int dbm_store(DBM *, datum, datum, int); +``` + +This API defines two types: `DBM` and `datum`. + +The `DBM` type was called an "encapsulated" type above. +It is designed to contain internal state, and acts as an entry point for the +library's behavior. + +It is completely opaque to the user, who cannot create a `DBM` themselves since +they don't know its size or layout. Instead, they must call `dbm_open`, and that +only gives them *a pointer to one*. + +This means all `DBM`s are "owned" by the library in a Rust sense. +The internal state of unknown size is kept in memory controlled by the library, +not the user. The user can only manage its life cycle with `open` and `close`, +and perform operations on it with the other functions. + +The `datum` type was called a "transactional" type above. +It is designed to facilitate the exchange of information between the library and +its user. + +The database is designed to store "unstructured data", with no pre-defined length +or meaning. As a result, the `datum` is the C equivalent of a Rust slice: a bunch +of bytes, and a count of how many there are. The main difference is that there is +no type information, which is what `void` indicates. + +Keep in mind that this header is written from the library's point of view. +The user likely has some type they are using, which has a known size. +But the library does not care, and by the rules of C casting, any type behind a +pointer can be cast to `void`. + +As noted earlier, this type is *transparent* to the user. But also, this type is +*owned* by the user. +This has subtle ramifications, due to that pointer inside it. +The question is, who owns the memory that pointer points to? + +The answer for best memory safety is, "the user". +But in cases such as retrieving a value, the user does not know how to allocate +it correctly (since they don't know how long the value is). In this case, the library +code is expected to use the heap that the user has access to -- such as the C library +`malloc` and `free` -- and then *transfer ownership* in the Rust sense. + +This may all seem speculative, but this is what a pointer means in C. +It means the same thing as Rust: "user defined lifetime." +The user of the library needs to read the documentation in order to use it correctly. +That said, there are some decisions that have fewer or greater consequences if users +do it wrong. Minimizing those is what this best practice is about, and the key +is to *transfer ownership of everything that is transparent*. + +## Advantages + +This minimizes the number of memory safety guarantees the user must uphold to a +relatively small number: + +1. Do not call any function with a pointer not returned by `dbm_open` (invalid + access or corruption). +2. Do not call any function on a pointer after close (use after free). +3. The `dptr` on any `datum` must be `NULL`, or point to a valid slice of memory + at the advertised length. + +In addition, it avoids a lot of pointer provenance issues. +To understand why, let us consider an alternative in some depth: key iteration. + +Rust is well known for its iterators. +When implementing one, the programmer makes a separate type with a bounded lifetime +to its owner, and implements the `Iterator` trait. + +Here is how iteration would be done in Rust for `DBM`: + +```rust,ignore +struct Dbm { ... } + +impl Dbm { + /* ... */ + pub fn keys<'it>(&'it self) -> DbmKeysIter<'it> { ... } + /* ... */ +} + +struct DbmKeysIter<'it> { + owner: &'it Dbm, +} + +impl<'it> Iterator for DbmKeysIter<'it> { ... } +``` + +This is clean, idiomatic, and safe. thanks to Rust's guarantees. +However, consider what a straightforward API translation would look like: + +```rust,ignore +#[no_mangle] +pub extern "C" fn dbm_iter_new(owner: *const Dbm) -> *mut DbmKeysIter { + // THIS API IS A BAD IDEA! For real applications, use object-based design instead. +} +#[no_mangle] +pub extern "C" fn dbm_iter_next( + iter: *mut DbmKeysIter, + key_out: *const datum +) -> libc::c_int { + // THIS API IS A BAD IDEA! For real applications, use object-based design instead. +} +#[no_mangle] +pub extern "C" fn dbm_iter_del(*mut DbmKeysIter) { + // THIS API IS A BAD IDEA! For real applications, use object-based design instead. +} +``` + +This API loses a key piece of information: the lifetime of the iterator must not +exceed the lifetime of the `Dbm` object that owns it. A user of the library could +use it in a way which causes the iterator to outlive the data it is iterating on, +resulting in reading uninitialized memory. + +This example written in C contains a bug that will be explained afterwards: + +```C +int count_key_sizes(DBM *db) { + // DO NOT USE THIS FUNCTION. IT HAS A SUBTLE BUT SERIOUS BUG! + datum key; + int len = 0; + + if (!dbm_iter_new(db)) { + dbm_close(db); + return -1; + } + + int l; + while ((l = dbm_iter_next(owner, &key)) >= 0) { // an error is indicated by -1 + free(key.dptr); + len += key.dsize; + if (l == 0) { // end of the iterator + dbm_close(owner); + } + } + if l >= 0 { + return -1; + } else { + return len; + } +} +``` + +This bug is a classic. Here's what happens when the iterator returns the +end-of-iteration marker: + +1. The loop condition sets `l` to zero, and enters the loop because `0 >= 0`. +2. The length is incremented, in this case by zero. +3. The if statement is true, so the database is closed. There should be a break + statement here. +4. The loop condition executes again, causing a `next` call on the closed object. + +The worst part about this bug? +If the Rust implementation was careful, this code will work most of the time! +If the memory for the `Dbm` object is not immediately reused, an internal check +will almost certainly fail, resulting in the iterator returning a `-1` indicating +an error. But occasionally, it will cause a segmentation fault, or even worse, +nonsensical memory corruption! + +None of this can be avoided by Rust. +From its perspective, it put those objects on its heap, returned pointers to them, +and gave up control of their lifetimes. The C code simply must "play nice". + +The programmer must read and understand the API documentation. +While some consider that par for the course in C, a good API design can mitigate +this risk. The POSIX API for `DBM` did this by *consolidating the ownership* of +the iterator with its parent: + +```C +datum dbm_firstkey(DBM *); +datum dbm_nextkey(DBM *); +``` + +Thus, all of the lifetimes were bound together, and such unsafety was prevented. + +## Disadvantages + +However, this design choice also has a number of drawbacks, which should be +considered as well. + +First, the API itself becomes less expressive. +With POSIX DBM, there is only one iterator per object, and every call changes +its state. This is much more restrictive than iterators in almost any language, +even though it is safe. Perhaps with other related objects, whose lifetimes are +less hierarchical, this limitation is more of a cost than the safety. + +Second, depending on the relationships of the API's parts, significant design effort +may be involved. Many of the easier design points have other patterns associated +with them: + +- [Wrapper Type Consolidation](./ffi-wrappers.md) groups multiple Rust types together + into an opaque "object" + +- [FFI Error Passing](../idioms/ffi-errors.md) explains error handling with integer + codes and sentinel return values (such as `NULL` pointers) + +- [Accepting Foreign Strings](../idioms/ffi-accepting-strings.md) allows accepting + strings with minimal unsafe code, and is easier to get right than + [Passing Strings to FFI](../idioms/ffi-passing-strings.md) + +However, not every API can be done this way. +It is up to the best judgement of the programmer as to who their audience is. diff --git a/src/patterns/ffi-intro.md b/src/patterns/ffi-intro.md new file mode 100644 index 0000000..b2df4bd --- /dev/null +++ b/src/patterns/ffi-intro.md @@ -0,0 +1,13 @@ +# FFI Patterns + +Writing FFI code is an entire course in itself. +However, there are several idioms here that can act as pointers, and avoid traps +for inexperienced users of unsafe Rust. + +This section contains design patterns that may be useful when doing FFI. + +1. [Object-Based API](./ffi-export.md) design that has good memory safety characteristics, + and a clean boundary of what is safe and what is unsafe + +2. [Type Consolidation into Wrappers](./ffi-wrappers.md) - group multiple Rust types + together into an opaque "object" diff --git a/src/patterns/ffi-wrappers.md b/src/patterns/ffi-wrappers.md new file mode 100644 index 0000000..7adcf88 --- /dev/null +++ b/src/patterns/ffi-wrappers.md @@ -0,0 +1,162 @@ +# Type Consolidation into Wrappers + +## Description + +This pattern is designed to allow gracefully handling multiple related types, +while minimizing the surface area for memory unsafety. + +One of the cornerstones of Rust's aliasing rules is lifetimes. +This ensures that many patterns of access between types can be memory safe, +data race safety included. + +However, when Rust types are exported to other languages, they are usually transformed +into pointers. In Rust, a pointer means "the user manages the lifetime of the pointee." +It is their responsibility to avoid memory unsafety. + +Some level of trust in the user code is thus required, notably around use-after-free +which Rust can do nothing about. However, some API designs place higher burdens +than others on the code written in the other language. + +The lowest risk API is the "consolidated wrapper", where all possible interactions +with an object are folded into a "wrapper type", while keeping the Rust API clean. + +## Code Example + +To understand this, let us look at a classic example of an API to export: iteration +through a collection. + +That API looks like this: + +1. The iterator is initialized with `first_key`. +2. Each call to `next_key` will advance the iterator. +3. Calls to `next_key` if the iterator is at the end will do nothing. +4. As noted above, the iterator is "wrapped into" the collection (unlike the native + Rust API). + +If the iterator implements `nth()` efficiently, then it is possible to make it +ephemeral to each function call: + +```rust,ignore +struct MySetWrapper { + myset: MySet, + iter_next: usize, +} + +impl MySetWrapper { + pub fn first_key(&mut self) -> Option<&Key> { + self.iter_next = 0; + self.next_key() + } + pub fn next_key(&mut self) -> Option<&Key> { + if let Some(next) = self.myset.keys().nth(self.iter_next) { + self.iter_next += 1; + Some(next) + } else { + None + } + } +} +``` + +As a result, the wrapper is simple and contains no `unsafe` code. + +## Advantages + +This makes APIs safer to use, avoiding issues with lifetimes between types. +See [Object-Based APIs](./ffi-export.md) for more on the advantages and pitfalls +this avoids. + +## Disadvantages + +Often, wrapping types is quite difficult, and sometimes a Rust API compromise +would make things easier. + +As an example, consider an iterator which does not efficiently implement `nth()`. +It would definitely be worth putting in special logic to make the object handle +iteration internally, or to support a different access pattern efficiently that +only the Foreign Function API will use. + +### Trying to Wrap Iterators (and Failing) + +To wrap any type of iterator into the API correctly, the wrapper would need to +do what a C version of the code would do: erase the lifetime of the iterator, +and manage it manually. + +Suffice it to say, this is *incredibly* difficult. + +Here is an illustration of just *one* pitfall. + +A first version of `MySetWrapper` would look like this: + +```rust,ignore +struct MySetWrapper { + myset: MySet, + iter_next: usize, + // created from a transmuted Box<KeysIter + 'self> + iterator: Option<NonNull<KeysIter<'static>>>, +} +``` + +With `transmute` being used to extend a lifetime, and a pointer to hide it, +it's ugly already. But it gets even worse: *any other operation can cause +Rust `undefined behaviour`*. + +Consider that the `MySet` in the wrapper could be manipulated by other +functions during iteration, such as storing a new value to the key it was +iterating over. The API doesn't discourage this, and in fact some similar C +libraries expect it. + +A simple implementation of `myset_store` would be: + +```rust,ignore +pub mod unsafe_module { + + // other module content + + pub fn myset_store( + myset: *mut MySetWrapper, + key: datum, + value: datum) -> libc::c_int { + + // DO NOT USE THIS CODE. IT IS UNSAFE TO DEMONSTRATE A PROLBEM. + + let myset: &mut MySet = unsafe { // SAFETY: whoops, UB occurs in here! + &mut (*myset).myset + }; + + /* ...check and cast key and value data... */ + + match myset.store(casted_key, casted_value) { + Ok(_) => 0, + Err(e) => e.into() + } + } +} +``` + +If the iterator exists when this function is called, we have violated one of Rust's +aliasing rules. According to Rust, the mutable reference in this block must have +*exclusive* access to the object. If the iterator simply exists, it's not exclusive, +so we have `undefined behaviour`! [^1] + +To avoid this, we must have a way of ensuring that mutable reference really is exclusive. +That basically means clearing out the iterator's shared reference while it exists, +and then reconstructing it. In most cases, that will still be less efficient than +the C version. + +Some may ask: how can C do this more efficiently? +The answer is, it cheats. Rust's aliasing rules are the problem, and C simply ignores +them for its pointers. In exchange, it is common to see code that is declared +in the manual as "not thread safe" under some or all circumstances. In fact, +the [GNU C library](https://manpages.debian.org/buster/manpages/attributes.7.en.html) +has an entire lexicon dedicated to concurrent behavior! + +Rust would rather make everything memory safe all the time, for both safety and +optimizations that C code cannot attain. Being denied access to certain shortcuts +is the price Rust programmers need to pay. + +[^1]: For the C programmers out there scratching their heads, the iterator need + not be read *during* this code cause the UB. The exclusivity rule also enables + compiler optimizations which may cause inconsistent observations by the iterator's + shared reference (e.g. stack spills or reordering instructions for efficiency). + These observations may happen *any time after* the mutable reference is created. diff --git a/src/patterns/fold.md b/src/patterns/fold.md new file mode 100644 index 0000000..5b91bf7 --- /dev/null +++ b/src/patterns/fold.md @@ -0,0 +1,122 @@ +# Fold + +## Description + +Run an algorithm over each item in a collection of data to create a new item, +thus creating a whole new collection. + +The etymology here is unclear to me. The terms 'fold' and 'folder' are used +in the Rust compiler, although it appears to me to be more like a map than a +fold in the usual sense. See the discussion below for more details. + +## Example + +```rust,ignore +// The data we will fold, a simple AST. +mod ast { + pub enum Stmt { + Expr(Box<Expr>), + Let(Box<Name>, Box<Expr>), + } + + pub struct Name { + value: String, + } + + pub enum Expr { + IntLit(i64), + Add(Box<Expr>, Box<Expr>), + Sub(Box<Expr>, Box<Expr>), + } +} + +// The abstract folder +mod fold { + use ast::*; + + pub trait Folder { + // A leaf node just returns the node itself. In some cases, we can do this + // to inner nodes too. + fn fold_name(&mut self, n: Box<Name>) -> Box<Name> { n } + // Create a new inner node by folding its children. + fn fold_stmt(&mut self, s: Box<Stmt>) -> Box<Stmt> { + match *s { + Stmt::Expr(e) => Box::new(Stmt::Expr(self.fold_expr(e))), + Stmt::Let(n, e) => Box::new(Stmt::Let(self.fold_name(n), self.fold_expr(e))), + } + } + fn fold_expr(&mut self, e: Box<Expr>) -> Box<Expr> { ... } + } +} + +use fold::*; +use ast::*; + +// An example concrete implementation - renames every name to 'foo'. +struct Renamer; +impl Folder for Renamer { + fn fold_name(&mut self, n: Box<Name>) -> Box<Name> { + Box::new(Name { value: "foo".to_owned() }) + } + // Use the default methods for the other nodes. +} +``` + +The result of running the `Renamer` on an AST is a new AST identical to the old +one, but with every name changed to `foo`. A real life folder might have some +state preserved between nodes in the struct itself. + +A folder can also be defined to map one data structure to a different (but +usually similar) data structure. For example, we could fold an AST into a HIR +tree (HIR stands for high-level intermediate representation). + +## Motivation + +It is common to want to map a data structure by performing some operation on +each node in the structure. For simple operations on simple data structures, +this can be done using `Iterator::map`. For more complex operations, perhaps +where earlier nodes can affect the operation on later nodes, or where iteration +over the data structure is non-trivial, using the fold pattern is more +appropriate. + +Like the visitor pattern, the fold pattern allows us to separate traversal of a +data structure from the operations performed to each node. + +## Discussion + +Mapping data structures in this fashion is common in functional languages. In OO +languages, it would be more common to mutate the data structure in place. The +'functional' approach is common in Rust, mostly due to the preference for +immutability. Using fresh data structures, rather than mutating old ones, makes +reasoning about the code easier in most circumstances. + +The trade-off between efficiency and reusability can be tweaked by changing how +nodes are accepted by the `fold_*` methods. + +In the above example we operate on `Box` pointers. Since these own their data +exclusively, the original copy of the data structure cannot be re-used. On the +other hand if a node is not changed, reusing it is very efficient. + +If we were to operate on borrowed references, the original data structure can be +reused; however, a node must be cloned even if unchanged, which can be +expensive. + +Using a reference counted pointer gives the best of both worlds - we can reuse +the original data structure and we don't need to clone unchanged nodes. However, +they are less ergonomic to use and mean that the data structures cannot be +mutable. + +## See also + +Iterators have a `fold` method, however this folds a data structure into a +value, rather than into a new data structure. An iterator's `map` is more like +this fold pattern. + +In other languages, fold is usually used in the sense of Rust's iterators, +rather than this pattern. Some functional languages have powerful constructs for +performing flexible maps over data structures. + +The [visitor](visitor.md) pattern is closely related to fold. They share the +concept of walking a data structure performing an operation on each node. +However, the visitor does not create a new data structure nor consume the old +one. diff --git a/src/patterns/index.md b/src/patterns/index.md new file mode 100644 index 0000000..1bdda78 --- /dev/null +++ b/src/patterns/index.md @@ -0,0 +1,31 @@ +# Design Patterns + +[Design patterns](https://en.wikipedia.org/wiki/Software_design_pattern) are +"general reusable solutions to a commonly occurring problem within a given +context in software design". Design patterns are a great way to describe the +culture of a programming language. Design patterns are very language-specific - +what is a pattern in one language may be unnecessary in another due to a +language feature, or impossible to express due to a missing feature. + +If overused, design patterns can add unnecessary complexity to programs. +However, they are a great way to share intermediate and advanced level knowledge +about a programming language. + +## Design patterns in Rust + +Rust has many very unique features. These features give us great benefit by removing +whole classes of problems. Some of them are also patterns that are _unique_ to Rust. + +## YAGNI + +If you're not familiar with it, YAGNI is an acronym that stands for +`You Aren't Going to Need It`. It's an important software design principle to apply +as you write code. + +> The best code I ever wrote was code I never wrote. + +If we apply YAGNI to design patterns, we see that the features of Rust allow us to +throw out many patterns. For instance, there is no need for the [strategy pattern](https://en.wikipedia.org/wiki/Strategy_pattern) +in Rust because we can just use [traits](https://doc.rust-lang.org/book/traits.html). + +TODO: Maybe include some code to illustrate the traits. diff --git a/src/patterns/interpreter.md b/src/patterns/interpreter.md new file mode 100644 index 0000000..5f97558 --- /dev/null +++ b/src/patterns/interpreter.md @@ -0,0 +1,147 @@ +# Interpreter + +## Description + +If a problem occurs very often and requires long and repetitive steps to solve +it, then the problem instances might be expressed in a simple language and an +interpreter object could solve it by interpreting the sentences written in this +simple language. + +Basically, for any kind of problems we define: + +- a [domain specific language](https://en.wikipedia.org/wiki/Domain-specific_language), +- a grammar for this language, +- an interpreter that solves the problem instances. + +## Motivation + +Our goal is to translate simple mathematical expressions into postfix expressions +(or [Reverse Polish notation](https://en.wikipedia.org/wiki/Reverse_Polish_notation)) +For simplicity, our expressions consist of ten digits `0`, ..., `9` and two +operations `+`, `-`. For example, the expression `2 + 4` is translated into +`2 4 +`. + +## Context Free Grammar for our problem + +Our task is translate infix expressions into postfix ones. Let's define a context +free grammar for a set of infix expressions over `0`, ..., `9`, `+`, and `-`, +where: + +- terminal symbols: `0`, ..., `9`, `+`, `-` +- non-terminal symbols: `exp`, `term` +- start symbol is `exp` +- and the following are production rules + +```ignore +exp -> exp + term +exp -> exp - term +exp -> term +term -> 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 +``` + +__NOTE:__ This grammar should be further transformed depending on what we are going +to do with it. For example, we might need to remove left recursion. For more +details please see [Compilers: Principles,Techniques, and Tools +](https://en.wikipedia.org/wiki/Compilers:_Principles,_Techniques,_and_Tools) +(aka Dragon Book). + +## Solution + +We simply implement a recursive descent parser. For simplicity's sake, the code +panics when an expression is syntactically wrong (for example `2-34` or `2+5-` +are wrong according to the grammar definition). + +```rust +pub struct Interpreter<'a> { + it: std::str::Chars<'a>, +} + +impl<'a> Interpreter<'a> { + + pub fn new(infix: &'a str) -> Self { + Self { it: infix.chars() } + } + + fn next_char(&mut self) -> Option<char> { + self.it.next() + } + + pub fn interpret(&mut self, out: &mut String) { + self.term(out); + + while let Some(op) = self.next_char() { + if op == '+' || op == '-' { + self.term(out); + out.push(op); + } else { + panic!("Unexpected symbol '{}'", op); + } + } + } + + fn term(&mut self, out: &mut String) { + match self.next_char() { + Some(ch) if ch.is_digit(10) => out.push(ch), + Some(ch) => panic!("Unexpected symbol '{}'", ch), + None => panic!("Unexpected end of string"), + } + } +} + +pub fn main() { + let mut intr = Interpreter::new("2+3"); + let mut postfix = String::new(); + intr.interpret(&mut postfix); + assert_eq!(postfix, "23+"); + + intr = Interpreter::new("1-2+3-4"); + postfix.clear(); + intr.interpret(&mut postfix); + assert_eq!(postfix, "12-3+4-"); +} +``` + +## Discussion + +There may be a wrong perception that the Interpreter design pattern is about design +grammars for formal languages and implementation of parsers for these grammars. +In fact, this pattern is about expressing problem instances in a more specific +way and implementing functions/classes/structs that solve these problem instances. +Rust language has `macro_rules!` that allow to define special syntax and rules +on how to expand this syntax into source code. + +In the following example we create a simple `macro_rules!` that computes +[Euclidean length](https://en.wikipedia.org/wiki/Euclidean_distance) of `n` +dimensional vectors. Writing `norm!(x,1,2)` might be easier to express and more +efficient than packing `x,1,2` into a `Vec` and calling a function computing +the length. + +```rust +macro_rules! norm { + ($($element:expr),*) => { + { + let mut n = 0.0; + $( + n += ($element as f64)*($element as f64); + )* + n.sqrt() + } + }; +} + +fn main() { + let x = -3f64; + let y = 4f64; + + assert_eq!(3f64, norm!(x)); + assert_eq!(5f64, norm!(x, y)); + assert_eq!(0f64, norm!(0, 0, 0)); + assert_eq!(1f64, norm!(0.5, -0.5, 0.5, -0.5)); +} +``` + +## See also + +- [Interpreter pattern](https://en.wikipedia.org/wiki/Interpreter_pattern) +- [Context free grammar](https://en.wikipedia.org/wiki/Context-free_grammar) +- [macro_rules!](https://doc.rust-lang.org/rust-by-example/macros.html) diff --git a/src/patterns/late-bounds.md b/src/patterns/late-bounds.md new file mode 100644 index 0000000..fe5ebe7 --- /dev/null +++ b/src/patterns/late-bounds.md @@ -0,0 +1,35 @@ +# Late bound bounds + +## Description + +TODO late binding of bounds for better APIs (i.e., Mutex's don't require Send) + +## Example + +```rust +// An example of the pattern in action, should be mostly code, commented +// liberally. +``` + +## Motivation + +Why and where you should use the pattern + +## Advantages + +Good things about this pattern. + +## Disadvantages + +Bad things about this pattern. Possible contraindications. + +## Discussion + +A deeper discussion about this pattern. You might want to cover how this is done +in other languages, alternative approaches, why this is particularly nice in +Rust, etc. + +## See also + +Related patterns (link to the pattern file). Versions of this pattern in other +languages. diff --git a/src/patterns/newtype.md b/src/patterns/newtype.md new file mode 100644 index 0000000..6b02374 --- /dev/null +++ b/src/patterns/newtype.md @@ -0,0 +1,111 @@ +# Newtype + +What if in some cases we want a type to behave similar to another type or +enforce some behaviour at compile time where using only type aliases would +not be enough? + +For example, if we want to create a custom `Display` implementation for `String` +due to security considerations (e.g. passwords). + +For such cases we could use the `Newtype` pattern to provide __type safety__ +and __encapsulation__. + +## Description + +Use a tuple struct with a single field to make an opaque wrapper for a type. +This creates a new type, rather than an alias to a type (`type` items). + +## Example + +```rust,ignore +// Some type, not necessarily in the same module or even crate. +struct Foo { + //.. +} + +impl Foo { + // These functions are not present on Bar. + //.. +} + +// The newtype. +pub struct Bar(Foo); + +impl Bar { + // Constructor. + pub fn new( + //.. + ) -> Bar { + + //.. + + } + + //.. +} + +fn main() { + let b = Bar::new(...); + + // Foo and Bar are type incompatible, the following do not type check. + // let f: Foo = b; + // let b: Bar = Foo { ... }; +} +``` + +## Motivation + +The primary motivation for newtypes is abstraction. It allows you to share +implementation details between types while precisely controlling the interface. +By using a newtype rather than exposing the implementation type as part of an +API, it allows you to change implementation backwards compatibly. + +Newtypes can be used for distinguishing units, e.g., wrapping `f64` to give +distinguishable `Miles` and `Kms`. + +## Advantages + +The wrapped and wrapper types are not type compatible (as opposed to using +`type`), so users of the newtype will never 'confuse' the wrapped and wrapper +types. + +Newtypes are a zero-cost abstraction - there is no runtime overhead. + +The privacy system ensures that users cannot access the wrapped type (if the +field is private, which it is by default). + +## Disadvantages + +The downside of newtypes (especially compared with type aliases), is that there +is no special language support. This means there can be *a lot* of boilerplate. +You need a 'pass through' method for every method you want to expose on the +wrapped type, and an impl for every trait you want to also be implemented for +the wrapper type. + +## Discussion + +Newtypes are very common in Rust code. Abstraction or representing units are the +most common uses, but they can be used for other reasons: + +- restricting functionality (reduce the functions exposed or traits implemented), +- making a type with copy semantics have move semantics, +- abstraction by providing a more concrete type and thus hiding internal types, + e.g., + +```rust,ignore +pub struct Foo(Bar<T1, T2>); +``` + +Here, `Bar` might be some public, generic type and `T1` and `T2` are some internal +types. Users of our module shouldn't know that we implement `Foo` by using a `Bar`, +but what we're really hiding here is the types `T1` and `T2`, and how they are used +with `Bar`. + +## See also + +- [Advanced Types in the book](https://doc.rust-lang.org/book/ch19-04-advanced-types.html?highlight=newtype#using-the-newtype-pattern-for-type-safety-and-abstraction) +- [Newtypes in Haskell](https://wiki.haskell.org/Newtype) +- [Type aliases](https://doc.rust-lang.org/stable/book/ch19-04-advanced-types.html#creating-type-synonyms-with-type-aliases) +- [derive_more](https://crates.io/crates/derive_more), a crate for deriving many + builtin traits on newtypes. +- [The Newtype Pattern In Rust](https://www.worthe-it.co.za/blog/2020-10-31-newtype-pattern-in-rust.html) diff --git a/src/patterns/small-crates.md b/src/patterns/small-crates.md new file mode 100644 index 0000000..d777ffd --- /dev/null +++ b/src/patterns/small-crates.md @@ -0,0 +1,46 @@ +# Prefer small crates + +## Description + +Prefer small crates that do one thing well. + +Cargo and crates.io make it easy to add third-party libraries, much more so than +in say C or C++. Moreover, since packages on crates.io cannot be edited or removed +after publication, any build that works now should continue to work in the future. +We should take advantage of this tooling, and use smaller, more fine-grained dependencies. + +## Advantages + +* Small crates are easier to understand, and encourage more modular code. +* Crates allow for re-using code between projects. + For example, the `url` crate was developed as part of the Servo browser engine, + but has since found wide use outside the project. * Since the compilation unit + of Rust is the crate, splitting a project into multiple crates can allow more of + the code to be built in parallel. + +## Disadvantages + +* This can lead to "dependency hell", when a project depends on multiple conflicting + versions of a crate at the same time. For example, the `url` crate has both versions + 1.0 and 0.5. Since the `Url` from `url:1.0` and the `Url` from `url:0.5` are + different types, an HTTP client that uses `url:0.5` would not accept `Url` values + from a web scraper that uses `url:1.0`. +* Packages on crates.io are not curated. A crate may be poorly written, have + unhelpful documentation, or be outright malicious. +* Two small crates may be less optimized than one large one, since the compiler + does not perform link-time optimization (LTO) by default. + +## Examples + +The [`ref_slice`](https://crates.io/crates/ref_slice) crate provides functions +for converting `&T` to `&[T]`. + +The [`url`](https://crates.io/crates/url) crate provides tools for working with +URLs. + +The [`num_cpus`](https://crates.io/crates/num_cpus) crate provides a function to +query the number of CPUs on a machine. + +## See also + +* [crates.io: The Rust community crate host](https://crates.io/) diff --git a/src/patterns/strategy.md b/src/patterns/strategy.md new file mode 100644 index 0000000..e573b0a --- /dev/null +++ b/src/patterns/strategy.md @@ -0,0 +1,178 @@ +# Strategy (aka Policy) + +## Description + +The [Strategy design pattern](https://en.wikipedia.org/wiki/Strategy_pattern) +is a technique that enables separation of concerns. +It also allows to decouple software modules through [Dependency Inversion](https://en.wikipedia.org/wiki/Dependency_inversion_principle). + +The basic idea behind the Strategy pattern is that, given an algorithm solving +a particular problem, we define only the skeleton of the algorithm at an abstract +level and we separate the specific algorithm’s implementation into different parts. + +In this way, a client using the algorithm may choose a specific implementation, +while the general algorithm workflow remains the same. In other words, the abstract +specification of the class does not depend on the specific implementation of the +derived class, but specific implementation must adhere to the abstract specification. +This is why we call it "Dependency Inversion". + +## Motivation + +Imagine we are working on a project that generates reports every month. +We need the reports to be generated in different formats (strategies), e.g., +in `JSON` or `Plain Text` formats. +But things vary over time and we don't know what kind of requirement we may get +in the future. For example, we may need to generate our report in a completly new +format, or just modify one of the existing formats. + +## Example + +In this example our invariants (or abstractions) are `Context`, `Formatter`, +and `Report`, while `Text` and `Json` are our strategy structs. These strategies +have to implement the `Formatter` trait. + +```rust +use std::collections::HashMap; +type Data = HashMap<String, u32>; + +trait Formatter { + fn format(&self, data: &Data, s: &mut String); +} + +struct Report; + +impl Report { + fn generate<T: Formatter>(g: T, s: &mut String) { + // backend operations... + let mut data = HashMap::new(); + data.insert("one".to_string(), 1); + data.insert("two".to_string(), 2); + // generate report + g.format(&data, s); + } +} + +struct Text; +impl Formatter for Text { + fn format(&self, data: &Data, s: &mut String) { + *s = data + .iter() + .map(|(key, val)| format!("{} {}\n", key, val)) + .collect(); + } +} + +struct Json; +impl Formatter for Json { + fn format(&self, data: &Data, s: &mut String) { + *s = String::from("["); + let mut iter = data.into_iter(); + if let Some((key, val)) = iter.next() { + let entry = format!(r#"{{"{}":"{}"}}"#, key, val); + s.push_str(&entry); + while let Some((key, val)) = iter.next() { + s.push(','); + let entry = format!(r#"{{"{}":"{}"}}"#, key, val); + s.push_str(&entry); + } + } + s.push(']'); + } +} + +fn main() { + let mut s = String::from(""); + Report::generate(Text, &mut s); + assert!(s.contains("one 1")); + assert!(s.contains("two 2")); + + Report::generate(Json, &mut s); + assert!(s.contains(r#"{"one":"1"}"#)); + assert!(s.contains(r#"{"two":"2"}"#)); +} +``` + +## Advantages + +The main advantage is separation of concerns. For example, in this case `Report` +does not know anything about specific implementations of `Json` and `Text`, +whereas the output implementations does not care about how data is preprocessed, +stored, and fetched. The only thing they have to know is context and a specific +trait and method to implement, i.e,`Formatter` and `run`. + +## Disadvantages + +For each strategy there must be implemented at least one module, so number of modules +increases with number of strategies. If there are many strategies to choose from +then users have to know how strategies differ from one another. + +## Discussion + +In the previous example all strategies are implemented in a single file. +Ways of providing different strategies includes: + +- All in one file (as shown in this example, similar to being separated as modules) +- Separated as modules, E.g. `formatter::json` module, `formatter::text` module +- Use compiler feature flags, E.g. `json` feature, `text` feature +- Separated as crates, E.g. `json` crate, `text` crate + +Serde crate is a good example of the `Strategy` pattern in action. Serde allows +[full customization](https://serde.rs/custom-serialization.html) of the serialization +behavior by manually implementing `Serialize` and `Deserialize` traits for our +type. For example, we could easily swap `serde_json` with `serde_cbor` since they +expose similar methods. Having this makes the helper crate `serde_transcode` much +more useful and ergonomic. + +However, we don't need to use traits in order to design this pattern in Rust. + +The following toy example demonstrates the idea of the Strategy pattern using Rust +`closures`: + +```rust +struct Adder; +impl Adder { + pub fn add<F>(x: u8, y: u8, f: F) -> u8 + where + F: Fn(u8, u8) -> u8, + { + f(x, y) + } +} + +fn main() { + let arith_adder = |x, y| x + y; + let bool_adder = |x, y| { + if x == 1 || y == 1 { + 1 + } else { + 0 + } + }; + let custom_adder = |x, y| 2 * x + y; + + assert_eq!(9, Adder::add(4, 5, arith_adder)); + assert_eq!(0, Adder::add(0, 0, bool_adder)); + assert_eq!(5, Adder::add(1, 3, custom_adder)); +} + +``` + +In fact, Rust already uses this idea for `Options`'s `map` method: + +```rust +fn main() { + let val = Some("Rust"); + + let len_strategy = |s: &str| s.len(); + assert_eq!(4, val.map(len_strategy).unwrap()); + + let first_byte_strategy = |s: &str| s.bytes().next().unwrap(); + assert_eq!(82, val.map(first_byte_strategy).unwrap()); +} +``` + +## See also + +- [Strategy Pattern](https://en.wikipedia.org/wiki/Strategy_pattern) +- [Dependency Injection](https://en.wikipedia.org/wiki/Dependency_injection) +- [Policy Based Design](https://en.wikipedia.org/wiki/Modern_C++_Design#Policy-based_design) diff --git a/src/patterns/unsafe-mods.md b/src/patterns/unsafe-mods.md new file mode 100644 index 0000000..d746687 --- /dev/null +++ b/src/patterns/unsafe-mods.md @@ -0,0 +1,33 @@ +# Contain unsafety in small modules + +## Description + +If you have `unsafe` code, create the smallest possible module that can uphold +the needed invariants to build a minimal safe interface upon the unsafety. Embed +this into a larger module that contains only safe code and presents an ergonomi +interface. Note that the outer module can contain unsafe functions and methods +that call directly into the unsafe code. Users may use this to gain speed benefits. + +## Advantages + +* This restricts the unsafe code that must be audited +* Writing the outer module is much easier, since you can count on the guarantees +of the inner module + +## Disadvantages + +* Sometimes, it may be hard to find a suitable interface. +* The abstraction may introduce inefficiencies. + +## Examples + +* The [`toolshed`](https://docs.rs/toolshed) crate contains its unsafe operations + in submodules, presenting a safe interface to users. * `std`s `String` class + is a wrapper over `Vec<u8>` with the added invariant that the contents must be + valid UTF-8. The operations on `String` ensure this behavior. However, users + have the option of using an `unsafe` method to create a `String`, in which case + the onus is on them to guarantee the validity of the contents. + +## See also + +* [Ralf Jung's Blog about invariants in unsafe code](https://www.ralfj.de/blog/2018/08/22/two-kinds-of-invariants.html) diff --git a/src/patterns/visitor.md b/src/patterns/visitor.md new file mode 100644 index 0000000..9f5de67 --- /dev/null +++ b/src/patterns/visitor.md @@ -0,0 +1,113 @@ +# Visitor + +## Description + +A visitor encapsulates an algorithm that operates over a heterogeneous +collection of objects. It allows multiple different algorithms to be written +over the same data without having to modify the data (or their primary +behaviour). + +Furthermore, the visitor pattern allows separating the traversal of +a collection of objects from the operations performed on each object. + +## Example + +```rust,ignore +// The data we will visit +mod ast { + pub enum Stmt { + Expr(Expr), + Let(Name, Expr), + } + + pub struct Name { + value: String, + } + + pub enum Expr { + IntLit(i64), + Add(Box<Expr>, Box<Expr>), + Sub(Box<Expr>, Box<Expr>), + } +} + +// The abstract visitor +mod visit { + use ast::*; + + pub trait Visitor<T> { + fn visit_name(&mut self, n: &Name) -> T; + fn visit_stmt(&mut self, s: &Stmt) -> T; + fn visit_expr(&mut self, e: &Expr) -> T; + } +} + +use visit::*; +use ast::*; + +// An example concrete implementation - walks the AST interpreting it as code. +struct Interpreter; +impl Visitor<i64> for Interpreter { + fn visit_name(&mut self, n: &Name) -> i64 { panic!() } + fn visit_stmt(&mut self, s: &Stmt) -> i64 { + match *s { + Stmt::Expr(ref e) => self.visit_expr(e), + Stmt::Let(..) => unimplemented!(), + } + } + + fn visit_expr(&mut self, e: &Expr) -> i64 { + match *e { + Expr::IntLit(n) => n, + Expr::Add(ref lhs, ref rhs) => self.visit_expr(lhs) + self.visit_expr(rhs), + Expr::Sub(ref lhs, ref rhs) => self.visit_expr(lhs) - self.visit_expr(rhs), + } + } +} +``` + +One could implement further visitors, for example a type checker, without having +to modify the AST data. + +## Motivation + +The visitor pattern is useful anywhere that you want to apply an algorithm to +heterogeneous data. If data is homogeneous, you can use an iterator-like pattern. +Using a visitor object (rather than a functional approach) allows the visitor to +be stateful and thus communicate information between nodes. + +## Discussion + +It is common for the `visit_*` methods to return void (as opposed to in the +example). In that case it is possible to factor out the traversal code and share +it between algorithms (and also to provide noop default methods). In Rust, the +common way to do this is to provide `walk_*` functions for each datum. For +example, + +```rust,ignore +pub fn walk_expr(visitor: &mut Visitor, e: &Expr) { + match *e { + Expr::IntLit(_) => {}, + Expr::Add(ref lhs, ref rhs) => { + visitor.visit_expr(lhs); + visitor.visit_expr(rhs); + } + Expr::Sub(ref lhs, ref rhs) => { + visitor.visit_expr(lhs); + visitor.visit_expr(rhs); + } + } +} +``` + +In other languages (e.g., Java) it is common for data to have an `accept` method +which performs the same duty. + +## See also + +The visitor pattern is a common pattern in most OO languages. + +[Wikipedia article](https://en.wikipedia.org/wiki/Visitor_pattern) + +The [fold](fold.md) pattern is similar to visitor but produces a new version of +the visited data structure. diff --git a/src/prelude/builder.rs b/src/prelude/builder.rs deleted file mode 100644 index e8bc8dc..0000000 --- a/src/prelude/builder.rs +++ /dev/null @@ -1,40 +0,0 @@ -use crate::foundation::patterns::builder::Builder; - - -/// Powerfull way to build simple objects. -pub trait With<T> { - /// Set param - fn with(self, param: T) -> Self; -} - -/// Powerfull way to get object properties. -pub trait Getter<T> { - /// Typed getter - fn get(from: &T) -> &Self; -} - -/// Powerfull way to configure objects. -pub trait SetterMut<T> { - /// Set param - fn set(&mut self, param: T) -> &mut Self; -} - -/// Powerfull way to configure objects. -pub trait Setter<T> { - /// Set param - fn set(&self, param: T) -> &Self; -} - -/// Powerfull way to configure objects with builder pattern. -pub trait BuildWith<T> { - /// Set param - fn with(param: T) -> Self; -} - -/// Trait which generate builder by set the some parameter. -pub trait WithBuilder<P>: Sized { - /// Generate builder for struct - fn with(param: P) -> Builder<Self> - where - Self: Default; -} diff --git a/src/prelude/command.rs b/src/prelude/command.rs deleted file mode 100644 index b2376d8..0000000 --- a/src/prelude/command.rs +++ /dev/null @@ -1,12 +0,0 @@ -use std::{fmt::Debug, rc::Rc}; - -use super::Notification; - -/// The definition for a PureMVC Command. -pub trait Command<Body>: Debug -where - Body: Debug + 'static, -{ - /// Execute the [Command]'s logic to handle a given [Notification]. - fn execute(&self, notification: Rc<dyn Notification<Body>>); -} diff --git a/src/prelude/controller.rs b/src/prelude/controller.rs deleted file mode 100644 index 3cad351..0000000 --- a/src/prelude/controller.rs +++ /dev/null @@ -1,42 +0,0 @@ -use std::{fmt::Debug, rc::Rc}; - -use super::{Command, Interest, Notification}; - -/// The definition for a PureMVC Controller. -/// -/// In PureMVC, an [Controller] implementor -/// follows the 'Command and Controller' strategy, and -/// assumes these responsibilities: -/// -/// - Remembering which [Command]'s -/// are intended to handle which [Notification]'s -/// - Registering itself as an [Observer] with -/// the [View] for each [Notification] -/// that it has an [Command] mapping for -/// - Creating a new instance of the proper [Command] -/// to handle a given [Notification] when notified by the [View] -/// - Calling the [Command]'s [execute] -/// method, passing in the [Notification] -/// -/// [Observer]: crate::prelude::Observer -/// [View]: crate::prelude::View -/// [execute]: Command::execute - -pub trait Controller<Body> -where - Body: Debug + 'static, -{ - /// Register a particular [Command] class as the handler - /// for a particular [Notification]. - fn register_command(&self, interest: Interest, command: Rc<dyn Command<Body>>); - - /// Execute the [Command] previously registered as the - /// handler for [Notification]'s with the given notification name. - fn execute_command(&self, notification: Rc<dyn Notification<Body>>); - - /// Remove a previously registered [Command] to [Notification] mapping. - fn remove_command(&self, interest: &Interest); - - /// Check if a Command is registered for a given Notification - fn has_command(&self, interest: &Interest) -> bool; -} diff --git a/src/prelude/facade.rs b/src/prelude/facade.rs deleted file mode 100644 index a3a8240..0000000 --- a/src/prelude/facade.rs +++ /dev/null @@ -1,45 +0,0 @@ -use std::{fmt::Debug, rc::Rc}; - -use super::{Command, Interest}; - -/// The definition for a PureMVC Facade. -/// -/// The Facade Pattern suggests providing a single -/// class to act as a central point of communication -/// for a subsystem. -/// -/// In PureMVC, the Facade acts as an interface between -/// the core MVC actors [Model], [View], [Controller] and -/// the rest of your application. -/// -/// Also Facade should implement IModel trait with Model -/// for different data types and IView -/// -/// [Model]: crate::prelude::Model -/// [View]: crate::prelude::View -/// [Controller]: crate::prelude::Controller -pub trait Facade<Body> -where - Body: Debug + 'static, -{ - /// Register an [Command] with the [Controller]. - /// - /// [Controller]: crate::prelude::Controller - fn register_command(&self, interest: Interest, command: Rc<dyn Command<Body>>); - - /// Remove a previously registered [Command] to [Notification] mapping from the [Controller]. - /// - /// [Notification]: crate::prelude::Notification - /// [Controller]: crate::prelude::Controller - fn remove_command(&self, interest: &Interest); - - /// Check if a [Command] is registered for a given [Notification] - /// - /// [Notification]: crate::prelude::Notification - fn has_command(&self, interest: &Interest) -> bool; - - /// Create and send an [Notification]. - /// - /// [Notification]: crate::prelude::Notification - fn send(&self, interest: Interest, body: Option<Body>); -} diff --git a/src/prelude/mediator.rs b/src/prelude/mediator.rs deleted file mode 100644 index 044f6ac..0000000 --- a/src/prelude/mediator.rs +++ /dev/null @@ -1,58 +0,0 @@ -use std::{any::Any, fmt::Debug, rc::Rc}; - -use super::{Interest, Notification, NotifyContext, View}; - -/// The definition for a PureMVC [Mediator]. -/// -/// In PureMVC, [Mediator] implementors assume these responsibilities: -/// -/// - Implement a common method which returns a list of all [Notification]'s -/// the [Mediator] has interest in. -/// - Implement a common notification (callback) method. -/// -/// Additionally, [Mediator]'s typically: -/// -/// - Act as an intermediary between one or more view components such as text boxes or -/// list controls, maintaining references and coordinating their behavior. -/// - In Flash-based apps, this is often the place where event listeners are -/// added to view components, and their handlers implemented. -/// - Respond to and generate [Notification]'s, interacting with of -/// the rest of the PureMVC app. -/// -/// When an [Mediator] is registered with the [View], -/// the [View] will call the [Mediator]'s -/// [list_notification_interests] method. The [Mediator] will -/// return an [Vec] of [Notification] names which -/// it wishes to be notified about. -/// -/// The [View] will then create an [Observer] object -/// encapsulating that [Mediator]'s [handle_notification] method -/// and register it as an [Observer] for each [Notification] name returned by -/// [list_notification_interests]. -/// -/// [Observer]: crate::prelude::Observer -/// [handle_notification]: Mediator::handle_notification -/// [list_notification_interests]: Mediator::list_notification_interests - -pub trait Mediator<Body>: NotifyContext + Debug + Sized + Any -where - Body: Debug + 'static, -{ - /// Get the [Mediator]'s view component. - fn view_component(&self) -> Option<Rc<dyn View<Body>>>; - - /// Set the [Mediator]'s view component. - fn set_view_component(&mut self, component: Option<Rc<dyn View<Body>>>); - - /// List [Notification] interests. - fn list_notification_interests(&self) -> &[Interest]; - - /// Handle an [Notification]. - fn handle_notification(&self, notification: Rc<dyn Notification<Body>>); - - /// Called by the [View] when the [Mediator] is registered - fn on_register(&self); - - /// Called by the [View] when the [Mediator] is removed - fn on_remove(&self); -} diff --git a/src/prelude/mod.rs b/src/prelude/mod.rs deleted file mode 100644 index d77d70d..0000000 --- a/src/prelude/mod.rs +++ /dev/null @@ -1,45 +0,0 @@ -//! The `ruex` prelude. -//! -//! The purpose of this module is to alleviate imports of many common ruex -//! traits by adding a glob import to the top of ruex heavy modules: -//! -//! ``` -//! # #![allow(unused_imports)] -//! use ruex::prelude::*; -//! ``` - -mod builder; -pub use self::builder::*; - -mod command; -pub use self::command::*; - -mod controller; -pub use self::controller::*; - -mod facade; -pub use self::facade::*; - -mod mediator; -pub use self::mediator::*; - -mod model; -pub use self::model::*; - -mod notification; -pub use self::notification::*; - -mod notifier; -pub use self::notifier::*; - -mod observer; -pub use self::observer::*; - -mod proxy; -pub use self::proxy::*; - -mod singleton; -pub use self::singleton::*; - -mod view; -pub use self::view::*; diff --git a/src/prelude/model.rs b/src/prelude/model.rs deleted file mode 100644 index 5279ac1..0000000 --- a/src/prelude/model.rs +++ /dev/null @@ -1,28 +0,0 @@ -use std::rc::Rc; - -use super::Proxy; - -/// The definition for a PureMVC Model. -/// -/// In PureMVC, [Model] implementors provide -/// access to [Proxy] objects by named lookup. -/// -/// An [Model] assumes these responsibilities: -/// -/// - Maintain a cache of [Proxy] instances -/// - Provide methods for registering, retrieving, and removing [Proxy] instances -/// - -pub trait Model { - /// Register an [Proxy] instance with the [Model]. - fn register_proxy<P: Proxy>(&self, proxy: Rc<P>); - - /// Retrieve an [Proxy] instance from the Model. - fn retrieve_proxy<P: Proxy>(&self) -> Option<Rc<P>>; - - /// Remove an [Proxy] instance from the Model. - fn remove_proxy<P: Proxy>(&self) -> Option<Rc<P>>; - - /// Check if a [Proxy] is registered - fn has_proxy<P: Proxy>(&self) -> bool; -} diff --git a/src/prelude/notification.rs b/src/prelude/notification.rs deleted file mode 100644 index a96f3b9..0000000 --- a/src/prelude/notification.rs +++ /dev/null @@ -1,53 +0,0 @@ -use std::fmt; - -/// Represent [Notification]'s interest -#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub struct Interest(pub u64); - -/// The definition for a PureMVC Notification. -/// -/// PureMVC does not rely upon underlying event models such -/// as the one provided with Flash. -/// -/// The Observer Pattern as implemented within PureMVC exists -/// to support event-driven communication between the -/// application and the actors of the MVC triad. -/// -/// Notifications are not meant to be a replacement for Events. -/// Generally, [Mediator] implementors -/// place event listeners on their view components, which they -/// then handle in the usual way. This may lead to the broadcast of [Notification]'s to -/// trigger [Command]'s or to communicate with other [Mediator]'s. [Proxy] and [Command] -/// instances communicate with each other and [Mediator]'s -/// by broadcasting [Notification]'s. -/// -/// A key difference between native event's and PureMVC -/// [Notification]'s is that event's follow the -/// 'Chain of Responsibility' pattern, 'bubbling' up the display hierarchy -/// until some parent component handles the event, while -/// PureMVC [Notification]'s follow a 'Publish/Subscribe' -/// pattern. PureMVC classes need not be related to each other in a -/// parent/child relationship in order to communicate with one another -/// using [Notification]'s. -/// -/// Should implement fmt::Debug to get the string representation of -/// the [Notification] instance -/// -/// [Mediator]: crate::prelude::Mediator -/// [Proxy]: crate::prelude::Proxy -/// [Command]: crate::prelude::Command - -pub trait Notification<Body>: fmt::Debug -where - Body: fmt::Debug + 'static, -{ - /// Get the interest of the [Notification] instance - /// No setter, should be set by constructor only - fn interest(&self) -> Interest; - - /// Set the body of the [Notification] instance - fn set_body(&mut self, body: Option<Body>); - - /// Get the body of the [Notification] instance - fn body(&self) -> Option<&Body>; -} diff --git a/src/prelude/notifier.rs b/src/prelude/notifier.rs deleted file mode 100644 index 83cdac2..0000000 --- a/src/prelude/notifier.rs +++ /dev/null @@ -1,40 +0,0 @@ -use std::fmt::Debug; - -use super::Interest; - -/// The definition for a PureMVC Notifier. -/// -/// [MacroCommand, Command, Mediator] and [Proxy] -/// all have a need to send [Notification]'s. -/// -/// The [Notifier] interface provides a common method called -/// [send] that relieves implementation code of -/// the necessity to actually construct [Notification]'s. -/// -/// The [Notifier] class, which all of the above mentioned classes -/// extend, also provides an initialized reference to the [Facade] -/// Singleton, which is required for the convienience method -/// for sending [Notification]'s, but also eases implementation as these -/// classes have frequent [Facade] interactions and usually require -/// access to the facade anyway. -/// -/// [Notification]: crate::prelude::Notification -/// [Facade]: crate::prelude::Facade -/// [MacroCommand]: crate::prelude::MacroCommand -/// [Command]: crate::prelude::Command -/// [Mediator]: crate::prelude::Mediator -/// [Proxy]: crate::prelude::Proxy -/// [send]: Notifier::send - -pub trait Notifier<Body> -where - Body: Debug + 'static, -{ - /// Send a [Notification]. - /// - /// Convenience method to prevent having to construct new - /// notification instances in our implementation code. - /// - /// [Notification]: crate::prelude::Notification - fn send(&self, interest: Interest, body: Option<Body>); -} diff --git a/src/prelude/observer.rs b/src/prelude/observer.rs deleted file mode 100644 index 8f75c4d..0000000 --- a/src/prelude/observer.rs +++ /dev/null @@ -1,58 +0,0 @@ -use std::{fmt::Debug, rc::Rc}; - -use super::Notification; - -/// Defines NotifyContext identity -pub trait NotifyContext: Debug { - /// Retrieve context identity - fn id(&self) -> u64; -} - -/// The definition for a PureMVC Observer. -/// -/// In PureMVC, [Observer] implementors assume these responsibilities: -/// -/// - Encapsulate the notification (callback) method of the interested object. -/// - Encapsulate the notification context (this) of the interested object. -/// - Provide methods for setting the interested object' notification method and context. -/// - Provide a method for notifying the interested object. -/// -/// -/// PureMVC does not rely upon underlying event -/// models such as the one provided with Flash. -/// -/// The Observer Pattern as implemented within -/// PureMVC exists to support event driven communication -/// between the application and the actors of the MVC triad. -/// -/// An Observer is an object that encapsulates information -/// about an interested object with a notification method that -/// should be called when an [Notification] is broadcast. The Observer then -/// acts as a proxy for notifying the interested object. -/// -/// Observers can receive [Notification]'s by having their -/// [notify](Observer::notify) method invoked, passing -/// in an object implementing the [Notification] interface, such -/// as a subclass of [Notification]. - -pub trait Observer<Body>: Debug -where - Body: Debug + 'static, -{ - /// Get the notification context. - fn context(&self) -> &Rc<dyn NotifyContext>; - - /// Set the notification method. - /// - /// The notification method should take one parameter of type [Notification] - fn set_method(&mut self, notify_method: Box<dyn Fn(Rc<dyn Notification<Body>>)>); - - /// Set the notification context. - fn set_context(&mut self, notify_context: Rc<dyn NotifyContext>); - - /// Notify the interested object. - fn notify(&self, notification: Rc<dyn Notification<Body>>); - - /// Compare the given object to the notificaiton context object. - fn compare_context(&self, object: &Rc<dyn NotifyContext>) -> bool; -} diff --git a/src/prelude/proxy.rs b/src/prelude/proxy.rs deleted file mode 100644 index 95ab3cd..0000000 --- a/src/prelude/proxy.rs +++ /dev/null @@ -1,27 +0,0 @@ -use std::{any::Any, fmt::Debug}; - -/// The definition for a PureMVC Proxy. -/// -/// In PureMVC, [Proxy] implementors assume these responsibilities: -/// -/// - Implement a common method which returns the name of the [Proxy]. -/// -/// Additionally, [Proxy]'s typically: -/// -/// - Maintain references to one or more pieces of model data. -/// - Provide methods for manipulating that data. -/// - Generate [Notification]'s when their model data changes. -/// - Expose their name using [Debug] or [Display]. -/// - Encapsulate interaction with local or remote services used to fetch and persist model data. -/// -/// [Notification]: crate::prelude::Notification -/// [Debug]: std::fmt::Debug -/// [Display]: std::fmt::Display - -pub trait Proxy: Debug + Sized + Any { - /// Called by the Model when the [Proxy] is registered - fn on_register(&self); - - /// Called by the Model when the [Proxy] is removed - fn on_remove(&self); -} diff --git a/src/prelude/singleton.rs b/src/prelude/singleton.rs deleted file mode 100644 index 4ef3fd5..0000000 --- a/src/prelude/singleton.rs +++ /dev/null @@ -1,6 +0,0 @@ -/// Singleton Factory method -/// -pub trait Singleton { - /// Retrieve instance of Singleton - fn global() -> &'static Self; -} diff --git a/src/prelude/view.rs b/src/prelude/view.rs deleted file mode 100644 index f9e84cf..0000000 --- a/src/prelude/view.rs +++ /dev/null @@ -1,61 +0,0 @@ -use std::{fmt::Debug, rc::Rc}; - -use super::{Interest, Mediator, Notification, NotifyContext, Observer}; - -/// The definition for a PureMVC View. -/// -/// In PureMVC, the [View] class assumes these responsibilities: -/// -/// - Maintain a cache of [Mediator] instances. -/// - Provide methods for registering, retrieving, and removing [Mediator]'s. -/// - Managing the observer lists for each [Notification] in the application. -/// - Providing a method for attaching [Observer]'s to an [Notification]'s observer list. -/// - Providing a method for broadcasting an [Notification]. -/// - Notifying the [Observer]'s of a given [Notification] when it broadcast. -/// - -pub trait View<Body> -where - Body: Debug + 'static, -{ - /// Register an [Observer] to be notified of [Notification]'s with a given name. - fn register_observer(&self, interest: Interest, observer: Rc<dyn Observer<Body>>); - - /// Remove a group of observers from the observer list for a given Notification name. - fn remove_observer(&self, interest: &Interest, notify_context: &Rc<dyn NotifyContext>); - - /// Notify the [Observer]'s for a particular [Notification]. - /// - /// All previously attached [Observer]'s for this [Notification]'s - /// list are notified and are passed a reference to the [Notification] in - /// the order in which they were registered. - fn notify(&self, note: Rc<dyn Notification<Body>>); -} - - -/// Defines Mediator Registry functionality -pub trait MediatorRegistry<Body> -where - Body: Debug + 'static, -{ - /// Register an [Mediator] instance with the [View]. - /// - /// Registers the [Mediator] so that it can be retrieved by name, - /// and further interrogates the [Mediator] for its [Notification] interests. - /// - /// If the [Mediator] returns any [Notification] - /// names to be notified about, an [Observer] is created encapsulating - /// the [Mediator] instance's [handle_notification](Mediator::handle_notification) method - /// and registering it as an [Observer] for all [Notification]'s the - /// [Mediator] is interested in. - fn register_mediator<M: Mediator<Body>>(&self, mediator: Rc<M>); - - /// Retrieve an [Mediator] from the [View]. - fn retrieve_mediator<M: Mediator<Body>>(&self) -> Option<Rc<M>>; - - /// Remove an [Mediator] from the [View]. - fn remove_mediator<M: Mediator<Body>>(&self) -> Option<Rc<M>>; - - /// Check if a [Mediator] is registered or not - fn has_mediator<M: Mediator<Body>>(&self) -> bool; -} diff --git a/src/producer.rs b/src/producer.rs deleted file mode 100644 index 39ec248..0000000 --- a/src/producer.rs +++ /dev/null @@ -1,46 +0,0 @@ -// use crate::event_bus::{EventBus, Request}; -// use yew::agent::{Dispatched, Dispatcher}; -// use yew::prelude::*; - -// pub enum Msg { -// Clicked, -// } - -// pub struct Producer { -// link: ComponentLink<Producer>, -// event_bus: Dispatcher<EventBus>, -// } - -// impl Component for Producer { -// type Message = Msg; -// type Properties = (); - -// fn create(_props: Self::Properties, link: ComponentLink<Self>) -> Self { -// Self { -// link, -// event_bus: EventBus::dispatcher(), -// } -// } - -// fn change(&mut self, _props: Self::Properties) -> ShouldRender { -// false -// } - -// fn update(&mut self, msg: Self::Message) -> ShouldRender { -// match msg { -// Msg::Clicked => { -// self.event_bus -// .send(Request::EventBusMsg("Message received".to_owned())); -// false -// } -// } -// } - -// fn view(&self) -> Html { -// html! { -// <button onclick=self.link.callback(|_| Msg::Clicked)> -// { "PRESS ME" } -// </button> -// } -// } -// } diff --git a/src/refactoring/index.md b/src/refactoring/index.md new file mode 100644 index 0000000..178a53b --- /dev/null +++ b/src/refactoring/index.md @@ -0,0 +1,22 @@ +# Refactoring + +Refactoring is very important in relation to these topics. +Just as important as the other topics covered here, is how to take good code and +turn it into great code. + +We can use [design patterns](../patterns/index.md) to [DRY] up code and generalize +abstractions. We must avoid [anti-patterns](../anti_patterns/index.md) while we +do this. While they may be tempting to employ, their costs outweigh their benefits. + +> Shortcuts make for long days. + +We can also use [idioms](../idioms/index.md) to structure our code in a way that +is understandable. + +## Tests + +Tests are of vital importance during refactoring. + +## Small changes + +[DRY]: https://en.wikipedia.org/wiki/Don%27t_repeat_yourself diff --git a/src/subscriber.rs b/src/subscriber.rs deleted file mode 100644 index d8bb2e0..0000000 --- a/src/subscriber.rs +++ /dev/null @@ -1,43 +0,0 @@ -// use super::event_bus::EventBus; -// use yew::agent::Bridged; -// use yew::{html, Bridge, Component, ComponentLink, Html, ShouldRender}; - -// pub enum Msg { -// NewMessage(String), -// } - -// pub struct Subscriber { -// message: String, -// _producer: Box<dyn Bridge<EventBus>>, -// } - -// impl Component for Subscriber { -// type Message = Msg; -// type Properties = (); - -// fn create(_props: Self::Properties, link: ComponentLink<Self>) -> Self { -// Self { -// message: "No message yet.".to_owned(), -// _producer: EventBus::bridge(link.callback(Msg::NewMessage)), -// } -// } - -// fn change(&mut self, _props: Self::Properties) -> ShouldRender { -// false -// } - -// fn update(&mut self, msg: Self::Message) -> ShouldRender { -// match msg { -// Msg::NewMessage(s) => { -// self.message = s; -// true -// } -// } -// } - -// fn view(&self) -> Html { -// html! { -// <h1>{ &self.message }</h1> -// } -// } -// } diff --git a/src/theme/book.js b/src/theme/book.js new file mode 100644 index 0000000..5e38636 --- /dev/null +++ b/src/theme/book.js @@ -0,0 +1,660 @@ +"use strict"; + +// Fix back button cache problem +window.onunload = function () { }; + +// Global variable, shared between modules +function playground_text(playground) { + let code_block = playground.querySelector("code"); + + if (window.ace && code_block.classList.contains("editable")) { + let editor = window.ace.edit(code_block); + return editor.getValue(); + } else { + return code_block.textContent; + } +} + +(function codeSnippets() { + function fetch_with_timeout(url, options, timeout = 6000) { + return Promise.race([ + fetch(url, options), + new Promise((_, reject) => setTimeout(() => reject(new Error('timeout')), timeout)) + ]); + } + + var playgrounds = Array.from(document.querySelectorAll(".playground")); + if (playgrounds.length > 0) { + fetch_with_timeout("https://play.rust-lang.org/meta/crates", { + headers: { + 'Content-Type': "application/json", + }, + method: 'POST', + mode: 'cors', + }) + .then(response => response.json()) + .then(response => { + // get list of crates available in the rust playground + let playground_crates = response.crates.map(item => item["id"]); + playgrounds.forEach(block => handle_crate_list_update(block, playground_crates)); + }); + } + + function handle_crate_list_update(playground_block, playground_crates) { + // update the play buttons after receiving the response + update_play_button(playground_block, playground_crates); + + // and install on change listener to dynamically update ACE editors + if (window.ace) { + let code_block = playground_block.querySelector("code"); + if (code_block.classList.contains("editable")) { + let editor = window.ace.edit(code_block); + editor.addEventListener("change", function (e) { + update_play_button(playground_block, playground_crates); + }); + // add Ctrl-Enter command to execute rust code + editor.commands.addCommand({ + name: "run", + bindKey: { + win: "Ctrl-Enter", + mac: "Ctrl-Enter" + }, + exec: _editor => run_rust_code(playground_block) + }); + } + } + } + + // updates the visibility of play button based on `no_run` class and + // used crates vs ones available on http://play.rust-lang.org + function update_play_button(pre_block, playground_crates) { + var play_button = pre_block.querySelector(".play-button"); + + // skip if code is `no_run` + if (pre_block.querySelector('code').classList.contains("no_run")) { + play_button.classList.add("hidden"); + return; + } + + // get list of `extern crate`'s from snippet + var txt = playground_text(pre_block); + var re = /extern\s+crate\s+([a-zA-Z_0-9]+)\s*;/g; + var snippet_crates = []; + var item; + while (item = re.exec(txt)) { + snippet_crates.push(item[1]); + } + + // check if all used crates are available on play.rust-lang.org + var all_available = snippet_crates.every(function (elem) { + return playground_crates.indexOf(elem) > -1; + }); + + if (all_available) { + play_button.classList.remove("hidden"); + } else { + play_button.classList.add("hidden"); + } + } + + function run_rust_code(code_block) { + var result_block = code_block.querySelector(".result"); + if (!result_block) { + result_block = document.createElement('code'); + result_block.className = 'result hljs language-bash'; + + code_block.append(result_block); + } + + let text = playground_text(code_block); + let classes = code_block.querySelector('code').classList; + let has_2018 = classes.contains("edition2018"); + let edition = has_2018 ? "2018" : "2015"; + + var params = { + version: "stable", + optimize: "0", + code: text, + edition: edition + }; + + if (text.indexOf("#![feature") !== -1) { + params.version = "nightly"; + } + + result_block.innerText = "Running..."; + + fetch_with_timeout("https://play.rust-lang.org/evaluate.json", { + headers: { + 'Content-Type': "application/json", + }, + method: 'POST', + mode: 'cors', + body: JSON.stringify(params) + }) + .then(response => response.json()) + .then(response => result_block.innerText = response.result) + .catch(error => result_block.innerText = "Playground Communication: " + error.message); + } + + // Syntax highlighting Configuration + hljs.configure({ + tabReplace: ' ', // 4 spaces + languages: [], // Languages used for auto-detection + }); + + let code_nodes = Array + .from(document.querySelectorAll('code')) + // Don't highlight `inline code` blocks in headers. + .filter(function (node) {return !node.parentElement.classList.contains("header"); }); + + if (window.ace) { + // language-rust class needs to be removed for editable + // blocks or highlightjs will capture events + Array + .from(document.querySelectorAll('code.editable')) + .forEach(function (block) { block.classList.remove('language-rust'); }); + + Array + .from(document.querySelectorAll('code:not(.editable)')) + .forEach(function (block) { hljs.highlightBlock(block); }); + } else { + code_nodes.forEach(function (block) { hljs.highlightBlock(block); }); + } + + // Adding the hljs class gives code blocks the color css + // even if highlighting doesn't apply + code_nodes.forEach(function (block) { block.classList.add('hljs'); }); + + Array.from(document.querySelectorAll("code.language-rust")).forEach(function (block) { + + var lines = Array.from(block.querySelectorAll('.boring')); + // If no lines were hidden, return + if (!lines.length) { return; } + block.classList.add("hide-boring"); + + var buttons = document.createElement('div'); + buttons.className = 'buttons'; + buttons.innerHTML = "<button class=\"fa fa-eye\" title=\"Show hidden lines\" aria-label=\"Show hidden lines\"></button>"; + + // add expand button + var pre_block = block.parentNode; + pre_block.insertBefore(buttons, pre_block.firstChild); + + pre_block.querySelector('.buttons').addEventListener('click', function (e) { + if (e.target.classList.contains('fa-eye')) { + e.target.classList.remove('fa-eye'); + e.target.classList.add('fa-eye-slash'); + e.target.title = 'Hide lines'; + e.target.setAttribute('aria-label', e.target.title); + + block.classList.remove('hide-boring'); + } else if (e.target.classList.contains('fa-eye-slash')) { + e.target.classList.remove('fa-eye-slash'); + e.target.classList.add('fa-eye'); + e.target.title = 'Show hidden lines'; + e.target.setAttribute('aria-label', e.target.title); + + block.classList.add('hide-boring'); + } + }); + }); + + if (window.playground_copyable) { + Array.from(document.querySelectorAll('pre code')).forEach(function (block) { + var pre_block = block.parentNode; + if (!pre_block.classList.contains('playground')) { + var buttons = pre_block.querySelector(".buttons"); + if (!buttons) { + buttons = document.createElement('div'); + buttons.className = 'buttons'; + pre_block.insertBefore(buttons, pre_block.firstChild); + } + + var clipButton = document.createElement('button'); + clipButton.className = 'fa fa-copy clip-button'; + clipButton.title = 'Copy to clipboard'; + clipButton.setAttribute('aria-label', clipButton.title); + clipButton.innerHTML = '<i class=\"tooltiptext\"></i>'; + + buttons.insertBefore(clipButton, buttons.firstChild); + } + }); + } + + // Process playground code blocks + Array.from(document.querySelectorAll(".playground")).forEach(function (pre_block) { + // Add play button + var buttons = pre_block.querySelector(".buttons"); + if (!buttons) { + buttons = document.createElement('div'); + buttons.className = 'buttons'; + pre_block.insertBefore(buttons, pre_block.firstChild); + } + + var runCodeButton = document.createElement('button'); + runCodeButton.className = 'fa fa-play play-button'; + runCodeButton.hidden = true; + runCodeButton.title = 'Run this code'; + runCodeButton.setAttribute('aria-label', runCodeButton.title); + + buttons.insertBefore(runCodeButton, buttons.firstChild); + runCodeButton.addEventListener('click', function (e) { + run_rust_code(pre_block); + }); + + if (window.playground_copyable) { + var copyCodeClipboardButton = document.createElement('button'); + copyCodeClipboardButton.className = 'fa fa-copy clip-button'; + copyCodeClipboardButton.innerHTML = '<i class="tooltiptext"></i>'; + copyCodeClipboardButton.title = 'Copy to clipboard'; + copyCodeClipboardButton.setAttribute('aria-label', copyCodeClipboardButton.title); + + buttons.insertBefore(copyCodeClipboardButton, buttons.firstChild); + } + + let code_block = pre_block.querySelector("code"); + if (window.ace && code_block.classList.contains("editable")) { + var undoChangesButton = document.createElement('button'); + undoChangesButton.className = 'fa fa-history reset-button'; + undoChangesButton.title = 'Undo changes'; + undoChangesButton.setAttribute('aria-label', undoChangesButton.title); + + buttons.insertBefore(undoChangesButton, buttons.firstChild); + + undoChangesButton.addEventListener('click', function () { + let editor = window.ace.edit(code_block); + editor.setValue(editor.originalCode); + editor.clearSelection(); + }); + } + }); +})(); + +(function themes() { + var html = document.querySelector('html'); + var themeToggleButton = document.getElementById('theme-toggle'); + var themePopup = document.getElementById('theme-list'); + var themeColorMetaTag = document.querySelector('meta[name="theme-color"]'); + var stylesheets = { + ayuHighlight: document.querySelector("[href$='ayu-highlight.css']"), + tomorrowNight: document.querySelector("[href$='tomorrow-night.css']"), + highlight: document.querySelector("[href$='highlight.css']"), + }; + + function showThemes() { + themePopup.style.display = 'block'; + themeToggleButton.setAttribute('aria-expanded', true); + themePopup.querySelector("button#" + get_theme()).focus(); + } + + function hideThemes() { + themePopup.style.display = 'none'; + themeToggleButton.setAttribute('aria-expanded', false); + themeToggleButton.focus(); + } + + function get_theme() { + var theme; + try { theme = localStorage.getItem('mdbook-theme'); } catch (e) { } + if (theme === null || theme === undefined) { + return default_theme; + } else { + return theme; + } + } + + function set_theme(theme, store = true) { + let ace_theme; + + if (theme == 'coal' || theme == 'navy') { + stylesheets.ayuHighlight.disabled = true; + stylesheets.tomorrowNight.disabled = false; + stylesheets.highlight.disabled = true; + + ace_theme = "ace/theme/tomorrow_night"; + } else if (theme == 'ayu') { + stylesheets.ayuHighlight.disabled = false; + stylesheets.tomorrowNight.disabled = true; + stylesheets.highlight.disabled = true; + ace_theme = "ace/theme/tomorrow_night"; + } else { + stylesheets.ayuHighlight.disabled = true; + stylesheets.tomorrowNight.disabled = true; + stylesheets.highlight.disabled = false; + ace_theme = "ace/theme/dawn"; + } + + setTimeout(function () { + themeColorMetaTag.content = getComputedStyle(document.body).backgroundColor; + }, 1); + + if (window.ace && window.editors) { + window.editors.forEach(function (editor) { + editor.setTheme(ace_theme); + }); + } + + var previousTheme = get_theme(); + + if (store) { + try { localStorage.setItem('mdbook-theme', theme); } catch (e) { } + } + + html.classList.remove(previousTheme); + html.classList.add(theme); + } + + // Set theme + var theme = get_theme(); + + set_theme(theme, false); + + themeToggleButton.addEventListener('click', function () { + if (themePopup.style.display === 'block') { + hideThemes(); + } else { + showThemes(); + } + }); + + themePopup.addEventListener('click', function (e) { + var theme = e.target.id || e.target.parentElement.id; + set_theme(theme); + }); + + themePopup.addEventListener('focusout', function(e) { + // e.relatedTarget is null in Safari and Firefox on macOS (see workaround below) + if (!!e.relatedTarget && !themeToggleButton.contains(e.relatedTarget) && !themePopup.contains(e.relatedTarget)) { + hideThemes(); + } + }); + + // Should not be needed, but it works around an issue on macOS & iOS: https://github.com/rust-lang/mdBook/issues/628 + document.addEventListener('click', function(e) { + if (themePopup.style.display === 'block' && !themeToggleButton.contains(e.target) && !themePopup.contains(e.target)) { + hideThemes(); + } + }); + + document.addEventListener('keydown', function (e) { + if (e.altKey || e.ctrlKey || e.metaKey || e.shiftKey) { return; } + if (!themePopup.contains(e.target)) { return; } + + switch (e.key) { + case 'Escape': + e.preventDefault(); + hideThemes(); + break; + case 'ArrowUp': + e.preventDefault(); + var li = document.activeElement.parentElement; + if (li && li.previousElementSibling) { + li.previousElementSibling.querySelector('button').focus(); + } + break; + case 'ArrowDown': + e.preventDefault(); + var li = document.activeElement.parentElement; + if (li && li.nextElementSibling) { + li.nextElementSibling.querySelector('button').focus(); + } + break; + case 'Home': + e.preventDefault(); + themePopup.querySelector('li:first-child button').focus(); + break; + case 'End': + e.preventDefault(); + themePopup.querySelector('li:last-child button').focus(); + break; + } + }); +})(); + +(function sidebar() { + var html = document.querySelector("html"); + var sidebar = document.getElementById("sidebar"); + var sidebarLinks = document.querySelectorAll('#sidebar a'); + var sidebarToggleButton = document.getElementById("sidebar-toggle"); + var sidebarResizeHandle = document.getElementById("sidebar-resize-handle"); + var firstContact = null; + + function showSidebar() { + html.classList.remove('sidebar-hidden') + html.classList.add('sidebar-visible'); + Array.from(sidebarLinks).forEach(function (link) { + link.setAttribute('tabIndex', 0); + }); + sidebarToggleButton.setAttribute('aria-expanded', true); + sidebar.setAttribute('aria-hidden', false); + try { localStorage.setItem('mdbook-sidebar', 'visible'); } catch (e) { } + } + + + var sidebarAnchorToggles = document.querySelectorAll('#sidebar a.toggle'); + + function toggleSection(ev) { + ev.currentTarget.parentElement.classList.toggle('expanded'); + } + + Array.from(sidebarAnchorToggles).forEach(function (el) { + el.addEventListener('click', toggleSection); + }); + + function hideSidebar() { + html.classList.remove('sidebar-visible') + html.classList.add('sidebar-hidden'); + Array.from(sidebarLinks).forEach(function (link) { + link.setAttribute('tabIndex', -1); + }); + sidebarToggleButton.setAttribute('aria-expanded', false); + sidebar.setAttribute('aria-hidden', true); + try { localStorage.setItem('mdbook-sidebar', 'hidden'); } catch (e) { } + } + + // Toggle sidebar + sidebarToggleButton.addEventListener('click', function sidebarToggle() { + if (html.classList.contains("sidebar-hidden")) { + var current_width = parseInt( + document.documentElement.style.getPropertyValue('--sidebar-width'), 10); + if (current_width < 150) { + document.documentElement.style.setProperty('--sidebar-width', '150px'); + } + showSidebar(); + } else if (html.classList.contains("sidebar-visible")) { + hideSidebar(); + } else { + if (getComputedStyle(sidebar)['transform'] === 'none') { + hideSidebar(); + } else { + showSidebar(); + } + } + }); + + sidebarResizeHandle.addEventListener('mousedown', initResize, false); + + function initResize(e) { + window.addEventListener('mousemove', resize, false); + window.addEventListener('mouseup', stopResize, false); + html.classList.add('sidebar-resizing'); + } + function resize(e) { + var pos = (e.clientX - sidebar.offsetLeft); + if (pos < 20) { + hideSidebar(); + } else { + if (html.classList.contains("sidebar-hidden")) { + showSidebar(); + } + pos = Math.min(pos, window.innerWidth - 100); + document.documentElement.style.setProperty('--sidebar-width', pos + 'px'); + } + } + //on mouseup remove windows functions mousemove & mouseup + function stopResize(e) { + html.classList.remove('sidebar-resizing'); + window.removeEventListener('mousemove', resize, false); + window.removeEventListener('mouseup', stopResize, false); + } + + document.addEventListener('touchstart', function (e) { + firstContact = { + x: e.touches[0].clientX, + time: Date.now() + }; + }, { passive: true }); + + document.addEventListener('touchmove', function (e) { + if (!firstContact) + return; + + var curX = e.touches[0].clientX; + var xDiff = curX - firstContact.x, + tDiff = Date.now() - firstContact.time; + + if (tDiff < 250 && Math.abs(xDiff) >= 150) { + if (xDiff >= 0 && firstContact.x < Math.min(document.body.clientWidth * 0.25, 300)) + showSidebar(); + else if (xDiff < 0 && curX < 300) + hideSidebar(); + + firstContact = null; + } + }, { passive: true }); + + // Scroll sidebar to current active section + var activeSection = document.getElementById("sidebar").querySelector(".active"); + if (activeSection) { + // https://developer.mozilla.org/en-US/docs/Web/API/Element/scrollIntoView + activeSection.scrollIntoView({ block: 'center' }); + } +})(); + +(function chapterNavigation() { + document.addEventListener('keydown', function (e) { + if (e.altKey || e.ctrlKey || e.metaKey || e.shiftKey) { return; } + if (window.search && window.search.hasFocus()) { return; } + + switch (e.key) { + case 'ArrowRight': + e.preventDefault(); + var nextButton = document.querySelector('.nav-chapters.next'); + if (nextButton) { + window.location.href = nextButton.href; + } + break; + case 'ArrowLeft': + e.preventDefault(); + var previousButton = document.querySelector('.nav-chapters.previous'); + if (previousButton) { + window.location.href = previousButton.href; + } + break; + } + }); +})(); + +(function clipboard() { + var clipButtons = document.querySelectorAll('.clip-button'); + + function hideTooltip(elem) { + elem.firstChild.innerText = ""; + elem.className = 'fa fa-copy clip-button'; + } + + function showTooltip(elem, msg) { + elem.firstChild.innerText = msg; + elem.className = 'fa fa-copy tooltipped'; + } + + var clipboardSnippets = new ClipboardJS('.clip-button', { + text: function (trigger) { + hideTooltip(trigger); + let playground = trigger.closest("pre"); + return playground_text(playground); + } + }); + + Array.from(clipButtons).forEach(function (clipButton) { + clipButton.addEventListener('mouseout', function (e) { + hideTooltip(e.currentTarget); + }); + }); + + clipboardSnippets.on('success', function (e) { + e.clearSelection(); + showTooltip(e.trigger, "Copied!"); + }); + + clipboardSnippets.on('error', function (e) { + showTooltip(e.trigger, "Clipboard error!"); + }); +})(); + +(function scrollToTop () { + var menuTitle = document.querySelector('.menu-title'); + + menuTitle.addEventListener('click', function () { + document.scrollingElement.scrollTo({ top: 0, behavior: 'smooth' }); + }); +})(); + +(function controllMenu() { + var menu = document.getElementById('menu-bar'); + + (function controllPosition() { + var scrollTop = document.scrollingElement.scrollTop; + var prevScrollTop = scrollTop; + var minMenuY = -menu.clientHeight - 50; + // When the script loads, the page can be at any scroll (e.g. if you reforesh it). + menu.style.top = scrollTop + 'px'; + // Same as parseInt(menu.style.top.slice(0, -2), but faster + var topCache = menu.style.top.slice(0, -2); + menu.classList.remove('sticky'); + var stickyCache = false; // Same as menu.classList.contains('sticky'), but faster + document.addEventListener('scroll', function () { + scrollTop = Math.max(document.scrollingElement.scrollTop, 0); + // `null` means that it doesn't need to be updated + var nextSticky = null; + var nextTop = null; + var scrollDown = scrollTop > prevScrollTop; + var menuPosAbsoluteY = topCache - scrollTop; + if (scrollDown) { + nextSticky = false; + if (menuPosAbsoluteY > 0) { + nextTop = prevScrollTop; + } + } else { + if (menuPosAbsoluteY > 0) { + nextSticky = true; + } else if (menuPosAbsoluteY < minMenuY) { + nextTop = prevScrollTop + minMenuY; + } + } + if (nextSticky === true && stickyCache === false) { + menu.classList.add('sticky'); + stickyCache = true; + } else if (nextSticky === false && stickyCache === true) { + menu.classList.remove('sticky'); + stickyCache = false; + } + if (nextTop !== null) { + menu.style.top = nextTop + 'px'; + topCache = nextTop; + } + prevScrollTop = scrollTop; + }, { passive: true }); + })(); + (function controllBorder() { + menu.classList.remove('bordered'); + document.addEventListener('scroll', function () { + if (menu.offsetTop === 0) { + menu.classList.remove('bordered'); + } else { + menu.classList.add('bordered'); + } + }, { passive: true }); + })(); +})(); diff --git a/src/theme/css/chrome.css b/src/theme/css/chrome.css new file mode 100644 index 0000000..9ca8633 --- /dev/null +++ b/src/theme/css/chrome.css @@ -0,0 +1,495 @@ +/* CSS for UI elements (a.k.a. chrome) */ + +@import 'variables.css'; + +::-webkit-scrollbar { + background: var(--bg); +} +::-webkit-scrollbar-thumb { + background: var(--scrollbar); +} +html { + scrollbar-color: var(--scrollbar) var(--bg); +} +#searchresults a, +.content a:link, +a:visited, +a > .hljs { + color: var(--links); +} + +/* Menu Bar */ + +#menu-bar, +#menu-bar-hover-placeholder { + z-index: 101; + margin: auto calc(0px - var(--page-padding)); +} +#menu-bar { + position: relative; + display: flex; + flex-wrap: wrap; + background-color: var(--bg); + border-bottom-color: var(--bg); + border-bottom-width: 1px; + border-bottom-style: solid; +} +#menu-bar.sticky, +.js #menu-bar-hover-placeholder:hover + #menu-bar, +.js #menu-bar:hover, +.js.sidebar-visible #menu-bar { + position: -webkit-sticky; + position: sticky; + top: 0 !important; +} +#menu-bar-hover-placeholder { + position: sticky; + position: -webkit-sticky; + top: 0; + height: var(--menu-bar-height); +} +#menu-bar.bordered { + border-bottom-color: var(--table-border-color); +} +#menu-bar i, #menu-bar .icon-button { + position: relative; + padding: 0 8px; + z-index: 10; + line-height: var(--menu-bar-height); + cursor: pointer; + transition: color 0.5s; +} +@media only screen and (max-width: 420px) { + #menu-bar i, #menu-bar .icon-button { + padding: 0 5px; + } +} + +.icon-button { + border: none; + background: none; + padding: 0; + color: inherit; +} +.icon-button i { + margin: 0; +} + +.right-buttons { + margin: 0 15px; +} +.right-buttons a { + text-decoration: none; +} + +.left-buttons { + display: flex; + margin: 0 5px; +} +.no-js .left-buttons { + display: none; +} + +.menu-title { + display: inline-block; + font-weight: 200; + font-size: 2rem; + line-height: var(--menu-bar-height); + text-align: center; + margin: 0; + flex: 1; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} +.js .menu-title { + cursor: pointer; +} + +.menu-bar, +.menu-bar:visited, +.nav-chapters, +.nav-chapters:visited, +.mobile-nav-chapters, +.mobile-nav-chapters:visited, +.menu-bar .icon-button, +.menu-bar a i { + color: var(--icons); +} + +.menu-bar i:hover, +.menu-bar .icon-button:hover, +.nav-chapters:hover, +.mobile-nav-chapters i:hover { + color: var(--icons-hover); +} + +/* Nav Icons */ + +.nav-chapters { + font-size: 2.5em; + text-align: center; + text-decoration: none; + + position: fixed; + top: 0; + bottom: 0; + margin: 0; + max-width: 150px; + min-width: 90px; + + display: flex; + justify-content: center; + align-content: center; + flex-direction: column; + + transition: color 0.5s, background-color 0.5s; +} + +.nav-chapters:hover { + text-decoration: none; + background-color: var(--theme-hover); + transition: background-color 0.15s, color 0.15s; +} + +.nav-wrapper { + margin-top: 50px; + display: none; +} + +.mobile-nav-chapters { + font-size: 2.5em; + text-align: center; + text-decoration: none; + width: 90px; + border-radius: 5px; + background-color: var(--sidebar-bg); +} + +.previous { + float: left; +} + +.next { + float: right; + right: var(--page-padding); +} + +@media only screen and (max-width: 1080px) { + .nav-wide-wrapper { display: none; } + .nav-wrapper { display: block; } +} + +@media only screen and (max-width: 1380px) { + .sidebar-visible .nav-wide-wrapper { display: none; } + .sidebar-visible .nav-wrapper { display: block; } +} + +/* Inline code */ + +:not(pre) > .hljs { + display: inline; + padding: 0.1em 0.3em; + border-radius: 3px; +} + +:not(pre):not(a) > .hljs { + color: var(--inline-code-color); + overflow-x: initial; +} + +a:hover > .hljs { + text-decoration: underline; +} + +pre { + position: relative; +} +pre > .buttons { + position: absolute; + z-index: 100; + right: 5px; + top: 5px; + + color: var(--sidebar-fg); + cursor: pointer; +} +pre > .buttons :hover { + color: var(--sidebar-active); +} +pre > .buttons i { + margin-left: 8px; +} +pre > .buttons button { + color: inherit; + background: transparent; + border: none; + cursor: inherit; +} +pre > .result { + margin-top: 10px; +} + +/* Search */ + +#searchresults a { + text-decoration: none; +} + +mark { + border-radius: 2px; + padding: 0 3px 1px 3px; + margin: 0 -3px -1px -3px; + background-color: var(--search-mark-bg); + transition: background-color 300ms linear; + cursor: pointer; +} + +mark.fade-out { + background-color: rgba(0,0,0,0) !important; + cursor: auto; +} + +.searchbar-outer { + margin-left: auto; + margin-right: auto; + max-width: var(--content-max-width); +} + +#searchbar { + width: 100%; + margin: 5px auto 0px auto; + padding: 10px 16px; + transition: box-shadow 300ms ease-in-out; + border: 1px solid var(--searchbar-border-color); + border-radius: 3px; + background-color: var(--searchbar-bg); + color: var(--searchbar-fg); +} +#searchbar:focus, +#searchbar.active { + box-shadow: 0 0 3px var(--searchbar-shadow-color); +} + +.searchresults-header { + font-weight: bold; + font-size: 1em; + padding: 18px 0 0 5px; + color: var(--searchresults-header-fg); +} + +.searchresults-outer { + margin-left: auto; + margin-right: auto; + max-width: var(--content-max-width); + border-bottom: 1px dashed var(--searchresults-border-color); +} + +ul#searchresults { + list-style: none; + padding-left: 20px; +} +ul#searchresults li { + margin: 10px 0px; + padding: 2px; + border-radius: 2px; +} +ul#searchresults li.focus { + background-color: var(--searchresults-li-bg); +} +ul#searchresults span.teaser { + display: block; + clear: both; + margin: 5px 0 0 20px; + font-size: 0.8em; +} +ul#searchresults span.teaser em { + font-weight: bold; + font-style: normal; +} + +/* Sidebar */ + +.sidebar { + position: fixed; + left: 0; + top: 0; + bottom: 0; + width: var(--sidebar-width); + font-size: 0.875em; + box-sizing: border-box; + -webkit-overflow-scrolling: touch; + overscroll-behavior-y: contain; + background-color: var(--sidebar-bg); + color: var(--sidebar-fg); +} +.sidebar-resizing { + -moz-user-select: none; + -webkit-user-select: none; + -ms-user-select: none; + user-select: none; +} +.js:not(.sidebar-resizing) .sidebar { + transition: transform 0.3s; /* Animation: slide away */ +} +.sidebar code { + line-height: 2em; +} +.sidebar .sidebar-scrollbox { + overflow-y: auto; + position: absolute; + top: 0; + bottom: 0; + left: 0; + right: 0; + padding: 10px 10px; +} +.sidebar .sidebar-resize-handle { + position: absolute; + cursor: col-resize; + width: 0; + right: 0; + top: 0; + bottom: 0; +} +.js .sidebar .sidebar-resize-handle { + cursor: col-resize; + width: 5px; +} +.sidebar-hidden .sidebar { + transform: translateX(calc(0px - var(--sidebar-width))); +} +.sidebar::-webkit-scrollbar { + background: var(--sidebar-bg); +} +.sidebar::-webkit-scrollbar-thumb { + background: var(--scrollbar); +} + +.sidebar-visible .page-wrapper { + transform: translateX(var(--sidebar-width)); +} +@media only screen and (min-width: 620px) { + .sidebar-visible .page-wrapper { + transform: none; + margin-left: var(--sidebar-width); + } +} + +.chapter { + list-style: none outside none; + padding-left: 0; + line-height: 2.2em; +} + +.chapter ol { + width: 100%; +} + +.chapter li { + display: flex; + color: var(--sidebar-non-existant); +} +.chapter li a { + display: block; + padding: 0; + text-decoration: none; + color: var(--sidebar-fg); +} + +.chapter li a:hover { + color: var(--sidebar-active); +} + +.chapter li a.active { + color: var(--sidebar-active); +} + +.chapter li > a.toggle { + cursor: pointer; + display: block; + margin-left: auto; + padding: 0 10px; + user-select: none; + opacity: 0.68; +} + +.chapter li > a.toggle div { + transition: transform 0.5s; +} + +/* collapse the section */ +.chapter li:not(.expanded) + li > ol { + display: none; +} + +.chapter li.chapter-item { + line-height: 1.5em; + margin-top: 0.6em; +} + +.chapter li.expanded > a.toggle div { + transform: rotate(90deg); +} + +.spacer { + width: 100%; + height: 3px; + margin: 5px 0px; +} +.chapter .spacer { + background-color: var(--sidebar-spacer); +} + +@media (-moz-touch-enabled: 1), (pointer: coarse) { + .chapter li a { padding: 5px 0; } + .spacer { margin: 10px 0; } +} + +.section { + list-style: none outside none; + padding-left: 20px; + line-height: 1.9em; +} + +/* Theme Menu Popup */ + +.theme-popup { + position: absolute; + left: 10px; + top: var(--menu-bar-height); + z-index: 1000; + border-radius: 4px; + font-size: 0.7em; + color: var(--fg); + background: var(--theme-popup-bg); + border: 1px solid var(--theme-popup-border); + margin: 0; + padding: 0; + list-style: none; + display: none; +} +.theme-popup .default { + color: var(--icons); +} +.theme-popup .theme { + width: 100%; + border: 0; + margin: 0; + padding: 2px 10px; + line-height: 25px; + white-space: nowrap; + text-align: left; + cursor: pointer; + color: inherit; + background: inherit; + font-size: inherit; +} +.theme-popup .theme:hover { + background-color: var(--theme-hover); +} +.theme-popup .theme:hover:first-child, +.theme-popup .theme:hover:last-child { + border-top-left-radius: inherit; + border-top-right-radius: inherit; +} diff --git a/src/theme/css/general.css b/src/theme/css/general.css new file mode 100644 index 0000000..815dae1 --- /dev/null +++ b/src/theme/css/general.css @@ -0,0 +1,174 @@ +/* Base styles and content styles */ + +@import 'variables.css'; + +:root { + /* Browser default font-size is 16px, this way 1 rem = 10px */ + font-size: 62.5%; +} + +html { + font-family: "Open Sans", sans-serif; + color: var(--fg); + background-color: var(--bg); + text-size-adjust: none; +} + +body { + margin: 0; + font-size: 1.6rem; + overflow-x: hidden; +} + +code { + font-family: "Source Code Pro", Consolas, "Ubuntu Mono", Menlo, "DejaVu Sans Mono", monospace, monospace !important; + font-size: 0.875em; /* please adjust the ace font size accordingly in editor.js */ +} + +/* Don't change font size in headers. */ +h1 code, h2 code, h3 code, h4 code, h5 code, h6 code { + font-size: unset; +} + +.left { float: left; } +.right { float: right; } +.boring { opacity: 0.6; } +.hide-boring .boring { display: none; } +.hidden { display: none !important; } + +h2, h3 { margin-top: 2.5em; } +h4, h5 { margin-top: 2em; } + +.header + .header h3, +.header + .header h4, +.header + .header h5 { + margin-top: 1em; +} + +h1 a.header:target::before, +h2 a.header:target::before, +h3 a.header:target::before, +h4 a.header:target::before { + display: inline-block; + content: "»"; + margin-left: -30px; + width: 30px; +} + +h1 a.header:target, +h2 a.header:target, +h3 a.header:target, +h4 a.header:target { + scroll-margin-top: calc(var(--menu-bar-height) + 0.5em); +} + +.page { + outline: 0; + padding: 0 var(--page-padding); + margin-top: calc(0px - var(--menu-bar-height)); /* Compensate for the #menu-bar-hover-placeholder */ +} +.page-wrapper { + box-sizing: border-box; +} +.js:not(.sidebar-resizing) .page-wrapper { + transition: margin-left 0.3s ease, transform 0.3s ease; /* Animation: slide away */ +} + +.content { + overflow-y: auto; + padding: 0 15px; + padding-bottom: 50px; +} +.content main { + margin-left: auto; + margin-right: auto; + max-width: var(--content-max-width); +} +.content p { line-height: 1.45em; } +.content ol { line-height: 1.45em; } +.content ul { line-height: 1.45em; } +.content a { text-decoration: none; } +.content a:hover { text-decoration: underline; } +.content img { max-width: 100%; } +.content .header:link, +.content .header:visited { + color: var(--fg); +} +.content .header:link, +.content .header:visited:hover { + text-decoration: none; +} + +table { + margin: 0 auto; + border-collapse: collapse; +} +table td { + padding: 3px 20px; + border: 1px var(--table-border-color) solid; +} +table thead { + background: var(--table-header-bg); +} +table thead td { + font-weight: 700; + border: none; +} +table thead th { + padding: 3px 20px; +} +table thead tr { + border: 1px var(--table-header-bg) solid; +} +/* Alternate background colors for rows */ +table tbody tr:nth-child(2n) { + background: var(--table-alternate-bg); +} + + +blockquote { + margin: 20px 0; + padding: 0 20px; + color: var(--fg); + background-color: var(--quote-bg); + border-top: .1em solid var(--quote-border); + border-bottom: .1em solid var(--quote-border); +} + + +:not(.footnote-definition) + .footnote-definition, +.footnote-definition + :not(.footnote-definition) { + margin-top: 2em; +} +.footnote-definition { + font-size: 0.9em; + margin: 0.5em 0; +} +.footnote-definition p { + display: inline; +} + +.tooltiptext { + position: absolute; + visibility: hidden; + color: #fff; + background-color: #333; + transform: translateX(-50%); /* Center by moving tooltip 50% of its width left */ + left: -8px; /* Half of the width of the icon */ + top: -35px; + font-size: 0.8em; + text-align: center; + border-radius: 6px; + padding: 5px 8px; + margin: 5px; + z-index: 1000; +} +.tooltipped .tooltiptext { + visibility: visible; +} + +.chapter li.part-title { + color: var(--sidebar-fg); + margin: 5px 0px; + font-weight: bold; +} diff --git a/src/theme/css/print.css b/src/theme/css/print.css new file mode 100644 index 0000000..5e690f7 --- /dev/null +++ b/src/theme/css/print.css @@ -0,0 +1,54 @@ + +#sidebar, +#menu-bar, +.nav-chapters, +.mobile-nav-chapters { + display: none; +} + +#page-wrapper.page-wrapper { + transform: none; + margin-left: 0px; + overflow-y: initial; +} + +#content { + max-width: none; + margin: 0; + padding: 0; +} + +.page { + overflow-y: initial; +} + +code { + background-color: #666666; + border-radius: 5px; + + /* Force background to be printed in Chrome */ + -webkit-print-color-adjust: exact; +} + +pre > .buttons { + z-index: 2; +} + +a, a:visited, a:active, a:hover { + color: #4183c4; + text-decoration: none; +} + +h1, h2, h3, h4, h5, h6 { + page-break-inside: avoid; + page-break-after: avoid; +} + +pre, code { + page-break-inside: avoid; + white-space: pre-wrap; +} + +.fa { + display: none !important; +} diff --git a/src/theme/css/variables.css b/src/theme/css/variables.css new file mode 100644 index 0000000..9534ec8 --- /dev/null +++ b/src/theme/css/variables.css @@ -0,0 +1,253 @@ + +/* Globals */ + +:root { + --sidebar-width: 300px; + --page-padding: 15px; + --content-max-width: 750px; + --menu-bar-height: 50px; +} + +/* Themes */ + +.ayu { + --bg: hsl(210, 25%, 8%); + --fg: #c5c5c5; + + --sidebar-bg: #14191f; + --sidebar-fg: #c8c9db; + --sidebar-non-existant: #5c6773; + --sidebar-active: #ffb454; + --sidebar-spacer: #2d334f; + + --scrollbar: var(--sidebar-fg); + + --icons: #737480; + --icons-hover: #b7b9cc; + + --links: #0096cf; + + --inline-code-color: #ffb454; + + --theme-popup-bg: #14191f; + --theme-popup-border: #5c6773; + --theme-hover: #191f26; + + --quote-bg: hsl(226, 15%, 17%); + --quote-border: hsl(226, 15%, 22%); + + --table-border-color: hsl(210, 25%, 13%); + --table-header-bg: hsl(210, 25%, 28%); + --table-alternate-bg: hsl(210, 25%, 11%); + + --searchbar-border-color: #848484; + --searchbar-bg: #424242; + --searchbar-fg: #fff; + --searchbar-shadow-color: #d4c89f; + --searchresults-header-fg: #666; + --searchresults-border-color: #888; + --searchresults-li-bg: #252932; + --search-mark-bg: #e3b171; +} + +.coal { + --bg: hsl(200, 7%, 8%); + --fg: #98a3ad; + + --sidebar-bg: #292c2f; + --sidebar-fg: #a1adb8; + --sidebar-non-existant: #505254; + --sidebar-active: #3473ad; + --sidebar-spacer: #393939; + + --scrollbar: var(--sidebar-fg); + + --icons: #43484d; + --icons-hover: #b3c0cc; + + --links: #2b79a2; + + --inline-code-color: #c5c8c6;; + + --theme-popup-bg: #141617; + --theme-popup-border: #43484d; + --theme-hover: #1f2124; + + --quote-bg: hsl(234, 21%, 18%); + --quote-border: hsl(234, 21%, 23%); + + --table-border-color: hsl(200, 7%, 13%); + --table-header-bg: hsl(200, 7%, 28%); + --table-alternate-bg: hsl(200, 7%, 11%); + + --searchbar-border-color: #aaa; + --searchbar-bg: #b7b7b7; + --searchbar-fg: #000; + --searchbar-shadow-color: #aaa; + --searchresults-header-fg: #666; + --searchresults-border-color: #98a3ad; + --searchresults-li-bg: #2b2b2f; + --search-mark-bg: #355c7d; +} + +.light { + --bg: hsl(0, 0%, 100%); + --fg: #333333; + + --sidebar-bg: #fafafa; + --sidebar-fg: #364149; + --sidebar-non-existant: #aaaaaa; + --sidebar-active: #008cff; + --sidebar-spacer: #f4f4f4; + + --scrollbar: #cccccc; + + --icons: #cccccc; + --icons-hover: #333333; + + --links: #4183c4; + + --inline-code-color: #6e6b5e; + + --theme-popup-bg: #fafafa; + --theme-popup-border: #cccccc; + --theme-hover: #e6e6e6; + + --quote-bg: hsl(197, 37%, 96%); + --quote-border: hsl(197, 37%, 91%); + + --table-border-color: hsl(0, 0%, 95%); + --table-header-bg: hsl(0, 0%, 80%); + --table-alternate-bg: hsl(0, 0%, 97%); + + --searchbar-border-color: #aaa; + --searchbar-bg: #fafafa; + --searchbar-fg: #000; + --searchbar-shadow-color: #aaa; + --searchresults-header-fg: #666; + --searchresults-border-color: #888; + --searchresults-li-bg: #e4f2fe; + --search-mark-bg: #a2cff5; +} + +.navy { + --bg: hsl(226, 23%, 11%); + --fg: #bcbdd0; + + --sidebar-bg: #282d3f; + --sidebar-fg: #c8c9db; + --sidebar-non-existant: #505274; + --sidebar-active: #2b79a2; + --sidebar-spacer: #2d334f; + + --scrollbar: var(--sidebar-fg); + + --icons: #737480; + --icons-hover: #b7b9cc; + + --links: #2b79a2; + + --inline-code-color: #c5c8c6;; + + --theme-popup-bg: #161923; + --theme-popup-border: #737480; + --theme-hover: #282e40; + + --quote-bg: hsl(226, 15%, 17%); + --quote-border: hsl(226, 15%, 22%); + + --table-border-color: hsl(226, 23%, 16%); + --table-header-bg: hsl(226, 23%, 31%); + --table-alternate-bg: hsl(226, 23%, 14%); + + --searchbar-border-color: #aaa; + --searchbar-bg: #aeaec6; + --searchbar-fg: #000; + --searchbar-shadow-color: #aaa; + --searchresults-header-fg: #5f5f71; + --searchresults-border-color: #5c5c68; + --searchresults-li-bg: #242430; + --search-mark-bg: #a2cff5; +} + +.rust { + --bg: hsl(60, 9%, 87%); + --fg: #262625; + + --sidebar-bg: #3b2e2a; + --sidebar-fg: #c8c9db; + --sidebar-non-existant: #505254; + --sidebar-active: #e69f67; + --sidebar-spacer: #45373a; + + --scrollbar: var(--sidebar-fg); + + --icons: #737480; + --icons-hover: #262625; + + --links: #2b79a2; + + --inline-code-color: #6e6b5e; + + --theme-popup-bg: #e1e1db; + --theme-popup-border: #b38f6b; + --theme-hover: #99908a; + + --quote-bg: hsl(60, 5%, 75%); + --quote-border: hsl(60, 5%, 70%); + + --table-border-color: hsl(60, 9%, 82%); + --table-header-bg: #b3a497; + --table-alternate-bg: hsl(60, 9%, 84%); + + --searchbar-border-color: #aaa; + --searchbar-bg: #fafafa; + --searchbar-fg: #000; + --searchbar-shadow-color: #aaa; + --searchresults-header-fg: #666; + --searchresults-border-color: #888; + --searchresults-li-bg: #dec2a2; + --search-mark-bg: #e69f67; +} + +@media (prefers-color-scheme: dark) { + .light.no-js { + --bg: hsl(200, 7%, 8%); + --fg: #98a3ad; + + --sidebar-bg: #292c2f; + --sidebar-fg: #a1adb8; + --sidebar-non-existant: #505254; + --sidebar-active: #3473ad; + --sidebar-spacer: #393939; + + --scrollbar: var(--sidebar-fg); + + --icons: #43484d; + --icons-hover: #b3c0cc; + + --links: #2b79a2; + + --inline-code-color: #c5c8c6;; + + --theme-popup-bg: #141617; + --theme-popup-border: #43484d; + --theme-hover: #1f2124; + + --quote-bg: hsl(234, 21%, 18%); + --quote-border: hsl(234, 21%, 23%); + + --table-border-color: hsl(200, 7%, 13%); + --table-header-bg: hsl(200, 7%, 28%); + --table-alternate-bg: hsl(200, 7%, 11%); + + --searchbar-border-color: #aaa; + --searchbar-bg: #b7b7b7; + --searchbar-fg: #000; + --searchbar-shadow-color: #aaa; + --searchresults-header-fg: #666; + --searchresults-border-color: #98a3ad; + --searchresults-li-bg: #2b2b2f; + --search-mark-bg: #355c7d; + } +} diff --git a/src/theme/favicon.png b/src/theme/favicon.png new file mode 100644 index 0000000..a5b1aa1 Binary files /dev/null and b/src/theme/favicon.png differ diff --git a/src/theme/favicon.svg b/src/theme/favicon.svg new file mode 100644 index 0000000..90e0ea5 --- /dev/null +++ b/src/theme/favicon.svg @@ -0,0 +1,22 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 199.7 184.2"> + <style> + @media (prefers-color-scheme: dark) { + svg { fill: white; } + } + </style> +<path d="M189.5,36.8c0.2,2.8,0,5.1-0.6,6.8L153,162c-0.6,2.1-2,3.7-4.2,5c-2.2,1.2-4.4,1.9-6.7,1.9H31.4c-9.6,0-15.3-2.8-17.3-8.4 + c-0.8-2.2-0.8-3.9,0.1-5.2c0.9-1.2,2.4-1.8,4.6-1.8H123c7.4,0,12.6-1.4,15.4-4.1s5.7-8.9,8.6-18.4l32.9-108.6 + c1.8-5.9,1-11.1-2.2-15.6S169.9,0,164,0H72.7c-1,0-3.1,0.4-6.1,1.1l0.1-0.4C64.5,0.2,62.6,0,61,0.1s-3,0.5-4.3,1.4 + c-1.3,0.9-2.4,1.8-3.2,2.8S52,6.5,51.2,8.1c-0.8,1.6-1.4,3-1.9,4.3s-1.1,2.7-1.8,4.2c-0.7,1.5-1.3,2.7-2,3.7c-0.5,0.6-1.2,1.5-2,2.5 + s-1.6,2-2.2,2.8s-0.9,1.5-1.1,2.2c-0.2,0.7-0.1,1.8,0.2,3.2c0.3,1.4,0.4,2.4,0.4,3.1c-0.3,3-1.4,6.9-3.3,11.6 + c-1.9,4.7-3.6,8.1-5.1,10.1c-0.3,0.4-1.2,1.3-2.6,2.7c-1.4,1.4-2.3,2.6-2.6,3.7c-0.3,0.4-0.3,1.5-0.1,3.4c0.3,1.8,0.4,3.1,0.3,3.8 + c-0.3,2.7-1.3,6.3-3,10.8c-1.7,4.5-3.4,8.2-5,11c-0.2,0.5-0.9,1.4-2,2.8c-1.1,1.4-1.8,2.5-2,3.4c-0.2,0.6-0.1,1.8,0.1,3.4 + c0.2,1.6,0.2,2.8-0.1,3.6c-0.6,3-1.8,6.7-3.6,11c-1.8,4.3-3.6,7.9-5.4,11c-0.5,0.8-1.1,1.7-2,2.8c-0.8,1.1-1.5,2-2,2.8 + s-0.8,1.6-1,2.5c-0.1,0.5,0,1.3,0.4,2.3c0.3,1.1,0.4,1.9,0.4,2.6c-0.1,1.1-0.2,2.6-0.5,4.4c-0.2,1.8-0.4,2.9-0.4,3.2 + c-1.8,4.8-1.7,9.9,0.2,15.2c2.2,6.2,6.2,11.5,11.9,15.8c5.7,4.3,11.7,6.4,17.8,6.4h110.7c5.2,0,10.1-1.7,14.7-5.2s7.7-7.8,9.2-12.9 + l33-108.6c1.8-5.8,1-10.9-2.2-15.5C194.9,39.7,192.6,38,189.5,36.8z M59.6,122.8L73.8,80c0,0,7,0,10.8,0s28.8-1.7,25.4,17.5 + c-3.4,19.2-18.8,25.2-36.8,25.4S59.6,122.8,59.6,122.8z M78.6,116.8c4.7-0.1,18.9-2.9,22.1-17.1S89.2,86.3,89.2,86.3l-8.9,0 + l-10.2,30.5C70.2,116.9,74,116.9,78.6,116.8z M75.3,68.7L89,26.2h9.8l0.8,34l23.6-34h9.9l-13.6,42.5h-7.1l12.5-35.4l-24.5,35.4h-6.8 + l-0.8-35L82,68.7H75.3z"/> +</svg> +<!-- Original image Copyright Dave Gandy — CC BY 4.0 License --> diff --git a/src/theme/highlight.css b/src/theme/highlight.css new file mode 100644 index 0000000..ab8c49c --- /dev/null +++ b/src/theme/highlight.css @@ -0,0 +1,79 @@ +/* Base16 Atelier Dune Light - Theme */ +/* by Bram de Haan (http://atelierbram.github.io/syntax-highlighting/atelier-schemes/dune) */ +/* Original Base16 color scheme by Chris Kempson (https://github.com/chriskempson/base16) */ + +/* Atelier-Dune Comment */ +.hljs-comment, +.hljs-quote { + color: #AAA; +} + +/* Atelier-Dune Red */ +.hljs-variable, +.hljs-template-variable, +.hljs-attribute, +.hljs-tag, +.hljs-name, +.hljs-regexp, +.hljs-link, +.hljs-name, +.hljs-selector-id, +.hljs-selector-class { + color: #d73737; +} + +/* Atelier-Dune Orange */ +.hljs-number, +.hljs-meta, +.hljs-built_in, +.hljs-builtin-name, +.hljs-literal, +.hljs-type, +.hljs-params { + color: #b65611; +} + +/* Atelier-Dune Green */ +.hljs-string, +.hljs-symbol, +.hljs-bullet { + color: #60ac39; +} + +/* Atelier-Dune Blue */ +.hljs-title, +.hljs-section { + color: #6684e1; +} + +/* Atelier-Dune Purple */ +.hljs-keyword, +.hljs-selector-tag { + color: #b854d4; +} + +.hljs { + display: block; + overflow-x: auto; + background: #f1f1f1; + color: #6e6b5e; + padding: 0.5em; +} + +.hljs-emphasis { + font-style: italic; +} + +.hljs-strong { + font-weight: bold; +} + +.hljs-addition { + color: #22863a; + background-color: #f0fff4; +} + +.hljs-deletion { + color: #b31d28; + background-color: #ffeef0; +} diff --git a/src/theme/highlight.js b/src/theme/highlight.js new file mode 100644 index 0000000..180385b --- /dev/null +++ b/src/theme/highlight.js @@ -0,0 +1,6 @@ +/* + Highlight.js 10.1.1 (93fd0d73) + License: BSD-3-Clause + Copyright (c) 2006-2020, Ivan Sagalaev +*/ +var hljs=function(){"use strict";function e(n){Object.freeze(n);var t="function"==typeof n;return Object.getOwnPropertyNames(n).forEach((function(r){!Object.hasOwnProperty.call(n,r)||null===n[r]||"object"!=typeof n[r]&&"function"!=typeof n[r]||t&&("caller"===r||"callee"===r||"arguments"===r)||Object.isFrozen(n[r])||e(n[r])})),n}class n{constructor(e){void 0===e.data&&(e.data={}),this.data=e.data}ignoreMatch(){this.ignore=!0}}function t(e){return e.replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,""").replace(/'/g,"'")}function r(e,...n){var t={};for(const n in e)t[n]=e[n];return n.forEach((function(e){for(const n in e)t[n]=e[n]})),t}function a(e){return e.nodeName.toLowerCase()}var i=Object.freeze({__proto__:null,escapeHTML:t,inherit:r,nodeStream:function(e){var n=[];return function e(t,r){for(var i=t.firstChild;i;i=i.nextSibling)3===i.nodeType?r+=i.nodeValue.length:1===i.nodeType&&(n.push({event:"start",offset:r,node:i}),r=e(i,r),a(i).match(/br|hr|img|input/)||n.push({event:"stop",offset:r,node:i}));return r}(e,0),n},mergeStreams:function(e,n,r){var i=0,s="",o=[];function l(){return e.length&&n.length?e[0].offset!==n[0].offset?e[0].offset<n[0].offset?e:n:"start"===n[0].event?e:n:e.length?e:n}function c(e){s+="<"+a(e)+[].map.call(e.attributes,(function(e){return" "+e.nodeName+'="'+t(e.value)+'"'})).join("")+">"}function u(e){s+="</"+a(e)+">"}function d(e){("start"===e.event?c:u)(e.node)}for(;e.length||n.length;){var g=l();if(s+=t(r.substring(i,g[0].offset)),i=g[0].offset,g===e){o.reverse().forEach(u);do{d(g.splice(0,1)[0]),g=l()}while(g===e&&g.length&&g[0].offset===i);o.reverse().forEach(c)}else"start"===g[0].event?o.push(g[0].node):o.pop(),d(g.splice(0,1)[0])}return s+t(r.substr(i))}});const s="</span>",o=e=>!!e.kind;class l{constructor(e,n){this.buffer="",this.classPrefix=n.classPrefix,e.walk(this)}addText(e){this.buffer+=t(e)}openNode(e){if(!o(e))return;let n=e.kind;e.sublanguage||(n=`${this.classPrefix}${n}`),this.span(n)}closeNode(e){o(e)&&(this.buffer+=s)}value(){return this.buffer}span(e){this.buffer+=`<span class="${e}">`}}class c{constructor(){this.rootNode={children:[]},this.stack=[this.rootNode]}get top(){return this.stack[this.stack.length-1]}get root(){return this.rootNode}add(e){this.top.children.push(e)}openNode(e){const n={kind:e,children:[]};this.add(n),this.stack.push(n)}closeNode(){if(this.stack.length>1)return this.stack.pop()}closeAllNodes(){for(;this.closeNode(););}toJSON(){return JSON.stringify(this.rootNode,null,4)}walk(e){return this.constructor._walk(e,this.rootNode)}static _walk(e,n){return"string"==typeof n?e.addText(n):n.children&&(e.openNode(n),n.children.forEach(n=>this._walk(e,n)),e.closeNode(n)),e}static _collapse(e){"string"!=typeof e&&e.children&&(e.children.every(e=>"string"==typeof e)?e.children=[e.children.join("")]:e.children.forEach(e=>{c._collapse(e)}))}}class u extends c{constructor(e){super(),this.options=e}addKeyword(e,n){""!==e&&(this.openNode(n),this.addText(e),this.closeNode())}addText(e){""!==e&&this.add(e)}addSublanguage(e,n){const t=e.root;t.kind=n,t.sublanguage=!0,this.add(t)}toHTML(){return new l(this,this.options).value()}finalize(){return!0}}function d(e){return e?"string"==typeof e?e:e.source:null}const g="(-?)(\\b0[xX][a-fA-F0-9]+|(\\b\\d+(\\.\\d*)?|\\.\\d+)([eE][-+]?\\d+)?)",h={begin:"\\\\[\\s\\S]",relevance:0},f={className:"string",begin:"'",end:"'",illegal:"\\n",contains:[h]},p={className:"string",begin:'"',end:'"',illegal:"\\n",contains:[h]},b={begin:/\b(a|an|the|are|I'm|isn't|don't|doesn't|won't|but|just|should|pretty|simply|enough|gonna|going|wtf|so|such|will|you|your|they|like|more)\b/},m=function(e,n,t={}){var a=r({className:"comment",begin:e,end:n,contains:[]},t);return a.contains.push(b),a.contains.push({className:"doctag",begin:"(?:TODO|FIXME|NOTE|BUG|OPTIMIZE|HACK|XXX):",relevance:0}),a},v=m("//","$"),x=m("/\\*","\\*/"),E=m("#","$");var _=Object.freeze({__proto__:null,IDENT_RE:"[a-zA-Z]\\w*",UNDERSCORE_IDENT_RE:"[a-zA-Z_]\\w*",NUMBER_RE:"\\b\\d+(\\.\\d+)?",C_NUMBER_RE:g,BINARY_NUMBER_RE:"\\b(0b[01]+)",RE_STARTERS_RE:"!|!=|!==|%|%=|&|&&|&=|\\*|\\*=|\\+|\\+=|,|-|-=|/=|/|:|;|<<|<<=|<=|<|===|==|=|>>>=|>>=|>=|>>>|>>|>|\\?|\\[|\\{|\\(|\\^|\\^=|\\||\\|=|\\|\\||~",SHEBANG:(e={})=>{const n=/^#![ ]*\//;return e.binary&&(e.begin=function(...e){return e.map(e=>d(e)).join("")}(n,/.*\b/,e.binary,/\b.*/)),r({className:"meta",begin:n,end:/$/,relevance:0,"on:begin":(e,n)=>{0!==e.index&&n.ignoreMatch()}},e)},BACKSLASH_ESCAPE:h,APOS_STRING_MODE:f,QUOTE_STRING_MODE:p,PHRASAL_WORDS_MODE:b,COMMENT:m,C_LINE_COMMENT_MODE:v,C_BLOCK_COMMENT_MODE:x,HASH_COMMENT_MODE:E,NUMBER_MODE:{className:"number",begin:"\\b\\d+(\\.\\d+)?",relevance:0},C_NUMBER_MODE:{className:"number",begin:g,relevance:0},BINARY_NUMBER_MODE:{className:"number",begin:"\\b(0b[01]+)",relevance:0},CSS_NUMBER_MODE:{className:"number",begin:"\\b\\d+(\\.\\d+)?(%|em|ex|ch|rem|vw|vh|vmin|vmax|cm|mm|in|pt|pc|px|deg|grad|rad|turn|s|ms|Hz|kHz|dpi|dpcm|dppx)?",relevance:0},REGEXP_MODE:{begin:/(?=\/[^/\n]*\/)/,contains:[{className:"regexp",begin:/\//,end:/\/[gimuy]*/,illegal:/\n/,contains:[h,{begin:/\[/,end:/\]/,relevance:0,contains:[h]}]}]},TITLE_MODE:{className:"title",begin:"[a-zA-Z]\\w*",relevance:0},UNDERSCORE_TITLE_MODE:{className:"title",begin:"[a-zA-Z_]\\w*",relevance:0},METHOD_GUARD:{begin:"\\.\\s*[a-zA-Z_]\\w*",relevance:0},END_SAME_AS_BEGIN:function(e){return Object.assign(e,{"on:begin":(e,n)=>{n.data._beginMatch=e[1]},"on:end":(e,n)=>{n.data._beginMatch!==e[1]&&n.ignoreMatch()}})}}),N="of and for in not or if then".split(" ");function w(e,n){return n?+n:function(e){return N.includes(e.toLowerCase())}(e)?0:1}const R=t,y=r,{nodeStream:k,mergeStreams:O}=i,M=Symbol("nomatch");return function(t){var a=[],i={},s={},o=[],l=!0,c=/(^(<[^>]+>|\t|)+|\n)/gm,g="Could not find the language '{}', did you forget to load/include a language module?";const h={disableAutodetect:!0,name:"Plain text",contains:[]};var f={noHighlightRe:/^(no-?highlight)$/i,languageDetectRe:/\blang(?:uage)?-([\w-]+)\b/i,classPrefix:"hljs-",tabReplace:null,useBR:!1,languages:null,__emitter:u};function p(e){return f.noHighlightRe.test(e)}function b(e,n,t,r){var a={code:n,language:e};S("before:highlight",a);var i=a.result?a.result:m(a.language,a.code,t,r);return i.code=a.code,S("after:highlight",i),i}function m(e,t,a,s){var o=t;function c(e,n){var t=E.case_insensitive?n[0].toLowerCase():n[0];return Object.prototype.hasOwnProperty.call(e.keywords,t)&&e.keywords[t]}function u(){null!=y.subLanguage?function(){if(""!==A){var e=null;if("string"==typeof y.subLanguage){if(!i[y.subLanguage])return void O.addText(A);e=m(y.subLanguage,A,!0,k[y.subLanguage]),k[y.subLanguage]=e.top}else e=v(A,y.subLanguage.length?y.subLanguage:null);y.relevance>0&&(I+=e.relevance),O.addSublanguage(e.emitter,e.language)}}():function(){if(!y.keywords)return void O.addText(A);let e=0;y.keywordPatternRe.lastIndex=0;let n=y.keywordPatternRe.exec(A),t="";for(;n;){t+=A.substring(e,n.index);const r=c(y,n);if(r){const[e,a]=r;O.addText(t),t="",I+=a,O.addKeyword(n[0],e)}else t+=n[0];e=y.keywordPatternRe.lastIndex,n=y.keywordPatternRe.exec(A)}t+=A.substr(e),O.addText(t)}(),A=""}function h(e){return e.className&&O.openNode(e.className),y=Object.create(e,{parent:{value:y}})}function p(e){return 0===y.matcher.regexIndex?(A+=e[0],1):(L=!0,0)}var b={};function x(t,r){var i=r&&r[0];if(A+=t,null==i)return u(),0;if("begin"===b.type&&"end"===r.type&&b.index===r.index&&""===i){if(A+=o.slice(r.index,r.index+1),!l){const n=Error("0 width match regex");throw n.languageName=e,n.badRule=b.rule,n}return 1}if(b=r,"begin"===r.type)return function(e){var t=e[0],r=e.rule;const a=new n(r),i=[r.__beforeBegin,r["on:begin"]];for(const n of i)if(n&&(n(e,a),a.ignore))return p(t);return r&&r.endSameAsBegin&&(r.endRe=RegExp(t.replace(/[-/\\^$*+?.()|[\]{}]/g,"\\$&"),"m")),r.skip?A+=t:(r.excludeBegin&&(A+=t),u(),r.returnBegin||r.excludeBegin||(A=t)),h(r),r.returnBegin?0:t.length}(r);if("illegal"===r.type&&!a){const e=Error('Illegal lexeme "'+i+'" for mode "'+(y.className||"<unnamed>")+'"');throw e.mode=y,e}if("end"===r.type){var s=function(e){var t=e[0],r=o.substr(e.index),a=function e(t,r,a){let i=function(e,n){var t=e&&e.exec(n);return t&&0===t.index}(t.endRe,a);if(i){if(t["on:end"]){const e=new n(t);t["on:end"](r,e),e.ignore&&(i=!1)}if(i){for(;t.endsParent&&t.parent;)t=t.parent;return t}}if(t.endsWithParent)return e(t.parent,r,a)}(y,e,r);if(!a)return M;var i=y;i.skip?A+=t:(i.returnEnd||i.excludeEnd||(A+=t),u(),i.excludeEnd&&(A=t));do{y.className&&O.closeNode(),y.skip||y.subLanguage||(I+=y.relevance),y=y.parent}while(y!==a.parent);return a.starts&&(a.endSameAsBegin&&(a.starts.endRe=a.endRe),h(a.starts)),i.returnEnd?0:t.length}(r);if(s!==M)return s}if("illegal"===r.type&&""===i)return 1;if(B>1e5&&B>3*r.index)throw Error("potential infinite loop, way more iterations than matches");return A+=i,i.length}var E=T(e);if(!E)throw console.error(g.replace("{}",e)),Error('Unknown language: "'+e+'"');var _=function(e){function n(n,t){return RegExp(d(n),"m"+(e.case_insensitive?"i":"")+(t?"g":""))}class t{constructor(){this.matchIndexes={},this.regexes=[],this.matchAt=1,this.position=0}addRule(e,n){n.position=this.position++,this.matchIndexes[this.matchAt]=n,this.regexes.push([n,e]),this.matchAt+=function(e){return RegExp(e.toString()+"|").exec("").length-1}(e)+1}compile(){0===this.regexes.length&&(this.exec=()=>null);const e=this.regexes.map(e=>e[1]);this.matcherRe=n(function(e,n="|"){for(var t=/\[(?:[^\\\]]|\\.)*\]|\(\??|\\([1-9][0-9]*)|\\./,r=0,a="",i=0;i<e.length;i++){var s=r+=1,o=d(e[i]);for(i>0&&(a+=n),a+="(";o.length>0;){var l=t.exec(o);if(null==l){a+=o;break}a+=o.substring(0,l.index),o=o.substring(l.index+l[0].length),"\\"===l[0][0]&&l[1]?a+="\\"+(+l[1]+s):(a+=l[0],"("===l[0]&&r++)}a+=")"}return a}(e),!0),this.lastIndex=0}exec(e){this.matcherRe.lastIndex=this.lastIndex;const n=this.matcherRe.exec(e);if(!n)return null;const t=n.findIndex((e,n)=>n>0&&void 0!==e),r=this.matchIndexes[t];return n.splice(0,t),Object.assign(n,r)}}class a{constructor(){this.rules=[],this.multiRegexes=[],this.count=0,this.lastIndex=0,this.regexIndex=0}getMatcher(e){if(this.multiRegexes[e])return this.multiRegexes[e];const n=new t;return this.rules.slice(e).forEach(([e,t])=>n.addRule(e,t)),n.compile(),this.multiRegexes[e]=n,n}considerAll(){this.regexIndex=0}addRule(e,n){this.rules.push([e,n]),"begin"===n.type&&this.count++}exec(e){const n=this.getMatcher(this.regexIndex);n.lastIndex=this.lastIndex;const t=n.exec(e);return t&&(this.regexIndex+=t.position+1,this.regexIndex===this.count&&(this.regexIndex=0)),t}}function i(e,n){const t=e.input[e.index-1],r=e.input[e.index+e[0].length];"."!==t&&"."!==r||n.ignoreMatch()}if(e.contains&&e.contains.includes("self"))throw Error("ERR: contains `self` is not supported at the top-level of a language. See documentation.");return function t(s,o){const l=s;if(s.compiled)return l;s.compiled=!0,s.__beforeBegin=null,s.keywords=s.keywords||s.beginKeywords;let c=null;if("object"==typeof s.keywords&&(c=s.keywords.$pattern,delete s.keywords.$pattern),s.keywords&&(s.keywords=function(e,n){var t={};return"string"==typeof e?r("keyword",e):Object.keys(e).forEach((function(n){r(n,e[n])})),t;function r(e,r){n&&(r=r.toLowerCase()),r.split(" ").forEach((function(n){var r=n.split("|");t[r[0]]=[e,w(r[0],r[1])]}))}}(s.keywords,e.case_insensitive)),s.lexemes&&c)throw Error("ERR: Prefer `keywords.$pattern` to `mode.lexemes`, BOTH are not allowed. (see mode reference) ");return l.keywordPatternRe=n(s.lexemes||c||/\w+/,!0),o&&(s.beginKeywords&&(s.begin="\\b("+s.beginKeywords.split(" ").join("|")+")(?=\\b|\\s)",s.__beforeBegin=i),s.begin||(s.begin=/\B|\b/),l.beginRe=n(s.begin),s.endSameAsBegin&&(s.end=s.begin),s.end||s.endsWithParent||(s.end=/\B|\b/),s.end&&(l.endRe=n(s.end)),l.terminator_end=d(s.end)||"",s.endsWithParent&&o.terminator_end&&(l.terminator_end+=(s.end?"|":"")+o.terminator_end)),s.illegal&&(l.illegalRe=n(s.illegal)),void 0===s.relevance&&(s.relevance=1),s.contains||(s.contains=[]),s.contains=[].concat(...s.contains.map((function(e){return function(e){return e.variants&&!e.cached_variants&&(e.cached_variants=e.variants.map((function(n){return r(e,{variants:null},n)}))),e.cached_variants?e.cached_variants:function e(n){return!!n&&(n.endsWithParent||e(n.starts))}(e)?r(e,{starts:e.starts?r(e.starts):null}):Object.isFrozen(e)?r(e):e}("self"===e?s:e)}))),s.contains.forEach((function(e){t(e,l)})),s.starts&&t(s.starts,o),l.matcher=function(e){const n=new a;return e.contains.forEach(e=>n.addRule(e.begin,{rule:e,type:"begin"})),e.terminator_end&&n.addRule(e.terminator_end,{type:"end"}),e.illegal&&n.addRule(e.illegal,{type:"illegal"}),n}(l),l}(e)}(E),N="",y=s||_,k={},O=new f.__emitter(f);!function(){for(var e=[],n=y;n!==E;n=n.parent)n.className&&e.unshift(n.className);e.forEach(e=>O.openNode(e))}();var A="",I=0,S=0,B=0,L=!1;try{for(y.matcher.considerAll();;){B++,L?L=!1:(y.matcher.lastIndex=S,y.matcher.considerAll());const e=y.matcher.exec(o);if(!e)break;const n=x(o.substring(S,e.index),e);S=e.index+n}return x(o.substr(S)),O.closeAllNodes(),O.finalize(),N=O.toHTML(),{relevance:I,value:N,language:e,illegal:!1,emitter:O,top:y}}catch(n){if(n.message&&n.message.includes("Illegal"))return{illegal:!0,illegalBy:{msg:n.message,context:o.slice(S-100,S+100),mode:n.mode},sofar:N,relevance:0,value:R(o),emitter:O};if(l)return{illegal:!1,relevance:0,value:R(o),emitter:O,language:e,top:y,errorRaised:n};throw n}}function v(e,n){n=n||f.languages||Object.keys(i);var t=function(e){const n={relevance:0,emitter:new f.__emitter(f),value:R(e),illegal:!1,top:h};return n.emitter.addText(e),n}(e),r=t;return n.filter(T).filter(I).forEach((function(n){var a=m(n,e,!1);a.language=n,a.relevance>r.relevance&&(r=a),a.relevance>t.relevance&&(r=t,t=a)})),r.language&&(t.second_best=r),t}function x(e){return f.tabReplace||f.useBR?e.replace(c,e=>"\n"===e?f.useBR?"<br>":e:f.tabReplace?e.replace(/\t/g,f.tabReplace):e):e}function E(e){let n=null;const t=function(e){var n=e.className+" ";n+=e.parentNode?e.parentNode.className:"";const t=f.languageDetectRe.exec(n);if(t){var r=T(t[1]);return r||(console.warn(g.replace("{}",t[1])),console.warn("Falling back to no-highlight mode for this block.",e)),r?t[1]:"no-highlight"}return n.split(/\s+/).find(e=>p(e)||T(e))}(e);if(p(t))return;S("before:highlightBlock",{block:e,language:t}),f.useBR?(n=document.createElement("div")).innerHTML=e.innerHTML.replace(/\n/g,"").replace(/<br[ /]*>/g,"\n"):n=e;const r=n.textContent,a=t?b(t,r,!0):v(r),i=k(n);if(i.length){const e=document.createElement("div");e.innerHTML=a.value,a.value=O(i,k(e),r)}a.value=x(a.value),S("after:highlightBlock",{block:e,result:a}),e.innerHTML=a.value,e.className=function(e,n,t){var r=n?s[n]:t,a=[e.trim()];return e.match(/\bhljs\b/)||a.push("hljs"),e.includes(r)||a.push(r),a.join(" ").trim()}(e.className,t,a.language),e.result={language:a.language,re:a.relevance,relavance:a.relevance},a.second_best&&(e.second_best={language:a.second_best.language,re:a.second_best.relevance,relavance:a.second_best.relevance})}const N=()=>{if(!N.called){N.called=!0;var e=document.querySelectorAll("pre code");a.forEach.call(e,E)}};function T(e){return e=(e||"").toLowerCase(),i[e]||i[s[e]]}function A(e,{languageName:n}){"string"==typeof e&&(e=[e]),e.forEach(e=>{s[e]=n})}function I(e){var n=T(e);return n&&!n.disableAutodetect}function S(e,n){var t=e;o.forEach((function(e){e[t]&&e[t](n)}))}Object.assign(t,{highlight:b,highlightAuto:v,fixMarkup:x,highlightBlock:E,configure:function(e){f=y(f,e)},initHighlighting:N,initHighlightingOnLoad:function(){window.addEventListener("DOMContentLoaded",N,!1)},registerLanguage:function(e,n){var r=null;try{r=n(t)}catch(n){if(console.error("Language definition for '{}' could not be registered.".replace("{}",e)),!l)throw n;console.error(n),r=h}r.name||(r.name=e),i[e]=r,r.rawDefinition=n.bind(null,t),r.aliases&&A(r.aliases,{languageName:e})},listLanguages:function(){return Object.keys(i)},getLanguage:T,registerAliases:A,requireLanguage:function(e){var n=T(e);if(n)return n;throw Error("The '{}' language is required, but not loaded.".replace("{}",e))},autoDetection:I,inherit:y,addPlugin:function(e){o.push(e)}}),t.debugMode=function(){l=!1},t.safeMode=function(){l=!0},t.versionString="10.1.1";for(const n in _)"object"==typeof _[n]&&e(_[n]);return Object.assign(t,_),t}({})}();"object"==typeof exports&&"undefined"!=typeof module&&(module.exports=hljs);hljs.registerLanguage("php",function(){"use strict";return function(e){var r={begin:"\\$+[a-zA-Z_-ÿ][a-zA-Z0-9_-ÿ]*"},t={className:"meta",variants:[{begin:/<\?php/,relevance:10},{begin:/<\?[=]?/},{begin:/\?>/}]},a={className:"string",contains:[e.BACKSLASH_ESCAPE,t],variants:[{begin:'b"',end:'"'},{begin:"b'",end:"'"},e.inherit(e.APOS_STRING_MODE,{illegal:null}),e.inherit(e.QUOTE_STRING_MODE,{illegal:null})]},n={variants:[e.BINARY_NUMBER_MODE,e.C_NUMBER_MODE]},i={keyword:"__CLASS__ __DIR__ __FILE__ __FUNCTION__ __LINE__ __METHOD__ __NAMESPACE__ __TRAIT__ die echo exit include include_once print require require_once array abstract and as binary bool boolean break callable case catch class clone const continue declare default do double else elseif empty enddeclare endfor endforeach endif endswitch endwhile eval extends final finally float for foreach from global goto if implements instanceof insteadof int integer interface isset iterable list new object or private protected public real return string switch throw trait try unset use var void while xor yield",literal:"false null true",built_in:"Error|0 AppendIterator ArgumentCountError ArithmeticError ArrayIterator ArrayObject AssertionError BadFunctionCallException BadMethodCallException CachingIterator CallbackFilterIterator CompileError Countable DirectoryIterator DivisionByZeroError DomainException EmptyIterator ErrorException Exception FilesystemIterator FilterIterator GlobIterator InfiniteIterator InvalidArgumentException IteratorIterator LengthException LimitIterator LogicException MultipleIterator NoRewindIterator OutOfBoundsException OutOfRangeException OuterIterator OverflowException ParentIterator ParseError RangeException RecursiveArrayIterator RecursiveCachingIterator RecursiveCallbackFilterIterator RecursiveDirectoryIterator RecursiveFilterIterator RecursiveIterator RecursiveIteratorIterator RecursiveRegexIterator RecursiveTreeIterator RegexIterator RuntimeException SeekableIterator SplDoublyLinkedList SplFileInfo SplFileObject SplFixedArray SplHeap SplMaxHeap SplMinHeap SplObjectStorage SplObserver SplObserver SplPriorityQueue SplQueue SplStack SplSubject SplSubject SplTempFileObject TypeError UnderflowException UnexpectedValueException ArrayAccess Closure Generator Iterator IteratorAggregate Serializable Throwable Traversable WeakReference Directory __PHP_Incomplete_Class parent php_user_filter self static stdClass"};return{aliases:["php","php3","php4","php5","php6","php7"],case_insensitive:!0,keywords:i,contains:[e.HASH_COMMENT_MODE,e.COMMENT("//","$",{contains:[t]}),e.COMMENT("/\\*","\\*/",{contains:[{className:"doctag",begin:"@[A-Za-z]+"}]}),e.COMMENT("__halt_compiler.+?;",!1,{endsWithParent:!0,keywords:"__halt_compiler"}),{className:"string",begin:/<<<['"]?\w+['"]?$/,end:/^\w+;?$/,contains:[e.BACKSLASH_ESCAPE,{className:"subst",variants:[{begin:/\$\w+/},{begin:/\{\$/,end:/\}/}]}]},t,{className:"keyword",begin:/\$this\b/},r,{begin:/(::|->)+[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*/},{className:"function",beginKeywords:"fn function",end:/[;{]/,excludeEnd:!0,illegal:"[$%\\[]",contains:[e.UNDERSCORE_TITLE_MODE,{className:"params",begin:"\\(",end:"\\)",excludeBegin:!0,excludeEnd:!0,keywords:i,contains:["self",r,e.C_BLOCK_COMMENT_MODE,a,n]}]},{className:"class",beginKeywords:"class interface",end:"{",excludeEnd:!0,illegal:/[:\(\$"]/,contains:[{beginKeywords:"extends implements"},e.UNDERSCORE_TITLE_MODE]},{beginKeywords:"namespace",end:";",illegal:/[\.']/,contains:[e.UNDERSCORE_TITLE_MODE]},{beginKeywords:"use",end:";",contains:[e.UNDERSCORE_TITLE_MODE]},{begin:"=>"},a,n]}}}());hljs.registerLanguage("nginx",function(){"use strict";return function(e){var n={className:"variable",variants:[{begin:/\$\d+/},{begin:/\$\{/,end:/}/},{begin:"[\\$\\@]"+e.UNDERSCORE_IDENT_RE}]},a={endsWithParent:!0,keywords:{$pattern:"[a-z/_]+",literal:"on off yes no true false none blocked debug info notice warn error crit select break last permanent redirect kqueue rtsig epoll poll /dev/poll"},relevance:0,illegal:"=>",contains:[e.HASH_COMMENT_MODE,{className:"string",contains:[e.BACKSLASH_ESCAPE,n],variants:[{begin:/"/,end:/"/},{begin:/'/,end:/'/}]},{begin:"([a-z]+):/",end:"\\s",endsWithParent:!0,excludeEnd:!0,contains:[n]},{className:"regexp",contains:[e.BACKSLASH_ESCAPE,n],variants:[{begin:"\\s\\^",end:"\\s|{|;",returnEnd:!0},{begin:"~\\*?\\s+",end:"\\s|{|;",returnEnd:!0},{begin:"\\*(\\.[a-z\\-]+)+"},{begin:"([a-z\\-]+\\.)+\\*"}]},{className:"number",begin:"\\b\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}(:\\d{1,5})?\\b"},{className:"number",begin:"\\b\\d+[kKmMgGdshdwy]*\\b",relevance:0},n]};return{name:"Nginx config",aliases:["nginxconf"],contains:[e.HASH_COMMENT_MODE,{begin:e.UNDERSCORE_IDENT_RE+"\\s+{",returnBegin:!0,end:"{",contains:[{className:"section",begin:e.UNDERSCORE_IDENT_RE}],relevance:0},{begin:e.UNDERSCORE_IDENT_RE+"\\s",end:";|{",returnBegin:!0,contains:[{className:"attribute",begin:e.UNDERSCORE_IDENT_RE,starts:a}],relevance:0}],illegal:"[^\\s\\}]"}}}());hljs.registerLanguage("csharp",function(){"use strict";return function(e){var n={keyword:"abstract as base bool break byte case catch char checked const continue decimal default delegate do double enum event explicit extern finally fixed float for foreach goto if implicit in int interface internal is lock long object operator out override params private protected public readonly ref sbyte sealed short sizeof stackalloc static string struct switch this try typeof uint ulong unchecked unsafe ushort using virtual void volatile while add alias ascending async await by descending dynamic equals from get global group into join let nameof on orderby partial remove select set value var when where yield",literal:"null false true"},i=e.inherit(e.TITLE_MODE,{begin:"[a-zA-Z](\\.?\\w)*"}),a={className:"number",variants:[{begin:"\\b(0b[01']+)"},{begin:"(-?)\\b([\\d']+(\\.[\\d']*)?|\\.[\\d']+)(u|U|l|L|ul|UL|f|F|b|B)"},{begin:"(-?)(\\b0[xX][a-fA-F0-9']+|(\\b[\\d']+(\\.[\\d']*)?|\\.[\\d']+)([eE][-+]?[\\d']+)?)"}],relevance:0},s={className:"string",begin:'@"',end:'"',contains:[{begin:'""'}]},t=e.inherit(s,{illegal:/\n/}),l={className:"subst",begin:"{",end:"}",keywords:n},r=e.inherit(l,{illegal:/\n/}),c={className:"string",begin:/\$"/,end:'"',illegal:/\n/,contains:[{begin:"{{"},{begin:"}}"},e.BACKSLASH_ESCAPE,r]},o={className:"string",begin:/\$@"/,end:'"',contains:[{begin:"{{"},{begin:"}}"},{begin:'""'},l]},g=e.inherit(o,{illegal:/\n/,contains:[{begin:"{{"},{begin:"}}"},{begin:'""'},r]});l.contains=[o,c,s,e.APOS_STRING_MODE,e.QUOTE_STRING_MODE,a,e.C_BLOCK_COMMENT_MODE],r.contains=[g,c,t,e.APOS_STRING_MODE,e.QUOTE_STRING_MODE,a,e.inherit(e.C_BLOCK_COMMENT_MODE,{illegal:/\n/})];var d={variants:[o,c,s,e.APOS_STRING_MODE,e.QUOTE_STRING_MODE]},E={begin:"<",end:">",contains:[{beginKeywords:"in out"},i]},_=e.IDENT_RE+"(<"+e.IDENT_RE+"(\\s*,\\s*"+e.IDENT_RE+")*>)?(\\[\\])?",b={begin:"@"+e.IDENT_RE,relevance:0};return{name:"C#",aliases:["cs","c#"],keywords:n,illegal:/::/,contains:[e.COMMENT("///","$",{returnBegin:!0,contains:[{className:"doctag",variants:[{begin:"///",relevance:0},{begin:"\x3c!--|--\x3e"},{begin:"</?",end:">"}]}]}),e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,{className:"meta",begin:"#",end:"$",keywords:{"meta-keyword":"if else elif endif define undef warning error line region endregion pragma checksum"}},d,a,{beginKeywords:"class interface",end:/[{;=]/,illegal:/[^\s:,]/,contains:[{beginKeywords:"where class"},i,E,e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE]},{beginKeywords:"namespace",end:/[{;=]/,illegal:/[^\s:]/,contains:[i,e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE]},{className:"meta",begin:"^\\s*\\[",excludeBegin:!0,end:"\\]",excludeEnd:!0,contains:[{className:"meta-string",begin:/"/,end:/"/}]},{beginKeywords:"new return throw await else",relevance:0},{className:"function",begin:"("+_+"\\s+)+"+e.IDENT_RE+"\\s*(\\<.+\\>)?\\s*\\(",returnBegin:!0,end:/\s*[{;=]/,excludeEnd:!0,keywords:n,contains:[{begin:e.IDENT_RE+"\\s*(\\<.+\\>)?\\s*\\(",returnBegin:!0,contains:[e.TITLE_MODE,E],relevance:0},{className:"params",begin:/\(/,end:/\)/,excludeBegin:!0,excludeEnd:!0,keywords:n,relevance:0,contains:[d,a,e.C_BLOCK_COMMENT_MODE]},e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE]},b]}}}());hljs.registerLanguage("perl",function(){"use strict";return function(e){var n={$pattern:/[\w.]+/,keyword:"getpwent getservent quotemeta msgrcv scalar kill dbmclose undef lc ma syswrite tr send umask sysopen shmwrite vec qx utime local oct semctl localtime readpipe do return format read sprintf dbmopen pop getpgrp not getpwnam rewinddir qq fileno qw endprotoent wait sethostent bless s|0 opendir continue each sleep endgrent shutdown dump chomp connect getsockname die socketpair close flock exists index shmget sub for endpwent redo lstat msgctl setpgrp abs exit select print ref gethostbyaddr unshift fcntl syscall goto getnetbyaddr join gmtime symlink semget splice x|0 getpeername recv log setsockopt cos last reverse gethostbyname getgrnam study formline endhostent times chop length gethostent getnetent pack getprotoent getservbyname rand mkdir pos chmod y|0 substr endnetent printf next open msgsnd readdir use unlink getsockopt getpriority rindex wantarray hex system getservbyport endservent int chr untie rmdir prototype tell listen fork shmread ucfirst setprotoent else sysseek link getgrgid shmctl waitpid unpack getnetbyname reset chdir grep split require caller lcfirst until warn while values shift telldir getpwuid my getprotobynumber delete and sort uc defined srand accept package seekdir getprotobyname semop our rename seek if q|0 chroot sysread setpwent no crypt getc chown sqrt write setnetent setpriority foreach tie sin msgget map stat getlogin unless elsif truncate exec keys glob tied closedir ioctl socket readlink eval xor readline binmode setservent eof ord bind alarm pipe atan2 getgrent exp time push setgrent gt lt or ne m|0 break given say state when"},t={className:"subst",begin:"[$@]\\{",end:"\\}",keywords:n},s={begin:"->{",end:"}"},r={variants:[{begin:/\$\d/},{begin:/[\$%@](\^\w\b|#\w+(::\w+)*|{\w+}|\w+(::\w*)*)/},{begin:/[\$%@][^\s\w{]/,relevance:0}]},i=[e.BACKSLASH_ESCAPE,t,r],a=[r,e.HASH_COMMENT_MODE,e.COMMENT("^\\=\\w","\\=cut",{endsWithParent:!0}),s,{className:"string",contains:i,variants:[{begin:"q[qwxr]?\\s*\\(",end:"\\)",relevance:5},{begin:"q[qwxr]?\\s*\\[",end:"\\]",relevance:5},{begin:"q[qwxr]?\\s*\\{",end:"\\}",relevance:5},{begin:"q[qwxr]?\\s*\\|",end:"\\|",relevance:5},{begin:"q[qwxr]?\\s*\\<",end:"\\>",relevance:5},{begin:"qw\\s+q",end:"q",relevance:5},{begin:"'",end:"'",contains:[e.BACKSLASH_ESCAPE]},{begin:'"',end:'"'},{begin:"`",end:"`",contains:[e.BACKSLASH_ESCAPE]},{begin:"{\\w+}",contains:[],relevance:0},{begin:"-?\\w+\\s*\\=\\>",contains:[],relevance:0}]},{className:"number",begin:"(\\b0[0-7_]+)|(\\b0x[0-9a-fA-F_]+)|(\\b[1-9][0-9_]*(\\.[0-9_]+)?)|[0_]\\b",relevance:0},{begin:"(\\/\\/|"+e.RE_STARTERS_RE+"|\\b(split|return|print|reverse|grep)\\b)\\s*",keywords:"split return print reverse grep",relevance:0,contains:[e.HASH_COMMENT_MODE,{className:"regexp",begin:"(s|tr|y)/(\\\\.|[^/])*/(\\\\.|[^/])*/[a-z]*",relevance:10},{className:"regexp",begin:"(m|qr)?/",end:"/[a-z]*",contains:[e.BACKSLASH_ESCAPE],relevance:0}]},{className:"function",beginKeywords:"sub",end:"(\\s*\\(.*?\\))?[;{]",excludeEnd:!0,relevance:5,contains:[e.TITLE_MODE]},{begin:"-\\w\\b",relevance:0},{begin:"^__DATA__$",end:"^__END__$",subLanguage:"mojolicious",contains:[{begin:"^@@.*",end:"$",className:"comment"}]}];return t.contains=a,s.contains=a,{name:"Perl",aliases:["pl","pm"],keywords:n,contains:a}}}());hljs.registerLanguage("swift",function(){"use strict";return function(e){var i={keyword:"#available #colorLiteral #column #else #elseif #endif #file #fileLiteral #function #if #imageLiteral #line #selector #sourceLocation _ __COLUMN__ __FILE__ __FUNCTION__ __LINE__ Any as as! as? associatedtype associativity break case catch class continue convenience default defer deinit didSet do dynamic dynamicType else enum extension fallthrough false fileprivate final for func get guard if import in indirect infix init inout internal is lazy left let mutating nil none nonmutating open operator optional override postfix precedence prefix private protocol Protocol public repeat required rethrows return right self Self set static struct subscript super switch throw throws true try try! try? Type typealias unowned var weak where while willSet",literal:"true false nil",built_in:"abs advance alignof alignofValue anyGenerator assert assertionFailure bridgeFromObjectiveC bridgeFromObjectiveCUnconditional bridgeToObjectiveC bridgeToObjectiveCUnconditional c compactMap contains count countElements countLeadingZeros debugPrint debugPrintln distance dropFirst dropLast dump encodeBitsAsWords enumerate equal fatalError filter find getBridgedObjectiveCType getVaList indices insertionSort isBridgedToObjectiveC isBridgedVerbatimToObjectiveC isUniquelyReferenced isUniquelyReferencedNonObjC join lazy lexicographicalCompare map max maxElement min minElement numericCast overlaps partition posix precondition preconditionFailure print println quickSort readLine reduce reflect reinterpretCast reverse roundUpToAlignment sizeof sizeofValue sort split startsWith stride strideof strideofValue swap toString transcode underestimateCount unsafeAddressOf unsafeBitCast unsafeDowncast unsafeUnwrap unsafeReflect withExtendedLifetime withObjectAtPlusZero withUnsafePointer withUnsafePointerToObject withUnsafeMutablePointer withUnsafeMutablePointers withUnsafePointer withUnsafePointers withVaList zip"},n=e.COMMENT("/\\*","\\*/",{contains:["self"]}),t={className:"subst",begin:/\\\(/,end:"\\)",keywords:i,contains:[]},a={className:"string",contains:[e.BACKSLASH_ESCAPE,t],variants:[{begin:/"""/,end:/"""/},{begin:/"/,end:/"/}]},r={className:"number",begin:"\\b([\\d_]+(\\.[\\deE_]+)?|0x[a-fA-F0-9_]+(\\.[a-fA-F0-9p_]+)?|0b[01_]+|0o[0-7_]+)\\b",relevance:0};return t.contains=[r],{name:"Swift",keywords:i,contains:[a,e.C_LINE_COMMENT_MODE,n,{className:"type",begin:"\\b[A-Z][\\wÀ-ʸ']*[!?]"},{className:"type",begin:"\\b[A-Z][\\wÀ-ʸ']*",relevance:0},r,{className:"function",beginKeywords:"func",end:"{",excludeEnd:!0,contains:[e.inherit(e.TITLE_MODE,{begin:/[A-Za-z$_][0-9A-Za-z$_]*/}),{begin:/</,end:/>/},{className:"params",begin:/\(/,end:/\)/,endsParent:!0,keywords:i,contains:["self",r,a,e.C_BLOCK_COMMENT_MODE,{begin:":"}],illegal:/["']/}],illegal:/\[|%/},{className:"class",beginKeywords:"struct protocol class extension enum",keywords:i,end:"\\{",excludeEnd:!0,contains:[e.inherit(e.TITLE_MODE,{begin:/[A-Za-z$_][\u00C0-\u02B80-9A-Za-z$_]*/})]},{className:"meta",begin:"(@discardableResult|@warn_unused_result|@exported|@lazy|@noescape|@NSCopying|@NSManaged|@objc|@objcMembers|@convention|@required|@noreturn|@IBAction|@IBDesignable|@IBInspectable|@IBOutlet|@infix|@prefix|@postfix|@autoclosure|@testable|@available|@nonobjc|@NSApplicationMain|@UIApplicationMain|@dynamicMemberLookup|@propertyWrapper)\\b"},{beginKeywords:"import",end:/$/,contains:[e.C_LINE_COMMENT_MODE,n]}]}}}());hljs.registerLanguage("makefile",function(){"use strict";return function(e){var i={className:"variable",variants:[{begin:"\\$\\("+e.UNDERSCORE_IDENT_RE+"\\)",contains:[e.BACKSLASH_ESCAPE]},{begin:/\$[@%<?\^\+\*]/}]},n={className:"string",begin:/"/,end:/"/,contains:[e.BACKSLASH_ESCAPE,i]},a={className:"variable",begin:/\$\([\w-]+\s/,end:/\)/,keywords:{built_in:"subst patsubst strip findstring filter filter-out sort word wordlist firstword lastword dir notdir suffix basename addsuffix addprefix join wildcard realpath abspath error warning shell origin flavor foreach if or and call eval file value"},contains:[i]},r={begin:"^"+e.UNDERSCORE_IDENT_RE+"\\s*(?=[:+?]?=)"},s={className:"section",begin:/^[^\s]+:/,end:/$/,contains:[i]};return{name:"Makefile",aliases:["mk","mak"],keywords:{$pattern:/[\w-]+/,keyword:"define endef undefine ifdef ifndef ifeq ifneq else endif include -include sinclude override export unexport private vpath"},contains:[e.HASH_COMMENT_MODE,i,n,a,r,{className:"meta",begin:/^\.PHONY:/,end:/$/,keywords:{$pattern:/[\.\w]+/,"meta-keyword":".PHONY"}},s]}}}());hljs.registerLanguage("css",function(){"use strict";return function(e){var n={begin:/(?:[A-Z\_\.\-]+|--[a-zA-Z0-9_-]+)\s*:/,returnBegin:!0,end:";",endsWithParent:!0,contains:[{className:"attribute",begin:/\S/,end:":",excludeEnd:!0,starts:{endsWithParent:!0,excludeEnd:!0,contains:[{begin:/[\w-]+\(/,returnBegin:!0,contains:[{className:"built_in",begin:/[\w-]+/},{begin:/\(/,end:/\)/,contains:[e.APOS_STRING_MODE,e.QUOTE_STRING_MODE,e.CSS_NUMBER_MODE]}]},e.CSS_NUMBER_MODE,e.QUOTE_STRING_MODE,e.APOS_STRING_MODE,e.C_BLOCK_COMMENT_MODE,{className:"number",begin:"#[0-9A-Fa-f]+"},{className:"meta",begin:"!important"}]}}]};return{name:"CSS",case_insensitive:!0,illegal:/[=\/|'\$]/,contains:[e.C_BLOCK_COMMENT_MODE,{className:"selector-id",begin:/#[A-Za-z0-9_-]+/},{className:"selector-class",begin:/\.[A-Za-z0-9_-]+/},{className:"selector-attr",begin:/\[/,end:/\]/,illegal:"$",contains:[e.APOS_STRING_MODE,e.QUOTE_STRING_MODE]},{className:"selector-pseudo",begin:/:(:)?[a-zA-Z0-9\_\-\+\(\)"'.]+/},{begin:"@(page|font-face)",lexemes:"@[a-z-]+",keywords:"@page @font-face"},{begin:"@",end:"[{;]",illegal:/:/,returnBegin:!0,contains:[{className:"keyword",begin:/@\-?\w[\w]*(\-\w+)*/},{begin:/\s/,endsWithParent:!0,excludeEnd:!0,relevance:0,keywords:"and or not only",contains:[{begin:/[a-z-]+:/,className:"attribute"},e.APOS_STRING_MODE,e.QUOTE_STRING_MODE,e.CSS_NUMBER_MODE]}]},{className:"selector-tag",begin:"[a-zA-Z-][a-zA-Z0-9_-]*",relevance:0},{begin:"{",end:"}",illegal:/\S/,contains:[e.C_BLOCK_COMMENT_MODE,n]}]}}}());hljs.registerLanguage("xml",function(){"use strict";return function(e){var n={className:"symbol",begin:"&[a-z]+;|&#[0-9]+;|&#x[a-f0-9]+;"},a={begin:"\\s",contains:[{className:"meta-keyword",begin:"#?[a-z_][a-z1-9_-]+",illegal:"\\n"}]},s=e.inherit(a,{begin:"\\(",end:"\\)"}),t=e.inherit(e.APOS_STRING_MODE,{className:"meta-string"}),i=e.inherit(e.QUOTE_STRING_MODE,{className:"meta-string"}),c={endsWithParent:!0,illegal:/</,relevance:0,contains:[{className:"attr",begin:"[A-Za-z0-9\\._:-]+",relevance:0},{begin:/=\s*/,relevance:0,contains:[{className:"string",endsParent:!0,variants:[{begin:/"/,end:/"/,contains:[n]},{begin:/'/,end:/'/,contains:[n]},{begin:/[^\s"'=<>`]+/}]}]}]};return{name:"HTML, XML",aliases:["html","xhtml","rss","atom","xjb","xsd","xsl","plist","wsf","svg"],case_insensitive:!0,contains:[{className:"meta",begin:"<![a-z]",end:">",relevance:10,contains:[a,i,t,s,{begin:"\\[",end:"\\]",contains:[{className:"meta",begin:"<![a-z]",end:">",contains:[a,s,i,t]}]}]},e.COMMENT("\x3c!--","--\x3e",{relevance:10}),{begin:"<\\!\\[CDATA\\[",end:"\\]\\]>",relevance:10},n,{className:"meta",begin:/<\?xml/,end:/\?>/,relevance:10},{className:"tag",begin:"<style(?=\\s|>)",end:">",keywords:{name:"style"},contains:[c],starts:{end:"</style>",returnEnd:!0,subLanguage:["css","xml"]}},{className:"tag",begin:"<script(?=\\s|>)",end:">",keywords:{name:"script"},contains:[c],starts:{end:"<\/script>",returnEnd:!0,subLanguage:["javascript","handlebars","xml"]}},{className:"tag",begin:"</?",end:"/?>",contains:[{className:"name",begin:/[^\/><\s]+/,relevance:0},c]}]}}}());hljs.registerLanguage("bash",function(){"use strict";return function(e){const s={};Object.assign(s,{className:"variable",variants:[{begin:/\$[\w\d#@][\w\d_]*/},{begin:/\$\{/,end:/\}/,contains:[{begin:/:-/,contains:[s]}]}]});const t={className:"subst",begin:/\$\(/,end:/\)/,contains:[e.BACKSLASH_ESCAPE]},n={className:"string",begin:/"/,end:/"/,contains:[e.BACKSLASH_ESCAPE,s,t]};t.contains.push(n);const a={begin:/\$\(\(/,end:/\)\)/,contains:[{begin:/\d+#[0-9a-f]+/,className:"number"},e.NUMBER_MODE,s]},i=e.SHEBANG({binary:"(fish|bash|zsh|sh|csh|ksh|tcsh|dash|scsh)",relevance:10}),c={className:"function",begin:/\w[\w\d_]*\s*\(\s*\)\s*\{/,returnBegin:!0,contains:[e.inherit(e.TITLE_MODE,{begin:/\w[\w\d_]*/})],relevance:0};return{name:"Bash",aliases:["sh","zsh"],keywords:{$pattern:/\b-?[a-z\._]+\b/,keyword:"if then else elif fi for while in do done case esac function",literal:"true false",built_in:"break cd continue eval exec exit export getopts hash pwd readonly return shift test times trap umask unset alias bind builtin caller command declare echo enable help let local logout mapfile printf read readarray source type typeset ulimit unalias set shopt autoload bg bindkey bye cap chdir clone comparguments compcall compctl compdescribe compfiles compgroups compquote comptags comptry compvalues dirs disable disown echotc echoti emulate fc fg float functions getcap getln history integer jobs kill limit log noglob popd print pushd pushln rehash sched setcap setopt stat suspend ttyctl unfunction unhash unlimit unsetopt vared wait whence where which zcompile zformat zftp zle zmodload zparseopts zprof zpty zregexparse zsocket zstyle ztcp",_:"-ne -eq -lt -gt -f -d -e -s -l -a"},contains:[i,e.SHEBANG(),c,a,e.HASH_COMMENT_MODE,n,{className:"",begin:/\\"/},{className:"string",begin:/'/,end:/'/},s]}}}());hljs.registerLanguage("c-like",function(){"use strict";return function(e){function t(e){return"(?:"+e+")?"}var n="(decltype\\(auto\\)|"+t("[a-zA-Z_]\\w*::")+"[a-zA-Z_]\\w*"+t("<.*?>")+")",r={className:"keyword",begin:"\\b[a-z\\d_]*_t\\b"},a={className:"string",variants:[{begin:'(u8?|U|L)?"',end:'"',illegal:"\\n",contains:[e.BACKSLASH_ESCAPE]},{begin:"(u8?|U|L)?'(\\\\(x[0-9A-Fa-f]{2}|u[0-9A-Fa-f]{4,8}|[0-7]{3}|\\S)|.)",end:"'",illegal:"."},e.END_SAME_AS_BEGIN({begin:/(?:u8?|U|L)?R"([^()\\ ]{0,16})\(/,end:/\)([^()\\ ]{0,16})"/})]},i={className:"number",variants:[{begin:"\\b(0b[01']+)"},{begin:"(-?)\\b([\\d']+(\\.[\\d']*)?|\\.[\\d']+)(u|U|l|L|ul|UL|f|F|b|B)"},{begin:"(-?)(\\b0[xX][a-fA-F0-9']+|(\\b[\\d']+(\\.[\\d']*)?|\\.[\\d']+)([eE][-+]?[\\d']+)?)"}],relevance:0},s={className:"meta",begin:/#\s*[a-z]+\b/,end:/$/,keywords:{"meta-keyword":"if else elif endif define undef warning error line pragma _Pragma ifdef ifndef include"},contains:[{begin:/\\\n/,relevance:0},e.inherit(a,{className:"meta-string"}),{className:"meta-string",begin:/<.*?>/,end:/$/,illegal:"\\n"},e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE]},o={className:"title",begin:t("[a-zA-Z_]\\w*::")+e.IDENT_RE,relevance:0},c=t("[a-zA-Z_]\\w*::")+e.IDENT_RE+"\\s*\\(",l={keyword:"int float while private char char8_t char16_t char32_t catch import module export virtual operator sizeof dynamic_cast|10 typedef const_cast|10 const for static_cast|10 union namespace unsigned long volatile static protected bool template mutable if public friend do goto auto void enum else break extern using asm case typeid wchar_t short reinterpret_cast|10 default double register explicit signed typename try this switch continue inline delete alignas alignof constexpr consteval constinit decltype concept co_await co_return co_yield requires noexcept static_assert thread_local restrict final override atomic_bool atomic_char atomic_schar atomic_uchar atomic_short atomic_ushort atomic_int atomic_uint atomic_long atomic_ulong atomic_llong atomic_ullong new throw return and and_eq bitand bitor compl not not_eq or or_eq xor xor_eq",built_in:"std string wstring cin cout cerr clog stdin stdout stderr stringstream istringstream ostringstream auto_ptr deque list queue stack vector map set pair bitset multiset multimap unordered_set unordered_map unordered_multiset unordered_multimap priority_queue make_pair array shared_ptr abort terminate abs acos asin atan2 atan calloc ceil cosh cos exit exp fabs floor fmod fprintf fputs free frexp fscanf future isalnum isalpha iscntrl isdigit isgraph islower isprint ispunct isspace isupper isxdigit tolower toupper labs ldexp log10 log malloc realloc memchr memcmp memcpy memset modf pow printf putchar puts scanf sinh sin snprintf sprintf sqrt sscanf strcat strchr strcmp strcpy strcspn strlen strncat strncmp strncpy strpbrk strrchr strspn strstr tanh tan vfprintf vprintf vsprintf endl initializer_list unique_ptr _Bool complex _Complex imaginary _Imaginary",literal:"true false nullptr NULL"},d=[r,e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,i,a],_={variants:[{begin:/=/,end:/;/},{begin:/\(/,end:/\)/},{beginKeywords:"new throw return else",end:/;/}],keywords:l,contains:d.concat([{begin:/\(/,end:/\)/,keywords:l,contains:d.concat(["self"]),relevance:0}]),relevance:0},u={className:"function",begin:"("+n+"[\\*&\\s]+)+"+c,returnBegin:!0,end:/[{;=]/,excludeEnd:!0,keywords:l,illegal:/[^\w\s\*&:<>]/,contains:[{begin:"decltype\\(auto\\)",keywords:l,relevance:0},{begin:c,returnBegin:!0,contains:[o],relevance:0},{className:"params",begin:/\(/,end:/\)/,keywords:l,relevance:0,contains:[e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,a,i,r,{begin:/\(/,end:/\)/,keywords:l,relevance:0,contains:["self",e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,a,i,r]}]},r,e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,s]};return{aliases:["c","cc","h","c++","h++","hpp","hh","hxx","cxx"],keywords:l,disableAutodetect:!0,illegal:"</",contains:[].concat(_,u,d,[s,{begin:"\\b(deque|list|queue|priority_queue|pair|stack|vector|map|set|bitset|multiset|multimap|unordered_map|unordered_set|unordered_multiset|unordered_multimap|array)\\s*<",end:">",keywords:l,contains:["self",r]},{begin:e.IDENT_RE+"::",keywords:l},{className:"class",beginKeywords:"class struct",end:/[{;:]/,contains:[{begin:/</,end:/>/,contains:["self"]},e.TITLE_MODE]}]),exports:{preprocessor:s,strings:a,keywords:l}}}}());hljs.registerLanguage("coffeescript",function(){"use strict";const e=["as","in","of","if","for","while","finally","var","new","function","do","return","void","else","break","catch","instanceof","with","throw","case","default","try","switch","continue","typeof","delete","let","yield","const","class","debugger","async","await","static","import","from","export","extends"],n=["true","false","null","undefined","NaN","Infinity"],a=[].concat(["setInterval","setTimeout","clearInterval","clearTimeout","require","exports","eval","isFinite","isNaN","parseFloat","parseInt","decodeURI","decodeURIComponent","encodeURI","encodeURIComponent","escape","unescape"],["arguments","this","super","console","window","document","localStorage","module","global"],["Intl","DataView","Number","Math","Date","String","RegExp","Object","Function","Boolean","Error","Symbol","Set","Map","WeakSet","WeakMap","Proxy","Reflect","JSON","Promise","Float64Array","Int16Array","Int32Array","Int8Array","Uint16Array","Uint32Array","Float32Array","Array","Uint8Array","Uint8ClampedArray","ArrayBuffer"],["EvalError","InternalError","RangeError","ReferenceError","SyntaxError","TypeError","URIError"]);return function(r){var t={keyword:e.concat(["then","unless","until","loop","by","when","and","or","is","isnt","not"]).filter((e=>n=>!e.includes(n))(["var","const","let","function","static"])).join(" "),literal:n.concat(["yes","no","on","off"]).join(" "),built_in:a.concat(["npm","print"]).join(" ")},i="[A-Za-z$_][0-9A-Za-z$_]*",s={className:"subst",begin:/#\{/,end:/}/,keywords:t},o=[r.BINARY_NUMBER_MODE,r.inherit(r.C_NUMBER_MODE,{starts:{end:"(\\s*/)?",relevance:0}}),{className:"string",variants:[{begin:/'''/,end:/'''/,contains:[r.BACKSLASH_ESCAPE]},{begin:/'/,end:/'/,contains:[r.BACKSLASH_ESCAPE]},{begin:/"""/,end:/"""/,contains:[r.BACKSLASH_ESCAPE,s]},{begin:/"/,end:/"/,contains:[r.BACKSLASH_ESCAPE,s]}]},{className:"regexp",variants:[{begin:"///",end:"///",contains:[s,r.HASH_COMMENT_MODE]},{begin:"//[gim]{0,3}(?=\\W)",relevance:0},{begin:/\/(?![ *]).*?(?![\\]).\/[gim]{0,3}(?=\W)/}]},{begin:"@"+i},{subLanguage:"javascript",excludeBegin:!0,excludeEnd:!0,variants:[{begin:"```",end:"```"},{begin:"`",end:"`"}]}];s.contains=o;var c=r.inherit(r.TITLE_MODE,{begin:i}),l={className:"params",begin:"\\([^\\(]",returnBegin:!0,contains:[{begin:/\(/,end:/\)/,keywords:t,contains:["self"].concat(o)}]};return{name:"CoffeeScript",aliases:["coffee","cson","iced"],keywords:t,illegal:/\/\*/,contains:o.concat([r.COMMENT("###","###"),r.HASH_COMMENT_MODE,{className:"function",begin:"^\\s*"+i+"\\s*=\\s*(\\(.*\\))?\\s*\\B[-=]>",end:"[-=]>",returnBegin:!0,contains:[c,l]},{begin:/[:\(,=]\s*/,relevance:0,contains:[{className:"function",begin:"(\\(.*\\))?\\s*\\B[-=]>",end:"[-=]>",returnBegin:!0,contains:[l]}]},{className:"class",beginKeywords:"class",end:"$",illegal:/[:="\[\]]/,contains:[{beginKeywords:"extends",endsWithParent:!0,illegal:/[:="\[\]]/,contains:[c]},c]},{begin:i+":",end:":",returnBegin:!0,returnEnd:!0,relevance:0}])}}}());hljs.registerLanguage("ruby",function(){"use strict";return function(e){var n="[a-zA-Z_]\\w*[!?=]?|[-+~]\\@|<<|>>|=~|===?|<=>|[<>]=?|\\*\\*|[-/+%^&*~`|]|\\[\\]=?",a={keyword:"and then defined module in return redo if BEGIN retry end for self when next until do begin unless END rescue else break undef not super class case require yield alias while ensure elsif or include attr_reader attr_writer attr_accessor",literal:"true false nil"},s={className:"doctag",begin:"@[A-Za-z]+"},i={begin:"#<",end:">"},r=[e.COMMENT("#","$",{contains:[s]}),e.COMMENT("^\\=begin","^\\=end",{contains:[s],relevance:10}),e.COMMENT("^__END__","\\n$")],c={className:"subst",begin:"#\\{",end:"}",keywords:a},t={className:"string",contains:[e.BACKSLASH_ESCAPE,c],variants:[{begin:/'/,end:/'/},{begin:/"/,end:/"/},{begin:/`/,end:/`/},{begin:"%[qQwWx]?\\(",end:"\\)"},{begin:"%[qQwWx]?\\[",end:"\\]"},{begin:"%[qQwWx]?{",end:"}"},{begin:"%[qQwWx]?<",end:">"},{begin:"%[qQwWx]?/",end:"/"},{begin:"%[qQwWx]?%",end:"%"},{begin:"%[qQwWx]?-",end:"-"},{begin:"%[qQwWx]?\\|",end:"\\|"},{begin:/\B\?(\\\d{1,3}|\\x[A-Fa-f0-9]{1,2}|\\u[A-Fa-f0-9]{4}|\\?\S)\b/},{begin:/<<[-~]?'?(\w+)(?:.|\n)*?\n\s*\1\b/,returnBegin:!0,contains:[{begin:/<<[-~]?'?/},e.END_SAME_AS_BEGIN({begin:/(\w+)/,end:/(\w+)/,contains:[e.BACKSLASH_ESCAPE,c]})]}]},b={className:"params",begin:"\\(",end:"\\)",endsParent:!0,keywords:a},d=[t,i,{className:"class",beginKeywords:"class module",end:"$|;",illegal:/=/,contains:[e.inherit(e.TITLE_MODE,{begin:"[A-Za-z_]\\w*(::\\w+)*(\\?|\\!)?"}),{begin:"<\\s*",contains:[{begin:"("+e.IDENT_RE+"::)?"+e.IDENT_RE}]}].concat(r)},{className:"function",beginKeywords:"def",end:"$|;",contains:[e.inherit(e.TITLE_MODE,{begin:n}),b].concat(r)},{begin:e.IDENT_RE+"::"},{className:"symbol",begin:e.UNDERSCORE_IDENT_RE+"(\\!|\\?)?:",relevance:0},{className:"symbol",begin:":(?!\\s)",contains:[t,{begin:n}],relevance:0},{className:"number",begin:"(\\b0[0-7_]+)|(\\b0x[0-9a-fA-F_]+)|(\\b[1-9][0-9_]*(\\.[0-9_]+)?)|[0_]\\b",relevance:0},{begin:"(\\$\\W)|((\\$|\\@\\@?)(\\w+))"},{className:"params",begin:/\|/,end:/\|/,keywords:a},{begin:"("+e.RE_STARTERS_RE+"|unless)\\s*",keywords:"unless",contains:[i,{className:"regexp",contains:[e.BACKSLASH_ESCAPE,c],illegal:/\n/,variants:[{begin:"/",end:"/[a-z]*"},{begin:"%r{",end:"}[a-z]*"},{begin:"%r\\(",end:"\\)[a-z]*"},{begin:"%r!",end:"![a-z]*"},{begin:"%r\\[",end:"\\][a-z]*"}]}].concat(r),relevance:0}].concat(r);c.contains=d,b.contains=d;var g=[{begin:/^\s*=>/,starts:{end:"$",contains:d}},{className:"meta",begin:"^([>?]>|[\\w#]+\\(\\w+\\):\\d+:\\d+>|(\\w+-)?\\d+\\.\\d+\\.\\d(p\\d+)?[^>]+>)",starts:{end:"$",contains:d}}];return{name:"Ruby",aliases:["rb","gemspec","podspec","thor","irb"],keywords:a,illegal:/\/\*/,contains:r.concat(g).concat(d)}}}());hljs.registerLanguage("yaml",function(){"use strict";return function(e){var n="true false yes no null",a="[\\w#;/?:@&=+$,.~*\\'()[\\]]+",s={className:"string",relevance:0,variants:[{begin:/'/,end:/'/},{begin:/"/,end:/"/},{begin:/\S+/}],contains:[e.BACKSLASH_ESCAPE,{className:"template-variable",variants:[{begin:"{{",end:"}}"},{begin:"%{",end:"}"}]}]},i=e.inherit(s,{variants:[{begin:/'/,end:/'/},{begin:/"/,end:/"/},{begin:/[^\s,{}[\]]+/}]}),l={end:",",endsWithParent:!0,excludeEnd:!0,contains:[],keywords:n,relevance:0},t={begin:"{",end:"}",contains:[l],illegal:"\\n",relevance:0},g={begin:"\\[",end:"\\]",contains:[l],illegal:"\\n",relevance:0},b=[{className:"attr",variants:[{begin:"\\w[\\w :\\/.-]*:(?=[ \t]|$)"},{begin:'"\\w[\\w :\\/.-]*":(?=[ \t]|$)'},{begin:"'\\w[\\w :\\/.-]*':(?=[ \t]|$)"}]},{className:"meta",begin:"^---s*$",relevance:10},{className:"string",begin:"[\\|>]([0-9]?[+-])?[ ]*\\n( *)[\\S ]+\\n(\\2[\\S ]+\\n?)*"},{begin:"<%[%=-]?",end:"[%-]?%>",subLanguage:"ruby",excludeBegin:!0,excludeEnd:!0,relevance:0},{className:"type",begin:"!\\w+!"+a},{className:"type",begin:"!<"+a+">"},{className:"type",begin:"!"+a},{className:"type",begin:"!!"+a},{className:"meta",begin:"&"+e.UNDERSCORE_IDENT_RE+"$"},{className:"meta",begin:"\\*"+e.UNDERSCORE_IDENT_RE+"$"},{className:"bullet",begin:"\\-(?=[ ]|$)",relevance:0},e.HASH_COMMENT_MODE,{beginKeywords:n,keywords:{literal:n}},{className:"number",begin:"\\b[0-9]{4}(-[0-9][0-9]){0,2}([Tt \\t][0-9][0-9]?(:[0-9][0-9]){2})?(\\.[0-9]*)?([ \\t])*(Z|[-+][0-9][0-9]?(:[0-9][0-9])?)?\\b"},{className:"number",begin:e.C_NUMBER_RE+"\\b"},t,g,s],c=[...b];return c.pop(),c.push(i),l.contains=c,{name:"YAML",case_insensitive:!0,aliases:["yml","YAML"],contains:b}}}());hljs.registerLanguage("d",function(){"use strict";return function(e){var a={$pattern:e.UNDERSCORE_IDENT_RE,keyword:"abstract alias align asm assert auto body break byte case cast catch class const continue debug default delete deprecated do else enum export extern final finally for foreach foreach_reverse|10 goto if immutable import in inout int interface invariant is lazy macro mixin module new nothrow out override package pragma private protected public pure ref return scope shared static struct super switch synchronized template this throw try typedef typeid typeof union unittest version void volatile while with __FILE__ __LINE__ __gshared|10 __thread __traits __DATE__ __EOF__ __TIME__ __TIMESTAMP__ __VENDOR__ __VERSION__",built_in:"bool cdouble cent cfloat char creal dchar delegate double dstring float function idouble ifloat ireal long real short string ubyte ucent uint ulong ushort wchar wstring",literal:"false null true"},d="((0|[1-9][\\d_]*)|0[bB][01_]+|0[xX]([\\da-fA-F][\\da-fA-F_]*|_[\\da-fA-F][\\da-fA-F_]*))",n="\\\\(['\"\\?\\\\abfnrtv]|u[\\dA-Fa-f]{4}|[0-7]{1,3}|x[\\dA-Fa-f]{2}|U[\\dA-Fa-f]{8})|&[a-zA-Z\\d]{2,};",t={className:"number",begin:"\\b"+d+"(L|u|U|Lu|LU|uL|UL)?",relevance:0},_={className:"number",begin:"\\b(((0[xX](([\\da-fA-F][\\da-fA-F_]*|_[\\da-fA-F][\\da-fA-F_]*)\\.([\\da-fA-F][\\da-fA-F_]*|_[\\da-fA-F][\\da-fA-F_]*)|\\.?([\\da-fA-F][\\da-fA-F_]*|_[\\da-fA-F][\\da-fA-F_]*))[pP][+-]?(0|[1-9][\\d_]*|\\d[\\d_]*|[\\d_]+?\\d))|((0|[1-9][\\d_]*|\\d[\\d_]*|[\\d_]+?\\d)(\\.\\d*|([eE][+-]?(0|[1-9][\\d_]*|\\d[\\d_]*|[\\d_]+?\\d)))|\\d+\\.(0|[1-9][\\d_]*|\\d[\\d_]*|[\\d_]+?\\d)(0|[1-9][\\d_]*|\\d[\\d_]*|[\\d_]+?\\d)|\\.(0|[1-9][\\d_]*)([eE][+-]?(0|[1-9][\\d_]*|\\d[\\d_]*|[\\d_]+?\\d))?))([fF]|L|i|[fF]i|Li)?|"+d+"(i|[fF]i|Li))",relevance:0},r={className:"string",begin:"'("+n+"|.)",end:"'",illegal:"."},i={className:"string",begin:'"',contains:[{begin:n,relevance:0}],end:'"[cwd]?'},s=e.COMMENT("\\/\\+","\\+\\/",{contains:["self"],relevance:10});return{name:"D",keywords:a,contains:[e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,s,{className:"string",begin:'x"[\\da-fA-F\\s\\n\\r]*"[cwd]?',relevance:10},i,{className:"string",begin:'[rq]"',end:'"[cwd]?',relevance:5},{className:"string",begin:"`",end:"`[cwd]?"},{className:"string",begin:'q"\\{',end:'\\}"'},_,t,r,{className:"meta",begin:"^#!",end:"$",relevance:5},{className:"meta",begin:"#(line)",end:"$",relevance:5},{className:"keyword",begin:"@[a-zA-Z_][a-zA-Z_\\d]*"}]}}}());hljs.registerLanguage("properties",function(){"use strict";return function(e){var n="[ \\t\\f]*",t="("+n+"[:=]"+n+"|[ \\t\\f]+)",a="([^\\\\:= \\t\\f\\n]|\\\\.)+",s={end:t,relevance:0,starts:{className:"string",end:/$/,relevance:0,contains:[{begin:"\\\\\\n"}]}};return{name:".properties",case_insensitive:!0,illegal:/\S/,contains:[e.COMMENT("^\\s*[!#]","$"),{begin:"([^\\\\\\W:= \\t\\f\\n]|\\\\.)+"+t,returnBegin:!0,contains:[{className:"attr",begin:"([^\\\\\\W:= \\t\\f\\n]|\\\\.)+",endsParent:!0,relevance:0}],starts:s},{begin:a+t,returnBegin:!0,relevance:0,contains:[{className:"meta",begin:a,endsParent:!0,relevance:0}],starts:s},{className:"attr",relevance:0,begin:a+n+"$"}]}}}());hljs.registerLanguage("http",function(){"use strict";return function(e){var n="HTTP/[0-9\\.]+";return{name:"HTTP",aliases:["https"],illegal:"\\S",contains:[{begin:"^"+n,end:"$",contains:[{className:"number",begin:"\\b\\d{3}\\b"}]},{begin:"^[A-Z]+ (.*?) "+n+"$",returnBegin:!0,end:"$",contains:[{className:"string",begin:" ",end:" ",excludeBegin:!0,excludeEnd:!0},{begin:n},{className:"keyword",begin:"[A-Z]+"}]},{className:"attribute",begin:"^\\w",end:": ",excludeEnd:!0,illegal:"\\n|\\s|=",starts:{end:"$",relevance:0}},{begin:"\\n\\n",starts:{subLanguage:[],endsWithParent:!0}}]}}}());hljs.registerLanguage("haskell",function(){"use strict";return function(e){var n={variants:[e.COMMENT("--","$"),e.COMMENT("{-","-}",{contains:["self"]})]},i={className:"meta",begin:"{-#",end:"#-}"},a={className:"meta",begin:"^#",end:"$"},s={className:"type",begin:"\\b[A-Z][\\w']*",relevance:0},l={begin:"\\(",end:"\\)",illegal:'"',contains:[i,a,{className:"type",begin:"\\b[A-Z][\\w]*(\\((\\.\\.|,|\\w+)\\))?"},e.inherit(e.TITLE_MODE,{begin:"[_a-z][\\w']*"}),n]};return{name:"Haskell",aliases:["hs"],keywords:"let in if then else case of where do module import hiding qualified type data newtype deriving class instance as default infix infixl infixr foreign export ccall stdcall cplusplus jvm dotnet safe unsafe family forall mdo proc rec",contains:[{beginKeywords:"module",end:"where",keywords:"module where",contains:[l,n],illegal:"\\W\\.|;"},{begin:"\\bimport\\b",end:"$",keywords:"import qualified as hiding",contains:[l,n],illegal:"\\W\\.|;"},{className:"class",begin:"^(\\s*)?(class|instance)\\b",end:"where",keywords:"class family instance where",contains:[s,l,n]},{className:"class",begin:"\\b(data|(new)?type)\\b",end:"$",keywords:"data family type newtype deriving",contains:[i,s,l,{begin:"{",end:"}",contains:l.contains},n]},{beginKeywords:"default",end:"$",contains:[s,l,n]},{beginKeywords:"infix infixl infixr",end:"$",contains:[e.C_NUMBER_MODE,n]},{begin:"\\bforeign\\b",end:"$",keywords:"foreign import export ccall stdcall cplusplus jvm dotnet safe unsafe",contains:[s,e.QUOTE_STRING_MODE,n]},{className:"meta",begin:"#!\\/usr\\/bin\\/env runhaskell",end:"$"},i,a,e.QUOTE_STRING_MODE,e.C_NUMBER_MODE,s,e.inherit(e.TITLE_MODE,{begin:"^[_a-z][\\w']*"}),n,{begin:"->|<-"}]}}}());hljs.registerLanguage("handlebars",function(){"use strict";function e(...e){return e.map(e=>(function(e){return e?"string"==typeof e?e:e.source:null})(e)).join("")}return function(n){const a={"builtin-name":"action bindattr collection component concat debugger each each-in get hash if in input link-to loc log lookup mut outlet partial query-params render template textarea unbound unless view with yield"},t=/\[.*?\]/,s=/[^\s!"#%&'()*+,.\/;<=>@\[\\\]^`{|}~]+/,i=e("(",/'.*?'/,"|",/".*?"/,"|",t,"|",s,"|",/\.|\//,")+"),r=e("(",t,"|",s,")(?==)"),l={begin:i,lexemes:/[\w.\/]+/},c=n.inherit(l,{keywords:{literal:"true false undefined null"}}),o={begin:/\(/,end:/\)/},m={className:"attr",begin:r,relevance:0,starts:{begin:/=/,end:/=/,starts:{contains:[n.NUMBER_MODE,n.QUOTE_STRING_MODE,n.APOS_STRING_MODE,c,o]}}},d={contains:[n.NUMBER_MODE,n.QUOTE_STRING_MODE,n.APOS_STRING_MODE,{begin:/as\s+\|/,keywords:{keyword:"as"},end:/\|/,contains:[{begin:/\w+/}]},m,c,o],returnEnd:!0},g=n.inherit(l,{className:"name",keywords:a,starts:n.inherit(d,{end:/\)/})});o.contains=[g];const u=n.inherit(l,{keywords:a,className:"name",starts:n.inherit(d,{end:/}}/})}),b=n.inherit(l,{keywords:a,className:"name"}),h=n.inherit(l,{className:"name",keywords:a,starts:n.inherit(d,{end:/}}/})});return{name:"Handlebars",aliases:["hbs","html.hbs","html.handlebars","htmlbars"],case_insensitive:!0,subLanguage:"xml",contains:[{begin:/\\\{\{/,skip:!0},{begin:/\\\\(?=\{\{)/,skip:!0},n.COMMENT(/\{\{!--/,/--\}\}/),n.COMMENT(/\{\{!/,/\}\}/),{className:"template-tag",begin:/\{\{\{\{(?!\/)/,end:/\}\}\}\}/,contains:[u],starts:{end:/\{\{\{\{\//,returnEnd:!0,subLanguage:"xml"}},{className:"template-tag",begin:/\{\{\{\{\//,end:/\}\}\}\}/,contains:[b]},{className:"template-tag",begin:/\{\{#/,end:/\}\}/,contains:[u]},{className:"template-tag",begin:/\{\{(?=else\}\})/,end:/\}\}/,keywords:"else"},{className:"template-tag",begin:/\{\{\//,end:/\}\}/,contains:[b]},{className:"template-variable",begin:/\{\{\{/,end:/\}\}\}/,contains:[h]},{className:"template-variable",begin:/\{\{/,end:/\}\}/,contains:[h]}]}}}());hljs.registerLanguage("rust",function(){"use strict";return function(e){var n="([ui](8|16|32|64|128|size)|f(32|64))?",t="drop i8 i16 i32 i64 i128 isize u8 u16 u32 u64 u128 usize f32 f64 str char bool Box Option Result String Vec Copy Send Sized Sync Drop Fn FnMut FnOnce ToOwned Clone Debug PartialEq PartialOrd Eq Ord AsRef AsMut Into From Default Iterator Extend IntoIterator DoubleEndedIterator ExactSizeIterator SliceConcatExt ToString assert! assert_eq! bitflags! bytes! cfg! col! concat! concat_idents! debug_assert! debug_assert_eq! env! panic! file! format! format_args! include_bin! include_str! line! local_data_key! module_path! option_env! print! println! select! stringify! try! unimplemented! unreachable! vec! write! writeln! macro_rules! assert_ne! debug_assert_ne!";return{name:"Rust",aliases:["rs"],keywords:{$pattern:e.IDENT_RE+"!?",keyword:"abstract as async await become box break const continue crate do dyn else enum extern false final fn for if impl in let loop macro match mod move mut override priv pub ref return self Self static struct super trait true try type typeof unsafe unsized use virtual where while yield",literal:"true false Some None Ok Err",built_in:t},illegal:"</",contains:[e.C_LINE_COMMENT_MODE,e.COMMENT("/\\*","\\*/",{contains:["self"]}),e.inherit(e.QUOTE_STRING_MODE,{begin:/b?"/,illegal:null}),{className:"string",variants:[{begin:/r(#*)"(.|\n)*?"\1(?!#)/},{begin:/b?'\\?(x\w{2}|u\w{4}|U\w{8}|.)'/}]},{className:"symbol",begin:/'[a-zA-Z_][a-zA-Z0-9_]*/},{className:"number",variants:[{begin:"\\b0b([01_]+)"+n},{begin:"\\b0o([0-7_]+)"+n},{begin:"\\b0x([A-Fa-f0-9_]+)"+n},{begin:"\\b(\\d[\\d_]*(\\.[0-9_]+)?([eE][+-]?[0-9_]+)?)"+n}],relevance:0},{className:"function",beginKeywords:"fn",end:"(\\(|<)",excludeEnd:!0,contains:[e.UNDERSCORE_TITLE_MODE]},{className:"meta",begin:"#\\!?\\[",end:"\\]",contains:[{className:"meta-string",begin:/"/,end:/"/}]},{className:"class",beginKeywords:"type",end:";",contains:[e.inherit(e.UNDERSCORE_TITLE_MODE,{endsParent:!0})],illegal:"\\S"},{className:"class",beginKeywords:"trait enum struct union",end:"{",contains:[e.inherit(e.UNDERSCORE_TITLE_MODE,{endsParent:!0})],illegal:"[\\w\\d]"},{begin:e.IDENT_RE+"::",keywords:{built_in:t}},{begin:"->"}]}}}());hljs.registerLanguage("cpp",function(){"use strict";return function(e){var t=e.getLanguage("c-like").rawDefinition();return t.disableAutodetect=!1,t.name="C++",t.aliases=["cc","c++","h++","hpp","hh","hxx","cxx"],t}}());hljs.registerLanguage("ini",function(){"use strict";function e(e){return e?"string"==typeof e?e:e.source:null}function n(...n){return n.map(n=>e(n)).join("")}return function(a){var s={className:"number",relevance:0,variants:[{begin:/([\+\-]+)?[\d]+_[\d_]+/},{begin:a.NUMBER_RE}]},i=a.COMMENT();i.variants=[{begin:/;/,end:/$/},{begin:/#/,end:/$/}];var t={className:"variable",variants:[{begin:/\$[\w\d"][\w\d_]*/},{begin:/\$\{(.*?)}/}]},r={className:"literal",begin:/\bon|off|true|false|yes|no\b/},l={className:"string",contains:[a.BACKSLASH_ESCAPE],variants:[{begin:"'''",end:"'''",relevance:10},{begin:'"""',end:'"""',relevance:10},{begin:'"',end:'"'},{begin:"'",end:"'"}]},c={begin:/\[/,end:/\]/,contains:[i,r,t,l,s,"self"],relevance:0},g="("+[/[A-Za-z0-9_-]+/,/"(\\"|[^"])*"/,/'[^']*'/].map(n=>e(n)).join("|")+")";return{name:"TOML, also INI",aliases:["toml"],case_insensitive:!0,illegal:/\S/,contains:[i,{className:"section",begin:/\[+/,end:/\]+/},{begin:n(g,"(\\s*\\.\\s*",g,")*",n("(?=",/\s*=\s*[^#\s]/,")")),className:"attr",starts:{end:/$/,contains:[i,c,r,t,l,s]}}]}}}());hljs.registerLanguage("objectivec",function(){"use strict";return function(e){var n=/[a-zA-Z@][a-zA-Z0-9_]*/,_={$pattern:n,keyword:"@interface @class @protocol @implementation"};return{name:"Objective-C",aliases:["mm","objc","obj-c"],keywords:{$pattern:n,keyword:"int float while char export sizeof typedef const struct for union unsigned long volatile static bool mutable if do return goto void enum else break extern asm case short default double register explicit signed typename this switch continue wchar_t inline readonly assign readwrite self @synchronized id typeof nonatomic super unichar IBOutlet IBAction strong weak copy in out inout bycopy byref oneway __strong __weak __block __autoreleasing @private @protected @public @try @property @end @throw @catch @finally @autoreleasepool @synthesize @dynamic @selector @optional @required @encode @package @import @defs @compatibility_alias __bridge __bridge_transfer __bridge_retained __bridge_retain __covariant __contravariant __kindof _Nonnull _Nullable _Null_unspecified __FUNCTION__ __PRETTY_FUNCTION__ __attribute__ getter setter retain unsafe_unretained nonnull nullable null_unspecified null_resettable class instancetype NS_DESIGNATED_INITIALIZER NS_UNAVAILABLE NS_REQUIRES_SUPER NS_RETURNS_INNER_POINTER NS_INLINE NS_AVAILABLE NS_DEPRECATED NS_ENUM NS_OPTIONS NS_SWIFT_UNAVAILABLE NS_ASSUME_NONNULL_BEGIN NS_ASSUME_NONNULL_END NS_REFINED_FOR_SWIFT NS_SWIFT_NAME NS_SWIFT_NOTHROW NS_DURING NS_HANDLER NS_ENDHANDLER NS_VALUERETURN NS_VOIDRETURN",literal:"false true FALSE TRUE nil YES NO NULL",built_in:"BOOL dispatch_once_t dispatch_queue_t dispatch_sync dispatch_async dispatch_once"},illegal:"</",contains:[{className:"built_in",begin:"\\b(AV|CA|CF|CG|CI|CL|CM|CN|CT|MK|MP|MTK|MTL|NS|SCN|SK|UI|WK|XC)\\w+"},e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,e.C_NUMBER_MODE,e.QUOTE_STRING_MODE,e.APOS_STRING_MODE,{className:"string",variants:[{begin:'@"',end:'"',illegal:"\\n",contains:[e.BACKSLASH_ESCAPE]}]},{className:"meta",begin:/#\s*[a-z]+\b/,end:/$/,keywords:{"meta-keyword":"if else elif endif define undef warning error line pragma ifdef ifndef include"},contains:[{begin:/\\\n/,relevance:0},e.inherit(e.QUOTE_STRING_MODE,{className:"meta-string"}),{className:"meta-string",begin:/<.*?>/,end:/$/,illegal:"\\n"},e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE]},{className:"class",begin:"("+_.keyword.split(" ").join("|")+")\\b",end:"({|$)",excludeEnd:!0,keywords:_,contains:[e.UNDERSCORE_TITLE_MODE]},{begin:"\\."+e.UNDERSCORE_IDENT_RE,relevance:0}]}}}());hljs.registerLanguage("apache",function(){"use strict";return function(e){var n={className:"number",begin:"\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}(:\\d{1,5})?"};return{name:"Apache config",aliases:["apacheconf"],case_insensitive:!0,contains:[e.HASH_COMMENT_MODE,{className:"section",begin:"</?",end:">",contains:[n,{className:"number",begin:":\\d{1,5}"},e.inherit(e.QUOTE_STRING_MODE,{relevance:0})]},{className:"attribute",begin:/\w+/,relevance:0,keywords:{nomarkup:"order deny allow setenv rewriterule rewriteengine rewritecond documentroot sethandler errordocument loadmodule options header listen serverroot servername"},starts:{end:/$/,relevance:0,keywords:{literal:"on off all deny allow"},contains:[{className:"meta",begin:"\\s\\[",end:"\\]$"},{className:"variable",begin:"[\\$%]\\{",end:"\\}",contains:["self",{className:"number",begin:"[\\$%]\\d+"}]},n,{className:"number",begin:"\\d+"},e.QUOTE_STRING_MODE]}}],illegal:/\S/}}}());hljs.registerLanguage("java",function(){"use strict";function e(e){return e?"string"==typeof e?e:e.source:null}function n(e){return a("(",e,")?")}function a(...n){return n.map(n=>e(n)).join("")}function s(...n){return"("+n.map(n=>e(n)).join("|")+")"}return function(e){var t="false synchronized int abstract float private char boolean var static null if const for true while long strictfp finally protected import native final void enum else break transient catch instanceof byte super volatile case assert short package default double public try this switch continue throws protected public private module requires exports do",i={className:"meta",begin:"@[À-ʸa-zA-Z_$][À-ʸa-zA-Z_$0-9]*",contains:[{begin:/\(/,end:/\)/,contains:["self"]}]},r=e=>a("[",e,"]+([",e,"_]*[",e,"]+)?"),c={className:"number",variants:[{begin:`\\b(0[bB]${r("01")})[lL]?`},{begin:`\\b(0${r("0-7")})[dDfFlL]?`},{begin:a(/\b0[xX]/,s(a(r("a-fA-F0-9"),/\./,r("a-fA-F0-9")),a(r("a-fA-F0-9"),/\.?/),a(/\./,r("a-fA-F0-9"))),/([pP][+-]?(\d+))?/,/[fFdDlL]?/)},{begin:a(/\b/,s(a(/\d*\./,r("\\d")),r("\\d")),/[eE][+-]?[\d]+[dDfF]?/)},{begin:a(/\b/,r(/\d/),n(/\.?/),n(r(/\d/)),/[dDfFlL]?/)}],relevance:0};return{name:"Java",aliases:["jsp"],keywords:t,illegal:/<\/|#/,contains:[e.COMMENT("/\\*\\*","\\*/",{relevance:0,contains:[{begin:/\w+@/,relevance:0},{className:"doctag",begin:"@[A-Za-z]+"}]}),e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,e.APOS_STRING_MODE,e.QUOTE_STRING_MODE,{className:"class",beginKeywords:"class interface",end:/[{;=]/,excludeEnd:!0,keywords:"class interface",illegal:/[:"\[\]]/,contains:[{beginKeywords:"extends implements"},e.UNDERSCORE_TITLE_MODE]},{beginKeywords:"new throw return else",relevance:0},{className:"function",begin:"([À-ʸa-zA-Z_$][À-ʸa-zA-Z_$0-9]*(<[À-ʸa-zA-Z_$][À-ʸa-zA-Z_$0-9]*(\\s*,\\s*[À-ʸa-zA-Z_$][À-ʸa-zA-Z_$0-9]*)*>)?\\s+)+"+e.UNDERSCORE_IDENT_RE+"\\s*\\(",returnBegin:!0,end:/[{;=]/,excludeEnd:!0,keywords:t,contains:[{begin:e.UNDERSCORE_IDENT_RE+"\\s*\\(",returnBegin:!0,relevance:0,contains:[e.UNDERSCORE_TITLE_MODE]},{className:"params",begin:/\(/,end:/\)/,keywords:t,relevance:0,contains:[i,e.APOS_STRING_MODE,e.QUOTE_STRING_MODE,e.C_NUMBER_MODE,e.C_BLOCK_COMMENT_MODE]},e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE]},c,i]}}}());hljs.registerLanguage("x86asm",function(){"use strict";return function(s){return{name:"Intel x86 Assembly",case_insensitive:!0,keywords:{$pattern:"[.%]?"+s.IDENT_RE,keyword:"lock rep repe repz repne repnz xaquire xrelease bnd nobnd aaa aad aam aas adc add and arpl bb0_reset bb1_reset bound bsf bsr bswap bt btc btr bts call cbw cdq cdqe clc cld cli clts cmc cmp cmpsb cmpsd cmpsq cmpsw cmpxchg cmpxchg486 cmpxchg8b cmpxchg16b cpuid cpu_read cpu_write cqo cwd cwde daa das dec div dmint emms enter equ f2xm1 fabs fadd faddp fbld fbstp fchs fclex fcmovb fcmovbe fcmove fcmovnb fcmovnbe fcmovne fcmovnu fcmovu fcom fcomi fcomip fcomp fcompp fcos fdecstp fdisi fdiv fdivp fdivr fdivrp femms feni ffree ffreep fiadd ficom ficomp fidiv fidivr fild fimul fincstp finit fist fistp fisttp fisub fisubr fld fld1 fldcw fldenv fldl2e fldl2t fldlg2 fldln2 fldpi fldz fmul fmulp fnclex fndisi fneni fninit fnop fnsave fnstcw fnstenv fnstsw fpatan fprem fprem1 fptan frndint frstor fsave fscale fsetpm fsin fsincos fsqrt fst fstcw fstenv fstp fstsw fsub fsubp fsubr fsubrp ftst fucom fucomi fucomip fucomp fucompp fxam fxch fxtract fyl2x fyl2xp1 hlt ibts icebp idiv imul in inc incbin insb insd insw int int01 int1 int03 int3 into invd invpcid invlpg invlpga iret iretd iretq iretw jcxz jecxz jrcxz jmp jmpe lahf lar lds lea leave les lfence lfs lgdt lgs lidt lldt lmsw loadall loadall286 lodsb lodsd lodsq lodsw loop loope loopne loopnz loopz lsl lss ltr mfence monitor mov movd movq movsb movsd movsq movsw movsx movsxd movzx mul mwait neg nop not or out outsb outsd outsw packssdw packsswb packuswb paddb paddd paddsb paddsiw paddsw paddusb paddusw paddw pand pandn pause paveb pavgusb pcmpeqb pcmpeqd pcmpeqw pcmpgtb pcmpgtd pcmpgtw pdistib pf2id pfacc pfadd pfcmpeq pfcmpge pfcmpgt pfmax pfmin pfmul pfrcp pfrcpit1 pfrcpit2 pfrsqit1 pfrsqrt pfsub pfsubr pi2fd pmachriw pmaddwd pmagw pmulhriw pmulhrwa pmulhrwc pmulhw pmullw pmvgezb pmvlzb pmvnzb pmvzb pop popa popad popaw popf popfd popfq popfw por prefetch prefetchw pslld psllq psllw psrad psraw psrld psrlq psrlw psubb psubd psubsb psubsiw psubsw psubusb psubusw psubw punpckhbw punpckhdq punpckhwd punpcklbw punpckldq punpcklwd push pusha pushad pushaw pushf pushfd pushfq pushfw pxor rcl rcr rdshr rdmsr rdpmc rdtsc rdtscp ret retf retn rol ror rdm rsdc rsldt rsm rsts sahf sal salc sar sbb scasb scasd scasq scasw sfence sgdt shl shld shr shrd sidt sldt skinit smi smint smintold smsw stc std sti stosb stosd stosq stosw str sub svdc svldt svts swapgs syscall sysenter sysexit sysret test ud0 ud1 ud2b ud2 ud2a umov verr verw fwait wbinvd wrshr wrmsr xadd xbts xchg xlatb xlat xor cmove cmovz cmovne cmovnz cmova cmovnbe cmovae cmovnb cmovb cmovnae cmovbe cmovna cmovg cmovnle cmovge cmovnl cmovl cmovnge cmovle cmovng cmovc cmovnc cmovo cmovno cmovs cmovns cmovp cmovpe cmovnp cmovpo je jz jne jnz ja jnbe jae jnb jb jnae jbe jna jg jnle jge jnl jl jnge jle jng jc jnc jo jno js jns jpo jnp jpe jp sete setz setne setnz seta setnbe setae setnb setnc setb setnae setcset setbe setna setg setnle setge setnl setl setnge setle setng sets setns seto setno setpe setp setpo setnp addps addss andnps andps cmpeqps cmpeqss cmpleps cmpless cmpltps cmpltss cmpneqps cmpneqss cmpnleps cmpnless cmpnltps cmpnltss cmpordps cmpordss cmpunordps cmpunordss cmpps cmpss comiss cvtpi2ps cvtps2pi cvtsi2ss cvtss2si cvttps2pi cvttss2si divps divss ldmxcsr maxps maxss minps minss movaps movhps movlhps movlps movhlps movmskps movntps movss movups mulps mulss orps rcpps rcpss rsqrtps rsqrtss shufps sqrtps sqrtss stmxcsr subps subss ucomiss unpckhps unpcklps xorps fxrstor fxrstor64 fxsave fxsave64 xgetbv xsetbv xsave xsave64 xsaveopt xsaveopt64 xrstor xrstor64 prefetchnta prefetcht0 prefetcht1 prefetcht2 maskmovq movntq pavgb pavgw pextrw pinsrw pmaxsw pmaxub pminsw pminub pmovmskb pmulhuw psadbw pshufw pf2iw pfnacc pfpnacc pi2fw pswapd maskmovdqu clflush movntdq movnti movntpd movdqa movdqu movdq2q movq2dq paddq pmuludq pshufd pshufhw pshuflw pslldq psrldq psubq punpckhqdq punpcklqdq addpd addsd andnpd andpd cmpeqpd cmpeqsd cmplepd cmplesd cmpltpd cmpltsd cmpneqpd cmpneqsd cmpnlepd cmpnlesd cmpnltpd cmpnltsd cmpordpd cmpordsd cmpunordpd cmpunordsd cmppd comisd cvtdq2pd cvtdq2ps cvtpd2dq cvtpd2pi cvtpd2ps cvtpi2pd cvtps2dq cvtps2pd cvtsd2si cvtsd2ss cvtsi2sd cvtss2sd cvttpd2pi cvttpd2dq cvttps2dq cvttsd2si divpd divsd maxpd maxsd minpd minsd movapd movhpd movlpd movmskpd movupd mulpd mulsd orpd shufpd sqrtpd sqrtsd subpd subsd ucomisd unpckhpd unpcklpd xorpd addsubpd addsubps haddpd haddps hsubpd hsubps lddqu movddup movshdup movsldup clgi stgi vmcall vmclear vmfunc vmlaunch vmload vmmcall vmptrld vmptrst vmread vmresume vmrun vmsave vmwrite vmxoff vmxon invept invvpid pabsb pabsw pabsd palignr phaddw phaddd phaddsw phsubw phsubd phsubsw pmaddubsw pmulhrsw pshufb psignb psignw psignd extrq insertq movntsd movntss lzcnt blendpd blendps blendvpd blendvps dppd dpps extractps insertps movntdqa mpsadbw packusdw pblendvb pblendw pcmpeqq pextrb pextrd pextrq phminposuw pinsrb pinsrd pinsrq pmaxsb pmaxsd pmaxud pmaxuw pminsb pminsd pminud pminuw pmovsxbw pmovsxbd pmovsxbq pmovsxwd pmovsxwq pmovsxdq pmovzxbw pmovzxbd pmovzxbq pmovzxwd pmovzxwq pmovzxdq pmuldq pmulld ptest roundpd roundps roundsd roundss crc32 pcmpestri pcmpestrm pcmpistri pcmpistrm pcmpgtq popcnt getsec pfrcpv pfrsqrtv movbe aesenc aesenclast aesdec aesdeclast aesimc aeskeygenassist vaesenc vaesenclast vaesdec vaesdeclast vaesimc vaeskeygenassist vaddpd vaddps vaddsd vaddss vaddsubpd vaddsubps vandpd vandps vandnpd vandnps vblendpd vblendps vblendvpd vblendvps vbroadcastss vbroadcastsd vbroadcastf128 vcmpeq_ospd vcmpeqpd vcmplt_ospd vcmpltpd vcmple_ospd vcmplepd vcmpunord_qpd vcmpunordpd vcmpneq_uqpd vcmpneqpd vcmpnlt_uspd vcmpnltpd vcmpnle_uspd vcmpnlepd vcmpord_qpd vcmpordpd vcmpeq_uqpd vcmpnge_uspd vcmpngepd vcmpngt_uspd vcmpngtpd vcmpfalse_oqpd vcmpfalsepd vcmpneq_oqpd vcmpge_ospd vcmpgepd vcmpgt_ospd vcmpgtpd vcmptrue_uqpd vcmptruepd vcmplt_oqpd vcmple_oqpd vcmpunord_spd vcmpneq_uspd vcmpnlt_uqpd vcmpnle_uqpd vcmpord_spd vcmpeq_uspd vcmpnge_uqpd vcmpngt_uqpd vcmpfalse_ospd vcmpneq_ospd vcmpge_oqpd vcmpgt_oqpd vcmptrue_uspd vcmppd vcmpeq_osps vcmpeqps vcmplt_osps vcmpltps vcmple_osps vcmpleps vcmpunord_qps vcmpunordps vcmpneq_uqps vcmpneqps vcmpnlt_usps vcmpnltps vcmpnle_usps vcmpnleps vcmpord_qps vcmpordps vcmpeq_uqps vcmpnge_usps vcmpngeps vcmpngt_usps vcmpngtps vcmpfalse_oqps vcmpfalseps vcmpneq_oqps vcmpge_osps vcmpgeps vcmpgt_osps vcmpgtps vcmptrue_uqps vcmptrueps vcmplt_oqps vcmple_oqps vcmpunord_sps vcmpneq_usps vcmpnlt_uqps vcmpnle_uqps vcmpord_sps vcmpeq_usps vcmpnge_uqps vcmpngt_uqps vcmpfalse_osps vcmpneq_osps vcmpge_oqps vcmpgt_oqps vcmptrue_usps vcmpps vcmpeq_ossd vcmpeqsd vcmplt_ossd vcmpltsd vcmple_ossd vcmplesd vcmpunord_qsd vcmpunordsd vcmpneq_uqsd vcmpneqsd vcmpnlt_ussd vcmpnltsd vcmpnle_ussd vcmpnlesd vcmpord_qsd vcmpordsd vcmpeq_uqsd vcmpnge_ussd vcmpngesd vcmpngt_ussd vcmpngtsd vcmpfalse_oqsd vcmpfalsesd vcmpneq_oqsd vcmpge_ossd vcmpgesd vcmpgt_ossd vcmpgtsd vcmptrue_uqsd vcmptruesd vcmplt_oqsd vcmple_oqsd vcmpunord_ssd vcmpneq_ussd vcmpnlt_uqsd vcmpnle_uqsd vcmpord_ssd vcmpeq_ussd vcmpnge_uqsd vcmpngt_uqsd vcmpfalse_ossd vcmpneq_ossd vcmpge_oqsd vcmpgt_oqsd vcmptrue_ussd vcmpsd vcmpeq_osss vcmpeqss vcmplt_osss vcmpltss vcmple_osss vcmpless vcmpunord_qss vcmpunordss vcmpneq_uqss vcmpneqss vcmpnlt_usss vcmpnltss vcmpnle_usss vcmpnless vcmpord_qss vcmpordss vcmpeq_uqss vcmpnge_usss vcmpngess vcmpngt_usss vcmpngtss vcmpfalse_oqss vcmpfalsess vcmpneq_oqss vcmpge_osss vcmpgess vcmpgt_osss vcmpgtss vcmptrue_uqss vcmptruess vcmplt_oqss vcmple_oqss vcmpunord_sss vcmpneq_usss vcmpnlt_uqss vcmpnle_uqss vcmpord_sss vcmpeq_usss vcmpnge_uqss vcmpngt_uqss vcmpfalse_osss vcmpneq_osss vcmpge_oqss vcmpgt_oqss vcmptrue_usss vcmpss vcomisd vcomiss vcvtdq2pd vcvtdq2ps vcvtpd2dq vcvtpd2ps vcvtps2dq vcvtps2pd vcvtsd2si vcvtsd2ss vcvtsi2sd vcvtsi2ss vcvtss2sd vcvtss2si vcvttpd2dq vcvttps2dq vcvttsd2si vcvttss2si vdivpd vdivps vdivsd vdivss vdppd vdpps vextractf128 vextractps vhaddpd vhaddps vhsubpd vhsubps vinsertf128 vinsertps vlddqu vldqqu vldmxcsr vmaskmovdqu vmaskmovps vmaskmovpd vmaxpd vmaxps vmaxsd vmaxss vminpd vminps vminsd vminss vmovapd vmovaps vmovd vmovq vmovddup vmovdqa vmovqqa vmovdqu vmovqqu vmovhlps vmovhpd vmovhps vmovlhps vmovlpd vmovlps vmovmskpd vmovmskps vmovntdq vmovntqq vmovntdqa vmovntpd vmovntps vmovsd vmovshdup vmovsldup vmovss vmovupd vmovups vmpsadbw vmulpd vmulps vmulsd vmulss vorpd vorps vpabsb vpabsw vpabsd vpacksswb vpackssdw vpackuswb vpackusdw vpaddb vpaddw vpaddd vpaddq vpaddsb vpaddsw vpaddusb vpaddusw vpalignr vpand vpandn vpavgb vpavgw vpblendvb vpblendw vpcmpestri vpcmpestrm vpcmpistri vpcmpistrm vpcmpeqb vpcmpeqw vpcmpeqd vpcmpeqq vpcmpgtb vpcmpgtw vpcmpgtd vpcmpgtq vpermilpd vpermilps vperm2f128 vpextrb vpextrw vpextrd vpextrq vphaddw vphaddd vphaddsw vphminposuw vphsubw vphsubd vphsubsw vpinsrb vpinsrw vpinsrd vpinsrq vpmaddwd vpmaddubsw vpmaxsb vpmaxsw vpmaxsd vpmaxub vpmaxuw vpmaxud vpminsb vpminsw vpminsd vpminub vpminuw vpminud vpmovmskb vpmovsxbw vpmovsxbd vpmovsxbq vpmovsxwd vpmovsxwq vpmovsxdq vpmovzxbw vpmovzxbd vpmovzxbq vpmovzxwd vpmovzxwq vpmovzxdq vpmulhuw vpmulhrsw vpmulhw vpmullw vpmulld vpmuludq vpmuldq vpor vpsadbw vpshufb vpshufd vpshufhw vpshuflw vpsignb vpsignw vpsignd vpslldq vpsrldq vpsllw vpslld vpsllq vpsraw vpsrad vpsrlw vpsrld vpsrlq vptest vpsubb vpsubw vpsubd vpsubq vpsubsb vpsubsw vpsubusb vpsubusw vpunpckhbw vpunpckhwd vpunpckhdq vpunpckhqdq vpunpcklbw vpunpcklwd vpunpckldq vpunpcklqdq vpxor vrcpps vrcpss vrsqrtps vrsqrtss vroundpd vroundps vroundsd vroundss vshufpd vshufps vsqrtpd vsqrtps vsqrtsd vsqrtss vstmxcsr vsubpd vsubps vsubsd vsubss vtestps vtestpd vucomisd vucomiss vunpckhpd vunpckhps vunpcklpd vunpcklps vxorpd vxorps vzeroall vzeroupper pclmullqlqdq pclmulhqlqdq pclmullqhqdq pclmulhqhqdq pclmulqdq vpclmullqlqdq vpclmulhqlqdq vpclmullqhqdq vpclmulhqhqdq vpclmulqdq vfmadd132ps vfmadd132pd vfmadd312ps vfmadd312pd vfmadd213ps vfmadd213pd vfmadd123ps vfmadd123pd vfmadd231ps vfmadd231pd vfmadd321ps vfmadd321pd vfmaddsub132ps vfmaddsub132pd vfmaddsub312ps vfmaddsub312pd vfmaddsub213ps vfmaddsub213pd vfmaddsub123ps vfmaddsub123pd vfmaddsub231ps vfmaddsub231pd vfmaddsub321ps vfmaddsub321pd vfmsub132ps vfmsub132pd vfmsub312ps vfmsub312pd vfmsub213ps vfmsub213pd vfmsub123ps vfmsub123pd vfmsub231ps vfmsub231pd vfmsub321ps vfmsub321pd vfmsubadd132ps vfmsubadd132pd vfmsubadd312ps vfmsubadd312pd vfmsubadd213ps vfmsubadd213pd vfmsubadd123ps vfmsubadd123pd vfmsubadd231ps vfmsubadd231pd vfmsubadd321ps vfmsubadd321pd vfnmadd132ps vfnmadd132pd vfnmadd312ps vfnmadd312pd vfnmadd213ps vfnmadd213pd vfnmadd123ps vfnmadd123pd vfnmadd231ps vfnmadd231pd vfnmadd321ps vfnmadd321pd vfnmsub132ps vfnmsub132pd vfnmsub312ps vfnmsub312pd vfnmsub213ps vfnmsub213pd vfnmsub123ps vfnmsub123pd vfnmsub231ps vfnmsub231pd vfnmsub321ps vfnmsub321pd vfmadd132ss vfmadd132sd vfmadd312ss vfmadd312sd vfmadd213ss vfmadd213sd vfmadd123ss vfmadd123sd vfmadd231ss vfmadd231sd vfmadd321ss vfmadd321sd vfmsub132ss vfmsub132sd vfmsub312ss vfmsub312sd vfmsub213ss vfmsub213sd vfmsub123ss vfmsub123sd vfmsub231ss vfmsub231sd vfmsub321ss vfmsub321sd vfnmadd132ss vfnmadd132sd vfnmadd312ss vfnmadd312sd vfnmadd213ss vfnmadd213sd vfnmadd123ss vfnmadd123sd vfnmadd231ss vfnmadd231sd vfnmadd321ss vfnmadd321sd vfnmsub132ss vfnmsub132sd vfnmsub312ss vfnmsub312sd vfnmsub213ss vfnmsub213sd vfnmsub123ss vfnmsub123sd vfnmsub231ss vfnmsub231sd vfnmsub321ss vfnmsub321sd rdfsbase rdgsbase rdrand wrfsbase wrgsbase vcvtph2ps vcvtps2ph adcx adox rdseed clac stac xstore xcryptecb xcryptcbc xcryptctr xcryptcfb xcryptofb montmul xsha1 xsha256 llwpcb slwpcb lwpval lwpins vfmaddpd vfmaddps vfmaddsd vfmaddss vfmaddsubpd vfmaddsubps vfmsubaddpd vfmsubaddps vfmsubpd vfmsubps vfmsubsd vfmsubss vfnmaddpd vfnmaddps vfnmaddsd vfnmaddss vfnmsubpd vfnmsubps vfnmsubsd vfnmsubss vfrczpd vfrczps vfrczsd vfrczss vpcmov vpcomb vpcomd vpcomq vpcomub vpcomud vpcomuq vpcomuw vpcomw vphaddbd vphaddbq vphaddbw vphadddq vphaddubd vphaddubq vphaddubw vphaddudq vphadduwd vphadduwq vphaddwd vphaddwq vphsubbw vphsubdq vphsubwd vpmacsdd vpmacsdqh vpmacsdql vpmacssdd vpmacssdqh vpmacssdql vpmacsswd vpmacssww vpmacswd vpmacsww vpmadcsswd vpmadcswd vpperm vprotb vprotd vprotq vprotw vpshab vpshad vpshaq vpshaw vpshlb vpshld vpshlq vpshlw vbroadcasti128 vpblendd vpbroadcastb vpbroadcastw vpbroadcastd vpbroadcastq vpermd vpermpd vpermps vpermq vperm2i128 vextracti128 vinserti128 vpmaskmovd vpmaskmovq vpsllvd vpsllvq vpsravd vpsrlvd vpsrlvq vgatherdpd vgatherqpd vgatherdps vgatherqps vpgatherdd vpgatherqd vpgatherdq vpgatherqq xabort xbegin xend xtest andn bextr blci blcic blsi blsic blcfill blsfill blcmsk blsmsk blsr blcs bzhi mulx pdep pext rorx sarx shlx shrx tzcnt tzmsk t1mskc valignd valignq vblendmpd vblendmps vbroadcastf32x4 vbroadcastf64x4 vbroadcasti32x4 vbroadcasti64x4 vcompresspd vcompressps vcvtpd2udq vcvtps2udq vcvtsd2usi vcvtss2usi vcvttpd2udq vcvttps2udq vcvttsd2usi vcvttss2usi vcvtudq2pd vcvtudq2ps vcvtusi2sd vcvtusi2ss vexpandpd vexpandps vextractf32x4 vextractf64x4 vextracti32x4 vextracti64x4 vfixupimmpd vfixupimmps vfixupimmsd vfixupimmss vgetexppd vgetexpps vgetexpsd vgetexpss vgetmantpd vgetmantps vgetmantsd vgetmantss vinsertf32x4 vinsertf64x4 vinserti32x4 vinserti64x4 vmovdqa32 vmovdqa64 vmovdqu32 vmovdqu64 vpabsq vpandd vpandnd vpandnq vpandq vpblendmd vpblendmq vpcmpltd vpcmpled vpcmpneqd vpcmpnltd vpcmpnled vpcmpd vpcmpltq vpcmpleq vpcmpneqq vpcmpnltq vpcmpnleq vpcmpq vpcmpequd vpcmpltud vpcmpleud vpcmpnequd vpcmpnltud vpcmpnleud vpcmpud vpcmpequq vpcmpltuq vpcmpleuq vpcmpnequq vpcmpnltuq vpcmpnleuq vpcmpuq vpcompressd vpcompressq vpermi2d vpermi2pd vpermi2ps vpermi2q vpermt2d vpermt2pd vpermt2ps vpermt2q vpexpandd vpexpandq vpmaxsq vpmaxuq vpminsq vpminuq vpmovdb vpmovdw vpmovqb vpmovqd vpmovqw vpmovsdb vpmovsdw vpmovsqb vpmovsqd vpmovsqw vpmovusdb vpmovusdw vpmovusqb vpmovusqd vpmovusqw vpord vporq vprold vprolq vprolvd vprolvq vprord vprorq vprorvd vprorvq vpscatterdd vpscatterdq vpscatterqd vpscatterqq vpsraq vpsravq vpternlogd vpternlogq vptestmd vptestmq vptestnmd vptestnmq vpxord vpxorq vrcp14pd vrcp14ps vrcp14sd vrcp14ss vrndscalepd vrndscaleps vrndscalesd vrndscaless vrsqrt14pd vrsqrt14ps vrsqrt14sd vrsqrt14ss vscalefpd vscalefps vscalefsd vscalefss vscatterdpd vscatterdps vscatterqpd vscatterqps vshuff32x4 vshuff64x2 vshufi32x4 vshufi64x2 kandnw kandw kmovw knotw kortestw korw kshiftlw kshiftrw kunpckbw kxnorw kxorw vpbroadcastmb2q vpbroadcastmw2d vpconflictd vpconflictq vplzcntd vplzcntq vexp2pd vexp2ps vrcp28pd vrcp28ps vrcp28sd vrcp28ss vrsqrt28pd vrsqrt28ps vrsqrt28sd vrsqrt28ss vgatherpf0dpd vgatherpf0dps vgatherpf0qpd vgatherpf0qps vgatherpf1dpd vgatherpf1dps vgatherpf1qpd vgatherpf1qps vscatterpf0dpd vscatterpf0dps vscatterpf0qpd vscatterpf0qps vscatterpf1dpd vscatterpf1dps vscatterpf1qpd vscatterpf1qps prefetchwt1 bndmk bndcl bndcu bndcn bndmov bndldx bndstx sha1rnds4 sha1nexte sha1msg1 sha1msg2 sha256rnds2 sha256msg1 sha256msg2 hint_nop0 hint_nop1 hint_nop2 hint_nop3 hint_nop4 hint_nop5 hint_nop6 hint_nop7 hint_nop8 hint_nop9 hint_nop10 hint_nop11 hint_nop12 hint_nop13 hint_nop14 hint_nop15 hint_nop16 hint_nop17 hint_nop18 hint_nop19 hint_nop20 hint_nop21 hint_nop22 hint_nop23 hint_nop24 hint_nop25 hint_nop26 hint_nop27 hint_nop28 hint_nop29 hint_nop30 hint_nop31 hint_nop32 hint_nop33 hint_nop34 hint_nop35 hint_nop36 hint_nop37 hint_nop38 hint_nop39 hint_nop40 hint_nop41 hint_nop42 hint_nop43 hint_nop44 hint_nop45 hint_nop46 hint_nop47 hint_nop48 hint_nop49 hint_nop50 hint_nop51 hint_nop52 hint_nop53 hint_nop54 hint_nop55 hint_nop56 hint_nop57 hint_nop58 hint_nop59 hint_nop60 hint_nop61 hint_nop62 hint_nop63",built_in:"ip eip rip al ah bl bh cl ch dl dh sil dil bpl spl r8b r9b r10b r11b r12b r13b r14b r15b ax bx cx dx si di bp sp r8w r9w r10w r11w r12w r13w r14w r15w eax ebx ecx edx esi edi ebp esp eip r8d r9d r10d r11d r12d r13d r14d r15d rax rbx rcx rdx rsi rdi rbp rsp r8 r9 r10 r11 r12 r13 r14 r15 cs ds es fs gs ss st st0 st1 st2 st3 st4 st5 st6 st7 mm0 mm1 mm2 mm3 mm4 mm5 mm6 mm7 xmm0 xmm1 xmm2 xmm3 xmm4 xmm5 xmm6 xmm7 xmm8 xmm9 xmm10 xmm11 xmm12 xmm13 xmm14 xmm15 xmm16 xmm17 xmm18 xmm19 xmm20 xmm21 xmm22 xmm23 xmm24 xmm25 xmm26 xmm27 xmm28 xmm29 xmm30 xmm31 ymm0 ymm1 ymm2 ymm3 ymm4 ymm5 ymm6 ymm7 ymm8 ymm9 ymm10 ymm11 ymm12 ymm13 ymm14 ymm15 ymm16 ymm17 ymm18 ymm19 ymm20 ymm21 ymm22 ymm23 ymm24 ymm25 ymm26 ymm27 ymm28 ymm29 ymm30 ymm31 zmm0 zmm1 zmm2 zmm3 zmm4 zmm5 zmm6 zmm7 zmm8 zmm9 zmm10 zmm11 zmm12 zmm13 zmm14 zmm15 zmm16 zmm17 zmm18 zmm19 zmm20 zmm21 zmm22 zmm23 zmm24 zmm25 zmm26 zmm27 zmm28 zmm29 zmm30 zmm31 k0 k1 k2 k3 k4 k5 k6 k7 bnd0 bnd1 bnd2 bnd3 cr0 cr1 cr2 cr3 cr4 cr8 dr0 dr1 dr2 dr3 dr8 tr3 tr4 tr5 tr6 tr7 r0 r1 r2 r3 r4 r5 r6 r7 r0b r1b r2b r3b r4b r5b r6b r7b r0w r1w r2w r3w r4w r5w r6w r7w r0d r1d r2d r3d r4d r5d r6d r7d r0h r1h r2h r3h r0l r1l r2l r3l r4l r5l r6l r7l r8l r9l r10l r11l r12l r13l r14l r15l db dw dd dq dt ddq do dy dz resb resw resd resq rest resdq reso resy resz incbin equ times byte word dword qword nosplit rel abs seg wrt strict near far a32 ptr",meta:"%define %xdefine %+ %undef %defstr %deftok %assign %strcat %strlen %substr %rotate %elif %else %endif %if %ifmacro %ifctx %ifidn %ifidni %ifid %ifnum %ifstr %iftoken %ifempty %ifenv %error %warning %fatal %rep %endrep %include %push %pop %repl %pathsearch %depend %use %arg %stacksize %local %line %comment %endcomment .nolist __FILE__ __LINE__ __SECT__ __BITS__ __OUTPUT_FORMAT__ __DATE__ __TIME__ __DATE_NUM__ __TIME_NUM__ __UTC_DATE__ __UTC_TIME__ __UTC_DATE_NUM__ __UTC_TIME_NUM__ __PASS__ struc endstruc istruc at iend align alignb sectalign daz nodaz up down zero default option assume public bits use16 use32 use64 default section segment absolute extern global common cpu float __utf16__ __utf16le__ __utf16be__ __utf32__ __utf32le__ __utf32be__ __float8__ __float16__ __float32__ __float64__ __float80m__ __float80e__ __float128l__ __float128h__ __Infinity__ __QNaN__ __SNaN__ Inf NaN QNaN SNaN float8 float16 float32 float64 float80m float80e float128l float128h __FLOAT_DAZ__ __FLOAT_ROUND__ __FLOAT__"},contains:[s.COMMENT(";","$",{relevance:0}),{className:"number",variants:[{begin:"\\b(?:([0-9][0-9_]*)?\\.[0-9_]*(?:[eE][+-]?[0-9_]+)?|(0[Xx])?[0-9][0-9_]*\\.?[0-9_]*(?:[pP](?:[+-]?[0-9_]+)?)?)\\b",relevance:0},{begin:"\\$[0-9][0-9A-Fa-f]*",relevance:0},{begin:"\\b(?:[0-9A-Fa-f][0-9A-Fa-f_]*[Hh]|[0-9][0-9_]*[DdTt]?|[0-7][0-7_]*[QqOo]|[0-1][0-1_]*[BbYy])\\b"},{begin:"\\b(?:0[Xx][0-9A-Fa-f_]+|0[DdTt][0-9_]+|0[QqOo][0-7_]+|0[BbYy][0-1_]+)\\b"}]},s.QUOTE_STRING_MODE,{className:"string",variants:[{begin:"'",end:"[^\\\\]'"},{begin:"`",end:"[^\\\\]`"}],relevance:0},{className:"symbol",variants:[{begin:"^\\s*[A-Za-z._?][A-Za-z0-9_$#@~.?]*(:|\\s+label)"},{begin:"^\\s*%%[A-Za-z0-9_$#@~.?]*:"}],relevance:0},{className:"subst",begin:"%[0-9]+",relevance:0},{className:"subst",begin:"%!S+",relevance:0},{className:"meta",begin:/^\s*\.[\w_-]+/}]}}}());hljs.registerLanguage("kotlin",function(){"use strict";return function(e){var n={keyword:"abstract as val var vararg get set class object open private protected public noinline crossinline dynamic final enum if else do while for when throw try catch finally import package is in fun override companion reified inline lateinit init interface annotation data sealed internal infix operator out by constructor super tailrec where const inner suspend typealias external expect actual trait volatile transient native default",built_in:"Byte Short Char Int Long Boolean Float Double Void Unit Nothing",literal:"true false null"},a={className:"symbol",begin:e.UNDERSCORE_IDENT_RE+"@"},i={className:"subst",begin:"\\${",end:"}",contains:[e.C_NUMBER_MODE]},s={className:"variable",begin:"\\$"+e.UNDERSCORE_IDENT_RE},t={className:"string",variants:[{begin:'"""',end:'"""(?=[^"])',contains:[s,i]},{begin:"'",end:"'",illegal:/\n/,contains:[e.BACKSLASH_ESCAPE]},{begin:'"',end:'"',illegal:/\n/,contains:[e.BACKSLASH_ESCAPE,s,i]}]};i.contains.push(t);var r={className:"meta",begin:"@(?:file|property|field|get|set|receiver|param|setparam|delegate)\\s*:(?:\\s*"+e.UNDERSCORE_IDENT_RE+")?"},l={className:"meta",begin:"@"+e.UNDERSCORE_IDENT_RE,contains:[{begin:/\(/,end:/\)/,contains:[e.inherit(t,{className:"meta-string"})]}]},c=e.COMMENT("/\\*","\\*/",{contains:[e.C_BLOCK_COMMENT_MODE]}),o={variants:[{className:"type",begin:e.UNDERSCORE_IDENT_RE},{begin:/\(/,end:/\)/,contains:[]}]},d=o;return d.variants[1].contains=[o],o.variants[1].contains=[d],{name:"Kotlin",aliases:["kt"],keywords:n,contains:[e.COMMENT("/\\*\\*","\\*/",{relevance:0,contains:[{className:"doctag",begin:"@[A-Za-z]+"}]}),e.C_LINE_COMMENT_MODE,c,{className:"keyword",begin:/\b(break|continue|return|this)\b/,starts:{contains:[{className:"symbol",begin:/@\w+/}]}},a,r,l,{className:"function",beginKeywords:"fun",end:"[(]|$",returnBegin:!0,excludeEnd:!0,keywords:n,illegal:/fun\s+(<.*>)?[^\s\(]+(\s+[^\s\(]+)\s*=/,relevance:5,contains:[{begin:e.UNDERSCORE_IDENT_RE+"\\s*\\(",returnBegin:!0,relevance:0,contains:[e.UNDERSCORE_TITLE_MODE]},{className:"type",begin:/</,end:/>/,keywords:"reified",relevance:0},{className:"params",begin:/\(/,end:/\)/,endsParent:!0,keywords:n,relevance:0,contains:[{begin:/:/,end:/[=,\/]/,endsWithParent:!0,contains:[o,e.C_LINE_COMMENT_MODE,c],relevance:0},e.C_LINE_COMMENT_MODE,c,r,l,t,e.C_NUMBER_MODE]},c]},{className:"class",beginKeywords:"class interface trait",end:/[:\{(]|$/,excludeEnd:!0,illegal:"extends implements",contains:[{beginKeywords:"public protected internal private constructor"},e.UNDERSCORE_TITLE_MODE,{className:"type",begin:/</,end:/>/,excludeBegin:!0,excludeEnd:!0,relevance:0},{className:"type",begin:/[,:]\s*/,end:/[<\(,]|$/,excludeBegin:!0,returnEnd:!0},r,l]},t,{className:"meta",begin:"^#!/usr/bin/env",end:"$",illegal:"\n"},{className:"number",begin:"\\b(0[bB]([01]+[01_]+[01]+|[01]+)|0[xX]([a-fA-F0-9]+[a-fA-F0-9_]+[a-fA-F0-9]+|[a-fA-F0-9]+)|(([\\d]+[\\d_]+[\\d]+|[\\d]+)(\\.([\\d]+[\\d_]+[\\d]+|[\\d]+))?|\\.([\\d]+[\\d_]+[\\d]+|[\\d]+))([eE][-+]?\\d+)?)[lLfF]?",relevance:0}]}}}());hljs.registerLanguage("armasm",function(){"use strict";return function(s){const e={variants:[s.COMMENT("^[ \\t]*(?=#)","$",{relevance:0,excludeBegin:!0}),s.COMMENT("[;@]","$",{relevance:0}),s.C_LINE_COMMENT_MODE,s.C_BLOCK_COMMENT_MODE]};return{name:"ARM Assembly",case_insensitive:!0,aliases:["arm"],keywords:{$pattern:"\\.?"+s.IDENT_RE,meta:".2byte .4byte .align .ascii .asciz .balign .byte .code .data .else .end .endif .endm .endr .equ .err .exitm .extern .global .hword .if .ifdef .ifndef .include .irp .long .macro .rept .req .section .set .skip .space .text .word .arm .thumb .code16 .code32 .force_thumb .thumb_func .ltorg ALIAS ALIGN ARM AREA ASSERT ATTR CN CODE CODE16 CODE32 COMMON CP DATA DCB DCD DCDU DCDO DCFD DCFDU DCI DCQ DCQU DCW DCWU DN ELIF ELSE END ENDFUNC ENDIF ENDP ENTRY EQU EXPORT EXPORTAS EXTERN FIELD FILL FUNCTION GBLA GBLL GBLS GET GLOBAL IF IMPORT INCBIN INCLUDE INFO KEEP LCLA LCLL LCLS LTORG MACRO MAP MEND MEXIT NOFP OPT PRESERVE8 PROC QN READONLY RELOC REQUIRE REQUIRE8 RLIST FN ROUT SETA SETL SETS SN SPACE SUBT THUMB THUMBX TTL WHILE WEND ",built_in:"r0 r1 r2 r3 r4 r5 r6 r7 r8 r9 r10 r11 r12 r13 r14 r15 pc lr sp ip sl sb fp a1 a2 a3 a4 v1 v2 v3 v4 v5 v6 v7 v8 f0 f1 f2 f3 f4 f5 f6 f7 p0 p1 p2 p3 p4 p5 p6 p7 p8 p9 p10 p11 p12 p13 p14 p15 c0 c1 c2 c3 c4 c5 c6 c7 c8 c9 c10 c11 c12 c13 c14 c15 q0 q1 q2 q3 q4 q5 q6 q7 q8 q9 q10 q11 q12 q13 q14 q15 cpsr_c cpsr_x cpsr_s cpsr_f cpsr_cx cpsr_cxs cpsr_xs cpsr_xsf cpsr_sf cpsr_cxsf spsr_c spsr_x spsr_s spsr_f spsr_cx spsr_cxs spsr_xs spsr_xsf spsr_sf spsr_cxsf s0 s1 s2 s3 s4 s5 s6 s7 s8 s9 s10 s11 s12 s13 s14 s15 s16 s17 s18 s19 s20 s21 s22 s23 s24 s25 s26 s27 s28 s29 s30 s31 d0 d1 d2 d3 d4 d5 d6 d7 d8 d9 d10 d11 d12 d13 d14 d15 d16 d17 d18 d19 d20 d21 d22 d23 d24 d25 d26 d27 d28 d29 d30 d31 {PC} {VAR} {TRUE} {FALSE} {OPT} {CONFIG} {ENDIAN} {CODESIZE} {CPU} {FPU} {ARCHITECTURE} {PCSTOREOFFSET} {ARMASM_VERSION} {INTER} {ROPI} {RWPI} {SWST} {NOSWST} . @"},contains:[{className:"keyword",begin:"\\b(adc|(qd?|sh?|u[qh]?)?add(8|16)?|usada?8|(q|sh?|u[qh]?)?(as|sa)x|and|adrl?|sbc|rs[bc]|asr|b[lx]?|blx|bxj|cbn?z|tb[bh]|bic|bfc|bfi|[su]bfx|bkpt|cdp2?|clz|clrex|cmp|cmn|cpsi[ed]|cps|setend|dbg|dmb|dsb|eor|isb|it[te]{0,3}|lsl|lsr|ror|rrx|ldm(([id][ab])|f[ds])?|ldr((s|ex)?[bhd])?|movt?|mvn|mra|mar|mul|[us]mull|smul[bwt][bt]|smu[as]d|smmul|smmla|mla|umlaal|smlal?([wbt][bt]|d)|mls|smlsl?[ds]|smc|svc|sev|mia([bt]{2}|ph)?|mrr?c2?|mcrr2?|mrs|msr|orr|orn|pkh(tb|bt)|rbit|rev(16|sh)?|sel|[su]sat(16)?|nop|pop|push|rfe([id][ab])?|stm([id][ab])?|str(ex)?[bhd]?|(qd?)?sub|(sh?|q|u[qh]?)?sub(8|16)|[su]xt(a?h|a?b(16)?)|srs([id][ab])?|swpb?|swi|smi|tst|teq|wfe|wfi|yield)(eq|ne|cs|cc|mi|pl|vs|vc|hi|ls|ge|lt|gt|le|al|hs|lo)?[sptrx]?(?=\\s)"},e,s.QUOTE_STRING_MODE,{className:"string",begin:"'",end:"[^\\\\]'",relevance:0},{className:"title",begin:"\\|",end:"\\|",illegal:"\\n",relevance:0},{className:"number",variants:[{begin:"[#$=]?0x[0-9a-f]+"},{begin:"[#$=]?0b[01]+"},{begin:"[#$=]\\d+"},{begin:"\\b\\d+"}],relevance:0},{className:"symbol",variants:[{begin:"^[ \\t]*[a-z_\\.\\$][a-z0-9_\\.\\$]+:"},{begin:"^[a-z_\\.\\$][a-z0-9_\\.\\$]+"},{begin:"[=#]\\w+"}],relevance:0}]}}}());hljs.registerLanguage("go",function(){"use strict";return function(e){var n={keyword:"break default func interface select case map struct chan else goto package switch const fallthrough if range type continue for import return var go defer bool byte complex64 complex128 float32 float64 int8 int16 int32 int64 string uint8 uint16 uint32 uint64 int uint uintptr rune",literal:"true false iota nil",built_in:"append cap close complex copy imag len make new panic print println real recover delete"};return{name:"Go",aliases:["golang"],keywords:n,illegal:"</",contains:[e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,{className:"string",variants:[e.QUOTE_STRING_MODE,e.APOS_STRING_MODE,{begin:"`",end:"`"}]},{className:"number",variants:[{begin:e.C_NUMBER_RE+"[i]",relevance:1},e.C_NUMBER_MODE]},{begin:/:=/},{className:"function",beginKeywords:"func",end:"\\s*(\\{|$)",excludeEnd:!0,contains:[e.TITLE_MODE,{className:"params",begin:/\(/,end:/\)/,keywords:n,illegal:/["']/}]}]}}}());hljs.registerLanguage("diff",function(){"use strict";return function(e){return{name:"Diff",aliases:["patch"],contains:[{className:"meta",relevance:10,variants:[{begin:/^@@ +\-\d+,\d+ +\+\d+,\d+ +@@$/},{begin:/^\*\*\* +\d+,\d+ +\*\*\*\*$/},{begin:/^\-\-\- +\d+,\d+ +\-\-\-\-$/}]},{className:"comment",variants:[{begin:/Index: /,end:/$/},{begin:/={3,}/,end:/$/},{begin:/^\-{3}/,end:/$/},{begin:/^\*{3} /,end:/$/},{begin:/^\+{3}/,end:/$/},{begin:/^\*{15}$/}]},{className:"addition",begin:"^\\+",end:"$"},{className:"deletion",begin:"^\\-",end:"$"},{className:"addition",begin:"^\\!",end:"$"}]}}}());hljs.registerLanguage("python",function(){"use strict";return function(e){var n={keyword:"and elif is global as in if from raise for except finally print import pass return exec else break not with class assert yield try while continue del or def lambda async await nonlocal|10",built_in:"Ellipsis NotImplemented",literal:"False None True"},a={className:"meta",begin:/^(>>>|\.\.\.) /},i={className:"subst",begin:/\{/,end:/\}/,keywords:n,illegal:/#/},s={begin:/\{\{/,relevance:0},r={className:"string",contains:[e.BACKSLASH_ESCAPE],variants:[{begin:/(u|b)?r?'''/,end:/'''/,contains:[e.BACKSLASH_ESCAPE,a],relevance:10},{begin:/(u|b)?r?"""/,end:/"""/,contains:[e.BACKSLASH_ESCAPE,a],relevance:10},{begin:/(fr|rf|f)'''/,end:/'''/,contains:[e.BACKSLASH_ESCAPE,a,s,i]},{begin:/(fr|rf|f)"""/,end:/"""/,contains:[e.BACKSLASH_ESCAPE,a,s,i]},{begin:/(u|r|ur)'/,end:/'/,relevance:10},{begin:/(u|r|ur)"/,end:/"/,relevance:10},{begin:/(b|br)'/,end:/'/},{begin:/(b|br)"/,end:/"/},{begin:/(fr|rf|f)'/,end:/'/,contains:[e.BACKSLASH_ESCAPE,s,i]},{begin:/(fr|rf|f)"/,end:/"/,contains:[e.BACKSLASH_ESCAPE,s,i]},e.APOS_STRING_MODE,e.QUOTE_STRING_MODE]},l={className:"number",relevance:0,variants:[{begin:e.BINARY_NUMBER_RE+"[lLjJ]?"},{begin:"\\b(0o[0-7]+)[lLjJ]?"},{begin:e.C_NUMBER_RE+"[lLjJ]?"}]},t={className:"params",variants:[{begin:/\(\s*\)/,skip:!0,className:null},{begin:/\(/,end:/\)/,excludeBegin:!0,excludeEnd:!0,contains:["self",a,l,r,e.HASH_COMMENT_MODE]}]};return i.contains=[r,l,a],{name:"Python",aliases:["py","gyp","ipython"],keywords:n,illegal:/(<\/|->|\?)|=>/,contains:[a,l,{beginKeywords:"if",relevance:0},r,e.HASH_COMMENT_MODE,{variants:[{className:"function",beginKeywords:"def"},{className:"class",beginKeywords:"class"}],end:/:/,illegal:/[${=;\n,]/,contains:[e.UNDERSCORE_TITLE_MODE,t,{begin:/->/,endsWithParent:!0,keywords:"None"}]},{className:"meta",begin:/^[\t ]*@/,end:/$/},{begin:/\b(print|exec)\(/}]}}}());hljs.registerLanguage("shell",function(){"use strict";return function(s){return{name:"Shell Session",aliases:["console"],contains:[{className:"meta",begin:"^\\s{0,3}[/\\w\\d\\[\\]()@-]*[>%$#]",starts:{end:"$",subLanguage:"bash"}}]}}}());hljs.registerLanguage("scala",function(){"use strict";return function(e){var n={className:"subst",variants:[{begin:"\\$[A-Za-z0-9_]+"},{begin:"\\${",end:"}"}]},a={className:"string",variants:[{begin:'"',end:'"',illegal:"\\n",contains:[e.BACKSLASH_ESCAPE]},{begin:'"""',end:'"""',relevance:10},{begin:'[a-z]+"',end:'"',illegal:"\\n",contains:[e.BACKSLASH_ESCAPE,n]},{className:"string",begin:'[a-z]+"""',end:'"""',contains:[n],relevance:10}]},s={className:"type",begin:"\\b[A-Z][A-Za-z0-9_]*",relevance:0},t={className:"title",begin:/[^0-9\n\t "'(),.`{}\[\]:;][^\n\t "'(),.`{}\[\]:;]+|[^0-9\n\t "'(),.`{}\[\]:;=]/,relevance:0},i={className:"class",beginKeywords:"class object trait type",end:/[:={\[\n;]/,excludeEnd:!0,contains:[{beginKeywords:"extends with",relevance:10},{begin:/\[/,end:/\]/,excludeBegin:!0,excludeEnd:!0,relevance:0,contains:[s]},{className:"params",begin:/\(/,end:/\)/,excludeBegin:!0,excludeEnd:!0,relevance:0,contains:[s]},t]},l={className:"function",beginKeywords:"def",end:/[:={\[(\n;]/,excludeEnd:!0,contains:[t]};return{name:"Scala",keywords:{literal:"true false null",keyword:"type yield lazy override def with val var sealed abstract private trait object if forSome for while throw finally protected extends import final return else break new catch super class case package default try this match continue throws implicit"},contains:[e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,a,{className:"symbol",begin:"'\\w[\\w\\d_]*(?!')"},s,l,i,e.C_NUMBER_MODE,{className:"meta",begin:"@[A-Za-z]+"}]}}}());hljs.registerLanguage("julia",function(){"use strict";return function(e){var r="[A-Za-z_\\u00A1-\\uFFFF][A-Za-z_0-9\\u00A1-\\uFFFF]*",t={$pattern:r,keyword:"in isa where baremodule begin break catch ccall const continue do else elseif end export false finally for function global if import importall let local macro module quote return true try using while type immutable abstract bitstype typealias ",literal:"true false ARGS C_NULL DevNull ENDIAN_BOM ENV I Inf Inf16 Inf32 Inf64 InsertionSort JULIA_HOME LOAD_PATH MergeSort NaN NaN16 NaN32 NaN64 PROGRAM_FILE QuickSort RoundDown RoundFromZero RoundNearest RoundNearestTiesAway RoundNearestTiesUp RoundToZero RoundUp STDERR STDIN STDOUT VERSION catalan e|0 eu|0 eulergamma golden im nothing pi γ π φ ",built_in:"ANY AbstractArray AbstractChannel AbstractFloat AbstractMatrix AbstractRNG AbstractSerializer AbstractSet AbstractSparseArray AbstractSparseMatrix AbstractSparseVector AbstractString AbstractUnitRange AbstractVecOrMat AbstractVector Any ArgumentError Array AssertionError Associative Base64DecodePipe Base64EncodePipe Bidiagonal BigFloat BigInt BitArray BitMatrix BitVector Bool BoundsError BufferStream CachingPool CapturedException CartesianIndex CartesianRange Cchar Cdouble Cfloat Channel Char Cint Cintmax_t Clong Clonglong ClusterManager Cmd CodeInfo Colon Complex Complex128 Complex32 Complex64 CompositeException Condition ConjArray ConjMatrix ConjVector Cptrdiff_t Cshort Csize_t Cssize_t Cstring Cuchar Cuint Cuintmax_t Culong Culonglong Cushort Cwchar_t Cwstring DataType Date DateFormat DateTime DenseArray DenseMatrix DenseVecOrMat DenseVector Diagonal Dict DimensionMismatch Dims DirectIndexString Display DivideError DomainError EOFError EachLine Enum Enumerate ErrorException Exception ExponentialBackOff Expr Factorization FileMonitor Float16 Float32 Float64 Function Future GlobalRef GotoNode HTML Hermitian IO IOBuffer IOContext IOStream IPAddr IPv4 IPv6 IndexCartesian IndexLinear IndexStyle InexactError InitError Int Int128 Int16 Int32 Int64 Int8 IntSet Integer InterruptException InvalidStateException Irrational KeyError LabelNode LinSpace LineNumberNode LoadError LowerTriangular MIME Matrix MersenneTwister Method MethodError MethodTable Module NTuple NewvarNode NullException Nullable Number ObjectIdDict OrdinalRange OutOfMemoryError OverflowError Pair ParseError PartialQuickSort PermutedDimsArray Pipe PollingFileWatcher ProcessExitedException Ptr QuoteNode RandomDevice Range RangeIndex Rational RawFD ReadOnlyMemoryError Real ReentrantLock Ref Regex RegexMatch RemoteChannel RemoteException RevString RoundingMode RowVector SSAValue SegmentationFault SerializationState Set SharedArray SharedMatrix SharedVector Signed SimpleVector Slot SlotNumber SparseMatrixCSC SparseVector StackFrame StackOverflowError StackTrace StepRange StepRangeLen StridedArray StridedMatrix StridedVecOrMat StridedVector String SubArray SubString SymTridiagonal Symbol Symmetric SystemError TCPSocket Task Text TextDisplay Timer Tridiagonal Tuple Type TypeError TypeMapEntry TypeMapLevel TypeName TypeVar TypedSlot UDPSocket UInt UInt128 UInt16 UInt32 UInt64 UInt8 UndefRefError UndefVarError UnicodeError UniformScaling Union UnionAll UnitRange Unsigned UpperTriangular Val Vararg VecElement VecOrMat Vector VersionNumber Void WeakKeyDict WeakRef WorkerConfig WorkerPool "},a={keywords:t,illegal:/<\//},n={className:"subst",begin:/\$\(/,end:/\)/,keywords:t},o={className:"variable",begin:"\\$"+r},i={className:"string",contains:[e.BACKSLASH_ESCAPE,n,o],variants:[{begin:/\w*"""/,end:/"""\w*/,relevance:10},{begin:/\w*"/,end:/"\w*/}]},l={className:"string",contains:[e.BACKSLASH_ESCAPE,n,o],begin:"`",end:"`"},s={className:"meta",begin:"@"+r};return a.name="Julia",a.contains=[{className:"number",begin:/(\b0x[\d_]*(\.[\d_]*)?|0x\.\d[\d_]*)p[-+]?\d+|\b0[box][a-fA-F0-9][a-fA-F0-9_]*|(\b\d[\d_]*(\.[\d_]*)?|\.\d[\d_]*)([eEfF][-+]?\d+)?/,relevance:0},{className:"string",begin:/'(.|\\[xXuU][a-zA-Z0-9]+)'/},i,l,s,{className:"comment",variants:[{begin:"#=",end:"=#",relevance:10},{begin:"#",end:"$"}]},e.HASH_COMMENT_MODE,{className:"keyword",begin:"\\b(((abstract|primitive)\\s+)type|(mutable\\s+)?struct)\\b"},{begin:/<:/}],n.contains=a.contains,a}}());hljs.registerLanguage("php-template",function(){"use strict";return function(n){return{name:"PHP template",subLanguage:"xml",contains:[{begin:/<\?(php|=)?/,end:/\?>/,subLanguage:"php",contains:[{begin:"/\\*",end:"\\*/",skip:!0},{begin:'b"',end:'"',skip:!0},{begin:"b'",end:"'",skip:!0},n.inherit(n.APOS_STRING_MODE,{illegal:null,className:null,contains:null,skip:!0}),n.inherit(n.QUOTE_STRING_MODE,{illegal:null,className:null,contains:null,skip:!0})]}]}}}());hljs.registerLanguage("scss",function(){"use strict";return function(e){var t={className:"variable",begin:"(\\$[a-zA-Z-][a-zA-Z0-9_-]*)\\b"},i={className:"number",begin:"#[0-9A-Fa-f]+"};return e.CSS_NUMBER_MODE,e.QUOTE_STRING_MODE,e.APOS_STRING_MODE,e.C_BLOCK_COMMENT_MODE,{name:"SCSS",case_insensitive:!0,illegal:"[=/|']",contains:[e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,{className:"selector-id",begin:"\\#[A-Za-z0-9_-]+",relevance:0},{className:"selector-class",begin:"\\.[A-Za-z0-9_-]+",relevance:0},{className:"selector-attr",begin:"\\[",end:"\\]",illegal:"$"},{className:"selector-tag",begin:"\\b(a|abbr|acronym|address|area|article|aside|audio|b|base|big|blockquote|body|br|button|canvas|caption|cite|code|col|colgroup|command|datalist|dd|del|details|dfn|div|dl|dt|em|embed|fieldset|figcaption|figure|footer|form|frame|frameset|(h[1-6])|head|header|hgroup|hr|html|i|iframe|img|input|ins|kbd|keygen|label|legend|li|link|map|mark|meta|meter|nav|noframes|noscript|object|ol|optgroup|option|output|p|param|pre|progress|q|rp|rt|ruby|samp|script|section|select|small|span|strike|strong|style|sub|sup|table|tbody|td|textarea|tfoot|th|thead|time|title|tr|tt|ul|var|video)\\b",relevance:0},{className:"selector-pseudo",begin:":(visited|valid|root|right|required|read-write|read-only|out-range|optional|only-of-type|only-child|nth-of-type|nth-last-of-type|nth-last-child|nth-child|not|link|left|last-of-type|last-child|lang|invalid|indeterminate|in-range|hover|focus|first-of-type|first-line|first-letter|first-child|first|enabled|empty|disabled|default|checked|before|after|active)"},{className:"selector-pseudo",begin:"::(after|before|choices|first-letter|first-line|repeat-index|repeat-item|selection|value)"},t,{className:"attribute",begin:"\\b(src|z-index|word-wrap|word-spacing|word-break|width|widows|white-space|visibility|vertical-align|unicode-bidi|transition-timing-function|transition-property|transition-duration|transition-delay|transition|transform-style|transform-origin|transform|top|text-underline-position|text-transform|text-shadow|text-rendering|text-overflow|text-indent|text-decoration-style|text-decoration-line|text-decoration-color|text-decoration|text-align-last|text-align|tab-size|table-layout|right|resize|quotes|position|pointer-events|perspective-origin|perspective|page-break-inside|page-break-before|page-break-after|padding-top|padding-right|padding-left|padding-bottom|padding|overflow-y|overflow-x|overflow-wrap|overflow|outline-width|outline-style|outline-offset|outline-color|outline|orphans|order|opacity|object-position|object-fit|normal|none|nav-up|nav-right|nav-left|nav-index|nav-down|min-width|min-height|max-width|max-height|mask|marks|margin-top|margin-right|margin-left|margin-bottom|margin|list-style-type|list-style-position|list-style-image|list-style|line-height|letter-spacing|left|justify-content|initial|inherit|ime-mode|image-orientation|image-resolution|image-rendering|icon|hyphens|height|font-weight|font-variant-ligatures|font-variant|font-style|font-stretch|font-size-adjust|font-size|font-language-override|font-kerning|font-feature-settings|font-family|font|float|flex-wrap|flex-shrink|flex-grow|flex-flow|flex-direction|flex-basis|flex|filter|empty-cells|display|direction|cursor|counter-reset|counter-increment|content|column-width|column-span|column-rule-width|column-rule-style|column-rule-color|column-rule|column-gap|column-fill|column-count|columns|color|clip-path|clip|clear|caption-side|break-inside|break-before|break-after|box-sizing|box-shadow|box-decoration-break|bottom|border-width|border-top-width|border-top-style|border-top-right-radius|border-top-left-radius|border-top-color|border-top|border-style|border-spacing|border-right-width|border-right-style|border-right-color|border-right|border-radius|border-left-width|border-left-style|border-left-color|border-left|border-image-width|border-image-source|border-image-slice|border-image-repeat|border-image-outset|border-image|border-color|border-collapse|border-bottom-width|border-bottom-style|border-bottom-right-radius|border-bottom-left-radius|border-bottom-color|border-bottom|border|background-size|background-repeat|background-position|background-origin|background-image|background-color|background-clip|background-attachment|background-blend-mode|background|backface-visibility|auto|animation-timing-function|animation-play-state|animation-name|animation-iteration-count|animation-fill-mode|animation-duration|animation-direction|animation-delay|animation|align-self|align-items|align-content)\\b",illegal:"[^\\s]"},{begin:"\\b(whitespace|wait|w-resize|visible|vertical-text|vertical-ideographic|uppercase|upper-roman|upper-alpha|underline|transparent|top|thin|thick|text|text-top|text-bottom|tb-rl|table-header-group|table-footer-group|sw-resize|super|strict|static|square|solid|small-caps|separate|se-resize|scroll|s-resize|rtl|row-resize|ridge|right|repeat|repeat-y|repeat-x|relative|progress|pointer|overline|outside|outset|oblique|nowrap|not-allowed|normal|none|nw-resize|no-repeat|no-drop|newspaper|ne-resize|n-resize|move|middle|medium|ltr|lr-tb|lowercase|lower-roman|lower-alpha|loose|list-item|line|line-through|line-edge|lighter|left|keep-all|justify|italic|inter-word|inter-ideograph|inside|inset|inline|inline-block|inherit|inactive|ideograph-space|ideograph-parenthesis|ideograph-numeric|ideograph-alpha|horizontal|hidden|help|hand|groove|fixed|ellipsis|e-resize|double|dotted|distribute|distribute-space|distribute-letter|distribute-all-lines|disc|disabled|default|decimal|dashed|crosshair|collapse|col-resize|circle|char|center|capitalize|break-word|break-all|bottom|both|bolder|bold|block|bidi-override|below|baseline|auto|always|all-scroll|absolute|table|table-cell)\\b"},{begin:":",end:";",contains:[t,i,e.CSS_NUMBER_MODE,e.QUOTE_STRING_MODE,e.APOS_STRING_MODE,{className:"meta",begin:"!important"}]},{begin:"@(page|font-face)",lexemes:"@[a-z-]+",keywords:"@page @font-face"},{begin:"@",end:"[{;]",returnBegin:!0,keywords:"and or not only",contains:[{begin:"@[a-z-]+",className:"keyword"},t,e.QUOTE_STRING_MODE,e.APOS_STRING_MODE,i,e.CSS_NUMBER_MODE]}]}}}());hljs.registerLanguage("r",function(){"use strict";return function(e){var n="([a-zA-Z]|\\.[a-zA-Z.])[a-zA-Z0-9._]*";return{name:"R",contains:[e.HASH_COMMENT_MODE,{begin:n,keywords:{$pattern:n,keyword:"function if in break next repeat else for return switch while try tryCatch stop warning require library attach detach source setMethod setGeneric setGroupGeneric setClass ...",literal:"NULL NA TRUE FALSE T F Inf NaN NA_integer_|10 NA_real_|10 NA_character_|10 NA_complex_|10"},relevance:0},{className:"number",begin:"0[xX][0-9a-fA-F]+[Li]?\\b",relevance:0},{className:"number",begin:"\\d+(?:[eE][+\\-]?\\d*)?L\\b",relevance:0},{className:"number",begin:"\\d+\\.(?!\\d)(?:i\\b)?",relevance:0},{className:"number",begin:"\\d+(?:\\.\\d*)?(?:[eE][+\\-]?\\d*)?i?\\b",relevance:0},{className:"number",begin:"\\.\\d+(?:[eE][+\\-]?\\d*)?i?\\b",relevance:0},{begin:"`",end:"`",relevance:0},{className:"string",contains:[e.BACKSLASH_ESCAPE],variants:[{begin:'"',end:'"'},{begin:"'",end:"'"}]}]}}}());hljs.registerLanguage("sql",function(){"use strict";return function(e){var t=e.COMMENT("--","$");return{name:"SQL",case_insensitive:!0,illegal:/[<>{}*]/,contains:[{beginKeywords:"begin end start commit rollback savepoint lock alter create drop rename call delete do handler insert load replace select truncate update set show pragma grant merge describe use explain help declare prepare execute deallocate release unlock purge reset change stop analyze cache flush optimize repair kill install uninstall checksum restore check backup revoke comment values with",end:/;/,endsWithParent:!0,keywords:{$pattern:/[\w\.]+/,keyword:"as abort abs absolute acc acce accep accept access accessed accessible account acos action activate add addtime admin administer advanced advise aes_decrypt aes_encrypt after agent aggregate ali alia alias all allocate allow alter always analyze ancillary and anti any anydata anydataset anyschema anytype apply archive archived archivelog are as asc ascii asin assembly assertion associate asynchronous at atan atn2 attr attri attrib attribu attribut attribute attributes audit authenticated authentication authid authors auto autoallocate autodblink autoextend automatic availability avg backup badfile basicfile before begin beginning benchmark between bfile bfile_base big bigfile bin binary_double binary_float binlog bit_and bit_count bit_length bit_or bit_xor bitmap blob_base block blocksize body both bound bucket buffer_cache buffer_pool build bulk by byte byteordermark bytes cache caching call calling cancel capacity cascade cascaded case cast catalog category ceil ceiling chain change changed char_base char_length character_length characters characterset charindex charset charsetform charsetid check checksum checksum_agg child choose chr chunk class cleanup clear client clob clob_base clone close cluster_id cluster_probability cluster_set clustering coalesce coercibility col collate collation collect colu colum column column_value columns columns_updated comment commit compact compatibility compiled complete composite_limit compound compress compute concat concat_ws concurrent confirm conn connec connect connect_by_iscycle connect_by_isleaf connect_by_root connect_time connection consider consistent constant constraint constraints constructor container content contents context contributors controlfile conv convert convert_tz corr corr_k corr_s corresponding corruption cos cost count count_big counted covar_pop covar_samp cpu_per_call cpu_per_session crc32 create creation critical cross cube cume_dist curdate current current_date current_time current_timestamp current_user cursor curtime customdatum cycle data database databases datafile datafiles datalength date_add date_cache date_format date_sub dateadd datediff datefromparts datename datepart datetime2fromparts day day_to_second dayname dayofmonth dayofweek dayofyear days db_role_change dbtimezone ddl deallocate declare decode decompose decrement decrypt deduplicate def defa defau defaul default defaults deferred defi defin define degrees delayed delegate delete delete_all delimited demand dense_rank depth dequeue des_decrypt des_encrypt des_key_file desc descr descri describ describe descriptor deterministic diagnostics difference dimension direct_load directory disable disable_all disallow disassociate discardfile disconnect diskgroup distinct distinctrow distribute distributed div do document domain dotnet double downgrade drop dumpfile duplicate duration each edition editionable editions element ellipsis else elsif elt empty enable enable_all enclosed encode encoding encrypt end end-exec endian enforced engine engines enqueue enterprise entityescaping eomonth error errors escaped evalname evaluate event eventdata events except exception exceptions exchange exclude excluding execu execut execute exempt exists exit exp expire explain explode export export_set extended extent external external_1 external_2 externally extract failed failed_login_attempts failover failure far fast feature_set feature_value fetch field fields file file_name_convert filesystem_like_logging final finish first first_value fixed flash_cache flashback floor flush following follows for forall force foreign form forma format found found_rows freelist freelists freepools fresh from from_base64 from_days ftp full function general generated get get_format get_lock getdate getutcdate global global_name globally go goto grant grants greatest group group_concat group_id grouping grouping_id groups gtid_subtract guarantee guard handler hash hashkeys having hea head headi headin heading heap help hex hierarchy high high_priority hosts hour hours http id ident_current ident_incr ident_seed identified identity idle_time if ifnull ignore iif ilike ilm immediate import in include including increment index indexes indexing indextype indicator indices inet6_aton inet6_ntoa inet_aton inet_ntoa infile initial initialized initially initrans inmemory inner innodb input insert install instance instantiable instr interface interleaved intersect into invalidate invisible is is_free_lock is_ipv4 is_ipv4_compat is_not is_not_null is_used_lock isdate isnull isolation iterate java join json json_exists keep keep_duplicates key keys kill language large last last_day last_insert_id last_value lateral lax lcase lead leading least leaves left len lenght length less level levels library like like2 like4 likec limit lines link list listagg little ln load load_file lob lobs local localtime localtimestamp locate locator lock locked log log10 log2 logfile logfiles logging logical logical_reads_per_call logoff logon logs long loop low low_priority lower lpad lrtrim ltrim main make_set makedate maketime managed management manual map mapping mask master master_pos_wait match matched materialized max maxextents maximize maxinstances maxlen maxlogfiles maxloghistory maxlogmembers maxsize maxtrans md5 measures median medium member memcompress memory merge microsecond mid migration min minextents minimum mining minus minute minutes minvalue missing mod mode model modification modify module monitoring month months mount move movement multiset mutex name name_const names nan national native natural nav nchar nclob nested never new newline next nextval no no_write_to_binlog noarchivelog noaudit nobadfile nocheck nocompress nocopy nocycle nodelay nodiscardfile noentityescaping noguarantee nokeep nologfile nomapping nomaxvalue nominimize nominvalue nomonitoring none noneditionable nonschema noorder nopr nopro noprom nopromp noprompt norely noresetlogs noreverse normal norowdependencies noschemacheck noswitch not nothing notice notnull notrim novalidate now nowait nth_value nullif nulls num numb numbe nvarchar nvarchar2 object ocicoll ocidate ocidatetime ociduration ociinterval ociloblocator ocinumber ociref ocirefcursor ocirowid ocistring ocitype oct octet_length of off offline offset oid oidindex old on online only opaque open operations operator optimal optimize option optionally or oracle oracle_date oradata ord ordaudio orddicom orddoc order ordimage ordinality ordvideo organization orlany orlvary out outer outfile outline output over overflow overriding package pad parallel parallel_enable parameters parent parse partial partition partitions pascal passing password password_grace_time password_lock_time password_reuse_max password_reuse_time password_verify_function patch path patindex pctincrease pctthreshold pctused pctversion percent percent_rank percentile_cont percentile_disc performance period period_add period_diff permanent physical pi pipe pipelined pivot pluggable plugin policy position post_transaction pow power pragma prebuilt precedes preceding precision prediction prediction_cost prediction_details prediction_probability prediction_set prepare present preserve prior priority private private_sga privileges procedural procedure procedure_analyze processlist profiles project prompt protection public publishingservername purge quarter query quick quiesce quota quotename radians raise rand range rank raw read reads readsize rebuild record records recover recovery recursive recycle redo reduced ref reference referenced references referencing refresh regexp_like register regr_avgx regr_avgy regr_count regr_intercept regr_r2 regr_slope regr_sxx regr_sxy reject rekey relational relative relaylog release release_lock relies_on relocate rely rem remainder rename repair repeat replace replicate replication required reset resetlogs resize resource respect restore restricted result result_cache resumable resume retention return returning returns reuse reverse revoke right rlike role roles rollback rolling rollup round row row_count rowdependencies rowid rownum rows rtrim rules safe salt sample save savepoint sb1 sb2 sb4 scan schema schemacheck scn scope scroll sdo_georaster sdo_topo_geometry search sec_to_time second seconds section securefile security seed segment select self semi sequence sequential serializable server servererror session session_user sessions_per_user set sets settings sha sha1 sha2 share shared shared_pool short show shrink shutdown si_averagecolor si_colorhistogram si_featurelist si_positionalcolor si_stillimage si_texture siblings sid sign sin size size_t sizes skip slave sleep smalldatetimefromparts smallfile snapshot some soname sort soundex source space sparse spfile split sql sql_big_result sql_buffer_result sql_cache sql_calc_found_rows sql_small_result sql_variant_property sqlcode sqldata sqlerror sqlname sqlstate sqrt square standalone standby start starting startup statement static statistics stats_binomial_test stats_crosstab stats_ks_test stats_mode stats_mw_test stats_one_way_anova stats_t_test_ stats_t_test_indep stats_t_test_one stats_t_test_paired stats_wsr_test status std stddev stddev_pop stddev_samp stdev stop storage store stored str str_to_date straight_join strcmp strict string struct stuff style subdate subpartition subpartitions substitutable substr substring subtime subtring_index subtype success sum suspend switch switchoffset switchover sync synchronous synonym sys sys_xmlagg sysasm sysaux sysdate sysdatetimeoffset sysdba sysoper system system_user sysutcdatetime table tables tablespace tablesample tan tdo template temporary terminated tertiary_weights test than then thread through tier ties time time_format time_zone timediff timefromparts timeout timestamp timestampadd timestampdiff timezone_abbr timezone_minute timezone_region to to_base64 to_date to_days to_seconds todatetimeoffset trace tracking transaction transactional translate translation treat trigger trigger_nestlevel triggers trim truncate try_cast try_convert try_parse type ub1 ub2 ub4 ucase unarchived unbounded uncompress under undo unhex unicode uniform uninstall union unique unix_timestamp unknown unlimited unlock unnest unpivot unrecoverable unsafe unsigned until untrusted unusable unused update updated upgrade upped upper upsert url urowid usable usage use use_stored_outlines user user_data user_resources users using utc_date utc_timestamp uuid uuid_short validate validate_password_strength validation valist value values var var_samp varcharc vari varia variab variabl variable variables variance varp varraw varrawc varray verify version versions view virtual visible void wait wallet warning warnings week weekday weekofyear wellformed when whene whenev wheneve whenever where while whitespace window with within without work wrapped xdb xml xmlagg xmlattributes xmlcast xmlcolattval xmlelement xmlexists xmlforest xmlindex xmlnamespaces xmlpi xmlquery xmlroot xmlschema xmlserialize xmltable xmltype xor year year_to_month years yearweek",literal:"true false null unknown",built_in:"array bigint binary bit blob bool boolean char character date dec decimal float int int8 integer interval number numeric real record serial serial8 smallint text time timestamp tinyint varchar varchar2 varying void"},contains:[{className:"string",begin:"'",end:"'",contains:[{begin:"''"}]},{className:"string",begin:'"',end:'"',contains:[{begin:'""'}]},{className:"string",begin:"`",end:"`"},e.C_NUMBER_MODE,e.C_BLOCK_COMMENT_MODE,t,e.HASH_COMMENT_MODE]},e.C_BLOCK_COMMENT_MODE,t,e.HASH_COMMENT_MODE]}}}());hljs.registerLanguage("c",function(){"use strict";return function(e){var n=e.getLanguage("c-like").rawDefinition();return n.name="C",n.aliases=["c","h"],n}}());hljs.registerLanguage("json",function(){"use strict";return function(n){var e={literal:"true false null"},i=[n.C_LINE_COMMENT_MODE,n.C_BLOCK_COMMENT_MODE],t=[n.QUOTE_STRING_MODE,n.C_NUMBER_MODE],a={end:",",endsWithParent:!0,excludeEnd:!0,contains:t,keywords:e},l={begin:"{",end:"}",contains:[{className:"attr",begin:/"/,end:/"/,contains:[n.BACKSLASH_ESCAPE],illegal:"\\n"},n.inherit(a,{begin:/:/})].concat(i),illegal:"\\S"},s={begin:"\\[",end:"\\]",contains:[n.inherit(a)],illegal:"\\S"};return t.push(l,s),i.forEach((function(n){t.push(n)})),{name:"JSON",contains:t,keywords:e,illegal:"\\S"}}}());hljs.registerLanguage("python-repl",function(){"use strict";return function(n){return{aliases:["pycon"],contains:[{className:"meta",starts:{end:/ |$/,starts:{end:"$",subLanguage:"python"}},variants:[{begin:/^>>>(?=[ ]|$)/},{begin:/^\.\.\.(?=[ ]|$)/}]}]}}}());hljs.registerLanguage("markdown",function(){"use strict";return function(n){const e={begin:"<",end:">",subLanguage:"xml",relevance:0},a={begin:"\\[.+?\\][\\(\\[].*?[\\)\\]]",returnBegin:!0,contains:[{className:"string",begin:"\\[",end:"\\]",excludeBegin:!0,returnEnd:!0,relevance:0},{className:"link",begin:"\\]\\(",end:"\\)",excludeBegin:!0,excludeEnd:!0},{className:"symbol",begin:"\\]\\[",end:"\\]",excludeBegin:!0,excludeEnd:!0}],relevance:10},i={className:"strong",contains:[],variants:[{begin:/_{2}/,end:/_{2}/},{begin:/\*{2}/,end:/\*{2}/}]},s={className:"emphasis",contains:[],variants:[{begin:/\*(?!\*)/,end:/\*/},{begin:/_(?!_)/,end:/_/,relevance:0}]};i.contains.push(s),s.contains.push(i);var c=[e,a];return i.contains=i.contains.concat(c),s.contains=s.contains.concat(c),{name:"Markdown",aliases:["md","mkdown","mkd"],contains:[{className:"section",variants:[{begin:"^#{1,6}",end:"$",contains:c=c.concat(i,s)},{begin:"(?=^.+?\\n[=-]{2,}$)",contains:[{begin:"^[=-]*$"},{begin:"^",end:"\\n",contains:c}]}]},e,{className:"bullet",begin:"^[ \t]*([*+-]|(\\d+\\.))(?=\\s+)",end:"\\s+",excludeEnd:!0},i,s,{className:"quote",begin:"^>\\s+",contains:c,end:"$"},{className:"code",variants:[{begin:"(`{3,})(.|\\n)*?\\1`*[ ]*"},{begin:"(~{3,})(.|\\n)*?\\1~*[ ]*"},{begin:"```",end:"```+[ ]*$"},{begin:"~~~",end:"~~~+[ ]*$"},{begin:"`.+?`"},{begin:"(?=^( {4}|\\t))",contains:[{begin:"^( {4}|\\t)",end:"(\\n)$"}],relevance:0}]},{begin:"^[-\\*]{3,}",end:"$"},a,{begin:/^\[[^\n]+\]:/,returnBegin:!0,contains:[{className:"symbol",begin:/\[/,end:/\]/,excludeBegin:!0,excludeEnd:!0},{className:"link",begin:/:\s*/,end:/$/,excludeBegin:!0}]}]}}}());hljs.registerLanguage("javascript",function(){"use strict";const e=["as","in","of","if","for","while","finally","var","new","function","do","return","void","else","break","catch","instanceof","with","throw","case","default","try","switch","continue","typeof","delete","let","yield","const","class","debugger","async","await","static","import","from","export","extends"],n=["true","false","null","undefined","NaN","Infinity"],a=[].concat(["setInterval","setTimeout","clearInterval","clearTimeout","require","exports","eval","isFinite","isNaN","parseFloat","parseInt","decodeURI","decodeURIComponent","encodeURI","encodeURIComponent","escape","unescape"],["arguments","this","super","console","window","document","localStorage","module","global"],["Intl","DataView","Number","Math","Date","String","RegExp","Object","Function","Boolean","Error","Symbol","Set","Map","WeakSet","WeakMap","Proxy","Reflect","JSON","Promise","Float64Array","Int16Array","Int32Array","Int8Array","Uint16Array","Uint32Array","Float32Array","Array","Uint8Array","Uint8ClampedArray","ArrayBuffer"],["EvalError","InternalError","RangeError","ReferenceError","SyntaxError","TypeError","URIError"]);function s(e){return r("(?=",e,")")}function r(...e){return e.map(e=>(function(e){return e?"string"==typeof e?e:e.source:null})(e)).join("")}return function(t){var i="[A-Za-z$_][0-9A-Za-z$_]*",c={begin:/<[A-Za-z0-9\\._:-]+/,end:/\/[A-Za-z0-9\\._:-]+>|\/>/},o={$pattern:"[A-Za-z$_][0-9A-Za-z$_]*",keyword:e.join(" "),literal:n.join(" "),built_in:a.join(" ")},l={className:"number",variants:[{begin:"\\b(0[bB][01]+)n?"},{begin:"\\b(0[oO][0-7]+)n?"},{begin:t.C_NUMBER_RE+"n?"}],relevance:0},E={className:"subst",begin:"\\$\\{",end:"\\}",keywords:o,contains:[]},d={begin:"html`",end:"",starts:{end:"`",returnEnd:!1,contains:[t.BACKSLASH_ESCAPE,E],subLanguage:"xml"}},g={begin:"css`",end:"",starts:{end:"`",returnEnd:!1,contains:[t.BACKSLASH_ESCAPE,E],subLanguage:"css"}},u={className:"string",begin:"`",end:"`",contains:[t.BACKSLASH_ESCAPE,E]};E.contains=[t.APOS_STRING_MODE,t.QUOTE_STRING_MODE,d,g,u,l,t.REGEXP_MODE];var b=E.contains.concat([{begin:/\(/,end:/\)/,contains:["self"].concat(E.contains,[t.C_BLOCK_COMMENT_MODE,t.C_LINE_COMMENT_MODE])},t.C_BLOCK_COMMENT_MODE,t.C_LINE_COMMENT_MODE]),_={className:"params",begin:/\(/,end:/\)/,excludeBegin:!0,excludeEnd:!0,contains:b};return{name:"JavaScript",aliases:["js","jsx","mjs","cjs"],keywords:o,contains:[t.SHEBANG({binary:"node",relevance:5}),{className:"meta",relevance:10,begin:/^\s*['"]use (strict|asm)['"]/},t.APOS_STRING_MODE,t.QUOTE_STRING_MODE,d,g,u,t.C_LINE_COMMENT_MODE,t.COMMENT("/\\*\\*","\\*/",{relevance:0,contains:[{className:"doctag",begin:"@[A-Za-z]+",contains:[{className:"type",begin:"\\{",end:"\\}",relevance:0},{className:"variable",begin:i+"(?=\\s*(-)|$)",endsParent:!0,relevance:0},{begin:/(?=[^\n])\s/,relevance:0}]}]}),t.C_BLOCK_COMMENT_MODE,l,{begin:r(/[{,\n]\s*/,s(r(/(((\/\/.*)|(\/\*(.|\n)*\*\/))\s*)*/,i+"\\s*:"))),relevance:0,contains:[{className:"attr",begin:i+s("\\s*:"),relevance:0}]},{begin:"("+t.RE_STARTERS_RE+"|\\b(case|return|throw)\\b)\\s*",keywords:"return throw case",contains:[t.C_LINE_COMMENT_MODE,t.C_BLOCK_COMMENT_MODE,t.REGEXP_MODE,{className:"function",begin:"(\\([^(]*(\\([^(]*(\\([^(]*\\))?\\))?\\)|"+t.UNDERSCORE_IDENT_RE+")\\s*=>",returnBegin:!0,end:"\\s*=>",contains:[{className:"params",variants:[{begin:t.UNDERSCORE_IDENT_RE},{className:null,begin:/\(\s*\)/,skip:!0},{begin:/\(/,end:/\)/,excludeBegin:!0,excludeEnd:!0,keywords:o,contains:b}]}]},{begin:/,/,relevance:0},{className:"",begin:/\s/,end:/\s*/,skip:!0},{variants:[{begin:"<>",end:"</>"},{begin:c.begin,end:c.end}],subLanguage:"xml",contains:[{begin:c.begin,end:c.end,skip:!0,contains:["self"]}]}],relevance:0},{className:"function",beginKeywords:"function",end:/\{/,excludeEnd:!0,contains:[t.inherit(t.TITLE_MODE,{begin:i}),_],illegal:/\[|%/},{begin:/\$[(.]/},t.METHOD_GUARD,{className:"class",beginKeywords:"class",end:/[{;=]/,excludeEnd:!0,illegal:/[:"\[\]]/,contains:[{beginKeywords:"extends"},t.UNDERSCORE_TITLE_MODE]},{beginKeywords:"constructor",end:/\{/,excludeEnd:!0},{begin:"(get|set)\\s+(?="+i+"\\()",end:/{/,keywords:"get set",contains:[t.inherit(t.TITLE_MODE,{begin:i}),{begin:/\(\)/},_]}],illegal:/#(?!!)/}}}());hljs.registerLanguage("typescript",function(){"use strict";const e=["as","in","of","if","for","while","finally","var","new","function","do","return","void","else","break","catch","instanceof","with","throw","case","default","try","switch","continue","typeof","delete","let","yield","const","class","debugger","async","await","static","import","from","export","extends"],n=["true","false","null","undefined","NaN","Infinity"],a=[].concat(["setInterval","setTimeout","clearInterval","clearTimeout","require","exports","eval","isFinite","isNaN","parseFloat","parseInt","decodeURI","decodeURIComponent","encodeURI","encodeURIComponent","escape","unescape"],["arguments","this","super","console","window","document","localStorage","module","global"],["Intl","DataView","Number","Math","Date","String","RegExp","Object","Function","Boolean","Error","Symbol","Set","Map","WeakSet","WeakMap","Proxy","Reflect","JSON","Promise","Float64Array","Int16Array","Int32Array","Int8Array","Uint16Array","Uint32Array","Float32Array","Array","Uint8Array","Uint8ClampedArray","ArrayBuffer"],["EvalError","InternalError","RangeError","ReferenceError","SyntaxError","TypeError","URIError"]);return function(r){var t={$pattern:"[A-Za-z$_][0-9A-Za-z$_]*",keyword:e.concat(["type","namespace","typedef","interface","public","private","protected","implements","declare","abstract","readonly"]).join(" "),literal:n.join(" "),built_in:a.concat(["any","void","number","boolean","string","object","never","enum"]).join(" ")},s={className:"meta",begin:"@[A-Za-z$_][0-9A-Za-z$_]*"},i={className:"number",variants:[{begin:"\\b(0[bB][01]+)n?"},{begin:"\\b(0[oO][0-7]+)n?"},{begin:r.C_NUMBER_RE+"n?"}],relevance:0},o={className:"subst",begin:"\\$\\{",end:"\\}",keywords:t,contains:[]},c={begin:"html`",end:"",starts:{end:"`",returnEnd:!1,contains:[r.BACKSLASH_ESCAPE,o],subLanguage:"xml"}},l={begin:"css`",end:"",starts:{end:"`",returnEnd:!1,contains:[r.BACKSLASH_ESCAPE,o],subLanguage:"css"}},E={className:"string",begin:"`",end:"`",contains:[r.BACKSLASH_ESCAPE,o]};o.contains=[r.APOS_STRING_MODE,r.QUOTE_STRING_MODE,c,l,E,i,r.REGEXP_MODE];var d={begin:"\\(",end:/\)/,keywords:t,contains:["self",r.QUOTE_STRING_MODE,r.APOS_STRING_MODE,r.NUMBER_MODE]},u={className:"params",begin:/\(/,end:/\)/,excludeBegin:!0,excludeEnd:!0,keywords:t,contains:[r.C_LINE_COMMENT_MODE,r.C_BLOCK_COMMENT_MODE,s,d]};return{name:"TypeScript",aliases:["ts"],keywords:t,contains:[r.SHEBANG(),{className:"meta",begin:/^\s*['"]use strict['"]/},r.APOS_STRING_MODE,r.QUOTE_STRING_MODE,c,l,E,r.C_LINE_COMMENT_MODE,r.C_BLOCK_COMMENT_MODE,i,{begin:"("+r.RE_STARTERS_RE+"|\\b(case|return|throw)\\b)\\s*",keywords:"return throw case",contains:[r.C_LINE_COMMENT_MODE,r.C_BLOCK_COMMENT_MODE,r.REGEXP_MODE,{className:"function",begin:"(\\([^(]*(\\([^(]*(\\([^(]*\\))?\\))?\\)|"+r.UNDERSCORE_IDENT_RE+")\\s*=>",returnBegin:!0,end:"\\s*=>",contains:[{className:"params",variants:[{begin:r.UNDERSCORE_IDENT_RE},{className:null,begin:/\(\s*\)/,skip:!0},{begin:/\(/,end:/\)/,excludeBegin:!0,excludeEnd:!0,keywords:t,contains:d.contains}]}]}],relevance:0},{className:"function",beginKeywords:"function",end:/[\{;]/,excludeEnd:!0,keywords:t,contains:["self",r.inherit(r.TITLE_MODE,{begin:"[A-Za-z$_][0-9A-Za-z$_]*"}),u],illegal:/%/,relevance:0},{beginKeywords:"constructor",end:/[\{;]/,excludeEnd:!0,contains:["self",u]},{begin:/module\./,keywords:{built_in:"module"},relevance:0},{beginKeywords:"module",end:/\{/,excludeEnd:!0},{beginKeywords:"interface",end:/\{/,excludeEnd:!0,keywords:"interface extends"},{begin:/\$[(.]/},{begin:"\\."+r.IDENT_RE,relevance:0},s,d]}}}());hljs.registerLanguage("plaintext",function(){"use strict";return function(t){return{name:"Plain text",aliases:["text","txt"],disableAutodetect:!0}}}());hljs.registerLanguage("less",function(){"use strict";return function(e){var n="([\\w-]+|@{[\\w-]+})",a=[],s=[],t=function(e){return{className:"string",begin:"~?"+e+".*?"+e}},r=function(e,n,a){return{className:e,begin:n,relevance:a}},i={begin:"\\(",end:"\\)",contains:s,relevance:0};s.push(e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,t("'"),t('"'),e.CSS_NUMBER_MODE,{begin:"(url|data-uri)\\(",starts:{className:"string",end:"[\\)\\n]",excludeEnd:!0}},r("number","#[0-9A-Fa-f]+\\b"),i,r("variable","@@?[\\w-]+",10),r("variable","@{[\\w-]+}"),r("built_in","~?`[^`]*?`"),{className:"attribute",begin:"[\\w-]+\\s*:",end:":",returnBegin:!0,excludeEnd:!0},{className:"meta",begin:"!important"});var c=s.concat({begin:"{",end:"}",contains:a}),l={beginKeywords:"when",endsWithParent:!0,contains:[{beginKeywords:"and not"}].concat(s)},o={begin:n+"\\s*:",returnBegin:!0,end:"[;}]",relevance:0,contains:[{className:"attribute",begin:n,end:":",excludeEnd:!0,starts:{endsWithParent:!0,illegal:"[<=$]",relevance:0,contains:s}}]},g={className:"keyword",begin:"@(import|media|charset|font-face|(-[a-z]+-)?keyframes|supports|document|namespace|page|viewport|host)\\b",starts:{end:"[;{}]",returnEnd:!0,contains:s,relevance:0}},d={className:"variable",variants:[{begin:"@[\\w-]+\\s*:",relevance:15},{begin:"@[\\w-]+"}],starts:{end:"[;}]",returnEnd:!0,contains:c}},b={variants:[{begin:"[\\.#:&\\[>]",end:"[;{}]"},{begin:n,end:"{"}],returnBegin:!0,returnEnd:!0,illegal:"[<='$\"]",relevance:0,contains:[e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,l,r("keyword","all\\b"),r("variable","@{[\\w-]+}"),r("selector-tag",n+"%?",0),r("selector-id","#"+n),r("selector-class","\\."+n,0),r("selector-tag","&",0),{className:"selector-attr",begin:"\\[",end:"\\]"},{className:"selector-pseudo",begin:/:(:)?[a-zA-Z0-9\_\-\+\(\)"'.]+/},{begin:"\\(",end:"\\)",contains:c},{begin:"!important"}]};return a.push(e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,g,d,o,b),{name:"Less",case_insensitive:!0,illegal:"[=>'/<($\"]",contains:a}}}());hljs.registerLanguage("lua",function(){"use strict";return function(e){var t={begin:"\\[=*\\[",end:"\\]=*\\]",contains:["self"]},a=[e.COMMENT("--(?!\\[=*\\[)","$"),e.COMMENT("--\\[=*\\[","\\]=*\\]",{contains:[t],relevance:10})];return{name:"Lua",keywords:{$pattern:e.UNDERSCORE_IDENT_RE,literal:"true false nil",keyword:"and break do else elseif end for goto if in local not or repeat return then until while",built_in:"_G _ENV _VERSION __index __newindex __mode __call __metatable __tostring __len __gc __add __sub __mul __div __mod __pow __concat __unm __eq __lt __le assert collectgarbage dofile error getfenv getmetatable ipairs load loadfile loadstring module next pairs pcall print rawequal rawget rawset require select setfenv setmetatable tonumber tostring type unpack xpcall arg self coroutine resume yield status wrap create running debug getupvalue debug sethook getmetatable gethook setmetatable setlocal traceback setfenv getinfo setupvalue getlocal getregistry getfenv io lines write close flush open output type read stderr stdin input stdout popen tmpfile math log max acos huge ldexp pi cos tanh pow deg tan cosh sinh random randomseed frexp ceil floor rad abs sqrt modf asin min mod fmod log10 atan2 exp sin atan os exit setlocale date getenv difftime remove time clock tmpname rename execute package preload loadlib loaded loaders cpath config path seeall string sub upper len gfind rep find match char dump gmatch reverse byte format gsub lower table setn insert getn foreachi maxn foreach concat sort remove"},contains:a.concat([{className:"function",beginKeywords:"function",end:"\\)",contains:[e.inherit(e.TITLE_MODE,{begin:"([_a-zA-Z]\\w*\\.)*([_a-zA-Z]\\w*:)?[_a-zA-Z]\\w*"}),{className:"params",begin:"\\(",endsWithParent:!0,contains:a}].concat(a)},e.C_NUMBER_MODE,e.APOS_STRING_MODE,e.QUOTE_STRING_MODE,{className:"string",begin:"\\[=*\\[",end:"\\]=*\\]",contains:[t],relevance:5}])}}}()); diff --git a/src/theme/index.hbs b/src/theme/index.hbs new file mode 100644 index 0000000..e9e6cff --- /dev/null +++ b/src/theme/index.hbs @@ -0,0 +1,306 @@ +<!DOCTYPE HTML> +<html lang="{{ language }}" class="sidebar-visible no-js {{ default_theme }}"> + <head> + <!-- Book generated using mdBook --> + <meta charset="UTF-8"> + <title>{{ title }} + {{#if is_print }} + + {{/if}} + {{#if base_url}} + + {{/if}} + + + + {{> head}} + + + + + + + {{#if favicon_svg}} + + {{/if}} + {{#if favicon_png}} + + {{/if}} + + + + {{#if print_enable}} + + {{/if}} + + + + {{#if copy_fonts}} + + {{/if}} + + + + + + + + {{#each additional_css}} + + {{/each}} + + {{#if mathjax_support}} + + + {{/if}} + + + + + + + + + + + + + + + + +
+ +
+ {{> header}} + + + + {{#if search_enabled}} + + {{/if}} + + + + +
+
+ {{{ content }}} +
+ + +
+
+ + + +
+ + {{#if livereload}} + + + {{/if}} + + {{#if google_analytics}} + + + {{/if}} + + {{#if playground_line_numbers}} + + {{/if}} + + {{#if playground_copyable}} + + {{/if}} + + {{#if playground_js}} + + + + + + {{/if}} + + {{#if search_js}} + + + + {{/if}} + + + + + + + {{#each additional_js}} + + {{/each}} + + {{#if is_print}} + {{#if mathjax_support}} + + {{else}} + + {{/if}} + {{/if}} + + + diff --git a/template.md b/template.md new file mode 100644 index 0000000..f5f6501 --- /dev/null +++ b/template.md @@ -0,0 +1,44 @@ +# A succinct name for the pattern + +## Description + +A short, prose description of the pattern. + +## Example + +```rust +// An example of the pattern in action, should be mostly code, commented +// liberally. +``` + +When writing examples, please try to make them compile. This allows us to test +them. If you fail to write an example that is both complete and readable, +please at least mark your example code with `ignore` as in here: + +```rust,ignore +// A non-runnable example of the pattern in action, should be mostly code, commented +// liberally. +``` + +## Motivation + +Why and where you should use the pattern + +## Advantages + +Good things about this pattern. + +## Disadvantages + +Bad things about this pattern. Possible contraindications. + +## Discussion + +A deeper discussion about this pattern. You might want to cover how this is done +in other languages, alternative approaches, why this is particularly nice in +Rust, etc. + +## See also + +Related patterns (link to the pattern file). Versions of this pattern in other +languages. diff --git a/tests/add_getters_setters.rs b/tests/add_getters_setters.rs deleted file mode 100644 index 139c2a7..0000000 --- a/tests/add_getters_setters.rs +++ /dev/null @@ -1,193 +0,0 @@ -// #[macro_use] -// extern crate add_getters_setters; - -// #[derive(AddGetter, AddGetterVal, AddGetterMut, AddSetter)] -// struct Ts { -// jaf: u8, - -// #[set] -// #[get_val] -// field_1: u8, - -// #[get] -// #[get_mut] -// field_2: String, -// } - -// // these functions shouldn't be set since there are not attrs on jaf. if they are set then it wont compile because these would be duplicate function definitions, so then we'd know theres something wrong. -// impl Ts { -// pub fn get_jaf(&self) -> & u8 { -// &self.field_1 -// } - -// pub fn jaf(&self) -> u8 { -// self.jaf -// } - -// pub fn get_jaf_mut(&mut self) -> &mut u8 { -// &mut self.field_1 -// } - -// pub fn set_jaf(&mut self, v: u8) { -// self.jaf = v; -// } -// } - -// #[test] -// fn test_add_setter() { -// let mut a = Ts {jaf: 4, field_1: 0, field_2: String::from("hello")}; -// a.set_field_1(14); -// assert_eq!(a.field_1, 14); -// } - -// #[test] -// #[should_panic] -// fn test_add_setter_should_panic() { -// let mut a = Ts {jaf: 4, field_1: 0, field_2: String::from("hello")}; -// a.set_field_1(20); -// assert_eq!(a.field_1, 11); -// } - -// #[test] -// fn test_add_getter() { -// let a = Ts {jaf: 4, field_1: 0, field_2: String::from("hello")}; -// assert_eq!(a.get_field_2(), &String::from("hello")); -// } - -// #[test] -// fn test_add_getter_mut() { -// let mut a = Ts {jaf: 4, field_1: 0, field_2: String::from("hello")}; -// let b = a.get_field_2_mut(); -// *b = String::from("world"); -// assert_eq!(a.get_field_2(), &String::from("world")); -// } - -// #[test] -// #[should_panic] -// fn test_add_getter_mut_should_panic() { -// let mut a = Ts {jaf: 4, field_1: 0, field_2: String::from("hello")}; -// let b = a.get_field_2_mut(); -// *b = String::from("world"); -// assert_eq!(a.get_field_2(), &String::from("hello")); -// } - -// #[test] -// fn test_add_getter_by_val() { -// let a = Ts {jaf: 4, field_1: 5, field_2: String::from("hello")}; -// let b = a.field_1(); -// assert_eq!(b, 5); -// } - -// #[test] -// #[should_panic] -// fn test_add_getter_by_val_should_panic() { -// let a = Ts {jaf: 4, field_1: 0, field_2: String::from("hello")}; -// let b = a.field_1(); -// assert_eq!(b, 5); -// } - -// // ********************************* -// // * test tags on the whole struct * -// // ********************************* - -// #[derive(Debug, PartialEq)] -// enum DragonClassifications { -// BlackDragon, -// LuckDragon, -// } - -// #[derive(AddGetter, AddGetterMut, AddSetter)] -// #[get] -// #[get_mut] -// #[set] -// struct Dragon { -// name: String, -// age: u64, // 18446744073709551615 year old dragons cos why not -// ty: DragonClassifications -// } - -// #[test] -// fn get_dragon_name() { -// let smaug = Dragon { -// name: "Smaug".to_owned(), -// age: 171, -// ty: DragonClassifications::BlackDragon -// }; -// assert_eq!(*smaug.get_name(), "Smaug".to_owned()); -// } - -// #[test] -// fn get_dragon_age_mut() { -// let mut smaug = Dragon { -// name: "Smaug".to_owned(), -// age: 171, -// ty: DragonClassifications::BlackDragon -// }; -// *smaug.get_age_mut() = 172; -// assert_eq!(*smaug.get_age(), 172); -// } - -// #[test] -// fn set_dragon_type() { -// let mut falkor = Dragon { -// name: "Falkor".to_owned(), -// age: 0xffffffffffffffff, -// ty: DragonClassifications::BlackDragon -// }; -// falkor.set_ty(DragonClassifications::LuckDragon); -// assert_eq!(*falkor.get_ty(), DragonClassifications::LuckDragon); -// } - -// // *************************** -// // * if statement benchmarks * -// // *************************** - -// // uncomment them and paste them into your own file to try for yourself -// // my results: -// // bench_try_func_call_first ....... 7 ns/iter (+/- 0) -// // bench_try_func_call_last ........ 0 ns/iter (+/- 0) - -// // #[cfg(test)] -// // mod benches { -// // use test::Bencher; - -// // fn all_in_range<'a, T: Iterator>(mut attribs: T, low: u64, high: u64) -> bool { -// // attribs.find_map(|v| { -// // if *v <= high && *v >= low { -// // Some(v) -// // } else { -// // None -// // } -// // }).is_some() -// // } - -// // #[bench] -// // fn bench_try_func_call_first(b: &mut Bencher) { // 7 ns/iter -// // let can_pass = true; -// // let collection: Vec = vec![34, 5431, 12344, 734125, 65426, 276, 7, 6, 7487, 987, 569, 222, 333, 444, 555, 666, 777, 888, 984302, 12, 1, 2, 3, 4, 18]; -// // b.iter(|| { -// // test::black_box( -// // if all_in_range(collection.iter(), 800, 900) || can_pass { -// // 18u8 // just return some random stuff so the complier doesn't skip code, see https://doc.rust-lang.org/1.16.0/book/benchmark-tests.html#gotcha-optimizations -// // } else { -// // 5u8 // just return some random stuff so the complier doesn't skip code, see https://doc.rust-lang.org/1.16.0/book/benchmark-tests.html#gotcha-optimizations -// // } -// // ) -// // }); -// // } - -// // #[bench] -// // fn bench_try_func_call_last(b: &mut Bencher) { // 0 ns/iter -// // let can_pass = true; -// // let collection: Vec = vec![34, 5431, 12344, 734125, 65426, 276, 7, 6, 7487, 987, 569, 222, 333, 444, 555, 666, 777, 888, 984302, 12, 1, 2, 3, 4, 18]; -// // b.iter(|| { -// // test::black_box( -// // if can_pass || all_in_range(collection.iter(), 800, 900) { -// // 18u8 // just return some random stuff so the complier doesn't skip code, see https://doc.rust-lang.org/1.16.0/book/benchmark-tests.html#gotcha-optimizations -// // } else { -// // 5u8 // just return some random stuff so the complier doesn't skip code, see https://doc.rust-lang.org/1.16.0/book/benchmark-tests.html#gotcha-optimizations -// // } -// // ) -// // }); -// // } -// // }