From 7a23bf615248032cba6c2447a09c1f83a0c035ca Mon Sep 17 00:00:00 2001 From: "shoney.arickathil" Date: Sat, 8 Aug 2026 05:01:33 +0200 Subject: [PATCH] remove scrap --- .gitignore | 55 +- crates/app/Cargo.toml | 9 - crates/app/src/lib.rs | 20 - crates/db/Cargo.toml | 9 - crates/db/src/lib.rs | 27 - crates/engine/Cargo.toml | 9 - crates/engine/src/lib.rs | 19 - crates/gen/Cargo.toml | 9 - crates/gen/src/lib.rs | 21 - crates/http/Cargo.toml | 9 - crates/http/src/lib.rs | 20 - crates/logic/Cargo.toml | 9 - crates/logic/src/lib.rs | 19 - crates/policy/Cargo.toml | 9 - crates/policy/src/lib.rs | 25 - crates/ql/Cargo.toml | 9 - crates/ql/src/lib.rs | 13 - crates/service/Cargo.toml | 9 - crates/service/src/lib.rs | 19 - crates/sub/Cargo.toml | 9 - crates/sub/src/lib.rs | 21 - crates/txn/Cargo.toml | 9 - crates/txn/src/lib.rs | 19 - crates/ui/Cargo.toml | 9 - crates/ui/src/lib.rs | 24 - crates/value/Cargo.toml | 9 - crates/value/src/lib.rs | 14 - crates/wal/Cargo.toml | 9 - crates/wal/src/lib.rs | 17 - prototypes/wo-rt-c/Makefile | 19 - prototypes/wo-rt-c/README.md | 78 -- prototypes/wo-rt-c/bench/bench.c | 201 ---- prototypes/wo-rt-c/bench/goref/go.mod | 3 - prototypes/wo-rt-c/bench/goref/main.go | 69 -- prototypes/wo-rt-c/wo-rt.c | 979 ------------------ reference/README.md | 51 - reference/crates/.gitignore | 3 - reference/crates/Cargo.toml | 40 - reference/crates/wo-event/Cargo.toml | 7 - reference/crates/wo-event/src/epoll.rs | 183 ---- reference/crates/wo-event/src/eventfd.rs | 66 -- reference/crates/wo-event/src/lib.rs | 9 - reference/crates/wo-event/src/signalfd.rs | 62 -- reference/crates/wo-event/src/timerfd.rs | 91 -- reference/crates/wo-htmlx/Cargo.toml | 13 - reference/crates/wo-htmlx/src/ast.rs | 18 - reference/crates/wo-htmlx/src/lib.rs | 11 - reference/crates/wo-htmlx/src/parser.rs | 173 ---- reference/crates/wo-htmlx/src/registry.rs | 121 --- reference/crates/wo-htmlx/src/render.rs | 140 --- reference/crates/wo-htmlx/src/value.rs | 122 --- reference/crates/wo-http/Cargo.toml | 10 - reference/crates/wo-http/src/connection.rs | 202 ---- reference/crates/wo-http/src/lib.rs | 4 - reference/crates/wo-http/src/listener.rs | 202 ---- reference/crates/wo-http/src/request.rs | 158 --- reference/crates/wo-http/src/response.rs | 110 -- reference/crates/wo-index/Cargo.toml | 13 - reference/crates/wo-index/src/date.rs | 194 ---- reference/crates/wo-index/src/lib.rs | 40 - reference/crates/wo-index/src/tags.rs | 97 -- reference/crates/wo-index/src/title.rs | 183 ---- reference/crates/wo-md/Cargo.toml | 6 - reference/crates/wo-md/src/block.rs | 324 ------ reference/crates/wo-md/src/highlight.rs | 154 --- reference/crates/wo-md/src/inline.rs | 171 --- reference/crates/wo-md/src/lib.rs | 7 - reference/crates/wo-model/Cargo.toml | 13 - reference/crates/wo-model/src/article.rs | 177 ---- reference/crates/wo-model/src/lib.rs | 5 - reference/crates/wo-model/src/loader.rs | 249 ----- reference/crates/wo-route/Cargo.toml | 7 - reference/crates/wo-route/src/lib.rs | 5 - reference/crates/wo-route/src/pattern.rs | 146 --- reference/crates/wo-route/src/router.rs | 127 --- reference/crates/wo-rt/Cargo.toml | 28 - reference/crates/wo-rt/src/config.rs | 31 - reference/crates/wo-rt/src/handlers.rs | 130 --- reference/crates/wo-rt/src/lib.rs | 6 - reference/crates/wo-rt/src/main.rs | 34 - reference/crates/wo-rt/src/runtime.rs | 481 --------- reference/crates/wo-seg/Cargo.toml | 13 - reference/crates/wo-seg/src/header.rs | 111 -- reference/crates/wo-seg/src/lib.rs | 14 - reference/crates/wo-seg/src/reader.rs | 221 ---- reference/crates/wo-seg/src/writer.rs | 129 --- reference/crates/wo-serve/Cargo.toml | 11 - reference/crates/wo-serve/src/lib.rs | 7 - reference/crates/wo-serve/src/mime.rs | 44 - reference/crates/wo-serve/src/resolve.rs | 80 -- reference/crates/wo-serve/src/sendfile.rs | 109 -- reference/crates/wo-store/Cargo.toml | 13 - reference/crates/wo-store/src/lib.rs | 355 ------- reference/crates/wo-sub/Cargo.toml | 16 - reference/crates/wo-sub/src/lib.rs | 470 --------- reference/crates/wo-watch/Cargo.toml | 11 - reference/crates/wo-watch/src/lib.rs | 280 ----- reference/rest/README.md | 92 -- reference/rest/blog.rest | 181 ---- reference/rest/ecommerce.rest | 142 --- reference/writeonce-api/.dockerignore | 38 - reference/writeonce-api/.env.sample | 4 - reference/writeonce-api/.gitignore | 50 - reference/writeonce-api/.gitlab-ci.yml | 44 - reference/writeonce-api/Cargo.toml | 51 - reference/writeonce-api/Cross.toml | 5 - reference/writeonce-api/Dockerfile | 66 -- reference/writeonce-api/Dockerfile.cross | 16 - reference/writeonce-api/DockerfileLambda | 38 - reference/writeonce-api/Makefile | 27 - reference/writeonce-api/README.md | 25 - reference/writeonce-api/diesel.toml | 9 - reference/writeonce-api/doc/index.html | 42 - reference/writeonce-api/doc/openapi.json | 520 ---------- reference/writeonce-api/docker-compose.yml | 53 - reference/writeonce-api/events/post.json | 51 - .../writeonce-api/infra/00-build-image.sh | 16 - .../writeonce-api/infra/02-docker-login.sh | 3 - .../writeonce-api/infra/03-upload-image.sh | 2 - reference/writeonce-api/infra/04-ssh-setup.sh | 7 - reference/writeonce-api/infra/05-deploy.sh | 8 - .../infra/aurora-serverless/Pulumi.prod.yaml | 4 - .../infra/aurora-serverless/Pulumi.yaml | 7 - .../infra/aurora-serverless/README.md | 3 - .../infra/aurora-serverless/go.mod | 94 -- .../infra/aurora-serverless/go.sum | 283 ----- .../infra/aurora-serverless/main.go | 269 ----- reference/writeonce-api/migrations/.keep | 0 .../down.sql | 6 - .../up.sql | 36 - .../down.sql | 2 - .../2024-10-26-110007_create_articles/up.sql | 7 - .../down.sql | 3 - .../up.sql | 3 - .../down.sql | 3 - .../up.sql | 3 - .../down.sql | 5 - .../up.sql | 5 - .../down.sql | 5 - .../up.sql | 5 - .../2024-11-12-154958_add_time_stamp/down.sql | 3 - .../2024-11-12-154958_add_time_stamp/up.sql | 3 - reference/writeonce-api/postgress/init.sql | 3 - reference/writeonce-api/src/api/article.rs | 183 ---- reference/writeonce-api/src/api/image.rs | 29 - reference/writeonce-api/src/api/mod.rs | 19 - reference/writeonce-api/src/api/spec.rs | 81 -- reference/writeonce-api/src/api/valid.rs | 33 - reference/writeonce-api/src/config/mod.rs | 53 - reference/writeonce-api/src/db.rs | 27 - reference/writeonce-api/src/error_handler.rs | 44 - .../writeonce-api/src/lambda/post_article.rs | 39 - reference/writeonce-api/src/lib.rs | 8 - reference/writeonce-api/src/main.rs | 23 - reference/writeonce-api/src/models.rs | 141 --- reference/writeonce-api/src/schema.rs | 13 - reference/writeonce-api/src/services/aws.rs | 28 - .../writeonce-api/src/services/aws_s3.rs | 102 -- reference/writeonce-api/src/services/error.rs | 67 -- reference/writeonce-api/src/services/mod.rs | 3 - reference/writeonce-app/.dockerignore | 42 - reference/writeonce-app/.editorconfig | 16 - reference/writeonce-app/.gitignore | 48 - reference/writeonce-app/.gitlab-ci.yml | 47 - reference/writeonce-app/.nvmrc | 1 - .../writeonce-app/.vscode/extensions.json | 4 - reference/writeonce-app/.vscode/launch.json | 19 - reference/writeonce-app/.vscode/tasks.json | 42 - reference/writeonce-app/Dockerfile | 24 - reference/writeonce-app/Dockerfile-Dev | 13 - reference/writeonce-app/README.md | 41 - reference/writeonce-app/UI_ENHANCEMENT.md | 216 ---- reference/writeonce-app/angular.json | 139 --- reference/writeonce-app/aspnetcore-https.js | 33 - reference/writeonce-app/deploy.sh | 21 - reference/writeonce-app/docker-compose.yml | 13 - reference/writeonce-app/karma.conf.js | 44 - reference/writeonce-app/nuget.config | 10 - reference/writeonce-app/package.json | 66 -- reference/writeonce-app/src/app/.dockerignore | 8 - .../src/app/about/about.component.css | 0 .../src/app/about/about.component.html | 1 - .../src/app/about/about.component.spec.ts | 23 - .../src/app/about/about.component.ts | 10 - .../src/app/app-routing.module.ts | 22 - .../writeonce-app/src/app/app.component.css | 22 - .../writeonce-app/src/app/app.component.html | 9 - .../src/app/app.component.spec.ts | 47 - .../writeonce-app/src/app/app.component.ts | 42 - reference/writeonce-app/src/app/app.module.ts | 63 -- .../article-code-snippet.component.css | 0 .../article-code-snippet.component.html | 22 - .../article-code-snippet.component.spec.ts | 23 - .../article-code-snippet.component.ts | 88 -- .../article-img-caption.component.css | 16 - .../article-img-caption.component.html | 4 - .../article-img-caption.component.spec.ts | 23 - .../article-img-caption.component.ts | 12 - .../article-reference.model.ts | 6 - .../article-references.component.css | 0 .../article-references.component.html | 12 - .../article-references.component.spec.ts | 23 - .../article-references.component.ts | 22 - .../article-section.component.css | 0 .../article-section.component.html | 13 - .../article-section.component.spec.ts | 23 - .../article-section.component.ts | 61 -- .../article-signature.component.css | 0 .../article-signature.component.html | 6 - .../article-signature.component.spec.ts | 23 - .../article-signature.component.ts | 16 - .../src/app/article/article.component.css | 0 .../src/app/article/article.component.html | 22 - .../src/app/article/article.component.spec.ts | 23 - .../src/app/article/article.component.ts | 72 -- .../screen-shot-image.component.css | 0 .../screen-shot-image.component.html | 4 - .../screen-shot-image.component.spec.ts | 23 - .../screen-shot-image.component.ts | 12 - .../summary-card/summary-card.component.css | 0 .../summary-card/summary-card.component.html | 18 - .../summary-card.component.spec.ts | 23 - .../summary-card/summary-card.component.ts | 14 - .../src/app/contact/contact.component.css | 0 .../src/app/contact/contact.component.html | 1 - .../src/app/contact/contact.component.spec.ts | 23 - .../src/app/contact/contact.component.ts | 10 - .../src/app/footer/footer.component.css | 0 .../src/app/footer/footer.component.html | 42 - .../src/app/footer/footer.component.spec.ts | 23 - .../src/app/footer/footer.component.ts | 13 - .../src/app/gitlab.service.spec.ts | 16 - .../writeonce-app/src/app/gitlab.service.ts | 29 - .../src/app/header/header.component.css | 0 .../src/app/header/header.component.html | 46 - .../src/app/header/header.component.spec.ts | 23 - .../src/app/header/header.component.ts | 25 - .../src/app/home/home.component.css | 8 - .../src/app/home/home.component.html | 49 - .../src/app/home/home.component.spec.ts | 23 - .../src/app/home/home.component.ts | 83 -- .../writeonce-app/src/app/models/article.ts | 120 --- .../navigation-bar.component.css | 0 .../navigation-bar.component.html | 1 - .../navigation-bar.component.spec.ts | 23 - .../navigation-bar.component.ts | 10 - .../repository-list.component.css | 0 .../repository-list.component.html | 25 - .../repository-list.component.spec.ts | 23 - .../repository-list.component.ts | 64 -- .../src/app/services/article.service.spec.ts | 16 - .../src/app/services/article.service.ts | 101 -- .../writeonce-app/src/app/theme.service.ts | 39 - reference/writeonce-app/src/app/types.ts | 2 - .../src/app/websocket.service.spec.ts | 16 - .../src/app/websocket.service.ts | 50 - .../src/app/writeoncedb.service.spec.ts | 16 - .../src/app/writeoncedb.service.ts | 38 - reference/writeonce-app/src/assets/.gitkeep | 0 .../images/auto-scale-gitlab-runner.webp | Bin 367684 -> 0 bytes .../writeonce-app/src/assets/images/logo.png | Bin 44917 -> 0 bytes .../src/assets/images/profile.jpg | Bin 71858 -> 0 bytes ...itlab-runner-with-kubernetes-executor.json | 72 -- .../src/assets/writeonce-articles/page.json | 73 -- .../assets/writeonce-articles/testreadme.md | 5 - .../src/environments/environment.prod.ts | 6 - .../src/environments/environment.ts | 6 - reference/writeonce-app/src/favicon.ico | Bin 63584 -> 0 bytes reference/writeonce-app/src/index.html | 22 - reference/writeonce-app/src/main.ts | 7 - reference/writeonce-app/src/proxy.conf.js | 18 - reference/writeonce-app/src/styles.css | 5 - reference/writeonce-app/src/web.config | 25 - reference/writeonce-app/tailwind.config.js | 12 - reference/writeonce-app/tsconfig.app.json | 14 - reference/writeonce-app/tsconfig.json | 35 - reference/writeonce-app/tsconfig.spec.json | 14 - .../writeonce-app/writeonce-frontend.esproj | 10 - 278 files changed, 35 insertions(+), 14188 deletions(-) delete mode 100644 crates/app/Cargo.toml delete mode 100644 crates/app/src/lib.rs delete mode 100644 crates/db/Cargo.toml delete mode 100644 crates/db/src/lib.rs delete mode 100644 crates/engine/Cargo.toml delete mode 100644 crates/engine/src/lib.rs delete mode 100644 crates/gen/Cargo.toml delete mode 100644 crates/gen/src/lib.rs delete mode 100644 crates/http/Cargo.toml delete mode 100644 crates/http/src/lib.rs delete mode 100644 crates/logic/Cargo.toml delete mode 100644 crates/logic/src/lib.rs delete mode 100644 crates/policy/Cargo.toml delete mode 100644 crates/policy/src/lib.rs delete mode 100644 crates/ql/Cargo.toml delete mode 100644 crates/ql/src/lib.rs delete mode 100644 crates/service/Cargo.toml delete mode 100644 crates/service/src/lib.rs delete mode 100644 crates/sub/Cargo.toml delete mode 100644 crates/sub/src/lib.rs delete mode 100644 crates/txn/Cargo.toml delete mode 100644 crates/txn/src/lib.rs delete mode 100644 crates/ui/Cargo.toml delete mode 100644 crates/ui/src/lib.rs delete mode 100644 crates/value/Cargo.toml delete mode 100644 crates/value/src/lib.rs delete mode 100644 crates/wal/Cargo.toml delete mode 100644 crates/wal/src/lib.rs delete mode 100644 prototypes/wo-rt-c/Makefile delete mode 100644 prototypes/wo-rt-c/README.md delete mode 100644 prototypes/wo-rt-c/bench/bench.c delete mode 100644 prototypes/wo-rt-c/bench/goref/go.mod delete mode 100644 prototypes/wo-rt-c/bench/goref/main.go delete mode 100644 prototypes/wo-rt-c/wo-rt.c delete mode 100644 reference/README.md delete mode 100644 reference/crates/.gitignore delete mode 100644 reference/crates/Cargo.toml delete mode 100644 reference/crates/wo-event/Cargo.toml delete mode 100644 reference/crates/wo-event/src/epoll.rs delete mode 100644 reference/crates/wo-event/src/eventfd.rs delete mode 100644 reference/crates/wo-event/src/lib.rs delete mode 100644 reference/crates/wo-event/src/signalfd.rs delete mode 100644 reference/crates/wo-event/src/timerfd.rs delete mode 100644 reference/crates/wo-htmlx/Cargo.toml delete mode 100644 reference/crates/wo-htmlx/src/ast.rs delete mode 100644 reference/crates/wo-htmlx/src/lib.rs delete mode 100644 reference/crates/wo-htmlx/src/parser.rs delete mode 100644 reference/crates/wo-htmlx/src/registry.rs delete mode 100644 reference/crates/wo-htmlx/src/render.rs delete mode 100644 reference/crates/wo-htmlx/src/value.rs delete mode 100644 reference/crates/wo-http/Cargo.toml delete mode 100644 reference/crates/wo-http/src/connection.rs delete mode 100644 reference/crates/wo-http/src/lib.rs delete mode 100644 reference/crates/wo-http/src/listener.rs delete mode 100644 reference/crates/wo-http/src/request.rs delete mode 100644 reference/crates/wo-http/src/response.rs delete mode 100644 reference/crates/wo-index/Cargo.toml delete mode 100644 reference/crates/wo-index/src/date.rs delete mode 100644 reference/crates/wo-index/src/lib.rs delete mode 100644 reference/crates/wo-index/src/tags.rs delete mode 100644 reference/crates/wo-index/src/title.rs delete mode 100644 reference/crates/wo-md/Cargo.toml delete mode 100644 reference/crates/wo-md/src/block.rs delete mode 100644 reference/crates/wo-md/src/highlight.rs delete mode 100644 reference/crates/wo-md/src/inline.rs delete mode 100644 reference/crates/wo-md/src/lib.rs delete mode 100644 reference/crates/wo-model/Cargo.toml delete mode 100644 reference/crates/wo-model/src/article.rs delete mode 100644 reference/crates/wo-model/src/lib.rs delete mode 100644 reference/crates/wo-model/src/loader.rs delete mode 100644 reference/crates/wo-route/Cargo.toml delete mode 100644 reference/crates/wo-route/src/lib.rs delete mode 100644 reference/crates/wo-route/src/pattern.rs delete mode 100644 reference/crates/wo-route/src/router.rs delete mode 100644 reference/crates/wo-rt/Cargo.toml delete mode 100644 reference/crates/wo-rt/src/config.rs delete mode 100644 reference/crates/wo-rt/src/handlers.rs delete mode 100644 reference/crates/wo-rt/src/lib.rs delete mode 100644 reference/crates/wo-rt/src/main.rs delete mode 100644 reference/crates/wo-rt/src/runtime.rs delete mode 100644 reference/crates/wo-seg/Cargo.toml delete mode 100644 reference/crates/wo-seg/src/header.rs delete mode 100644 reference/crates/wo-seg/src/lib.rs delete mode 100644 reference/crates/wo-seg/src/reader.rs delete mode 100644 reference/crates/wo-seg/src/writer.rs delete mode 100644 reference/crates/wo-serve/Cargo.toml delete mode 100644 reference/crates/wo-serve/src/lib.rs delete mode 100644 reference/crates/wo-serve/src/mime.rs delete mode 100644 reference/crates/wo-serve/src/resolve.rs delete mode 100644 reference/crates/wo-serve/src/sendfile.rs delete mode 100644 reference/crates/wo-store/Cargo.toml delete mode 100644 reference/crates/wo-store/src/lib.rs delete mode 100644 reference/crates/wo-sub/Cargo.toml delete mode 100644 reference/crates/wo-sub/src/lib.rs delete mode 100644 reference/crates/wo-watch/Cargo.toml delete mode 100644 reference/crates/wo-watch/src/lib.rs delete mode 100644 reference/rest/README.md delete mode 100644 reference/rest/blog.rest delete mode 100644 reference/rest/ecommerce.rest delete mode 100644 reference/writeonce-api/.dockerignore delete mode 100644 reference/writeonce-api/.env.sample delete mode 100644 reference/writeonce-api/.gitignore delete mode 100644 reference/writeonce-api/.gitlab-ci.yml delete mode 100644 reference/writeonce-api/Cargo.toml delete mode 100644 reference/writeonce-api/Cross.toml delete mode 100644 reference/writeonce-api/Dockerfile delete mode 100644 reference/writeonce-api/Dockerfile.cross delete mode 100644 reference/writeonce-api/DockerfileLambda delete mode 100644 reference/writeonce-api/Makefile delete mode 100644 reference/writeonce-api/README.md delete mode 100644 reference/writeonce-api/diesel.toml delete mode 100644 reference/writeonce-api/doc/index.html delete mode 100644 reference/writeonce-api/doc/openapi.json delete mode 100644 reference/writeonce-api/docker-compose.yml delete mode 100644 reference/writeonce-api/events/post.json delete mode 100644 reference/writeonce-api/infra/00-build-image.sh delete mode 100644 reference/writeonce-api/infra/02-docker-login.sh delete mode 100644 reference/writeonce-api/infra/03-upload-image.sh delete mode 100644 reference/writeonce-api/infra/04-ssh-setup.sh delete mode 100644 reference/writeonce-api/infra/05-deploy.sh delete mode 100644 reference/writeonce-api/infra/aurora-serverless/Pulumi.prod.yaml delete mode 100644 reference/writeonce-api/infra/aurora-serverless/Pulumi.yaml delete mode 100644 reference/writeonce-api/infra/aurora-serverless/README.md delete mode 100644 reference/writeonce-api/infra/aurora-serverless/go.mod delete mode 100644 reference/writeonce-api/infra/aurora-serverless/go.sum delete mode 100644 reference/writeonce-api/infra/aurora-serverless/main.go delete mode 100644 reference/writeonce-api/migrations/.keep delete mode 100644 reference/writeonce-api/migrations/00000000000000_diesel_initial_setup/down.sql delete mode 100644 reference/writeonce-api/migrations/00000000000000_diesel_initial_setup/up.sql delete mode 100644 reference/writeonce-api/migrations/2024-10-26-110007_create_articles/down.sql delete mode 100644 reference/writeonce-api/migrations/2024-10-26-110007_create_articles/up.sql delete mode 100644 reference/writeonce-api/migrations/2024-10-27-130201_add_articles_content/down.sql delete mode 100644 reference/writeonce-api/migrations/2024-10-27-130201_add_articles_content/up.sql delete mode 100644 reference/writeonce-api/migrations/2024-10-27-131600_add_articles_do_aws_sync/down.sql delete mode 100644 reference/writeonce-api/migrations/2024-10-27-131600_add_articles_do_aws_sync/up.sql delete mode 100644 reference/writeonce-api/migrations/2024-11-12-090859_add_sys_title_contrainst/down.sql delete mode 100644 reference/writeonce-api/migrations/2024-11-12-090859_add_sys_title_contrainst/up.sql delete mode 100644 reference/writeonce-api/migrations/2024-11-12-101350_add_sys_title_updated_contrainst/down.sql delete mode 100644 reference/writeonce-api/migrations/2024-11-12-101350_add_sys_title_updated_contrainst/up.sql delete mode 100644 reference/writeonce-api/migrations/2024-11-12-154958_add_time_stamp/down.sql delete mode 100644 reference/writeonce-api/migrations/2024-11-12-154958_add_time_stamp/up.sql delete mode 100644 reference/writeonce-api/postgress/init.sql delete mode 100644 reference/writeonce-api/src/api/article.rs delete mode 100644 reference/writeonce-api/src/api/image.rs delete mode 100644 reference/writeonce-api/src/api/mod.rs delete mode 100644 reference/writeonce-api/src/api/spec.rs delete mode 100644 reference/writeonce-api/src/api/valid.rs delete mode 100644 reference/writeonce-api/src/config/mod.rs delete mode 100644 reference/writeonce-api/src/db.rs delete mode 100644 reference/writeonce-api/src/error_handler.rs delete mode 100644 reference/writeonce-api/src/lambda/post_article.rs delete mode 100644 reference/writeonce-api/src/lib.rs delete mode 100644 reference/writeonce-api/src/main.rs delete mode 100644 reference/writeonce-api/src/models.rs delete mode 100644 reference/writeonce-api/src/schema.rs delete mode 100644 reference/writeonce-api/src/services/aws.rs delete mode 100644 reference/writeonce-api/src/services/aws_s3.rs delete mode 100644 reference/writeonce-api/src/services/error.rs delete mode 100644 reference/writeonce-api/src/services/mod.rs delete mode 100644 reference/writeonce-app/.dockerignore delete mode 100644 reference/writeonce-app/.editorconfig delete mode 100644 reference/writeonce-app/.gitignore delete mode 100644 reference/writeonce-app/.gitlab-ci.yml delete mode 100644 reference/writeonce-app/.nvmrc delete mode 100644 reference/writeonce-app/.vscode/extensions.json delete mode 100644 reference/writeonce-app/.vscode/launch.json delete mode 100644 reference/writeonce-app/.vscode/tasks.json delete mode 100644 reference/writeonce-app/Dockerfile delete mode 100644 reference/writeonce-app/Dockerfile-Dev delete mode 100644 reference/writeonce-app/README.md delete mode 100644 reference/writeonce-app/UI_ENHANCEMENT.md delete mode 100644 reference/writeonce-app/angular.json delete mode 100644 reference/writeonce-app/aspnetcore-https.js delete mode 100755 reference/writeonce-app/deploy.sh delete mode 100644 reference/writeonce-app/docker-compose.yml delete mode 100644 reference/writeonce-app/karma.conf.js delete mode 100644 reference/writeonce-app/nuget.config delete mode 100644 reference/writeonce-app/package.json delete mode 100644 reference/writeonce-app/src/app/.dockerignore delete mode 100644 reference/writeonce-app/src/app/about/about.component.css delete mode 100644 reference/writeonce-app/src/app/about/about.component.html delete mode 100644 reference/writeonce-app/src/app/about/about.component.spec.ts delete mode 100644 reference/writeonce-app/src/app/about/about.component.ts delete mode 100644 reference/writeonce-app/src/app/app-routing.module.ts delete mode 100644 reference/writeonce-app/src/app/app.component.css delete mode 100644 reference/writeonce-app/src/app/app.component.html delete mode 100644 reference/writeonce-app/src/app/app.component.spec.ts delete mode 100644 reference/writeonce-app/src/app/app.component.ts delete mode 100644 reference/writeonce-app/src/app/app.module.ts delete mode 100644 reference/writeonce-app/src/app/article/article-code-snippet/article-code-snippet.component.css delete mode 100644 reference/writeonce-app/src/app/article/article-code-snippet/article-code-snippet.component.html delete mode 100644 reference/writeonce-app/src/app/article/article-code-snippet/article-code-snippet.component.spec.ts delete mode 100644 reference/writeonce-app/src/app/article/article-code-snippet/article-code-snippet.component.ts delete mode 100644 reference/writeonce-app/src/app/article/article-img-caption/article-img-caption.component.css delete mode 100644 reference/writeonce-app/src/app/article/article-img-caption/article-img-caption.component.html delete mode 100644 reference/writeonce-app/src/app/article/article-img-caption/article-img-caption.component.spec.ts delete mode 100644 reference/writeonce-app/src/app/article/article-img-caption/article-img-caption.component.ts delete mode 100644 reference/writeonce-app/src/app/article/article-references/article-reference.model.ts delete mode 100644 reference/writeonce-app/src/app/article/article-references/article-references.component.css delete mode 100644 reference/writeonce-app/src/app/article/article-references/article-references.component.html delete mode 100644 reference/writeonce-app/src/app/article/article-references/article-references.component.spec.ts delete mode 100644 reference/writeonce-app/src/app/article/article-references/article-references.component.ts delete mode 100644 reference/writeonce-app/src/app/article/article-section/article-section.component.css delete mode 100644 reference/writeonce-app/src/app/article/article-section/article-section.component.html delete mode 100644 reference/writeonce-app/src/app/article/article-section/article-section.component.spec.ts delete mode 100644 reference/writeonce-app/src/app/article/article-section/article-section.component.ts delete mode 100644 reference/writeonce-app/src/app/article/article-signature/article-signature.component.css delete mode 100644 reference/writeonce-app/src/app/article/article-signature/article-signature.component.html delete mode 100644 reference/writeonce-app/src/app/article/article-signature/article-signature.component.spec.ts delete mode 100644 reference/writeonce-app/src/app/article/article-signature/article-signature.component.ts delete mode 100644 reference/writeonce-app/src/app/article/article.component.css delete mode 100644 reference/writeonce-app/src/app/article/article.component.html delete mode 100644 reference/writeonce-app/src/app/article/article.component.spec.ts delete mode 100644 reference/writeonce-app/src/app/article/article.component.ts delete mode 100644 reference/writeonce-app/src/app/article/screen-shot-image/screen-shot-image.component.css delete mode 100644 reference/writeonce-app/src/app/article/screen-shot-image/screen-shot-image.component.html delete mode 100644 reference/writeonce-app/src/app/article/screen-shot-image/screen-shot-image.component.spec.ts delete mode 100644 reference/writeonce-app/src/app/article/screen-shot-image/screen-shot-image.component.ts delete mode 100644 reference/writeonce-app/src/app/article/summary-card/summary-card.component.css delete mode 100644 reference/writeonce-app/src/app/article/summary-card/summary-card.component.html delete mode 100644 reference/writeonce-app/src/app/article/summary-card/summary-card.component.spec.ts delete mode 100644 reference/writeonce-app/src/app/article/summary-card/summary-card.component.ts delete mode 100644 reference/writeonce-app/src/app/contact/contact.component.css delete mode 100644 reference/writeonce-app/src/app/contact/contact.component.html delete mode 100644 reference/writeonce-app/src/app/contact/contact.component.spec.ts delete mode 100644 reference/writeonce-app/src/app/contact/contact.component.ts delete mode 100644 reference/writeonce-app/src/app/footer/footer.component.css delete mode 100644 reference/writeonce-app/src/app/footer/footer.component.html delete mode 100644 reference/writeonce-app/src/app/footer/footer.component.spec.ts delete mode 100644 reference/writeonce-app/src/app/footer/footer.component.ts delete mode 100644 reference/writeonce-app/src/app/gitlab.service.spec.ts delete mode 100644 reference/writeonce-app/src/app/gitlab.service.ts delete mode 100644 reference/writeonce-app/src/app/header/header.component.css delete mode 100644 reference/writeonce-app/src/app/header/header.component.html delete mode 100644 reference/writeonce-app/src/app/header/header.component.spec.ts delete mode 100644 reference/writeonce-app/src/app/header/header.component.ts delete mode 100644 reference/writeonce-app/src/app/home/home.component.css delete mode 100644 reference/writeonce-app/src/app/home/home.component.html delete mode 100644 reference/writeonce-app/src/app/home/home.component.spec.ts delete mode 100644 reference/writeonce-app/src/app/home/home.component.ts delete mode 100644 reference/writeonce-app/src/app/models/article.ts delete mode 100644 reference/writeonce-app/src/app/navigation-bar/navigation-bar.component.css delete mode 100644 reference/writeonce-app/src/app/navigation-bar/navigation-bar.component.html delete mode 100644 reference/writeonce-app/src/app/navigation-bar/navigation-bar.component.spec.ts delete mode 100644 reference/writeonce-app/src/app/navigation-bar/navigation-bar.component.ts delete mode 100644 reference/writeonce-app/src/app/repository-list/repository-list.component.css delete mode 100644 reference/writeonce-app/src/app/repository-list/repository-list.component.html delete mode 100644 reference/writeonce-app/src/app/repository-list/repository-list.component.spec.ts delete mode 100644 reference/writeonce-app/src/app/repository-list/repository-list.component.ts delete mode 100644 reference/writeonce-app/src/app/services/article.service.spec.ts delete mode 100644 reference/writeonce-app/src/app/services/article.service.ts delete mode 100644 reference/writeonce-app/src/app/theme.service.ts delete mode 100644 reference/writeonce-app/src/app/types.ts delete mode 100644 reference/writeonce-app/src/app/websocket.service.spec.ts delete mode 100644 reference/writeonce-app/src/app/websocket.service.ts delete mode 100644 reference/writeonce-app/src/app/writeoncedb.service.spec.ts delete mode 100644 reference/writeonce-app/src/app/writeoncedb.service.ts delete mode 100644 reference/writeonce-app/src/assets/.gitkeep delete mode 100644 reference/writeonce-app/src/assets/images/auto-scale-gitlab-runner.webp delete mode 100644 reference/writeonce-app/src/assets/images/logo.png delete mode 100644 reference/writeonce-app/src/assets/images/profile.jpg delete mode 100644 reference/writeonce-app/src/assets/writeonce-articles/gitlab-runner-with-kubernetes-executor.json delete mode 100644 reference/writeonce-app/src/assets/writeonce-articles/page.json delete mode 100644 reference/writeonce-app/src/assets/writeonce-articles/testreadme.md delete mode 100644 reference/writeonce-app/src/environments/environment.prod.ts delete mode 100644 reference/writeonce-app/src/environments/environment.ts delete mode 100644 reference/writeonce-app/src/favicon.ico delete mode 100644 reference/writeonce-app/src/index.html delete mode 100644 reference/writeonce-app/src/main.ts delete mode 100644 reference/writeonce-app/src/proxy.conf.js delete mode 100644 reference/writeonce-app/src/styles.css delete mode 100644 reference/writeonce-app/src/web.config delete mode 100644 reference/writeonce-app/tailwind.config.js delete mode 100644 reference/writeonce-app/tsconfig.app.json delete mode 100644 reference/writeonce-app/tsconfig.json delete mode 100644 reference/writeonce-app/tsconfig.spec.json delete mode 100644 reference/writeonce-app/writeonce-frontend.esproj diff --git a/.gitignore b/.gitignore index 3574d19..1afd125 100644 --- a/.gitignore +++ b/.gitignore @@ -1,15 +1,21 @@ # Cargo build artifacts /target -/reference/crates/target +/.dev/reference/crates/target # C++ prototype build output /prototypes/*/build -# C runtime prototype (prototypes/wo-rt-c): compiled binary + WAL/snapshot data. +# OCaml/dune build artifacts (compiler/, the woc compiler front) +/compiler/_build + +# C runtime (runtime/): compiled binary + WAL/snapshot data. # `just rt-c-demo` and the default WO_DATA write ./wo-data at the repo root. -/prototypes/wo-rt-c/wo-rt +/runtime/wo-rt /wo-data -/prototypes/wo-rt-c/wo-data +/runtime/wo-data +# wovm VM core: binary + test build dir (ASan test binaries, smoke fixtures) +/runtime/wovm +/runtime/build/ # Runtime data directories for the sample projects. # `wo.toml` points at `./data` which holds the per-project engine state. @@ -19,23 +25,33 @@ # Legacy blog content and data (v1 writeonce storage) /content -# Symlinks to research source trees — user-specific absolute paths. -# Each contributor sets their own via: -# ln -s reference/linux -# ln -s reference/go -# ln -s reference/postgresql -# ln -s reference/mcp-python-sdk -# (clone: https://github.com/modelcontextprotocol/python-sdk.git) -/reference/linux -/reference/go -/reference/postgresql -/reference/mcp-python-sdk - # Developer-local links into agent-tooling state (Claude Code project data, # opencode config/data, llama.cpp cache) — user-specific absolute paths. -# Only .dev/README.md is committed; recreate the links per machine as it describes. +# Committed inside .dev: README.md and reference/ (v1 archive + rest smoke files); +# recreate the per-machine links as .dev/README.md describes. /.dev/* !/.dev/README.md +!/.dev/reference/ + +# Symlinks to research source trees inside .dev/reference — user-specific +# absolute paths. Each contributor sets their own via: +# ln -s .dev/reference/linux +# ln -s .dev/reference/go +# ln -s .dev/reference/postgresql +# ln -s .dev/reference/mcp-python-sdk +# (clone: https://github.com/modelcontextprotocol/python-sdk.git) +# ln -s .dev/reference/colibri +# ln -s .dev/reference/llama-cpp +/.dev/reference/linux +/.dev/reference/go +/.dev/reference/postgresql +/.dev/reference/mcp-python-sdk +/.dev/reference/colibri +/.dev/reference/llama-cpp +/.dev/reference/* + +# Agent-orchestration scratch (SDD ledgers, briefs, review packages) +/.superpowers/ # Editor / OS noise — left broad on purpose so a contributor doesn't # accidentally commit their IDE scratch or macOS metadata. @@ -47,7 +63,6 @@ !/.vscode/settings.json.example !/.vscode/extensions.json -/prototypes/wo-db # Phase-F bench binaries (sources are committed; builds are not) -/prototypes/wo-rt-c/bench/bench -/prototypes/wo-rt-c/bench/goref/goref +/runtime/bench/bench +/runtime/bench/goref/goref diff --git a/crates/app/Cargo.toml b/crates/app/Cargo.toml deleted file mode 100644 index b389257..0000000 --- a/crates/app/Cargo.toml +++ /dev/null @@ -1,9 +0,0 @@ -[package] -name = "app" -version = "0.1.0" -edition = "2021" -description = "writeonce app manifest — `##app` routes, startup hooks, theme, i18n" - -[lib] -name = "app" -path = "src/lib.rs" diff --git a/crates/app/src/lib.rs b/crates/app/src/lib.rs deleted file mode 100644 index ddf0408..0000000 --- a/crates/app/src/lib.rs +++ /dev/null @@ -1,20 +0,0 @@ -//! `app` — `##app` manifest: routes, theme, i18n, startup hooks. -//! -//! **Status: placeholder.** Phase 6 (see -//! [06-lowcode-fullstack.md § `##app`]( -//! ../../../docs/runtime/database/06-lowcode-fullstack.md)). -//! -//! One `##app` block per project. Declares: -//! * the static route table (URL path → `ui.` binding, possibly -//! parameterised by dynamic segments like `/article/:slug`) -//! * cross-entity policies ("Admin bypasses row-level filters on every type") -//! * `on startup do …` hooks — idempotent seed code that runs once before -//! [`http`](../http/index.html) binds a listening socket -//! * theme tokens, i18n locale set, project metadata -//! -//! Consumed by [`ui`](../ui/index.html) for route rendering and by -//! [`policy`](../policy/index.html) for the cross-entity rules block. -//! -//! The [`docs/examples/blog/app.wo`](../../../docs/examples/blog/app.wo) and -//! [`docs/examples/ecommerce/app.wo`](../../../docs/examples/ecommerce/app.wo) -//! files are the reference shapes. diff --git a/crates/db/Cargo.toml b/crates/db/Cargo.toml deleted file mode 100644 index 4ba468e..0000000 --- a/crates/db/Cargo.toml +++ /dev/null @@ -1,9 +0,0 @@ -[package] -name = "db" -version = "0.1.0" -edition = "2021" -description = "writeonce top-level database facade — `open()`, `Tx`, `Query`, `Subscribe` over the engine" - -[lib] -name = "db" -path = "src/lib.rs" diff --git a/crates/db/src/lib.rs b/crates/db/src/lib.rs deleted file mode 100644 index 84f9362..0000000 --- a/crates/db/src/lib.rs +++ /dev/null @@ -1,27 +0,0 @@ -//! `wo-db` — the Rust-facing database SDK. -//! -//! **Status: placeholder.** Phases 2–4 integrator. -//! -//! The top-level crate that the rest of the workspace (`wo-rt`, `wo-http`, -//! future `wo-serve` integration glue) depends on. Composes: -//! -//! | | | -//! | --- | --- | -//! | [`ql`](../ql/index.html) | parse | -//! | [`value`](../value/index.html) | row representation | -//! | [`engine`](../engine/index.html) | storage + execution | -//! | [`txn`](../txn/index.html) | transaction coordinator | -//! | [`wal`](../wal/index.html) | durability (Phase 3) | -//! | [`sub`](../sub/index.html) | live subscriptions (Phase 4) | -//! -//! Public surface the rest of the workspace will call: -//! -//! ```text -//! let db = db::open(&catalog, &options)?; -//! db.wo(ctx, ".wo source", params).await?; // raw DML -//! db.tx(ctx, |tx| async move { ... }).await?; -//! let sub = db.subscribe(ctx, "LIVE SELECT ...", params).await?; -//! ``` -//! -//! See [05-go-sdk.md](../../../docs/runtime/database/05-go-sdk.md) — the Go -//! SDK's shape mirrors this one; both speak the same `.wo` wire protocol. diff --git a/crates/engine/Cargo.toml b/crates/engine/Cargo.toml deleted file mode 100644 index 5a344a6..0000000 --- a/crates/engine/Cargo.toml +++ /dev/null @@ -1,9 +0,0 @@ -[package] -name = "engine" -version = "0.1.0" -edition = "2021" -description = "writeonce in-memory executor — relational, document, and graph storage over a shared catalog" - -[lib] -name = "engine" -path = "src/lib.rs" diff --git a/crates/engine/src/lib.rs b/crates/engine/src/lib.rs deleted file mode 100644 index 7e1f65d..0000000 --- a/crates/engine/src/lib.rs +++ /dev/null @@ -1,19 +0,0 @@ -//! `wo-engine` — the in-memory executor. -//! -//! **Status: placeholder.** Phase 2 (see -//! [02-wo-language.md](../../../docs/runtime/database/02-wo-language.md)) -//! designs the three storage paradigms; Phase 3 -//! ([03-inmemory-engine.md](../../../docs/runtime/database/03-inmemory-engine.md)) -//! details the RAM-primary layout, arenas, MVCC version chains. -//! -//! The engine owns: -//! * relational tables — per-type `BTreeMap` today, B+ tree when -//! sized appropriately -//! * document collections — shape-free `Value::Object` rows; LSM when Phase 3 -//! activates -//! * graph nodes + edges — adjacency over the type catalog -//! * a schema catalog compiled from `wo-ql` output -//! -//! Stage 2 ships this as a module inside [`rt`](../rt/index.html). -//! It extracts here when `wo-wal` and `wo-txn` need to reach into the same -//! structures. diff --git a/crates/gen/Cargo.toml b/crates/gen/Cargo.toml deleted file mode 100644 index f4a9c89..0000000 --- a/crates/gen/Cargo.toml +++ /dev/null @@ -1,9 +0,0 @@ -[package] -name = "gen" -version = "0.1.0" -edition = "2021" -description = "writeonce codegen — `.wo` schema → typed Go / TypeScript / Rust / Python clients" - -[lib] -name = "gen" -path = "src/lib.rs" diff --git a/crates/gen/src/lib.rs b/crates/gen/src/lib.rs deleted file mode 100644 index eb43530..0000000 --- a/crates/gen/src/lib.rs +++ /dev/null @@ -1,21 +0,0 @@ -//! `wo-gen` — client-SDK codegen. -//! -//! **Status: placeholder.** Phase 5 (see -//! [05-go-sdk.md § Typed SDK via `.wo` Schema Codegen]( -//! ../../../docs/runtime/database/05-go-sdk.md)). -//! -//! Reads a compiled catalog (the same output [`ql`](../ql/index.html) + -//! [`engine`](../engine/index.html) produce from a `.wo` source tree) -//! and emits typed client code: -//! -//! * **Go** — structs with `wo:"column"` tags, `*Client`, -//! `TypedSubscription[T]` generics over channels -//! * **TypeScript** — types + `fetch` + WebSocket subscriptions -//! * **Rust** — `#[derive(Deserialize)]` structs + `impl Stream` -//! * **Python** — dataclasses + `async for delta in sub` -//! * **OpenAPI / GraphQL SDL** — machine-generated contracts served by -//! [`http`](../http/index.html) -//! -//! Invoked as `wo gen sdk --lang go --out ./client` via the toolchain; a bin -//! target will be added when the crate has real behaviour. Until then this is -//! an empty library scaffold. diff --git a/crates/http/Cargo.toml b/crates/http/Cargo.toml deleted file mode 100644 index 5f4d53b..0000000 --- a/crates/http/Cargo.toml +++ /dev/null @@ -1,9 +0,0 @@ -[package] -name = "http" -version = "0.1.0" -edition = "2021" -description = "writeonce wire protocol — native binary + GraphQL over WebSocket + REST dispatch" - -[lib] -name = "http" -path = "src/lib.rs" diff --git a/crates/http/src/lib.rs b/crates/http/src/lib.rs deleted file mode 100644 index 77178f3..0000000 --- a/crates/http/src/lib.rs +++ /dev/null @@ -1,20 +0,0 @@ -//! `wo-http` — HTTP + WebSocket + native wire protocol for the `.wo` runtime. -//! -//! **Status: placeholder.** Phase 4 (see -//! [04-client-api.md](../../../docs/runtime/database/04-client-api.md)). -//! -//! Three protocol surfaces, one dispatch: -//! * **REST** — JSON in/out, one route per `service rest` operation. -//! * **GraphQL over WebSocket** — SDL auto-generated from the schema; query, -//! mutation, `subscription` all routed through the same planner. -//! * **Native binary** — typed wire codec for `wo-db` and the Go SDK. -//! -//! Authentication middleware (`WithAPIKey`, `WithJWT`, `WithMTLS`) lives in -//! this crate — cross-cutting over every protocol surface. -//! -//! Stage 2 ships a minimal axum-based REST server inside -//! [`rt::server`](../rt/server/index.html). It migrates here when -//! Phase 4 activates and GraphQL / native surfaces join REST. -//! -//! Not the v1 `reference/crates/wo-http/` — that was a from-scratch HTTP -//! parser for the old single-threaded event loop. diff --git a/crates/logic/Cargo.toml b/crates/logic/Cargo.toml deleted file mode 100644 index cb4d7d0..0000000 --- a/crates/logic/Cargo.toml +++ /dev/null @@ -1,9 +0,0 @@ -[package] -name = "logic" -version = "0.1.0" -edition = "2021" -description = "writeonce trigger + stored-fn compiler — type-attached `on ` and cross-entity `##logic`" - -[lib] -name = "logic" -path = "src/lib.rs" diff --git a/crates/logic/src/lib.rs b/crates/logic/src/lib.rs deleted file mode 100644 index be5a7c6..0000000 --- a/crates/logic/src/lib.rs +++ /dev/null @@ -1,19 +0,0 @@ -//! `wo-logic` — triggers and stored-function interpreter. -//! -//! **Status: placeholder.** Phase 6 (see -//! [06-lowcode-fullstack.md § `##logic`]( -//! ../../../docs/runtime/database/06-lowcode-fullstack.md)). -//! -//! Two sources of runtime code: -//! * **Type-attached triggers** — `on insert | update | delete [when ] -//! do ` declared inside a `type` body. Fires inside the committing -//! transaction so timestamp stamps (`paid_at`, `shipped_at`) are atomic -//! with the state change, never observable half-way. -//! * **Standalone `##logic`** — cross-entity workflows like the ecommerce -//! on-order-placed hook that decrements inventory on every line item. -//! * **`fn name(args) in txn ... { ... }`** — transactional stored -//! functions (the `checkout` in `docs/examples/ecommerce/logic/`). -//! -//! All three compile to the same intermediate form that [`txn`](../txn/index.html) -//! invokes during commit (for triggers) or directly on the main loop (for -//! `fn` calls from REST/native wire). diff --git a/crates/policy/Cargo.toml b/crates/policy/Cargo.toml deleted file mode 100644 index b8f8fc6..0000000 --- a/crates/policy/Cargo.toml +++ /dev/null @@ -1,9 +0,0 @@ -[package] -name = "policy" -version = "0.1.0" -edition = "2021" -description = "writeonce policy engine — RBAC + row-level rules compiled into planner rewrites" - -[lib] -name = "policy" -path = "src/lib.rs" diff --git a/crates/policy/src/lib.rs b/crates/policy/src/lib.rs deleted file mode 100644 index 7bb3f08..0000000 --- a/crates/policy/src/lib.rs +++ /dev/null @@ -1,25 +0,0 @@ -//! `wo-policy` — RBAC + row-level policies. -//! -//! **Status: placeholder.** Phase 6 (see -//! [06-lowcode-fullstack.md](../../../docs/runtime/database/06-lowcode-fullstack.md)). -//! -//! Compiles type-attached `policy` blocks (and standalone `##policy` blocks -//! for cross-entity rules) into predicates that [`engine`](../engine/index.html) -//! AND-s into every query at plan time. The rule is enforced once, at the -//! planner, so no caller can bypass it — not the REST handler, not the -//! `wo-db` SDK, not an admin script calling `fn` directly. -//! -//! Shape of a compiled policy: -//! -//! ```text -//! for type Article: -//! read: published == true OR $session.role == Admin -//! OR author == $session.user -//! write: $session.role == Admin OR ($session.role == Author && author == $session.user) -//! delete: $session.role == Admin -//! ``` -//! -//! The planner merges the read predicate as a `WHERE` conjunct, the write -//! predicate as an assertion inside UPDATE/INSERT codegen, and delete as a -//! DELETE guard. Same mechanism as Postgres RLS — just authored declaratively -//! in the `type` block instead of via SQL migrations. diff --git a/crates/ql/Cargo.toml b/crates/ql/Cargo.toml deleted file mode 100644 index 96d7bcc..0000000 --- a/crates/ql/Cargo.toml +++ /dev/null @@ -1,9 +0,0 @@ -[package] -name = "ql" -version = "0.1.0" -edition = "2021" -description = "writeonce query language — lexer, parser, AST for the `.wo` grammar" - -[lib] -name = "ql" -path = "src/lib.rs" diff --git a/crates/ql/src/lib.rs b/crates/ql/src/lib.rs deleted file mode 100644 index f387c3d..0000000 --- a/crates/ql/src/lib.rs +++ /dev/null @@ -1,13 +0,0 @@ -//! `wo-ql` — the `.wo` grammar: lexer, parser, AST. -//! -//! **Status: placeholder.** Phase 2 of the [`.wo` runtime design]( -//! ../../../docs/runtime/database/02-wo-language.md). -//! -//! Stage 2 of the runtime (shipped) carries the lexer/parser/AST inside -//! [`rt`](../rt/index.html) as part of its monolithic cut. This crate -//! is where those modules move to when Stage 3+ needs the grammar from -//! multiple places — the HTTP raw-query handler, `wo-gen`, the server-side -//! `fn` interpreter. -//! -//! Reference implementation: the C++ prototype at -//! [`prototypes/wo-db/`](../../../prototypes/wo-db/). diff --git a/crates/service/Cargo.toml b/crates/service/Cargo.toml deleted file mode 100644 index e1741da..0000000 --- a/crates/service/Cargo.toml +++ /dev/null @@ -1,9 +0,0 @@ -[package] -name = "service" -version = "0.1.0" -edition = "2021" -description = "writeonce service dispatcher — type-attached `service rest/graphql/native` and standalone `##service` bundles" - -[lib] -name = "service" -path = "src/lib.rs" diff --git a/crates/service/src/lib.rs b/crates/service/src/lib.rs deleted file mode 100644 index b7a0c1f..0000000 --- a/crates/service/src/lib.rs +++ /dev/null @@ -1,19 +0,0 @@ -//! `service` — endpoint registration and dispatch. -//! -//! **Status: placeholder.** Phase 6 (see -//! [06-lowcode-fullstack.md § `##service`]( -//! ../../../docs/runtime/database/06-lowcode-fullstack.md)). -//! -//! Walks every `service rest "/api/..." expose ...` block declared on a type -//! (and every standalone `##service` bundle for multi-entity APIs) and binds -//! handlers on [`http`](../http/index.html)'s router. Each exposed operation -//! (`list` / `get` / `create` / `update` / `delete` / `subscribe` / `me`) -//! maps to a predetermined shape — declarative CRUD is the whole point. -//! -//! `fn` endpoints (`POST /api/fn/checkout`) are also dispatched from here — -//! the service declares the function-as-endpoint wiring, [`logic`](../logic/index.html) -//! executes. -//! -//! Stage 2 does this work inline inside [`rt::server`](../rt/server/index.html). -//! It extracts here when Phase 6 adds GraphQL + native-protocol surfaces that -//! share the same service-block source of truth. diff --git a/crates/sub/Cargo.toml b/crates/sub/Cargo.toml deleted file mode 100644 index daacd43..0000000 --- a/crates/sub/Cargo.toml +++ /dev/null @@ -1,9 +0,0 @@ -[package] -name = "sub" -version = "0.1.0" -edition = "2021" -description = "writeonce live subscriptions — LIVE query registry + delta frames on commit" - -[lib] -name = "sub" -path = "src/lib.rs" diff --git a/crates/sub/src/lib.rs b/crates/sub/src/lib.rs deleted file mode 100644 index 07f4136..0000000 --- a/crates/sub/src/lib.rs +++ /dev/null @@ -1,21 +0,0 @@ -//! `wo-sub` — the live-subscription engine. -//! -//! **Status: placeholder.** Phase 4 (see -//! [04-client-api.md](../../../docs/runtime/database/04-client-api.md)). -//! -//! Responsibilities: -//! * per-connection subscription registry — predicate + bound parameters -//! * commit-path dispatch: each [`txn`](../txn/index.html) commit -//! walks the registry, matches predicates against the delta set, and -//! pushes `Insert` / `Update` / `Delete` frames into per-subscription -//! ring buffers -//! * back-pressure policy (drop-and-resync / coalesce / disconnect) — -//! mirrors the client-side policy in [`wo-db`](../db/index.html) -//! / the Go SDK -//! -//! Stage 2 stubs the `/api//live` endpoint to 501 in -//! [`rt::server`](../rt/server/index.html). Stage 3 wires this crate -//! behind that route and upgrades the connection to WebSocket. -//! -//! Not related to the v1 `reference/crates/wo-sub/` crate — that one did -//! polling-based diff delivery over SSE for the old flat-file blog. diff --git a/crates/txn/Cargo.toml b/crates/txn/Cargo.toml deleted file mode 100644 index 86b3e70..0000000 --- a/crates/txn/Cargo.toml +++ /dev/null @@ -1,9 +0,0 @@ -[package] -name = "txn" -version = "0.1.0" -edition = "2021" -description = "writeonce transaction coordinator — MVCC, snapshot isolation, `RETURNING` alias table" - -[lib] -name = "txn" -path = "src/lib.rs" diff --git a/crates/txn/src/lib.rs b/crates/txn/src/lib.rs deleted file mode 100644 index 9058a57..0000000 --- a/crates/txn/src/lib.rs +++ /dev/null @@ -1,19 +0,0 @@ -//! `wo-txn` — the cross-paradigm transaction coordinator. -//! -//! **Status: placeholder.** Phase 2 milestone 3 (see -//! [02-wo-language.md § Cross-Paradigm Transaction Coordinator]( -//! ../../../docs/runtime/database/02-wo-language.md)). -//! -//! Responsibilities: -//! * `txn_id` allocation + snapshot timestamps -//! * ordering commits across `wo-engine`'s three storage paradigms -//! * the transaction-scoped **`RETURNING` alias table** — the state that -//! lets `INSERT … RETURNING id AS oid` thread `$oid` into a later -//! `CREATE (u)-[:PURCHASED {order_id: $oid}]->(p)` in the same -//! `BEGIN … COMMIT` block -//! * driving the in-process 2PC between the three engines on commit -//! -//! Under the single-threaded event-loop design (see -//! [§ Concurrency Model](../../../docs/runtime/database/02-wo-language.md#concurrency-model)) -//! this reduces to a sequential counter + a per-txn name table. When the -//! runtime grows past one core via sharding, this crate owns cross-shard 2PC. diff --git a/crates/ui/Cargo.toml b/crates/ui/Cargo.toml deleted file mode 100644 index 7581634..0000000 --- a/crates/ui/Cargo.toml +++ /dev/null @@ -1,9 +0,0 @@ -[package] -name = "ui" -version = "0.1.0" -edition = "2021" -description = "writeonce UI renderer — `##ui` screens compile to SSR HTML + thin client runtime" - -[lib] -name = "ui" -path = "src/lib.rs" diff --git a/crates/ui/src/lib.rs b/crates/ui/src/lib.rs deleted file mode 100644 index fc589ba..0000000 --- a/crates/ui/src/lib.rs +++ /dev/null @@ -1,24 +0,0 @@ -//! `ui` — `##ui` screens → SSR HTML + client-side delta runtime. -//! -//! **Status: placeholder.** Phase 6 (see -//! [06-lowcode-fullstack.md § `##ui`]( -//! ../../../docs/runtime/database/06-lowcode-fullstack.md)). -//! -//! Declarative screens compile to: -//! * a **server-rendered HTML** tree — a compact template + data-binding -//! form computed from the screen's `source:` projection and `columns:` / -//! `sections:` declarations -//! * a **client-side runtime** (~50 KB vanilla JS) — opens a WebSocket to -//! the engine's [`sub`](../sub/index.html) endpoint, binds incoming delta -//! frames to DOM fragments by row key, handles sort / filter / paginate -//! without refetching -//! * an **auto-admin fallback** — any declared `type` without an explicit -//! `##ui` gets a generic CRUD screen -//! -//! `live: true` on a screen auto-generates a matching `LIVE SELECT` that the -//! client runtime subscribes to — no hand-written subscription code on the UI -//! side. -//! -//! The [`docs/examples/blog/ui/`](../../../docs/examples/blog/ui/) and -//! [`docs/examples/ecommerce/ui/`](../../../docs/examples/ecommerce/ui/) -//! directories are the reference shapes this crate has to handle. diff --git a/crates/value/Cargo.toml b/crates/value/Cargo.toml deleted file mode 100644 index b8fbb5f..0000000 --- a/crates/value/Cargo.toml +++ /dev/null @@ -1,9 +0,0 @@ -[package] -name = "value" -version = "0.1.0" -edition = "2021" -description = "writeonce tagged `Value` + dotted-path utilities" - -[lib] -name = "value" -path = "src/lib.rs" diff --git a/crates/value/src/lib.rs b/crates/value/src/lib.rs deleted file mode 100644 index a2ec3f4..0000000 --- a/crates/value/src/lib.rs +++ /dev/null @@ -1,14 +0,0 @@ -//! `wo-value` — the runtime's tagged `Value` type plus dotted-path helpers. -//! -//! **Status: placeholder.** Phase 2 foundation. -//! -//! Carries the union of all scalar + document + array shapes the engines pass -//! around: `Null`, `Bool`, `Int`, `Str`, `Array`, `Object`. Plus `fetch_path` -//! / `assign_path` utilities that drive the dotted-access semantics shared by -//! SQL expressions, document `UPDATE SET meta.x.y = z`, and Cypher projections -//! (see [the two-layer language spec]( -//! ../../../docs/runtime/database/02-wo-language.md)). -//! -//! Stage 2 keeps these inside [`rt`](../rt/index.html)'s `engine` module; -//! they extract here once a second consumer (the WAL serializer, the wire codec) -//! needs them. diff --git a/crates/wal/Cargo.toml b/crates/wal/Cargo.toml deleted file mode 100644 index bdf8ad6..0000000 --- a/crates/wal/Cargo.toml +++ /dev/null @@ -1,9 +0,0 @@ -[package] -name = "wal" -version = "0.1.0" -edition = "2021" -description = "writeonce write-ahead log — io_uring + fsync + crash recovery" - -[lib] -name = "wal" -path = "src/lib.rs" diff --git a/crates/wal/src/lib.rs b/crates/wal/src/lib.rs deleted file mode 100644 index baf8184..0000000 --- a/crates/wal/src/lib.rs +++ /dev/null @@ -1,17 +0,0 @@ -//! `wo-wal` — the write-ahead log. -//! -//! **Status: placeholder.** Phase 3 (see -//! [03-inmemory-engine.md](../../../docs/runtime/database/03-inmemory-engine.md)). -//! -//! Responsibilities: -//! * append committed txn records to an on-disk ring buffer via `io_uring` -//! * issue `IORING_OP_FSYNC` with a linked SQE for durability -//! * batch concurrent commits into one fsync per tick (group commit) -//! * drive crash recovery by replaying the log from the last checkpoint -//! -//! Under the single-threaded event-loop design, the WAL is owned directly by -//! the main loop — no separate WAL-writer thread. `io_uring`'s kernel-owned -//! SQPOLL handler does the draining; userland just submits and parks on CQEs. -//! -//! This is the crate that turns the RAM-primary engine from "cache" into -//! "database": commits aren't acked until their WAL record is on the SSD. diff --git a/prototypes/wo-rt-c/Makefile b/prototypes/wo-rt-c/Makefile deleted file mode 100644 index 7c23d4e..0000000 --- a/prototypes/wo-rt-c/Makefile +++ /dev/null @@ -1,19 +0,0 @@ -CC ?= cc -CFLAGS ?= -O2 -Wall -Wextra -std=c11 -LDFLAGS ?= -pthread - -wo-rt: wo-rt.c - $(CC) $(CFLAGS) -o $@ $< $(LDFLAGS) - -bench/bench: bench/bench.c - $(CC) $(CFLAGS) -o $@ $< $(LDFLAGS) - -bench: bench/bench - -run: wo-rt - ./wo-rt - -clean: - rm -f wo-rt bench/bench - -.PHONY: bench run clean diff --git a/prototypes/wo-rt-c/README.md b/prototypes/wo-rt-c/README.md deleted file mode 100644 index 3cfcd71..0000000 --- a/prototypes/wo-rt-c/README.md +++ /dev/null @@ -1,78 +0,0 @@ -# `wo-rt-c` — the writeonce runtime environment, in C - -A single-file C implementation of the **runtime layer** the writeonce language runs on — now at **phase E: a durable RAM database**. Writes follow the dual-write order — RAM apply, framed WAL record (`len|crc32|payload|COMMIT`) to a per-shard `fallocate`'d log, one group-commit `fdatasync` per loop tick, **HTTP ack only after the fsync completion**. Boot performs the **first load, hard drive → RAM**: each shard replays its snapshot + WAL tail into its arena slice in parallel before any accept arms; clean shutdown snapshots each slice and truncates the WAL; a `meta` file pins the shard count so a mismatched `WO_THREADS` refuses to boot. `./wo-rt wal-check ` validates a log offline. `WO_THREADS` pinned threads (default = online cores), each owning its own raw io_uring ring (`io_uring_setup` + mmap'd SQ/CQ rings + `io_uring_enter` — **no liburing**), its own `SO_REUSEPORT` listener (multishot accept), its own keep-alive connections, and its own slice of the one mlock'd mmap arena — shared-nothing, no locks. Steady state is one `io_uring_enter` syscall per loop tick. Thread 0 owns the `signalfd`; shutdown broadcasts through per-thread `eventfd`s, both watched via `POLL_ADD` SQEs. **Zero dependencies beyond libc + kernel uapi headers.** The kernel is the runtime. - -This is the runtime-layer sibling of [`prototypes/wo-db/`](../wo-db/) (the C++ query-layer prototype): a reference card showing, with no abstraction in the way, exactly which kernel primitives the production Rust runtime (`crates/rt/`) drives through `libc`. Same role, different layer. - -``` -prototypes/wo-db/ C++ what the LANGUAGE executes (parser, engine, transactions) -prototypes/wo-rt-c/ C what the RUNTIME stands on (epoll, signalfd, sockets) -crates/rt/ Rust the product — both layers, libc only -``` - -## Build, run, poke - -```bash -make # cc -O2 -Wall -Wextra -std=c11 -pthread — no libraries -./wo-rt # 127.0.0.1:8085 (WO_PORT=9000 WO_THREADS=4 ./wo-rt to override) - -curl localhost:8085/ # {"runtime":"wo-rt-c","loop":"epoll-et","threads":4, - # "shard":2,"shard_requests":[68,36,44,53]} -curl -X POST localhost:8085/api/notes -d '{"title":"hello"}' - # {"id":2,"title":"hello","shard":1} ← ids interleave per shard -curl localhost:8085/api/notes # the connection's shard only — shared-nothing -# ctrl-C → signalfd on shard 0 → eventfd broadcast → all shards join -``` - -Each connection hashes to one shard for life (`SO_REUSEPORT` 4-tuple): a list may land on a different shard than the create that preceded it. That is the architecture, not a bug — cross-shard reads are a later phase / design decision (see the [architecture doc's improvements](../../docs/plan/exploration/c-runtime/01-architecture.md)). - -Or from the repo root: `just rt-c-demo`. - -## Module map - -Every block in `wo-rt.c` corresponds one-to-one to a module of the Rust runtime, which in turn mirrors Go's netpoller — the same lineage the docs trace: - -| `wo-rt.c` block | Rust (`crates/rt/src/`) | Go (`reference/go/src/runtime/`) | Kernel reference card | -| --- | --- | --- | --- | -| `main` event loop (`epoll_create1` / `epoll_wait`, `EPOLLET`) | `runtime/netpoll_epoll.rs` | `netpoll_epoll.go` | [`linux/01-epoll.md`](../../docs/plan/exploration/linux/01-epoll.md) | -| `sig_setup` (`sigprocmask` + `signalfd`) | `runtime/signalfd.rs` | signal mask handling | [`linux/04-signalfd.md`](../../docs/plan/exploration/linux/04-signalfd.md) | -| `listener_bind` (`SOCK_NONBLOCK`, `accept4`-to-EAGAIN) | `http/listener.rs` | `net.Listen` + accept loop | `socket(7)` | -| `conn_drive` (read-to-EAGAIN, one buffer per fd) | `http/connection.rs` | `conn.Read` loop | the edge-triggered contract | -| `notes[]` store | `engine.rs` (BTreeMaps) | — | [`03-inmemory-engine.md`](../../docs/runtime/database/03-inmemory-engine.md) | - -## What it demonstrates - -- **One thread owns each shard outright.** Accept, parse, store, respond — no locks, no worker pool, no connection migration. Scaling past one core is more shards ([`09-concurrency-scaleout.md`](../../docs/plan/09-concurrency-scaleout.md)), never shared mutable state. The single cross-thread touch is the relaxed-atomic stats counters on `/` — monotonic, never on the data path. -- **Edge-triggered discipline.** Every registration sets `EPOLLET`; every readiness event is drained to `EAGAIN` (the accept loop and the read loop both). Get this wrong and connections silently hang — the reason the Rust module documents the same contract at the top of `netpoll_epoll.rs`. -- **Signals as fd events.** `SIGINT`/`SIGTERM` are blocked, then read from a `signalfd` on the same epoll — no async-signal-unsafe handler, no self-pipe trick. -- **RAM is the read path.** `GET /api/notes` touches a C array. The production engine is the same idea with MVCC and a WAL behind it. - -## Architecture and roadmap - -Documentation lives under `docs/` (repo convention) — this README stays here as the directory's orientation page only: - -- [`docs/plan/exploration/c-runtime/01-architecture.md`](../../docs/plan/exploration/c-runtime/01-architecture.md) — the runtime defined by tracing **one memory address** through user space, kernel space, and hardware under a million concurrent connections, plus seven improvement proposals (seqlock reads, registered buffers, zero-copy send, SQPOLL, …). -- [`docs/plan/exploration/c-runtime/00-plan.md`](../../docs/plan/exploration/c-runtime/00-plan.md) — the phase sequence: **A → B → C → D → E → F, all ✅ shipped.** - -## Measured (phase F, 20-core Linux 6.14, tmpfs data dir, `just rt-c-bench`) - -Same C bench client (`bench/bench.c`, keep-alive, only 2xx counted) against both servers: - -| Benchmark | **wo-rt-c** (8 shards, durable WAL, io_uring + group commit) | **Go `net/http`** (go1.25.1, 20 cores, no durability) | **Rust `wo`** (release, 8 shards, durable WAL, io_uring group commit)¹ | -| --- | --- | --- | --- | -| `GET /healthz` | **859,033 req/s** · p50 71 µs · p99 159 µs | 336,444 req/s · p50 70 µs · p99 1,277 µs | 746,340 req/s · p50 73 µs · p99 186 µs | -| `GET /` (JSON) | **671,312 req/s** · p99 180 µs | — | 692,671 req/s · p99 167 µs | -| `POST` write (tmpfs) | **618,343 commits/s** — fsync-acked · p99 194 µs | 320,516 req/s — RAM only, no WAL · p99 1,581 µs | 330,285 commits/s — fsync-acked · p99 360 µs | -| `POST` write (real ext4/NVMe) | — | — | **27,014 commits/s group commit vs 5,765 per-commit (4.7×)** · p50 2.2 ms | -| 10,000 idle conns | 0 errors | 0 errors | 0 errors | - -¹ All numbers measured on a clean box with the same client (earlier parasite-contaminated runs superseded). The Rust column's history is the architecture roadmap, measured: 09a global mutex (74.9k writes/s) → 09b sharded engine (+51%) → 09c per-shard WAL (~1% durability cost) → **keep-alive: reads ×3.4 to 770k/s, durable writes ×1.9 to 331k/s, p99 under 350 µs everywhere**. Rust now beats Go on both columns *while fsyncing every write*, and sits within ~10% of the C prototype on reads — converging exactly as the same-architecture argument predicted. The one remaining C advantage is **group commit on io_uring** (one batched fsync + one syscall per tick vs per-commit fsync over epoll), which is the next port. Bonus finding: with `/tmp` accidentally full, the C runtime **refused to ack non-durable writes under ENOSPC** — the durability guarantee holding in an unplanned failure mode. - -wo-rt-c on 8 cores outpaces Go on 20 with ~8× tighter p99 (Go's GC shows there) — while fsyncing every write Go doesn't. Honest caveats: `net/http` does full general-purpose HTTP; our parser is minimal; .NET was not installed on the box. **ACID under load:** three crash rounds (`kill -9` mid-bench at ~2M commits) all showed WAL records ≥ acked; isolation probe: 300 concurrent commits → 300 distinct ids; torn-tail records drop whole by CRC. - -The crash-under-load test **found and fixed two real bugs** the lighter phase-D test missed: an ack-before-fsync race (`conn_continue` armed the send in the same tick the commit was staged) and an fd-reuse ABA hazard in ack parking (fixed with per-connection generation stamps). That is what phase F is for. -- [`docs/plan/exploration/c-runtime/02-single-binary.md`](../../docs/plan/exploration/c-runtime/02-single-binary.md) — the end goal: how the `wo build` **single binary** runs on this runtime environment — the runtime kernel is statically linked into every writeonce app (Go model, nothing to install), with the catalog/routes/bytecode payload consumed at boot. - -## Deliberate simplifications - -Single-shot RECV re-armed per request (multishot recv + buffer rings are a phase-F improvement), one outstanding SQE per connection, fixed-size buffers, naive `"title"` extraction instead of a JSON parser, no `timerfd`. Requires kernel ≥ 5.19 (multishot accept). This file is for reading; `crates/rt` is for running writeonce. diff --git a/prototypes/wo-rt-c/bench/bench.c b/prototypes/wo-rt-c/bench/bench.c deleted file mode 100644 index a238591..0000000 --- a/prototypes/wo-rt-c/bench/bench.c +++ /dev/null @@ -1,201 +0,0 @@ -/* - * bench.c — load client for wo-rt-c (phase F). Zero deps beyond libc. - * - * T threads, each driving ONE keep-alive connection in a tight request/ - * response loop (TCP_NODELAY, full-response framing via Content-Length). - * Every request is latency-stamped; the run prints req/s, p50, p99, errors. - * - * usage: ./bench [post-json] - * read: ./bench 127.0.0.1 8085 64 5 /api/notes - * write: ./bench 127.0.0.1 8085 64 5 /api/notes '{"title":"bench"}' - * idle: ./bench 127.0.0.1 8085 10000 0 /healthz # seconds=0: open conns, - * # one request each, hold, exit - */ - -#define _GNU_SOURCE -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#define MAX_SAMPLES 400000 /* per thread; counting continues past it */ - -static char g_req[2048]; -static int g_reqlen; -static struct sockaddr_in g_addr; -static long g_deadline_us; /* 0 = idle-connection mode */ -static int g_hold_secs; - -struct worker { - pthread_t tid; - long reqs, errs; - long ok2xx, non2xx; /* honest accounting: only 2xx is success */ - long *lat; /* µs samples */ - int nlat; -}; - -static long now_us(void) { - struct timespec ts; - clock_gettime(CLOCK_MONOTONIC, &ts); - return ts.tv_sec * 1000000L + ts.tv_nsec / 1000; -} - -static int conn_open(void) { - int fd = socket(AF_INET, SOCK_STREAM, 0); - if (fd < 0) return -1; - int one = 1; - setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, &one, sizeof one); - if (connect(fd, (struct sockaddr *)&g_addr, sizeof g_addr) < 0) { close(fd); return -1; } - return fd; -} - -/* One request/response round-trip. Returns HTTP status, or -1 conn-dead. */ -static int round_trip(int fd) { - size_t off = 0; - while (off < (size_t)g_reqlen) { - ssize_t n = write(fd, g_req + off, (size_t)g_reqlen - off); - if (n <= 0) { if (n < 0 && errno == EINTR) continue; return -1; } - off += (size_t)n; - } - static _Thread_local char buf[131072]; - size_t got = 0, need = 0; - for (;;) { - ssize_t n = read(fd, buf + got, sizeof buf - 1 - got); - if (n <= 0) { if (n < 0 && errno == EINTR) continue; return -1; } - got += (size_t)n; - buf[got] = 0; - if (!need) { - char *he = strstr(buf, "\r\n\r\n"); - if (!he) { if (got >= sizeof buf - 1) return -1; continue; } - size_t hdr = (size_t)(he + 4 - buf); - long cl = 0; - char *p = strcasestr(buf, "Content-Length:"); - if (p) cl = strtol(p + 15, NULL, 10); - need = hdr + (size_t)cl; - } - if (got >= need) { - int code = 0; - sscanf(buf, "HTTP/1.1 %d", &code); - return code; - } - if (got >= sizeof buf - 1) return -1; - } -} - -static void *worker_main(void *arg) { - struct worker *w = arg; - int fd = conn_open(); - if (fd < 0) { w->errs++; return NULL; } - - if (g_deadline_us == 0) { /* idle-connection mode */ - if (round_trip(fd) > 0) w->reqs++; else w->errs++; - sleep((unsigned)g_hold_secs); - close(fd); - return NULL; - } - - while (now_us() < g_deadline_us) { - long t0 = now_us(); - int code = round_trip(fd); - if (code < 0) { /* reconnect once, then count errs */ - close(fd); - fd = conn_open(); - if (fd < 0) { w->errs++; break; } - w->errs++; - continue; - } - long dt = now_us() - t0; - if (w->nlat < MAX_SAMPLES) w->lat[w->nlat++] = dt; - w->reqs++; - if (code >= 200 && code < 300) w->ok2xx++; else w->non2xx++; - } - close(fd); - return NULL; -} - -static int cmp_long(const void *a, const void *b) { - long x = *(const long *)a, y = *(const long *)b; - return (x > y) - (x < y); -} - -int main(int argc, char **argv) { - if (argc < 6) { - fprintf(stderr, "usage: %s [post-json]\n", argv[0]); - return 2; - } - const char *host = argv[1]; - int port = atoi(argv[2]); - int threads = atoi(argv[3]); - int secs = atoi(argv[4]); - const char *path = argv[5]; - const char *body = argc > 6 ? argv[6] : NULL; - - struct rlimit rl; - if (getrlimit(RLIMIT_NOFILE, &rl) == 0 && rl.rlim_cur < rl.rlim_max) { - rl.rlim_cur = rl.rlim_max; - setrlimit(RLIMIT_NOFILE, &rl); - } - - memset(&g_addr, 0, sizeof g_addr); - g_addr.sin_family = AF_INET; - g_addr.sin_port = htons((uint16_t)port); - inet_pton(AF_INET, host, &g_addr.sin_addr); - - if (body) - g_reqlen = snprintf(g_req, sizeof g_req, - "POST %s HTTP/1.1\r\nHost: %s\r\nContent-Type: application/json\r\n" - "Content-Length: %zu\r\nConnection: keep-alive\r\n\r\n%s", - path, host, strlen(body), body); - else - g_reqlen = snprintf(g_req, sizeof g_req, - "GET %s HTTP/1.1\r\nHost: %s\r\nConnection: keep-alive\r\n\r\n", path, host); - - g_hold_secs = 3; - g_deadline_us = secs > 0 ? now_us() + (long)secs * 1000000L : 0; - - struct worker *ws = calloc((size_t)threads, sizeof *ws); - for (int i = 0; i < threads; i++) { - ws[i].lat = secs > 0 ? malloc(MAX_SAMPLES * sizeof(long)) : NULL; - pthread_create(&ws[i].tid, NULL, worker_main, &ws[i]); - } - - long t0 = now_us(); - long total = 0, errs = 0, nlat = 0, ok = 0, bad = 0; - for (int i = 0; i < threads; i++) { - pthread_join(ws[i].tid, NULL); - total += ws[i].reqs; - errs += ws[i].errs; - nlat += ws[i].nlat; - ok += ws[i].ok2xx; - bad += ws[i].non2xx; - } - long wall_us = now_us() - t0; - - if (secs == 0) { - printf("idle-conns: opened %ld / %d connections (errs %ld), held %ds, server survived\n", - total, threads, errs, g_hold_secs); - return errs ? 1 : 0; - } - - long *all = malloc((size_t)nlat * sizeof(long)); - long k = 0; - for (int i = 0; i < threads; i++) { - memcpy(all + k, ws[i].lat, (size_t)ws[i].nlat * sizeof(long)); - k += ws[i].nlat; - } - qsort(all, (size_t)nlat, sizeof(long), cmp_long); - - double rps = (double)ok / ((double)wall_us / 1e6); - printf("%-22s %d conns %ds %ld ok (2xx) %.0f ok/s p50 %ld µs p99 %ld µs non-2xx %ld errs %ld\n", - body ? "WRITE (POST)" : path, threads, secs, ok, rps, - nlat ? all[nlat / 2] : 0, nlat ? all[(long)((double)nlat * 0.99)] : 0, bad, errs); - return 0; -} diff --git a/prototypes/wo-rt-c/bench/goref/go.mod b/prototypes/wo-rt-c/bench/goref/go.mod deleted file mode 100644 index 7d924dd..0000000 --- a/prototypes/wo-rt-c/bench/goref/go.mod +++ /dev/null @@ -1,3 +0,0 @@ -module goref - -go 1.25.1 diff --git a/prototypes/wo-rt-c/bench/goref/main.go b/prototypes/wo-rt-c/bench/goref/main.go deleted file mode 100644 index d0539ec..0000000 --- a/prototypes/wo-rt-c/bench/goref/main.go +++ /dev/null @@ -1,69 +0,0 @@ -// goref — the Go net/http comparison server for the phase-F benchmark. -// Same endpoints and semantics as wo-rt-c's RAM read path: /healthz, /, -// GET/POST /api/notes against an in-memory store. Durability is NOT -// implemented here (Go side has no WAL), so only READ benchmarks are -// apples-to-apples; the POST comparison measures Go's non-durable path -// against wo-rt-c's fsync-backed path and is labeled as such. -// -// go build -o goref . && ./goref # :8095, GOMAXPROCS = all cores -package main - -import ( - "encoding/json" - "fmt" - "io" - "net/http" - "sync" -) - -type note struct { - ID int `json:"id"` - Title string `json:"title"` -} - -var ( - mu sync.RWMutex - notes []note - nextID = 1 -) - -func main() { - http.HandleFunc("/healthz", func(w http.ResponseWriter, r *http.Request) { - io.WriteString(w, "ok") - }) - http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { - io.WriteString(w, `{"runtime":"go-net-http"}`) - }) - http.HandleFunc("/api/notes", func(w http.ResponseWriter, r *http.Request) { - switch r.Method { - case http.MethodGet: - mu.RLock() - b, _ := json.Marshal(notes) - mu.RUnlock() - w.Header().Set("Content-Type", "application/json") - w.Write(b) - case http.MethodPost: - var in struct { - Title string `json:"title"` - } - if json.NewDecoder(r.Body).Decode(&in) != nil || in.Title == "" { - http.Error(w, `{"error":"expected {\"title\":\"...\"}"}`, http.StatusBadRequest) - return - } - mu.Lock() - n := note{ID: nextID, Title: in.Title} - nextID++ - if len(notes) < 100000 { - notes = append(notes, n) - } - mu.Unlock() - w.Header().Set("Content-Type", "application/json") - w.WriteHeader(http.StatusCreated) - json.NewEncoder(w).Encode(n) - default: - http.Error(w, "method", http.StatusMethodNotAllowed) - } - }) - fmt.Println("[goref] listening on :8095") - http.ListenAndServe("127.0.0.1:8095", nil) -} diff --git a/prototypes/wo-rt-c/wo-rt.c b/prototypes/wo-rt-c/wo-rt.c deleted file mode 100644 index feee257..0000000 --- a/prototypes/wo-rt-c/wo-rt.c +++ /dev/null @@ -1,979 +0,0 @@ -/* - * wo-rt.c — the writeonce runtime environment, in C. Phase E: first load. - * - * Phases A–D: N pinned threads with raw io_uring loops, SO_REUSEPORT - * listeners, keep-alive connections, one mlock'd mmap arena, and durable - * commits (RAM apply → framed WAL record → per-tick group fdatasync → ack - * on the fsync CQE). Phase E closes the loop: at boot — BEFORE any accept - * is armed — each shard thread loads its snapshot and replays its WAL into - * its arena slice, in parallel, validating every frame's CRC + COMMIT - * trailer and truncating at the first torn record. Appends resume at the - * validated tail. A clean shutdown writes a per-shard snapshot - * (`shard-.data`) and truncates the WAL; boot prefers snapshot + WAL - * tail. The data directory carries a `meta` file pinning the shard count — - * a restart with a different WO_THREADS refuses to start (resharding is - * plan 09f, not silent data loss). Zero deps beyond libc + kernel uapi. - * - * build: make run: ./wo-rt [WO_PORT=8085 WO_THREADS=4 WO_DATA=./wo-data ./wo-rt] - * poke: curl -X POST localhost:8085/api/notes -d '{"title":"hello"}' # acked after fsync - * wal: ./wo-rt wal-check wo-data/shard-0.wal # offline frame/CRC validation - * - * Phase map: docs/plan/exploration/c-runtime/00-plan.md (A ✅ threads, - * B ✅ arena, C ✅ io_uring, D ✅ WAL, E this file, F bench). One-address - * trace: 01-architecture.md. Single-binary end goal: 02-single-binary.md. - * - * Module map (C ↔ Rust ↔ kernel reference card): - * ring_init/ring_enter ↔ (plan 09 decision 4: per-thread ring) ↔ linux/07-io_uring.md - * arena_init ↔ (plan 10 storage foundations) ↔ linux/08-mmap.md - * wal_flush / OP_FSYNC ↔ (plan 11 WAL + 09c per-shard WAL) ↔ linux/12-pwrite-fsync.md, 09-fallocate.md - * sig/evfd via POLL_ADD↔ runtime/{signalfd,eventfd}.rs ↔ linux/04-signalfd.md, 02-eventfd.md - * - * Requires IORING_FEAT_SINGLE_MMAP (≥5.4) and multishot accept (≥5.19). - * Phase D opens WALs with O_TRUNC (fresh log each boot) — replay-on-boot and - * snapshots are phase E; the crash test inspects the WAL offline via - * `wal-check` BEFORE any restart. Simplifications: single-shot RECV re-armed - * per request, naive JSON extraction, fixed-size WAL payloads. - */ - -#define _GNU_SOURCE -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#ifndef IORING_ACCEPT_MULTISHOT -#define IORING_ACCEPT_MULTISHOT (1U << 0) -#endif - -#define MAX_THREADS 64 -#ifndef MAX_FDS -#define MAX_FDS 16384 /* conn slots per shard, fd-indexed */ -#endif -#define IN_CAP 8192 -#define OUT_CAP 65536 -#define PAGE 4096 -#define HUGE_2M (2u * 1024 * 1024) -#ifndef SLOT_SIZE -#define SLOT_SIZE 256 /* -D overridable for scale runs (phase F) */ -#endif -#ifndef SLOTS_PER_SHARD -#define SLOTS_PER_SHARD 256 -#endif -#define RING_ENTRIES 1024 -#define WAL_PREALLOC (4u * 1024 * 1024) /* fallocate per shard */ -#define WAL_COMMIT 0xC0FFEE42u /* frame trailer magic */ -#define WAL_BATCH_CAP 65536 /* staged bytes per group commit */ -#define WAL_BATCH_CONNS 256 /* acks parked per batch */ - -/* ---------------------------------------------------------------- crc32 -- - * Hand-rolled (poly 0xEDB88320), table built once at boot. Zero deps. */ - -static uint32_t crc_table[256]; - -static void crc32_init(void) { - for (uint32_t i = 0; i < 256; i++) { - uint32_t c = i; - for (int k = 0; k < 8; k++) c = (c & 1) ? 0xEDB88320u ^ (c >> 1) : c >> 1; - crc_table[i] = c; - } -} - -static uint32_t crc32(const void *buf, size_t len) { - const uint8_t *p = buf; - uint32_t c = 0xFFFFFFFFu; - while (len--) c = crc_table[(c ^ *p++) & 0xFF] ^ (c >> 8); - return c ^ 0xFFFFFFFFu; -} - -/* ------------------------------------------------------------ WAL frame -- - * [u32 len][u32 crc(payload)][payload][u32 WAL_COMMIT]. A record replays - * whole or not at all: bad len, bad crc, or missing trailer = torn tail. - * The payload carries the (shard,slot) coordinates phase B made stable. */ - -struct wal_payload { - uint32_t op; /* 1 = insert note */ - uint32_t slot; - int32_t id; - char title[128]; -}; - -#define WAL_FRAME_BYTES (4 + 4 + sizeof(struct wal_payload) + 4) - -/* ---------------------------------------------------------------- arena -- - * Unchanged from phase B: [header page][shard 0: bitmap page + slots]... */ - -struct arena_hdr { - char magic[8]; - uint32_t version; - uint32_t n_shards; - uint32_t slots_per_shard; - uint32_t slot_size; -}; - -struct slot_note { int32_t id; char title[128]; }; - -static uint8_t *arena; -static size_t arena_bytes, arena_map_bytes, slice_bytes, bitmap_bytes; -static int arena_huge = 0, arena_locked = 0; - -static int arena_init(int n_shards) { - bitmap_bytes = ((size_t)SLOTS_PER_SHARD / 8 + PAGE - 1) & ~((size_t)PAGE - 1); - slice_bytes = bitmap_bytes + (size_t)SLOTS_PER_SHARD * SLOT_SIZE; - arena_bytes = PAGE + (size_t)n_shards * slice_bytes; - - arena_map_bytes = (arena_bytes + HUGE_2M - 1) & ~((size_t)HUGE_2M - 1); - arena = mmap(NULL, arena_map_bytes, PROT_READ | PROT_WRITE, - MAP_PRIVATE | MAP_ANONYMOUS | MAP_HUGETLB | MAP_POPULATE, -1, 0); - if (arena != MAP_FAILED) { - arena_huge = 1; - } else { - arena_map_bytes = (arena_bytes + PAGE - 1) & ~((size_t)PAGE - 1); - arena = mmap(NULL, arena_map_bytes, PROT_READ | PROT_WRITE, - MAP_PRIVATE | MAP_ANONYMOUS | MAP_POPULATE, -1, 0); - if (arena == MAP_FAILED) { perror("mmap arena"); return -1; } - } - - arena_locked = (mlock(arena, arena_map_bytes) == 0); - if (!arena_locked) - fprintf(stderr, "[wo-rt-c] warn: mlock refused (%s) — arena not pinned\n", strerror(errno)); - - struct arena_hdr *hdr = (struct arena_hdr *)arena; - memcpy(hdr->magic, "WORTC\0\0", 8); - hdr->version = 3; /* phase C */ - hdr->n_shards = (uint32_t)n_shards; - hdr->slots_per_shard = SLOTS_PER_SHARD; - hdr->slot_size = SLOT_SIZE; - return 0; -} - -static uint64_t *shard_bitmap(int t) { return (uint64_t *)(arena + PAGE + (size_t)t * slice_bytes); } -static uint8_t *shard_slots (int t) { return arena + PAGE + (size_t)t * slice_bytes + bitmap_bytes; } -static struct slot_note *slot_at(int t, uint32_t i) { - return (struct slot_note *)(shard_slots(t) + (size_t)i * SLOT_SIZE); -} - -/* ------------------------------------------------------------- io_uring -- - * The raw ring: three pieces of memory shared with the kernel — the SQ/CQ - * ring headers+arrays (one mmap, IORING_FEAT_SINGLE_MMAP) and the SQE array. - * Submission: fill sqes[tail&mask], publish tail with a release store, tell - * the kernel with ONE io_uring_enter that also waits for completions. */ - -struct ring { - int fd; - unsigned *sq_head, *sq_tail, *sq_mask, *sq_array; - unsigned *cq_head, *cq_tail, *cq_mask; - struct io_uring_sqe *sqes; - struct io_uring_cqe *cqes; - unsigned local_tail; /* SQEs filled, not yet published */ - unsigned to_submit; -}; - -static int ring_init(struct ring *r) { - struct io_uring_params p; - memset(&p, 0, sizeof p); - r->fd = (int)syscall(__NR_io_uring_setup, RING_ENTRIES, &p); - if (r->fd < 0) { perror("io_uring_setup"); return -1; } - if (!(p.features & IORING_FEAT_SINGLE_MMAP)) { - fprintf(stderr, "[wo-rt-c] kernel lacks IORING_FEAT_SINGLE_MMAP (need >= 5.4)\n"); - return -1; - } - - size_t sq_sz = p.sq_off.array + p.sq_entries * sizeof(unsigned); - size_t cq_sz = p.cq_off.cqes + p.cq_entries * sizeof(struct io_uring_cqe); - size_t sz = sq_sz > cq_sz ? sq_sz : cq_sz; - uint8_t *sqcq = mmap(NULL, sz, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_POPULATE, - r->fd, IORING_OFF_SQ_RING); - if (sqcq == MAP_FAILED) { perror("mmap sq/cq ring"); return -1; } - - r->sq_head = (unsigned *)(sqcq + p.sq_off.head); - r->sq_tail = (unsigned *)(sqcq + p.sq_off.tail); - r->sq_mask = (unsigned *)(sqcq + p.sq_off.ring_mask); - r->sq_array = (unsigned *)(sqcq + p.sq_off.array); - r->cq_head = (unsigned *)(sqcq + p.cq_off.head); - r->cq_tail = (unsigned *)(sqcq + p.cq_off.tail); - r->cq_mask = (unsigned *)(sqcq + p.cq_off.ring_mask); - r->cqes = (struct io_uring_cqe *)(sqcq + p.cq_off.cqes); - - r->sqes = mmap(NULL, p.sq_entries * sizeof(struct io_uring_sqe), - PROT_READ | PROT_WRITE, MAP_SHARED | MAP_POPULATE, - r->fd, IORING_OFF_SQES); - if (r->sqes == MAP_FAILED) { perror("mmap sqes"); return -1; } - - r->local_tail = *r->sq_tail; - r->to_submit = 0; - return 0; -} - -static struct io_uring_sqe *sqe_get(struct ring *r) { - unsigned idx = r->local_tail & *r->sq_mask; - struct io_uring_sqe *s = &r->sqes[idx]; - memset(s, 0, sizeof *s); - r->sq_array[idx] = idx; - r->local_tail++; - r->to_submit++; - return s; -} - -/* user_data = (op << 32) | fd-or-batch-index */ -enum { OP_ACCEPT = 1, OP_RECV, OP_SEND, OP_EVFD, OP_SIGFD, OP_WALWR, OP_FSYNC }; -static uint64_t ud(int op, int fd) { return ((uint64_t)op << 32) | (uint32_t)fd; } - -static int ring_enter(struct ring *r, unsigned wait) { - __atomic_store_n(r->sq_tail, r->local_tail, __ATOMIC_RELEASE); - unsigned n = r->to_submit; - r->to_submit = 0; - for (;;) { - int rc = (int)syscall(__NR_io_uring_enter, r->fd, n, wait, - IORING_ENTER_GETEVENTS, NULL, 0); - if (rc >= 0) return rc; - if (errno == EINTR) { n = 0; continue; } /* already submitted */ - perror("io_uring_enter"); - return -1; - } -} - -/* ----------------------------------------------------------- connection -- - * Keep-alive state machine. Exactly one outstanding SQE per connection: - * RECV while a request is being assembled, SEND while a response drains. - * Leftover bytes after a request (pipelining) are carried over and parsed - * before the next RECV is armed. */ - -struct conn { - char in[IN_CAP]; size_t in_len; - char out[OUT_CAP]; size_t out_len, out_off; - int in_use; - int closing; /* close once the out buffer drains */ - int await_durable;/* response parked until this tick's fsync CQE */ - uint64_t gen; /* incarnation stamp — kernel fds get reused */ -}; - -/* One group-commit batch: staged WAL bytes + the connections whose acks ride - * its fsync. Double-buffered: while batch[k] is in flight (write→fsync - * linked SQEs), new commits stage into batch[k^1]. - * Acks are parked as (fd, gen) pairs: an fd alone is ABA-unsafe — a parked - * connection can die, the kernel reuses its fd for a NEW connection whose - * commit sits in the OTHER batch, and a bare-fd release would ack that new - * connection before ITS record is durable. Found by the phase-F crash test - * (7 acked-but-unwritten records out of ~990k under reconnect churn). */ -struct wal_batch { - char buf[WAL_BATCH_CAP]; - size_t len; - int conns[WAL_BATCH_CONNS]; - uint64_t gens[WAL_BATCH_CONNS]; - int n_conns; -}; - -struct shard { - int id; - int lfd, evfd; - struct ring ring; - pthread_t tid; - int next_id; - _Atomic int used; - int wal_fd; - _Atomic size_t wal_off; /* owner-written; stats-readable cross-shard */ - struct wal_batch batch[2]; - int active; /* batch being staged */ - int in_flight; /* a write→fsync pair is on the ring */ - char snap_path[320]; - struct conn conns[MAX_FDS]; -}; - -/* Snapshot file: [snap_hdr][bitmap page][slot bytes]. Written on clean - * shutdown, loaded at boot before WAL replay. */ -struct snap_hdr { - char magic[8]; /* "WOSNAP\0\0" */ - uint32_t version; - int32_t next_id; - int32_t used; -}; - -static struct shard *shards; -static int n_threads = 1; -static int sigfd = -1; -static _Atomic unsigned long reqs[MAX_THREADS]; - -static int slot_alloc(struct shard *sh) { - uint64_t *bm = shard_bitmap(sh->id); - for (uint32_t w = 0; w < SLOTS_PER_SHARD / 64; w++) { - if (bm[w] == UINT64_MAX) continue; - uint32_t b = (uint32_t)__builtin_ctzll(~bm[w]); - uint32_t i = w * 64 + b; - if (i >= SLOTS_PER_SHARD) break; - bm[w] |= (1ULL << b); - atomic_fetch_add_explicit(&sh->used, 1, memory_order_relaxed); - return (int)i; - } - return -1; -} - -/* --------------------------------------------------------- SQE builders -- */ - -static void arm_accept(struct shard *sh) { /* multishot: arm once */ - struct io_uring_sqe *s = sqe_get(&sh->ring); - s->opcode = IORING_OP_ACCEPT; - s->fd = sh->lfd; - s->ioprio = IORING_ACCEPT_MULTISHOT; - s->user_data = ud(OP_ACCEPT, sh->lfd); -} - -static void arm_poll(struct shard *sh, int fd, int op) { - struct io_uring_sqe *s = sqe_get(&sh->ring); - s->opcode = IORING_OP_POLL_ADD; - s->fd = fd; - s->poll_events = POLLIN; - s->user_data = ud(op, fd); -} - -static void arm_recv(struct shard *sh, int fd) { - struct conn *c = &sh->conns[fd]; - struct io_uring_sqe *s = sqe_get(&sh->ring); - s->opcode = IORING_OP_RECV; - s->fd = fd; - s->addr = (uint64_t)(uintptr_t)(c->in + c->in_len); - s->len = (uint32_t)(IN_CAP - 1 - c->in_len); - s->user_data = ud(OP_RECV, fd); -} - -static void arm_send(struct shard *sh, int fd) { - struct conn *c = &sh->conns[fd]; - struct io_uring_sqe *s = sqe_get(&sh->ring); - s->opcode = IORING_OP_SEND; - s->fd = fd; - s->addr = (uint64_t)(uintptr_t)(c->out + c->out_off); - s->len = (uint32_t)(c->out_len - c->out_off); - s->msg_flags = MSG_NOSIGNAL; - s->user_data = ud(OP_SEND, fd); -} - -/* ------------------------------------------------------------ WAL flush -- - * Called once per loop tick. If commits were staged and no batch is in - * flight, submit ONE write SQE for the whole batch at the shard's tail - * offset, hard-linked to ONE fdatasync SQE. Every parked ack in the batch - * is released when the fsync CQE arrives — group commit. */ - -static void wal_flush(struct shard *sh) { - if (sh->in_flight) return; - struct wal_batch *b = &sh->batch[sh->active]; - if (b->len == 0) return; - - size_t off = atomic_load_explicit(&sh->wal_off, memory_order_relaxed); - - struct io_uring_sqe *w = sqe_get(&sh->ring); - w->opcode = IORING_OP_WRITE; - w->fd = sh->wal_fd; - w->addr = (uint64_t)(uintptr_t)b->buf; - w->len = (uint32_t)b->len; - w->off = off; - w->flags = IOSQE_IO_LINK; /* fsync follows the write */ - w->user_data = ud(OP_WALWR, sh->active); - - struct io_uring_sqe *f = sqe_get(&sh->ring); - f->opcode = IORING_OP_FSYNC; - f->fd = sh->wal_fd; - f->fsync_flags = IORING_FSYNC_DATASYNC; - f->user_data = ud(OP_FSYNC, sh->active); - - sh->in_flight = 1; - sh->active ^= 1; /* new commits stage in the twin */ -} - -/* Stage one commit's frame + park the connection's ack on the active batch. - * Returns 0 if the batch has no room (caller responds 503, no RAM apply). */ -static int wal_append(struct shard *sh, int connfd, uint32_t slot, int32_t id, const char *title) { - struct wal_batch *b = &sh->batch[sh->active]; - if (b->len + WAL_FRAME_BYTES > WAL_BATCH_CAP || b->n_conns >= WAL_BATCH_CONNS) - return 0; - - struct wal_payload p; - memset(&p, 0, sizeof p); - p.op = 1; - p.slot = slot; - p.id = id; - snprintf(p.title, sizeof p.title, "%s", title); - b->gens[b->n_conns] = sh->conns[connfd].gen; - - uint32_t len = (uint32_t)sizeof p; - uint32_t crc = crc32(&p, sizeof p); - uint32_t end = WAL_COMMIT; - char *dst = b->buf + b->len; - memcpy(dst, &len, 4); - memcpy(dst + 4, &crc, 4); - memcpy(dst + 8, &p, sizeof p); - memcpy(dst + 8 + sizeof p, &end, 4); - b->len += WAL_FRAME_BYTES; - b->conns[b->n_conns++] = connfd; - return 1; -} - -/* ----------------------------------------------------------------- http -- */ - -static void respond(struct conn *c, const char *status, const char *ctype, const char *body) { - size_t blen = strlen(body); - int n = snprintf(c->out + c->out_len, OUT_CAP - c->out_len, - "HTTP/1.1 %s\r\nContent-Type: %s\r\nContent-Length: %zu\r\nConnection: %s\r\n\r\n%s", - status, ctype, blen, c->closing ? "close" : "keep-alive", body); - if (n > 0 && (size_t)n < OUT_CAP - c->out_len) c->out_len += (size_t)n; - else c->closing = 1; /* response too big — drop conn */ -} - -static int json_title(const char *body, char *out, size_t cap) { - const char *p = strstr(body, "\"title\""); - if (!p) return 0; - p = strchr(p + 7, ':'); if (!p) return 0; - p = strchr(p, '"'); if (!p) return 0; - p++; - size_t i = 0; - while (*p && *p != '"' && i + 1 < cap) out[i++] = *p++; - out[i] = 0; - return i > 0; -} - -static void route(struct shard *sh, struct conn *c, const char *method, const char *path, const char *body) { - atomic_fetch_add_explicit(&reqs[sh->id], 1, memory_order_relaxed); - - if (!strcmp(method, "GET") && !strcmp(path, "/")) { - char out[1024]; - size_t off = (size_t)snprintf(out, sizeof out, - "{\"runtime\":\"wo-rt-c\",\"loop\":\"io_uring\",\"threads\":%d,\"shard\":%d," - "\"arena\":{\"bytes\":%zu,\"mapped\":%zu,\"hugepages\":%s,\"mlocked\":%s," - "\"slot_size\":%d,\"slots_per_shard\":%d},\"shard_used\":[", - n_threads, sh->id, arena_bytes, arena_map_bytes, - arena_huge ? "true" : "false", arena_locked ? "true" : "false", - SLOT_SIZE, SLOTS_PER_SHARD); - for (int t = 0; t < n_threads; t++) - off += (size_t)snprintf(out + off, sizeof out - off, "%s%d", t ? "," : "", - atomic_load_explicit(&shards[t].used, memory_order_relaxed)); - off += (size_t)snprintf(out + off, sizeof out - off, "],\"shard_requests\":["); - for (int t = 0; t < n_threads; t++) - off += (size_t)snprintf(out + off, sizeof out - off, "%s%lu", t ? "," : "", - atomic_load_explicit(&reqs[t], memory_order_relaxed)); - off += (size_t)snprintf(out + off, sizeof out - off, "],\"wal_bytes\":["); - for (int t = 0; t < n_threads; t++) - off += (size_t)snprintf(out + off, sizeof out - off, "%s%zu", t ? "," : "", - atomic_load_explicit(&shards[t].wal_off, memory_order_relaxed)); - snprintf(out + off, sizeof out - off, "]}"); - respond(c, "200 OK", "application/json", out); - } else if (!strcmp(method, "GET") && !strcmp(path, "/healthz")) { - respond(c, "200 OK", "text/plain", "ok"); - } else if (!strcmp(method, "GET") && !strcmp(path, "/api/notes")) { - static _Thread_local char out[SLOTS_PER_SHARD * 160 + 64]; - uint64_t *bm = shard_bitmap(sh->id); - size_t off = (size_t)snprintf(out, sizeof out, "{\"shard\":%d,\"notes\":[", sh->id); - int first = 1; - for (uint32_t i = 0; i < SLOTS_PER_SHARD; i++) { - if (!(bm[i / 64] & (1ULL << (i % 64)))) continue; - struct slot_note *n = slot_at(sh->id, i); - off += (size_t)snprintf(out + off, sizeof out - off, - "%s{\"id\":%d,\"title\":\"%s\"}", first ? "" : ",", n->id, n->title); - first = 0; - } - snprintf(out + off, sizeof out - off, "]}"); - respond(c, "200 OK", "application/json", out); - } else if (!strcmp(method, "POST") && !strcmp(path, "/api/notes")) { - char title[128]; - int i; - if (!json_title(body, title, sizeof title)) { - respond(c, "400 Bad Request", "application/json", "{\"error\":\"expected {\\\"title\\\":\\\"...\\\"}\"}"); - return; - } - /* Capacity gates BEFORE the RAM apply — an aborted op writes nothing. */ - struct wal_batch *b = &sh->batch[sh->active]; - if (b->len + WAL_FRAME_BYTES > WAL_BATCH_CAP || b->n_conns >= WAL_BATCH_CONNS) { - respond(c, "503 Service Unavailable", "application/json", "{\"error\":\"commit batch full, retry\"}"); - return; - } - if ((i = slot_alloc(sh)) < 0) { - respond(c, "507 Insufficient Storage", "application/json", "{\"error\":\"shard full\"}"); - return; - } - struct slot_note *n = slot_at(sh->id, (uint32_t)i); /* 1. the RAM apply */ - n->id = sh->next_id; - sh->next_id += n_threads; - snprintf(n->title, sizeof n->title, "%s", title); - char out[224]; - snprintf(out, sizeof out, "{\"id\":%d,\"title\":\"%s\",\"shard\":%d,\"slot\":%d}", - n->id, n->title, sh->id, i); - respond(c, "201 Created", "application/json", out); /* built, NOT sent */ - int connfd = (int)(c - sh->conns); /* conns is fd-indexed */ - wal_append(sh, connfd, (uint32_t)i, n->id, n->title);/* 2. stage WAL frame */ - c->await_durable = 1; /* 4. ack rides fsync */ - } else { - respond(c, "404 Not Found", "application/json", "{\"error\":\"no such route\"}"); - } -} - -/* ----------------------------------------------------- state machine ----- */ - -static void conn_open(struct shard *sh, int fd) { - struct conn *c = &sh->conns[fd]; - c->in_len = c->out_len = c->out_off = 0; - c->closing = 0; - c->await_durable = 0; - c->gen++; /* new incarnation — stale parked acks won't match */ - c->in_use = 1; -} - -static void conn_close(struct shard *sh, int fd) { - if (fd >= 0 && fd < MAX_FDS) sh->conns[fd].in_use = 0; - close(fd); -} - -/* Try to consume ONE complete request from the in buffer. Returns 1 if a - * response was produced (out has bytes), 0 if the request is incomplete. */ -static int try_process(struct shard *sh, struct conn *c) { - c->in[c->in_len] = 0; - char *hdr_end = strstr(c->in, "\r\n\r\n"); - if (!hdr_end) return 0; - char *body = hdr_end + 4; - size_t total = (size_t)(body - c->in); - - const char *cl = strcasestr(c->in, "Content-Length:"); - if (cl) { - long want = strtol(cl + 15, NULL, 10); - if (want < 0) want = 0; - if (c->in_len < total + (size_t)want) return 0; - total += (size_t)want; - } - - /* HTTP/1.1 defaults to keep-alive; honor an explicit close. */ - if (strcasestr(c->in, "connection: close") || - (strstr(c->in, "HTTP/1.0") && !strcasestr(c->in, "connection: keep-alive"))) - c->closing = 1; - - char method[8] = {0}, path[256] = {0}; - if (sscanf(c->in, "%7s %255s", method, path) == 2) - route(sh, c, method, path, body); - else - c->closing = 1; - - memmove(c->in, c->in + total, c->in_len - total); /* carry pipelined tail */ - c->in_len -= total; - return 1; -} - -/* Advance a connection: drain out via SEND, else parse, else arm RECV. - * A parked commit ack arms nothing — the fsync CQE handler resumes it. */ -static void conn_continue(struct shard *sh, int fd) { - struct conn *c = &sh->conns[fd]; - if (c->await_durable) { return; } - if (c->out_off < c->out_len) { arm_send(sh, fd); return; } - c->out_off = c->out_len = 0; - if (c->closing) { conn_close(sh, fd); return; } - if (try_process(sh, c)) { - /* route() may have JUST parked this response (await set inside - * try_process) — sending now would race the fsync. The fsync CQE - * re-enters here with await cleared and arms the send. - * (Found by the phase-F crash-under-load test: ~6 acked-but- - * unwritten records per ~750k at the kill instant.) */ - if (!c->await_durable) arm_send(sh, fd); - return; - } - if (c->in_len >= IN_CAP - 1) { conn_close(sh, fd); return; } /* oversize head */ - arm_recv(sh, fd); -} - -/* --------------------------------------------------------------- recovery -- - * First load: hard drive → RAM, per shard, in parallel, BEFORE accept arms. - * Snapshot (if any) restores the slice wholesale; the WAL tail replays - * commits since that snapshot. Frame validation is wal-check's logic with - * the printf swapped for the arena apply. */ - -static long now_ms(void) { - struct timespec ts; - clock_gettime(CLOCK_MONOTONIC, &ts); - return ts.tv_sec * 1000 + ts.tv_nsec / 1000000; -} - -static int snap_load(struct shard *sh) { - int fd = open(sh->snap_path, O_RDONLY | O_CLOEXEC); - if (fd < 0) return 0; - struct snap_hdr h; - size_t slot_bytes = (size_t)SLOTS_PER_SHARD * SLOT_SIZE; - if (read(fd, &h, sizeof h) != (ssize_t)sizeof h || - memcmp(h.magic, "WOSNAP\0", 8) != 0 || - pread(fd, shard_bitmap(sh->id), bitmap_bytes, (off_t)sizeof h) != (ssize_t)bitmap_bytes || - pread(fd, shard_slots(sh->id), slot_bytes, (off_t)(sizeof h + bitmap_bytes)) != (ssize_t)slot_bytes) { - fprintf(stderr, "[wo-rt-c] shard %d: snapshot unreadable — starting from WAL only\n", sh->id); - memset(shard_bitmap(sh->id), 0, bitmap_bytes + slot_bytes); - close(fd); - return 0; - } - sh->next_id = h.next_id; - atomic_store_explicit(&sh->used, h.used, memory_order_relaxed); - close(fd); - return h.used; -} - -static int wal_replay(struct shard *sh) { - size_t off = 0; - int recs = 0; - int32_t maxid = 0; - for (;;) { - uint32_t len, crc, end; - struct wal_payload p; - if (pread(sh->wal_fd, &len, 4, (off_t)off) != 4 || len == 0) break; - if (len != sizeof p || - pread(sh->wal_fd, &crc, 4, (off_t)(off + 4)) != 4 || - pread(sh->wal_fd, &p, sizeof p, (off_t)(off + 8)) != (ssize_t)sizeof p || - pread(sh->wal_fd, &end, 4, (off_t)(off + 8 + sizeof p)) != 4 || - crc32(&p, sizeof p) != crc || end != WAL_COMMIT) { - fprintf(stderr, "[wo-rt-c] shard %d: torn WAL record at byte %zu — truncating\n", - sh->id, off); - break; - } - if (p.op == 1 && p.slot < SLOTS_PER_SHARD) { /* idempotent apply */ - uint64_t *bm = shard_bitmap(sh->id); - if (!(bm[p.slot / 64] & (1ULL << (p.slot % 64)))) { - bm[p.slot / 64] |= (1ULL << (p.slot % 64)); - atomic_fetch_add_explicit(&sh->used, 1, memory_order_relaxed); - } - struct slot_note *n = slot_at(sh->id, p.slot); - n->id = p.id; - snprintf(n->title, sizeof n->title, "%s", p.title); - if (p.id > maxid) maxid = p.id; - } - recs++; - off += WAL_FRAME_BYTES; - } - /* Resume appends at the validated tail; drop torn bytes, re-preallocate. */ - if (ftruncate(sh->wal_fd, (off_t)off) == 0) - (void)!fallocate(sh->wal_fd, 0, 0, off > WAL_PREALLOC ? off : WAL_PREALLOC); - atomic_store_explicit(&sh->wal_off, off, memory_order_relaxed); - if (maxid > 0 && maxid + n_threads > sh->next_id) - sh->next_id = maxid + n_threads; /* interleaved high-water */ - return recs; -} - -/* Clean-shutdown snapshot: write slice → fsync → atomic rename → truncate WAL. */ -static void snap_write(struct shard *sh) { - char tmp[336]; - snprintf(tmp, sizeof tmp, "%s.tmp", sh->snap_path); - int fd = open(tmp, O_WRONLY | O_CREAT | O_TRUNC | O_CLOEXEC, 0644); - if (fd < 0) { perror("snapshot open"); return; } - struct snap_hdr h; - memset(&h, 0, sizeof h); - memcpy(h.magic, "WOSNAP\0", 8); - h.version = 1; - h.next_id = sh->next_id; - h.used = atomic_load_explicit(&sh->used, memory_order_relaxed); - size_t slot_bytes = (size_t)SLOTS_PER_SHARD * SLOT_SIZE; - int ok = write(fd, &h, sizeof h) == (ssize_t)sizeof h - && write(fd, shard_bitmap(sh->id), bitmap_bytes) == (ssize_t)bitmap_bytes - && write(fd, shard_slots(sh->id), slot_bytes) == (ssize_t)slot_bytes - && fsync(fd) == 0; - close(fd); - if (!ok || rename(tmp, sh->snap_path) < 0) { fprintf(stderr, "[wo-rt-c] shard %d: snapshot failed\n", sh->id); unlink(tmp); return; } - if (ftruncate(sh->wal_fd, 0) == 0) { /* WAL now redundant */ - (void)!fallocate(sh->wal_fd, 0, 0, WAL_PREALLOC); - fsync(sh->wal_fd); - } - printf("[wo-rt-c] shard %d: snapshot %d rows → %s, wal truncated\n", sh->id, h.used, sh->snap_path); -} - -/* ------------------------------------------------------------ shard loop -- */ - -static void *shard_main(void *arg) { - struct shard *sh = arg; - struct ring *r = &sh->ring; - - cpu_set_t set; - CPU_ZERO(&set); - CPU_SET((unsigned)sh->id % (unsigned)sysconf(_SC_NPROCESSORS_ONLN), &set); - pthread_setaffinity_np(pthread_self(), sizeof set, &set); - - /* First load: disk → RAM, before any accept is armed. */ - long t0 = now_ms(); - int srows = snap_load(sh); - int wrecs = wal_replay(sh); - if (srows || wrecs) - printf("[wo-rt-c] shard %d: recovered %d snapshot rows + %d wal records in %ld ms\n", - sh->id, srows, wrecs, now_ms() - t0); - - arm_accept(sh); - arm_poll(sh, sh->evfd, OP_EVFD); - if (sh->id == 0) arm_poll(sh, sigfd, OP_SIGFD); - - for (;;) { - if (ring_enter(r, 1) < 0) break; /* ONE syscall per tick */ - - unsigned head = *r->cq_head; - unsigned tail = __atomic_load_n(r->cq_tail, __ATOMIC_ACQUIRE); - for (; head != tail; head++) { - struct io_uring_cqe *cqe = &r->cqes[head & *r->cq_mask]; - int op = (int)(cqe->user_data >> 32); - int fd = (int)(uint32_t)cqe->user_data; - int res = cqe->res; - - switch (op) { - case OP_EVFD: { - uint64_t v; - (void)!read(sh->evfd, &v, sizeof v); - __atomic_store_n(r->cq_head, head + 1, __ATOMIC_RELEASE); - return NULL; - } - case OP_SIGFD: { /* shard 0 only */ - struct signalfd_siginfo si; - if (read(sigfd, &si, sizeof si) == sizeof si) - printf("\n[wo-rt-c] signal %u — broadcasting shutdown to %d shards\n", - si.ssi_signo, n_threads); - uint64_t one = 1; - for (int t = 0; t < n_threads; t++) - (void)!write(shards[t].evfd, &one, sizeof one); - break; - } - case OP_ACCEPT: { - if (res >= 0) { - int cfd = res; - if (cfd >= MAX_FDS) close(cfd); - else { conn_open(sh, cfd); arm_recv(sh, cfd); } - } - if (!(cqe->flags & IORING_CQE_F_MORE)) arm_accept(sh); /* re-arm */ - break; - } - case OP_RECV: { - struct conn *c = &sh->conns[fd]; - if (!c->in_use) break; - if (res <= 0) { conn_close(sh, fd); break; } - c->in_len += (size_t)res; - conn_continue(sh, fd); - break; - } - case OP_SEND: { - struct conn *c = &sh->conns[fd]; - if (!c->in_use) break; - if (res <= 0) { conn_close(sh, fd); break; } - c->out_off += (size_t)res; - conn_continue(sh, fd); - break; - } - case OP_WALWR: { /* fd field carries the batch idx */ - struct wal_batch *b = &sh->batch[fd]; - if (res != (int)b->len) - fprintf(stderr, "[wo-rt-c] shard %d: WAL write %d != %zu\n", sh->id, res, b->len); - else - atomic_fetch_add_explicit(&sh->wal_off, b->len, memory_order_relaxed); - break; - } - case OP_FSYNC: { /* group commit lands: release acks */ - struct wal_batch *b = &sh->batch[fd]; - int failed = (res < 0); /* incl. -ECANCELED from a failed link */ - if (failed) - fprintf(stderr, "[wo-rt-c] shard %d: fsync failed (%d) — dropping %d acks\n", - sh->id, res, b->n_conns); - for (int k = 0; k < b->n_conns; k++) { - int cfd = b->conns[k]; - if (cfd < 0 || cfd >= MAX_FDS) continue; - struct conn *c = &sh->conns[cfd]; - if (!c->in_use || !c->await_durable) continue; - if (c->gen != b->gens[k]) continue; /* fd reused — not ours */ - c->await_durable = 0; - if (failed) conn_close(sh, cfd); /* never ack non-durable */ - else conn_continue(sh, cfd); - } - b->len = 0; - b->n_conns = 0; - sh->in_flight = 0; - break; - } - } - } - __atomic_store_n(r->cq_head, head, __ATOMIC_RELEASE); - wal_flush(sh); /* one write→fsync pair per tick */ - } - return NULL; -} - -/* ------------------------------------------------------------- listener -- */ - -static int listener_bind(uint16_t port) { - int fd = socket(AF_INET, SOCK_STREAM | SOCK_CLOEXEC, 0); - if (fd < 0) { perror("socket"); return -1; } - int one = 1; - setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &one, sizeof one); - setsockopt(fd, SOL_SOCKET, SO_REUSEPORT, &one, sizeof one); - struct sockaddr_in addr = {0}; - addr.sin_family = AF_INET; - addr.sin_port = htons(port); - addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK); - if (bind(fd, (struct sockaddr *)&addr, sizeof addr) < 0) { perror("bind"); close(fd); return -1; } - if (listen(fd, SOMAXCONN) < 0) { perror("listen"); close(fd); return -1; } - return fd; -} - -static int sig_setup(void) { - sigset_t mask; - sigemptyset(&mask); - sigaddset(&mask, SIGINT); - sigaddset(&mask, SIGTERM); - if (sigprocmask(SIG_BLOCK, &mask, NULL) < 0) { perror("sigprocmask"); return -1; } - int fd = signalfd(-1, &mask, SFD_NONBLOCK | SFD_CLOEXEC); - if (fd < 0) perror("signalfd"); - return fd; -} - -/* ------------------------------------------------------------ wal-check -- - * Offline frame walker: validates every record's len/CRC/COMMIT trailer, - * reports the count and where (if anywhere) the log tears. This is the - * crash test's witness, and the skeleton of phase E's replay loop. */ - -static int wal_check(const char *path) { - int fd = open(path, O_RDONLY); - if (fd < 0) { fprintf(stderr, "wal-check: %s: %s\n", path, strerror(errno)); return 1; } - size_t off = 0; - int recs = 0; - for (;;) { - uint32_t len, crc, end; - struct wal_payload p; - if (pread(fd, &len, 4, (off_t)off) != 4) break; - if (len == 0) break; /* fallocate'd tail */ - if (len != sizeof p) { - printf("%s: TORN at byte %zu (bad len %u) — %d whole records before it\n", - path, off, len, recs); - close(fd); - return 0; - } - if (pread(fd, &crc, 4, (off_t)(off + 4)) != 4 || - pread(fd, &p, sizeof p, (off_t)(off + 8)) != (ssize_t)sizeof p || - pread(fd, &end, 4, (off_t)(off + 8 + sizeof p)) != 4 || - crc32(&p, sizeof p) != crc || end != WAL_COMMIT) { - printf("%s: TORN at byte %zu (bad crc/trailer) — %d whole records before it\n", - path, off, recs); - close(fd); - return 0; - } - printf("%s: rec %d op=%u slot=%u id=%d title=\"%s\"\n", path, recs, p.op, p.slot, p.id, p.title); - recs++; - off += WAL_FRAME_BYTES; - } - printf("%s: %d records, all frames valid, clean tail at byte %zu\n", path, recs, off); - close(fd); - return 0; -} - -/* ----------------------------------------------------------------- main -- */ - -int main(int argc, char **argv) { - crc32_init(); - - if (argc >= 3 && !strcmp(argv[1], "wal-check")) { - int rc = 0; - for (int a = 2; a < argc; a++) rc |= wal_check(argv[a]); - return rc; - } - - uint16_t port = 8085; - const char *env = getenv("WO_PORT"); - if (env && atoi(env) > 0) port = (uint16_t)atoi(env); - - long cores = sysconf(_SC_NPROCESSORS_ONLN); - n_threads = (int)cores; - env = getenv("WO_THREADS"); - if (env && atoi(env) > 0) n_threads = atoi(env); - if (n_threads < 1) n_threads = 1; - if (n_threads > MAX_THREADS) n_threads = MAX_THREADS; - - /* Million-connection posture: lift the fd ceiling to the hard max. */ - struct rlimit rl; - if (getrlimit(RLIMIT_NOFILE, &rl) == 0 && rl.rlim_cur < rl.rlim_max) { - rl.rlim_cur = rl.rlim_max; - setrlimit(RLIMIT_NOFILE, &rl); - } - - sigfd = sig_setup(); - if (sigfd < 0) return 1; - if (arena_init(n_threads) < 0) return 1; - - const char *data_dir = getenv("WO_DATA"); - if (!data_dir || !*data_dir) data_dir = "./wo-data"; - if (mkdir(data_dir, 0755) < 0 && errno != EEXIST) { perror("mkdir data dir"); return 1; } - - /* The data dir is sharded for exactly n_threads. A different WO_THREADS - * would strand WAL/snapshot files silently — refuse (resharding = 09f). */ - char mpath[512]; - snprintf(mpath, sizeof mpath, "%s/meta", data_dir); - FILE *mf = fopen(mpath, "r"); - if (mf) { - int prev = 0; - if (fscanf(mf, "%d", &prev) == 1 && prev != n_threads) { - fprintf(stderr, "[wo-rt-c] %s was written with WO_THREADS=%d — restart with that, or wipe the dir\n", - data_dir, prev); - fclose(mf); - return 1; - } - fclose(mf); - } else if ((mf = fopen(mpath, "w"))) { - fprintf(mf, "%d\n", n_threads); - fclose(mf); - } - - shards = calloc((size_t)n_threads, sizeof(struct shard)); - if (!shards) { perror("calloc"); return 1; } - - for (int t = 0; t < n_threads; t++) { - struct shard *sh = &shards[t]; - sh->id = t; - sh->next_id = t + 1; - sh->lfd = listener_bind(port); - sh->evfd = eventfd(0, EFD_NONBLOCK | EFD_CLOEXEC); - if (sh->lfd < 0 || sh->evfd < 0) return 1; - if (ring_init(&sh->ring) < 0) return 1; - - /* Per-shard WAL, preallocated, NOT truncated — boot replays it. */ - char path[512]; - snprintf(path, sizeof path, "%s/shard-%d.wal", data_dir, t); - sh->wal_fd = open(path, O_RDWR | O_CREAT | O_CLOEXEC, 0644); - if (sh->wal_fd < 0) { perror("open wal"); return 1; } - if (fallocate(sh->wal_fd, 0, 0, WAL_PREALLOC) < 0) - fprintf(stderr, "[wo-rt-c] warn: fallocate %s refused (%s)\n", path, strerror(errno)); - snprintf(sh->snap_path, sizeof sh->snap_path, "%s/shard-%d.data", data_dir, t); - } - - printf("[wo-rt-c] %d shard%s on http://127.0.0.1:%u — io_uring loops, keep-alive — arena %zu KB (%s pages, %s) — ctrl-C to stop\n", - n_threads, n_threads == 1 ? "" : "s", port, - arena_map_bytes / 1024, - arena_huge ? "2M huge" : "4K", - arena_locked ? "mlocked" : "NOT locked"); - printf(" GET / runtime + arena info, per-shard stats\n GET /healthz liveness\n"); - printf(" GET /api/notes list (connection's shard)\n POST /api/notes create {\"title\":\"...\"}\n"); - fflush(stdout); - - for (int t = 0; t < n_threads; t++) - pthread_create(&shards[t].tid, NULL, shard_main, &shards[t]); - for (int t = 0; t < n_threads; t++) - pthread_join(shards[t].tid, NULL); - - /* Clean shutdown: persist each slice as a snapshot, truncate the WALs. - * A kill -9 skips this — that's what boot-time WAL replay is for. */ - for (int t = 0; t < n_threads; t++) - snap_write(&shards[t]); - - printf("[wo-rt-c] all %d shards joined — bye\n", n_threads); - for (int t = 0; t < n_threads; t++) { - close(shards[t].ring.fd); close(shards[t].lfd); close(shards[t].evfd); - close(shards[t].wal_fd); - } - close(sigfd); - munmap(arena, arena_map_bytes); - free(shards); - return 0; -} diff --git a/reference/README.md b/reference/README.md deleted file mode 100644 index 42c38fa..0000000 --- a/reference/README.md +++ /dev/null @@ -1,51 +0,0 @@ -# `reference/` — archived for reference - -Source material preserved alongside the active codebase. Nothing here is on the build path of the root workspace. - -## `reference/crates/` — v1 writeonce blog - -The original writeonce blog engine: 13 Rust crates implementing a flat-file content store, three sidecar indexes, a minimal HTTP server, and an `.htmlx` template layer. - -| Crate | Responsibility | -| --- | --- | -| `wo-model` | `Article` domain type + content loader | -| `wo-seg` | Append-only `.seg` binary file format (~475 LOC) | -| `wo-index` | `title.idx`, `date.idx`, `tags.idx` sidecar indexes | -| `wo-store` | Composes seg + indexes, exposes the query API | -| `wo-watch` | inotify-driven content ingest | -| `wo-event` | Domain event type | -| `wo-sub` | Subscription layer (pre-engine-native `LIVE`) | -| `wo-rt` | v1 runtime glue | -| `wo-http`, `wo-route`, `wo-serve` | HTTP server | -| `wo-htmlx` | `.htmlx` template engine | -| `wo-md` | Markdown rendering | - -It is a nested workspace. Build or test it standalone: - -```bash -cd reference/crates -cargo build -cargo test -``` - -See [docs/runtime/database/07-wo-seg-migration.md](../docs/runtime/database/07-wo-seg-migration.md) for the plan to replace these crates with the new `.wo` runtime at `crates/wo-rt/` via phased coexistence. The v1 codebase is the source that migration reads from. - -## `reference/writeonce-api/` and `reference/writeonce-app/` - -Earlier exploration snapshots that predate the v1 crates. Self-contained; no active build wiring. - -## Research source trees (symlinks) - -Gitignored, user-specific absolute paths — each contributor sets their own: - -```bash -ln -s reference/linux -ln -s reference/go -ln -s reference/postgresql -ln -s reference/mcp-python-sdk # https://github.com/modelcontextprotocol/python-sdk.git -``` - -- **`reference/linux/`** — the Linux kernel source. Read `io_uring/`, `fs/notify/inotify/`, `kernel/eventfd.c`, `include/uapi/linux/*.h` when designing kernel-primitive modules. See per-primitive reference cards under [`docs/plan/linux/`](../docs/plan/linux/). -- **`reference/go/`** — the Go source tree. Read `src/runtime/netpoll_epoll.go`, `src/runtime/netpoll.go`, `src/runtime/asm_*.s`, `src/runtime/sys_linux_*.s` when designing the runtime layer — writeonce's `crates/rt/src/runtime/` mirrors Go's `src/runtime/` file-per-flavour naming. See [`docs/plan/assembly/`](../docs/plan/assembly/) for the asm policy doc that cites this tree. -- **`reference/postgresql/`** — the PostgreSQL source tree, for database-engine research. -- **`reference/mcp-python-sdk/`** — the official MCP Python SDK, the reference implementation for [plan 15](../docs/plan/15-mcp-streamable-http.md) (MCP over Streamable HTTP). Read `src/mcp/server/streamable_http.py` (transport server: POST/GET handling, SSE framing, session validation, `Last-Event-ID` replay) and `src/mcp/server/streamable_http_manager.py` (session lifecycle) when implementing the server side; `src/mcp/client/streamable_http.py` shows what a conforming client expects. writeonce ports the *behaviour*, not the code — the Rust implementation stays hand-rolled per the zero-deps doctrine. diff --git a/reference/crates/.gitignore b/reference/crates/.gitignore deleted file mode 100644 index fd3cd53..0000000 --- a/reference/crates/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -# Nested v1 workspace — has its own Cargo.lock and target dir. -/target -Cargo.lock diff --git a/reference/crates/Cargo.toml b/reference/crates/Cargo.toml deleted file mode 100644 index 72cffa8..0000000 --- a/reference/crates/Cargo.toml +++ /dev/null @@ -1,40 +0,0 @@ -# Nested workspace for the v1 writeonce blog crates. -# -# The root workspace at /Cargo.toml drives the new `.wo` runtime at crates/wo-rt. -# These thirteen crates are the previous generation of writeonce — the flat-file -# blog engine described in docs/runtime/database/07-wo-seg-migration.md. They -# are preserved here for reference: source is readable, tests still run, and -# the Phase 7 migration plan assumes a gradual trait-abstracted coexistence -# rather than deletion. -# -# To build the v1 system standalone: -# cd reference/crates && cargo build -# cd reference/crates && cargo test -# -# The v1 crates are deliberately NOT members of the root workspace. Excluding -# them avoids the `wo-rt` name collision (both v1 and v2 claim that name) and -# keeps the root build fast while the new runtime is under active development. - -[workspace] -resolver = "2" -members = [ - "wo-model", - "wo-seg", - "wo-index", - "wo-store", - "wo-watch", - "wo-event", - "wo-sub", - "wo-rt", - "wo-http", - "wo-route", - "wo-htmlx", - "wo-md", - "wo-serve", -] - -[workspace.dependencies] -serde = { version = "1", features = ["derive"] } -serde_json = "1" -bincode = "1" -libc = "0.2" diff --git a/reference/crates/wo-event/Cargo.toml b/reference/crates/wo-event/Cargo.toml deleted file mode 100644 index fc76bc0..0000000 --- a/reference/crates/wo-event/Cargo.toml +++ /dev/null @@ -1,7 +0,0 @@ -[package] -name = "wo-event" -version = "0.1.0" -edition = "2021" - -[dependencies] -libc = { workspace = true } diff --git a/reference/crates/wo-event/src/epoll.rs b/reference/crates/wo-event/src/epoll.rs deleted file mode 100644 index 8ac5e53..0000000 --- a/reference/crates/wo-event/src/epoll.rs +++ /dev/null @@ -1,183 +0,0 @@ -use std::io; -use std::os::unix::io::RawFd; -use std::time::Duration; - -/// Caller-assigned identifier for a registered file descriptor. -pub type Token = u64; - -/// Interest flags for epoll registration. -#[derive(Debug, Clone, Copy)] -pub enum Interest { - Readable, - Writable, - ReadWrite, -} - -impl Interest { - fn to_epoll_flags(self) -> u32 { - match self { - Interest::Readable => libc::EPOLLIN as u32, - Interest::Writable => libc::EPOLLOUT as u32, - Interest::ReadWrite => (libc::EPOLLIN | libc::EPOLLOUT) as u32, - } - } -} - -/// An event delivered by the event loop. -#[derive(Debug, Clone)] -pub struct Event { - pub token: Token, - pub readable: bool, - pub writable: bool, - pub error: bool, - pub hangup: bool, -} - -/// Single-threaded event loop built on epoll. -pub struct EventLoop { - epoll_fd: RawFd, -} - -impl EventLoop { - /// Create a new event loop. - pub fn new() -> io::Result { - let fd = unsafe { libc::epoll_create1(libc::EPOLL_CLOEXEC) }; - if fd < 0 { - return Err(io::Error::last_os_error()); - } - Ok(Self { epoll_fd: fd }) - } - - /// Register a file descriptor for the given interest. - pub fn register(&self, fd: RawFd, interest: Interest, token: Token) -> io::Result<()> { - let mut event = libc::epoll_event { - events: interest.to_epoll_flags() | libc::EPOLLRDHUP as u32, - u64: token, - }; - let ret = unsafe { libc::epoll_ctl(self.epoll_fd, libc::EPOLL_CTL_ADD, fd, &mut event) }; - if ret < 0 { - Err(io::Error::last_os_error()) - } else { - Ok(()) - } - } - - /// Modify interest for an already-registered fd. - pub fn modify(&self, fd: RawFd, interest: Interest, token: Token) -> io::Result<()> { - let mut event = libc::epoll_event { - events: interest.to_epoll_flags() | libc::EPOLLRDHUP as u32, - u64: token, - }; - let ret = unsafe { libc::epoll_ctl(self.epoll_fd, libc::EPOLL_CTL_MOD, fd, &mut event) }; - if ret < 0 { - Err(io::Error::last_os_error()) - } else { - Ok(()) - } - } - - /// Remove a file descriptor from the event loop. - pub fn deregister(&self, fd: RawFd) -> io::Result<()> { - let ret = unsafe { - libc::epoll_ctl(self.epoll_fd, libc::EPOLL_CTL_DEL, fd, std::ptr::null_mut()) - }; - if ret < 0 { - Err(io::Error::last_os_error()) - } else { - Ok(()) - } - } - - /// Wait for events. Returns when at least one event is ready or timeout expires. - /// - /// `timeout`: `None` blocks indefinitely, `Some(duration)` sets a timeout. - /// Returns up to 64 events per call. - pub fn poll(&self, timeout: Option) -> io::Result> { - let timeout_ms = match timeout { - None => -1i32, - Some(d) => d.as_millis() as i32, - }; - - let mut events = [libc::epoll_event { events: 0, u64: 0 }; 64]; - let n = unsafe { - libc::epoll_wait(self.epoll_fd, events.as_mut_ptr(), events.len() as i32, timeout_ms) - }; - - if n < 0 { - let err = io::Error::last_os_error(); - // EINTR is not an error — just return empty. - if err.raw_os_error() == Some(libc::EINTR) { - return Ok(vec![]); - } - return Err(err); - } - - let result = (0..n as usize) - .map(|i| { - let e = events[i].events; - Event { - token: events[i].u64, - readable: (e & libc::EPOLLIN as u32) != 0, - writable: (e & libc::EPOLLOUT as u32) != 0, - error: (e & libc::EPOLLERR as u32) != 0, - hangup: (e & (libc::EPOLLHUP | libc::EPOLLRDHUP) as u32) != 0, - } - }) - .collect(); - - Ok(result) - } - - /// Get the raw epoll file descriptor. - pub fn fd(&self) -> RawFd { - self.epoll_fd - } -} - -impl Drop for EventLoop { - fn drop(&mut self) { - unsafe { libc::close(self.epoll_fd) }; - } -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::EventFd; - - #[test] - fn register_and_poll_eventfd() { - let eloop = EventLoop::new().unwrap(); - let efd = EventFd::new().unwrap(); - - eloop.register(efd.fd(), Interest::Readable, 42).unwrap(); - - // Write to eventfd from the same thread. - efd.write(1).unwrap(); - - let events = eloop.poll(Some(Duration::from_millis(100))).unwrap(); - assert_eq!(events.len(), 1); - assert_eq!(events[0].token, 42); - assert!(events[0].readable); - } - - #[test] - fn poll_timeout_no_events() { - let eloop = EventLoop::new().unwrap(); - let events = eloop.poll(Some(Duration::from_millis(10))).unwrap(); - assert!(events.is_empty()); - } - - #[test] - fn deregister() { - let eloop = EventLoop::new().unwrap(); - let efd = EventFd::new().unwrap(); - - eloop.register(efd.fd(), Interest::Readable, 1).unwrap(); - eloop.deregister(efd.fd()).unwrap(); - - efd.write(1).unwrap(); - let events = eloop.poll(Some(Duration::from_millis(10))).unwrap(); - assert!(events.is_empty()); - } -} diff --git a/reference/crates/wo-event/src/eventfd.rs b/reference/crates/wo-event/src/eventfd.rs deleted file mode 100644 index 1c8ec59..0000000 --- a/reference/crates/wo-event/src/eventfd.rs +++ /dev/null @@ -1,66 +0,0 @@ -use std::io; -use std::os::unix::io::RawFd; - -/// Wrapper around Linux eventfd(2) for lightweight signaling. -pub struct EventFd { - fd: RawFd, -} - -impl EventFd { - /// Create a new eventfd with initial value 0. - pub fn new() -> io::Result { - let fd = unsafe { libc::eventfd(0, libc::EFD_NONBLOCK | libc::EFD_CLOEXEC) }; - if fd < 0 { - return Err(io::Error::last_os_error()); - } - Ok(Self { fd }) - } - - /// Write a value to the eventfd (signals waiters). - pub fn write(&self, val: u64) -> io::Result<()> { - let buf = val.to_ne_bytes(); - let ret = unsafe { libc::write(self.fd, buf.as_ptr() as *const libc::c_void, 8) }; - if ret < 0 { - Err(io::Error::last_os_error()) - } else { - Ok(()) - } - } - - /// Read the current value (resets to 0). - pub fn read(&self) -> io::Result { - let mut buf = [0u8; 8]; - let ret = unsafe { libc::read(self.fd, buf.as_mut_ptr() as *mut libc::c_void, 8) }; - if ret < 0 { - Err(io::Error::last_os_error()) - } else { - Ok(u64::from_ne_bytes(buf)) - } - } - - /// Get the raw file descriptor for epoll registration. - pub fn fd(&self) -> RawFd { - self.fd - } -} - -impl Drop for EventFd { - fn drop(&mut self) { - unsafe { libc::close(self.fd) }; - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn write_and_read() { - let efd = EventFd::new().unwrap(); - efd.write(5).unwrap(); - efd.write(3).unwrap(); - // eventfd accumulates writes. - let val = efd.read().unwrap(); - assert_eq!(val, 8); - } -} diff --git a/reference/crates/wo-event/src/lib.rs b/reference/crates/wo-event/src/lib.rs deleted file mode 100644 index 59103a2..0000000 --- a/reference/crates/wo-event/src/lib.rs +++ /dev/null @@ -1,9 +0,0 @@ -mod epoll; -mod eventfd; -mod timerfd; -mod signalfd; - -pub use epoll::{EventLoop, Event, Interest, Token}; -pub use eventfd::EventFd; -pub use timerfd::TimerFd; -pub use signalfd::SignalFd; diff --git a/reference/crates/wo-event/src/signalfd.rs b/reference/crates/wo-event/src/signalfd.rs deleted file mode 100644 index b1ef44f..0000000 --- a/reference/crates/wo-event/src/signalfd.rs +++ /dev/null @@ -1,62 +0,0 @@ -use std::io; -use std::os::unix::io::RawFd; - -/// Wrapper around Linux signalfd(2) for handling signals via fd. -/// -/// Blocks SIGINT and SIGTERM in the process signal mask and delivers -/// them as readable events on the fd instead. -pub struct SignalFd { - fd: RawFd, -} - -impl SignalFd { - /// Create a signalfd that catches SIGINT and SIGTERM. - /// - /// Also blocks these signals in the process signal mask so they - /// don't trigger default handlers. - pub fn new() -> io::Result { - let mut mask: libc::sigset_t = unsafe { std::mem::zeroed() }; - unsafe { - libc::sigemptyset(&mut mask); - libc::sigaddset(&mut mask, libc::SIGINT); - libc::sigaddset(&mut mask, libc::SIGTERM); - - // Block these signals so they go to signalfd instead. - let ret = libc::pthread_sigmask(libc::SIG_BLOCK, &mask, std::ptr::null_mut()); - if ret != 0 { - return Err(io::Error::from_raw_os_error(ret)); - } - } - - let fd = unsafe { libc::signalfd(-1, &mask, libc::SFD_NONBLOCK | libc::SFD_CLOEXEC) }; - if fd < 0 { - return Err(io::Error::last_os_error()); - } - - Ok(Self { fd }) - } - - /// Read a pending signal. Returns the signal number (e.g., SIGINT = 2). - pub fn read(&self) -> io::Result { - let mut info: libc::signalfd_siginfo = unsafe { std::mem::zeroed() }; - let size = std::mem::size_of::(); - let ret = unsafe { - libc::read(self.fd, &mut info as *mut _ as *mut libc::c_void, size) - }; - if ret < 0 { - Err(io::Error::last_os_error()) - } else { - Ok(info.ssi_signo as i32) - } - } - - pub fn fd(&self) -> RawFd { - self.fd - } -} - -impl Drop for SignalFd { - fn drop(&mut self) { - unsafe { libc::close(self.fd) }; - } -} diff --git a/reference/crates/wo-event/src/timerfd.rs b/reference/crates/wo-event/src/timerfd.rs deleted file mode 100644 index a89a2ff..0000000 --- a/reference/crates/wo-event/src/timerfd.rs +++ /dev/null @@ -1,91 +0,0 @@ -use std::io; -use std::os::unix::io::RawFd; -use std::time::Duration; - -/// Wrapper around Linux timerfd_create(2) for timer-as-fd. -pub struct TimerFd { - fd: RawFd, -} - -impl TimerFd { - /// Create a new monotonic timer fd. - pub fn new() -> io::Result { - let fd = unsafe { - libc::timerfd_create( - libc::CLOCK_MONOTONIC, - libc::TFD_NONBLOCK | libc::TFD_CLOEXEC, - ) - }; - if fd < 0 { - return Err(io::Error::last_os_error()); - } - Ok(Self { fd }) - } - - /// Arm the timer to fire once after `initial` and then repeat every `interval`. - /// - /// Pass `Duration::ZERO` for `interval` for a one-shot timer. - pub fn set(&self, initial: Duration, interval: Duration) -> io::Result<()> { - let spec = libc::itimerspec { - it_interval: duration_to_timespec(interval), - it_value: duration_to_timespec(initial), - }; - let ret = - unsafe { libc::timerfd_settime(self.fd, 0, &spec, std::ptr::null_mut()) }; - if ret < 0 { - Err(io::Error::last_os_error()) - } else { - Ok(()) - } - } - - /// Read the number of expirations since last read. - pub fn read(&self) -> io::Result { - let mut buf = [0u8; 8]; - let ret = unsafe { libc::read(self.fd, buf.as_mut_ptr() as *mut libc::c_void, 8) }; - if ret < 0 { - Err(io::Error::last_os_error()) - } else { - Ok(u64::from_ne_bytes(buf)) - } - } - - pub fn fd(&self) -> RawFd { - self.fd - } -} - -impl Drop for TimerFd { - fn drop(&mut self) { - unsafe { libc::close(self.fd) }; - } -} - -fn duration_to_timespec(d: Duration) -> libc::timespec { - libc::timespec { - tv_sec: d.as_secs() as libc::time_t, - tv_nsec: d.subsec_nanos() as libc::c_long, - } -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::{EventLoop, Interest}; - - #[test] - fn timer_fires() { - let eloop = EventLoop::new().unwrap(); - let timer = TimerFd::new().unwrap(); - - timer.set(Duration::from_millis(20), Duration::ZERO).unwrap(); - eloop.register(timer.fd(), Interest::Readable, 99).unwrap(); - - let events = eloop.poll(Some(Duration::from_millis(100))).unwrap(); - assert!(!events.is_empty()); - assert_eq!(events[0].token, 99); - - let expirations = timer.read().unwrap(); - assert!(expirations >= 1); - } -} diff --git a/reference/crates/wo-htmlx/Cargo.toml b/reference/crates/wo-htmlx/Cargo.toml deleted file mode 100644 index 503754d..0000000 --- a/reference/crates/wo-htmlx/Cargo.toml +++ /dev/null @@ -1,13 +0,0 @@ -[package] -name = "wo-htmlx" -version = "0.1.0" -edition = "2021" - -[dependencies] -wo-model = { path = "../wo-model" } -wo-md = { path = "../wo-md" } -serde = { workspace = true } -serde_json = { workspace = true } - -[dev-dependencies] -tempfile = "3" diff --git a/reference/crates/wo-htmlx/src/ast.rs b/reference/crates/wo-htmlx/src/ast.rs deleted file mode 100644 index 81d698a..0000000 --- a/reference/crates/wo-htmlx/src/ast.rs +++ /dev/null @@ -1,18 +0,0 @@ -/// A parsed template node. -#[derive(Debug, Clone, PartialEq)] -pub enum Node { - /// Raw HTML passthrough. - Literal(String), - /// Variable binding: `{{path.to.value}}` - Binding(Vec), - /// Iteration: `{{#each path}} ... {{/each}}` - Each { - path: Vec, - body: Vec, - }, - /// Partial inclusion: `{{> name arg=value}}` - Partial { - name: String, - args: Vec<(String, Vec)>, - }, -} diff --git a/reference/crates/wo-htmlx/src/lib.rs b/reference/crates/wo-htmlx/src/lib.rs deleted file mode 100644 index 4de9163..0000000 --- a/reference/crates/wo-htmlx/src/lib.rs +++ /dev/null @@ -1,11 +0,0 @@ -mod ast; -mod parser; -mod render; -mod registry; -mod value; - -pub use ast::Node; -pub use parser::parse; -pub use render::render; -pub use registry::TemplateRegistry; -pub use value::Value; diff --git a/reference/crates/wo-htmlx/src/parser.rs b/reference/crates/wo-htmlx/src/parser.rs deleted file mode 100644 index 98d447c..0000000 --- a/reference/crates/wo-htmlx/src/parser.rs +++ /dev/null @@ -1,173 +0,0 @@ -use crate::ast::Node; - -/// Parse a template string into a list of AST nodes. -pub fn parse(template: &str) -> Vec { - let mut nodes = Vec::new(); - let mut rest = template; - - while !rest.is_empty() { - if let Some(pos) = rest.find("{{") { - // Literal before the opening `{{`. - if pos > 0 { - nodes.push(Node::Literal(rest[..pos].to_string())); - } - - let after_open = &rest[pos + 2..]; - - if let Some(close) = after_open.find("}}") { - let expr = after_open[..close].trim(); - rest = &after_open[close + 2..]; - - if let Some(each_path) = expr.strip_prefix("#each ") { - // Block: {{#each path}} ... {{/each}} - let path = parse_path(each_path.trim()); - let (body, remaining) = parse_until_end_each(rest); - nodes.push(Node::Each { path, body }); - rest = remaining; - } else if let Some(partial_expr) = expr.strip_prefix("> ") { - // Partial: {{> name arg=value}} - let (name, args) = parse_partial_expr(partial_expr.trim()); - nodes.push(Node::Partial { name, args }); - } else if !expr.starts_with('/') { - // Binding: {{path.to.value}} - let path = parse_path(expr); - nodes.push(Node::Binding(path)); - } - // {{/each}} handled by parse_until_end_each - } else { - // No closing `}}` — treat rest as literal. - nodes.push(Node::Literal(rest.to_string())); - break; - } - } else { - // No more `{{` — rest is literal. - nodes.push(Node::Literal(rest.to_string())); - break; - } - } - - nodes -} - -fn parse_path(s: &str) -> Vec { - s.split('.').map(|p| p.trim().to_string()).collect() -} - -fn parse_until_end_each(input: &str) -> (Vec, &str) { - // Find the matching {{/each}}. - let mut depth = 1; - let mut pos = 0; - let bytes = input.as_bytes(); - - while pos < bytes.len() { - if let Some(open) = input[pos..].find("{{") { - let abs = pos + open; - let after = &input[abs + 2..]; - if let Some(close) = after.find("}}") { - let expr = after[..close].trim(); - if expr.starts_with("#each ") { - depth += 1; - } else if expr == "/each" { - depth -= 1; - if depth == 0 { - let body_str = &input[..abs]; - let rest = &after[close + 2..]; - return (parse(body_str), rest); - } - } - pos = abs + 2 + close + 2; - } else { - break; - } - } else { - break; - } - } - - // Unmatched {{#each}} — return rest as literal body. - (vec![Node::Literal(input.to_string())], "") -} - -fn parse_partial_expr(expr: &str) -> (String, Vec<(String, Vec)>) { - let parts: Vec<&str> = expr.splitn(2, ' ').collect(); - let name = parts[0].to_string(); - let mut args = Vec::new(); - - if parts.len() > 1 { - for arg in parts[1].split_whitespace() { - if let Some((key, val)) = arg.split_once('=') { - args.push((key.to_string(), parse_path(val))); - } - } - } - - (name, args) -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::ast::Node; - - #[test] - fn parse_literal() { - let nodes = parse("

Hello

"); - assert_eq!(nodes, vec![Node::Literal("

Hello

".into())]); - } - - #[test] - fn parse_binding() { - let nodes = parse("{{article.title}}"); - assert_eq!(nodes, vec![Node::Binding(vec!["article".into(), "title".into()])]); - } - - #[test] - fn parse_mixed() { - let nodes = parse("

{{title}}

"); - assert_eq!(nodes.len(), 3); - assert_eq!(nodes[0], Node::Literal("

".into())); - assert_eq!(nodes[1], Node::Binding(vec!["title".into()])); - assert_eq!(nodes[2], Node::Literal("

".into())); - } - - #[test] - fn parse_each() { - let nodes = parse("{{#each items}}
  • {{this}}
  • {{/each}}"); - assert_eq!(nodes.len(), 1); - match &nodes[0] { - Node::Each { path, body } => { - assert_eq!(path, &vec!["items".to_string()]); - assert_eq!(body.len(), 3); - } - _ => panic!("expected Each"), - } - } - - #[test] - fn parse_partial() { - let nodes = parse("{{> article-card article=this}}"); - assert_eq!(nodes.len(), 1); - match &nodes[0] { - Node::Partial { name, args } => { - assert_eq!(name, "article-card"); - assert_eq!(args.len(), 1); - assert_eq!(args[0].0, "article"); - assert_eq!(args[0].1, vec!["this".to_string()]); - } - _ => panic!("expected Partial"), - } - } - - #[test] - fn parse_nested_each() { - let nodes = parse("{{#each sections}}{{#each paragraphs}}{{this}}{{/each}}{{/each}}"); - assert_eq!(nodes.len(), 1); - match &nodes[0] { - Node::Each { body, .. } => { - assert_eq!(body.len(), 1); - assert!(matches!(&body[0], Node::Each { .. })); - } - _ => panic!("expected nested Each"), - } - } -} diff --git a/reference/crates/wo-htmlx/src/registry.rs b/reference/crates/wo-htmlx/src/registry.rs deleted file mode 100644 index 8c7d7c8..0000000 --- a/reference/crates/wo-htmlx/src/registry.rs +++ /dev/null @@ -1,121 +0,0 @@ -use std::collections::HashMap; -use std::fs; -use std::io; -use std::path::Path; - -use crate::ast::Node; -use crate::parser; - -/// Holds parsed templates and partials, loaded from a templates directory. -pub struct TemplateRegistry { - /// Page templates (e.g., "home" -> parsed nodes from home.htmlx). - templates: HashMap>, - /// Component partials (e.g., "article-card" -> parsed nodes). - partials: HashMap>, -} - -impl TemplateRegistry { - /// Load all .htmlx files from the given templates directory. - /// - /// Templates in the root are page templates. - /// Templates in `components/` are partials. - pub fn load(templates_dir: &Path) -> io::Result { - let mut templates = HashMap::new(); - let mut partials = HashMap::new(); - - // Load page templates from root. - if templates_dir.exists() { - for entry in fs::read_dir(templates_dir)? { - let entry = entry?; - let path = entry.path(); - if path.is_file() && has_htmlx_ext(&path) { - let name = stem(&path); - let content = fs::read_to_string(&path)?; - templates.insert(name, parser::parse(&content)); - } - } - } - - // Load partials from components/. - let components_dir = templates_dir.join("components"); - if components_dir.exists() { - for entry in fs::read_dir(&components_dir)? { - let entry = entry?; - let path = entry.path(); - if path.is_file() && has_htmlx_ext(&path) { - let name = stem(&path); - let content = fs::read_to_string(&path)?; - partials.insert(name, parser::parse(&content)); - } - } - } - - Ok(Self { templates, partials }) - } - - /// Get a page template by name. - pub fn get(&self, name: &str) -> Option<&Vec> { - self.templates.get(name) - } - - /// Get the partials map (for passing to render()). - pub fn partials(&self) -> &HashMap> { - &self.partials - } - - /// Number of loaded templates. - pub fn template_count(&self) -> usize { - self.templates.len() - } - - /// Number of loaded partials. - pub fn partial_count(&self) -> usize { - self.partials.len() - } -} - -fn has_htmlx_ext(path: &Path) -> bool { - path.extension().and_then(|e| e.to_str()) == Some("htmlx") -} - -fn stem(path: &Path) -> String { - path.file_stem() - .and_then(|s| s.to_str()) - .unwrap_or("") - .to_string() -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn load_templates() { - let tmp = tempfile::tempdir().unwrap(); - let dir = tmp.path(); - - fs::write(dir.join("home.htmlx"), "

    {{title}}

    ").unwrap(); - fs::write(dir.join("article.htmlx"), "
    {{body}}
    ").unwrap(); - - fs::create_dir_all(dir.join("components")).unwrap(); - fs::write( - dir.join("components/card.htmlx"), - "
    {{name}}
    ", - ) - .unwrap(); - - let reg = TemplateRegistry::load(dir).unwrap(); - assert_eq!(reg.template_count(), 2); - assert_eq!(reg.partial_count(), 1); - assert!(reg.get("home").is_some()); - assert!(reg.get("article").is_some()); - assert!(reg.partials().contains_key("card")); - } - - #[test] - fn empty_directory() { - let tmp = tempfile::tempdir().unwrap(); - let reg = TemplateRegistry::load(tmp.path()).unwrap(); - assert_eq!(reg.template_count(), 0); - } -} diff --git a/reference/crates/wo-htmlx/src/render.rs b/reference/crates/wo-htmlx/src/render.rs deleted file mode 100644 index 16deb51..0000000 --- a/reference/crates/wo-htmlx/src/render.rs +++ /dev/null @@ -1,140 +0,0 @@ -use std::collections::BTreeMap; - -use crate::ast::Node; -use crate::value::Value; - -/// Render a list of AST nodes against a context value. -/// -/// `partials` maps partial names to their parsed node lists. -pub fn render( - nodes: &[Node], - context: &Value, - partials: &std::collections::HashMap>, -) -> String { - let mut output = String::new(); - - for node in nodes { - match node { - Node::Literal(text) => { - output.push_str(text); - } - Node::Binding(path) => { - if path.len() == 1 && path[0] == "this" { - output.push_str(&context.to_display()); - } else { - let val = context.resolve(path); - output.push_str(&val.to_display()); - } - } - Node::Each { path, body } => { - let list_val = if path.len() == 1 && path[0] == "this" { - context - } else { - context.resolve(path) - }; - - for item in list_val.as_list() { - output.push_str(&render(body, item, partials)); - } - } - Node::Partial { name, args } => { - if let Some(partial_nodes) = partials.get(name.as_str()) { - // Build the partial context from args. - let partial_ctx = if args.is_empty() { - context.clone() - } else { - let mut map = BTreeMap::new(); - for (key, path) in args { - let val = if path.len() == 1 && path[0] == "this" { - context.clone() - } else { - context.resolve(path).clone() - }; - map.insert(key.clone(), val); - } - Value::Object(map) - }; - output.push_str(&render(partial_nodes, &partial_ctx, partials)); - } - } - } - } - - output -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::parser; - use std::collections::HashMap; - - fn ctx(json: serde_json::Value) -> Value { - Value::from_json(&json) - } - - #[test] - fn render_literal() { - let nodes = parser::parse("

    Hello

    "); - let result = render(&nodes, &Value::Null, &HashMap::new()); - assert_eq!(result, "

    Hello

    "); - } - - #[test] - fn render_binding() { - let nodes = parser::parse("

    {{title}}

    "); - let context = ctx(serde_json::json!({"title": "My Article"})); - let result = render(&nodes, &context, &HashMap::new()); - assert_eq!(result, "

    My Article

    "); - } - - #[test] - fn render_nested_binding() { - let nodes = parser::parse("by {{article.author}}"); - let context = ctx(serde_json::json!({"article": {"author": "Shoney"}})); - let result = render(&nodes, &context, &HashMap::new()); - assert_eq!(result, "by Shoney"); - } - - #[test] - fn render_each() { - let nodes = parser::parse("
      {{#each tags}}
    • {{this}}
    • {{/each}}
    "); - let context = ctx(serde_json::json!({"tags": ["rust", "linux"]})); - let result = render(&nodes, &context, &HashMap::new()); - assert_eq!(result, "
    • rust
    • linux
    "); - } - - #[test] - fn render_each_objects() { - let nodes = parser::parse("{{#each sections}}

    {{heading}}

    {{/each}}"); - let context = ctx(serde_json::json!({ - "sections": [ - {"heading": "Intro"}, - {"heading": "Details"} - ] - })); - let result = render(&nodes, &context, &HashMap::new()); - assert_eq!(result, "

    Intro

    Details

    "); - } - - #[test] - fn render_partial() { - let nodes = parser::parse("{{> card item=this}}"); - let mut partials = HashMap::new(); - partials.insert( - "card".to_string(), - parser::parse("
    {{item.name}}
    "), - ); - let context = ctx(serde_json::json!({"name": "Test"})); - let result = render(&nodes, &context, &partials); - assert_eq!(result, "
    Test
    "); - } - - #[test] - fn render_missing_binding() { - let nodes = parser::parse("{{nonexistent}}"); - let context = ctx(serde_json::json!({})); - let result = render(&nodes, &context, &HashMap::new()); - assert_eq!(result, ""); - } -} diff --git a/reference/crates/wo-htmlx/src/value.rs b/reference/crates/wo-htmlx/src/value.rs deleted file mode 100644 index 18772e3..0000000 --- a/reference/crates/wo-htmlx/src/value.rs +++ /dev/null @@ -1,122 +0,0 @@ -use std::collections::BTreeMap; - -/// Template context value — the data that template bindings resolve against. -#[derive(Debug, Clone, PartialEq)] -pub enum Value { - String(String), - Number(f64), - Bool(bool), - List(Vec), - Object(BTreeMap), - Null, -} - -impl Value { - /// Resolve a dotted path like ["article", "title"] against this value. - pub fn resolve(&self, path: &[String]) -> &Value { - let mut current = self; - for key in path { - match current { - Value::Object(map) => { - current = map.get(key.as_str()).unwrap_or(&Value::Null); - } - _ => return &Value::Null, - } - } - current - } - - /// Convert to a display string for template output. - pub fn to_display(&self) -> String { - match self { - Value::String(s) => s.clone(), - Value::Number(n) => { - if *n == (*n as i64) as f64 { - format!("{}", *n as i64) - } else { - format!("{}", n) - } - } - Value::Bool(b) => b.to_string(), - Value::Null => String::new(), - Value::List(items) => { - let parts: Vec = items.iter().map(|v| v.to_display()).collect(); - parts.join(", ") - } - Value::Object(_) => "[object]".to_string(), - } - } - - /// Convert to a list for iteration (returns empty vec if not a list). - pub fn as_list(&self) -> &[Value] { - match self { - Value::List(items) => items, - _ => &[], - } - } - - /// Convert a serde_json::Value to a template Value. - pub fn from_json(json: &serde_json::Value) -> Self { - match json { - serde_json::Value::Null => Value::Null, - serde_json::Value::Bool(b) => Value::Bool(*b), - serde_json::Value::Number(n) => Value::Number(n.as_f64().unwrap_or(0.0)), - serde_json::Value::String(s) => Value::String(s.clone()), - serde_json::Value::Array(arr) => { - Value::List(arr.iter().map(Value::from_json).collect()) - } - serde_json::Value::Object(map) => { - let btree = map - .iter() - .map(|(k, v)| (k.clone(), Value::from_json(v))) - .collect(); - Value::Object(btree) - } - } - } -} - -/// Convert an Article to a template Value using serde_json as intermediary. -pub fn article_to_value(article: &wo_model::Article) -> Value { - let json = serde_json::to_value(article).unwrap_or(serde_json::Value::Null); - Value::from_json(&json) -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn resolve_path() { - let mut inner = BTreeMap::new(); - inner.insert("title".into(), Value::String("Hello".into())); - let mut root = BTreeMap::new(); - root.insert("article".into(), Value::Object(inner)); - let val = Value::Object(root); - - let result = val.resolve(&["article".into(), "title".into()]); - assert_eq!(result, &Value::String("Hello".into())); - } - - #[test] - fn resolve_missing() { - let val = Value::Object(BTreeMap::new()); - assert_eq!(val.resolve(&["nope".into()]), &Value::Null); - } - - #[test] - fn from_json() { - let json: serde_json::Value = serde_json::json!({ - "name": "test", - "count": 42, - "active": true, - "tags": ["a", "b"] - }); - let val = Value::from_json(&json); - assert_eq!( - val.resolve(&["name".into()]), - &Value::String("test".into()) - ); - assert_eq!(val.resolve(&["count".into()]), &Value::Number(42.0)); - } -} diff --git a/reference/crates/wo-http/Cargo.toml b/reference/crates/wo-http/Cargo.toml deleted file mode 100644 index a16d575..0000000 --- a/reference/crates/wo-http/Cargo.toml +++ /dev/null @@ -1,10 +0,0 @@ -[package] -name = "wo-http" -version = "0.1.0" -edition = "2021" - -[dependencies] -libc = { workspace = true } - -[dev-dependencies] -tempfile = "3" diff --git a/reference/crates/wo-http/src/connection.rs b/reference/crates/wo-http/src/connection.rs deleted file mode 100644 index 53d495a..0000000 --- a/reference/crates/wo-http/src/connection.rs +++ /dev/null @@ -1,202 +0,0 @@ -use std::io; -use std::os::unix::io::RawFd; - -use crate::request::{self, ParseResult, Request}; -use crate::response::Response; - -/// Connection state machine. -#[derive(Debug, Clone, Copy, PartialEq)] -pub enum State { - /// Accumulating request bytes. - ReadingRequest, - /// Writing response bytes. - WritingResponse, - /// Initial response sent, fd handed to subscription manager. - /// Connection stays open for diff payloads. - Subscribed, - /// Ready to close. - Done, -} - -/// A single HTTP connection. -pub struct Connection { - pub fd: RawFd, - pub state: State, - read_buf: Vec, - write_buf: Vec, - write_offset: usize, - parsed_request: Option, -} - -impl Connection { - pub fn new(fd: RawFd) -> Self { - Self { - fd, - state: State::ReadingRequest, - read_buf: Vec::with_capacity(4096), - write_buf: Vec::new(), - write_offset: 0, - parsed_request: None, - } - } - - /// Read available data from the socket into the read buffer. - /// Returns Ok(true) if data was read, Ok(false) if connection closed. - pub fn read(&mut self) -> io::Result { - let mut buf = [0u8; 4096]; - let n = unsafe { - libc::read(self.fd, buf.as_mut_ptr() as *mut libc::c_void, buf.len()) - }; - if n < 0 { - let err = io::Error::last_os_error(); - if err.raw_os_error() == Some(libc::EAGAIN) { - return Ok(true); // no data yet, but connection still open - } - return Err(err); - } - if n == 0 { - return Ok(false); // connection closed - } - self.read_buf.extend_from_slice(&buf[..n as usize]); - Ok(true) - } - - /// Try to parse the request from the read buffer. - /// Returns Some(Request) if a full request was parsed. - pub fn try_parse(&mut self) -> Option { - if self.parsed_request.is_some() { - return self.parsed_request.clone(); - } - match request::parse(&self.read_buf) { - ParseResult::Complete(req, _consumed) => { - self.parsed_request = Some(req.clone()); - Some(req) - } - ParseResult::Incomplete => None, - ParseResult::Error(_) => { - self.state = State::Done; - None - } - } - } - - /// Set the response to write back to the client. - pub fn set_response(&mut self, response: &Response) { - self.write_buf = response.to_bytes(); - self.write_offset = 0; - self.state = State::WritingResponse; - } - - /// Write pending response bytes to the socket. - /// Returns Ok(true) if all bytes written, Ok(false) if partial (need EPOLLOUT). - pub fn write(&mut self) -> io::Result { - let remaining = &self.write_buf[self.write_offset..]; - if remaining.is_empty() { - return Ok(true); - } - - let n = unsafe { - libc::write( - self.fd, - remaining.as_ptr() as *const libc::c_void, - remaining.len(), - ) - }; - if n < 0 { - let err = io::Error::last_os_error(); - if err.raw_os_error() == Some(libc::EAGAIN) { - return Ok(false); - } - return Err(err); - } - self.write_offset += n as usize; - - if self.write_offset >= self.write_buf.len() { - Ok(true) // all written - } else { - Ok(false) // partial write - } - } - - /// Transition to subscribed state (connection stays open). - pub fn set_subscribed(&mut self) { - self.state = State::Subscribed; - } - - /// Mark connection as done (will be closed). - pub fn set_done(&mut self) { - self.state = State::Done; - } - - /// Take the parsed request out of the connection. - pub fn take_request(&mut self) -> Option { - self.parsed_request.take() - } -} - -impl Drop for Connection { - fn drop(&mut self) { - unsafe { libc::close(self.fd) }; - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn connection_lifecycle() { - // Use a socketpair to simulate a connection. - let mut fds = [0i32; 2]; - assert_eq!( - unsafe { libc::socketpair(libc::AF_UNIX, libc::SOCK_STREAM | libc::SOCK_NONBLOCK, 0, fds.as_mut_ptr()) }, - 0 - ); - let (server_fd, client_fd) = (fds[0], fds[1]); - - // Write a request from the "client" side. - let request_bytes = b"GET /blog/test HTTP/1.1\r\nHost: localhost\r\n\r\n"; - unsafe { - libc::write( - client_fd, - request_bytes.as_ptr() as *const libc::c_void, - request_bytes.len(), - ); - } - - // Create connection on the "server" side. - // We need to manage the fd manually since Connection will close it on drop. - let dup_fd = unsafe { libc::dup(server_fd) }; - let mut conn = Connection::new(dup_fd); - assert_eq!(conn.state, State::ReadingRequest); - - // Read and parse. - assert!(conn.read().unwrap()); - let req = conn.try_parse().unwrap(); - assert_eq!(req.path, "/blog/test"); - - // Set response. - let response = Response::html("

    Test

    ".into()); - conn.set_response(&response); - assert_eq!(conn.state, State::WritingResponse); - - // Write response. - let complete = conn.write().unwrap(); - assert!(complete); - - // Read from client side to verify. - let mut buf = [0u8; 4096]; - let n = unsafe { - libc::read(client_fd, buf.as_mut_ptr() as *mut libc::c_void, buf.len()) - }; - assert!(n > 0); - let response_str = std::str::from_utf8(&buf[..n as usize]).unwrap(); - assert!(response_str.contains("HTTP/1.1 200 OK")); - assert!(response_str.contains("

    Test

    ")); - - unsafe { - libc::close(server_fd); - libc::close(client_fd); - } - } -} diff --git a/reference/crates/wo-http/src/lib.rs b/reference/crates/wo-http/src/lib.rs deleted file mode 100644 index d75f708..0000000 --- a/reference/crates/wo-http/src/lib.rs +++ /dev/null @@ -1,4 +0,0 @@ -pub mod listener; -pub mod request; -pub mod response; -pub mod connection; diff --git a/reference/crates/wo-http/src/listener.rs b/reference/crates/wo-http/src/listener.rs deleted file mode 100644 index 2b837a0..0000000 --- a/reference/crates/wo-http/src/listener.rs +++ /dev/null @@ -1,202 +0,0 @@ -use std::io; -use std::os::unix::io::RawFd; - -/// A non-blocking TCP listener wrapping raw socket syscalls. -pub struct TcpListener { - fd: RawFd, -} - -impl TcpListener { - /// Bind to the given address (e.g., "0.0.0.0:3000") and start listening. - pub fn bind(addr: &str) -> io::Result { - let (ip, port) = parse_addr(addr)?; - - let fd = unsafe { - libc::socket( - libc::AF_INET, - libc::SOCK_STREAM | libc::SOCK_NONBLOCK | libc::SOCK_CLOEXEC, - 0, - ) - }; - if fd < 0 { - return Err(io::Error::last_os_error()); - } - - // SO_REUSEADDR to allow quick restart. - let optval: libc::c_int = 1; - let ret = unsafe { - libc::setsockopt( - fd, - libc::SOL_SOCKET, - libc::SO_REUSEADDR, - &optval as *const _ as *const libc::c_void, - std::mem::size_of::() as libc::socklen_t, - ) - }; - if ret < 0 { - unsafe { libc::close(fd) }; - return Err(io::Error::last_os_error()); - } - - let sockaddr = libc::sockaddr_in { - sin_family: libc::AF_INET as libc::sa_family_t, - sin_port: port.to_be(), - sin_addr: libc::in_addr { s_addr: ip }, - sin_zero: [0; 8], - }; - - let ret = unsafe { - libc::bind( - fd, - &sockaddr as *const _ as *const libc::sockaddr, - std::mem::size_of::() as libc::socklen_t, - ) - }; - if ret < 0 { - unsafe { libc::close(fd) }; - return Err(io::Error::last_os_error()); - } - - let ret = unsafe { libc::listen(fd, 128) }; - if ret < 0 { - unsafe { libc::close(fd) }; - return Err(io::Error::last_os_error()); - } - - Ok(Self { fd }) - } - - /// Accept a new connection. Returns the client fd or EAGAIN if none pending. - pub fn accept(&self) -> io::Result> { - let client_fd = unsafe { - libc::accept4( - self.fd, - std::ptr::null_mut(), - std::ptr::null_mut(), - libc::SOCK_NONBLOCK | libc::SOCK_CLOEXEC, - ) - }; - if client_fd < 0 { - let err = io::Error::last_os_error(); - if err.raw_os_error() == Some(libc::EAGAIN) - || err.raw_os_error() == Some(libc::EWOULDBLOCK) - { - return Ok(None); - } - return Err(err); - } - Ok(Some(client_fd)) - } - - /// The listener file descriptor for epoll registration. - pub fn fd(&self) -> RawFd { - self.fd - } - - /// Get the actual bound address (useful when binding to port 0). - pub fn local_addr(&self) -> io::Result<(String, u16)> { - let mut addr: libc::sockaddr_in = unsafe { std::mem::zeroed() }; - let mut len = std::mem::size_of::() as libc::socklen_t; - let ret = unsafe { - libc::getsockname( - self.fd, - &mut addr as *mut _ as *mut libc::sockaddr, - &mut len, - ) - }; - if ret < 0 { - return Err(io::Error::last_os_error()); - } - let ip = u32::from_be(addr.sin_addr.s_addr); - let port = u16::from_be(addr.sin_port); - let ip_str = format!( - "{}.{}.{}.{}", - (ip >> 24) & 0xFF, - (ip >> 16) & 0xFF, - (ip >> 8) & 0xFF, - ip & 0xFF, - ); - Ok((ip_str, port)) - } -} - -impl Drop for TcpListener { - fn drop(&mut self) { - unsafe { libc::close(self.fd) }; - } -} - -fn parse_addr(addr: &str) -> io::Result<(u32, u16)> { - let parts: Vec<&str> = addr.rsplitn(2, ':').collect(); - if parts.len() != 2 { - return Err(io::Error::new(io::ErrorKind::InvalidInput, "expected host:port")); - } - let port: u16 = parts[0] - .parse() - .map_err(|_| io::Error::new(io::ErrorKind::InvalidInput, "invalid port"))?; - let ip_str = parts[1]; - - let ip = if ip_str == "0.0.0.0" { - 0u32 - } else if ip_str == "127.0.0.1" { - 0x7F000001u32.to_be() - } else { - let octets: Vec = ip_str - .split('.') - .map(|s| s.parse().unwrap_or(0)) - .collect(); - if octets.len() != 4 { - return Err(io::Error::new(io::ErrorKind::InvalidInput, "invalid IP")); - } - u32::from_ne_bytes([octets[0], octets[1], octets[2], octets[3]]) - }; - - Ok((ip, port)) -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn bind_and_accept() { - let listener = TcpListener::bind("127.0.0.1:0").unwrap(); - let (_, port) = listener.local_addr().unwrap(); - assert!(port > 0); - - // No pending connections → None. - assert!(listener.accept().unwrap().is_none()); - - // Connect a client. - let client_fd = unsafe { - libc::socket(libc::AF_INET, libc::SOCK_STREAM, 0) - }; - assert!(client_fd >= 0); - - let addr = libc::sockaddr_in { - sin_family: libc::AF_INET as libc::sa_family_t, - sin_port: port.to_be(), - sin_addr: libc::in_addr { - s_addr: 0x7F000001u32.to_be(), - }, - sin_zero: [0; 8], - }; - let ret = unsafe { - libc::connect( - client_fd, - &addr as *const _ as *const libc::sockaddr, - std::mem::size_of::() as libc::socklen_t, - ) - }; - assert_eq!(ret, 0); - - // Now accept should return a fd. - let accepted = listener.accept().unwrap(); - assert!(accepted.is_some()); - - unsafe { - libc::close(client_fd); - libc::close(accepted.unwrap()); - } - } -} diff --git a/reference/crates/wo-http/src/request.rs b/reference/crates/wo-http/src/request.rs deleted file mode 100644 index a569500..0000000 --- a/reference/crates/wo-http/src/request.rs +++ /dev/null @@ -1,158 +0,0 @@ -use std::collections::HashMap; - -/// HTTP method. -#[derive(Debug, Clone, Copy, PartialEq)] -pub enum Method { - Get, - Post, - Head, - Unknown, -} - -/// A parsed HTTP/1.1 request. -#[derive(Debug, Clone)] -pub struct Request { - pub method: Method, - pub path: String, - pub query: Option, - pub headers: HashMap, -} - -/// Result of attempting to parse a request from a byte buffer. -pub enum ParseResult { - /// Request fully parsed; returns the request and number of bytes consumed. - Complete(Request, usize), - /// Need more data. - Incomplete, - /// Malformed request. - Error(String), -} - -/// Parse an HTTP/1.1 request from a byte buffer. -/// -/// Returns `ParseResult::Complete` when the full header has been received -/// (delimited by `\r\n\r\n`). -pub fn parse(buf: &[u8]) -> ParseResult { - // Find the end of headers. - let header_end = match find_header_end(buf) { - Some(pos) => pos, - None => { - if buf.len() > 8192 { - return ParseResult::Error("request too large".into()); - } - return ParseResult::Incomplete; - } - }; - - let header_str = match std::str::from_utf8(&buf[..header_end]) { - Ok(s) => s, - Err(_) => return ParseResult::Error("invalid UTF-8 in headers".into()), - }; - - let mut lines = header_str.lines(); - - // Request line: "GET /path HTTP/1.1" - let request_line = match lines.next() { - Some(l) => l, - None => return ParseResult::Error("empty request".into()), - }; - - let mut parts = request_line.split_whitespace(); - let method = match parts.next() { - Some("GET") => Method::Get, - Some("POST") => Method::Post, - Some("HEAD") => Method::Head, - Some(_) => Method::Unknown, - None => return ParseResult::Error("missing method".into()), - }; - - let raw_path = match parts.next() { - Some(p) => p, - None => return ParseResult::Error("missing path".into()), - }; - - // Split path and query string. - let (path, query) = match raw_path.split_once('?') { - Some((p, q)) => (p.to_string(), Some(q.to_string())), - None => (raw_path.to_string(), None), - }; - - // Parse headers. - let mut headers = HashMap::new(); - for line in lines { - if line.is_empty() { - break; - } - if let Some((key, value)) = line.split_once(':') { - headers.insert( - key.trim().to_lowercase(), - value.trim().to_string(), - ); - } - } - - // Bytes consumed: header + \r\n\r\n delimiter. - let consumed = header_end + 4; - - ParseResult::Complete(Request { method, path, query, headers }, consumed) -} - -fn find_header_end(buf: &[u8]) -> Option { - for i in 0..buf.len().saturating_sub(3) { - if &buf[i..i + 4] == b"\r\n\r\n" { - return Some(i); - } - } - None -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn parse_simple_get() { - let raw = b"GET /blog/linux-misc HTTP/1.1\r\nHost: localhost\r\n\r\n"; - match parse(raw) { - ParseResult::Complete(req, consumed) => { - assert_eq!(req.method, Method::Get); - assert_eq!(req.path, "/blog/linux-misc"); - assert!(req.query.is_none()); - assert_eq!(req.headers.get("host").unwrap(), "localhost"); - assert_eq!(consumed, raw.len()); - } - _ => panic!("expected Complete"), - } - } - - #[test] - fn parse_with_query_string() { - let raw = b"GET /tag/rust?page=2 HTTP/1.1\r\n\r\n"; - match parse(raw) { - ParseResult::Complete(req, _) => { - assert_eq!(req.path, "/tag/rust"); - assert_eq!(req.query.as_deref(), Some("page=2")); - } - _ => panic!("expected Complete"), - } - } - - #[test] - fn parse_incomplete() { - let raw = b"GET / HTTP/1.1\r\nHost: local"; - assert!(matches!(parse(raw), ParseResult::Incomplete)); - } - - #[test] - fn parse_multiple_headers() { - let raw = b"GET / HTTP/1.1\r\nHost: localhost\r\nAccept: text/html\r\nConnection: keep-alive\r\n\r\n"; - match parse(raw) { - ParseResult::Complete(req, _) => { - assert_eq!(req.headers.len(), 3); - assert_eq!(req.headers.get("accept").unwrap(), "text/html"); - assert_eq!(req.headers.get("connection").unwrap(), "keep-alive"); - } - _ => panic!("expected Complete"), - } - } -} diff --git a/reference/crates/wo-http/src/response.rs b/reference/crates/wo-http/src/response.rs deleted file mode 100644 index 832ee73..0000000 --- a/reference/crates/wo-http/src/response.rs +++ /dev/null @@ -1,110 +0,0 @@ -/// HTTP response builder. -#[derive(Debug, Clone)] -pub struct Response { - pub status: u16, - pub status_text: String, - pub headers: Vec<(String, String)>, - pub body: Vec, -} - -impl Response { - pub fn new(status: u16, status_text: &str) -> Self { - Self { - status, - status_text: status_text.to_string(), - headers: vec![], - body: vec![], - } - } - - /// 200 OK with HTML body. - pub fn html(body: String) -> Self { - let mut r = Self::new(200, "OK"); - r.header("Content-Type", "text/html; charset=utf-8"); - r.header("Content-Length", &body.len().to_string()); - r.body = body.into_bytes(); - r - } - - /// 200 OK with a raw body and content type. - pub fn ok(body: Vec, content_type: &str) -> Self { - let mut r = Self::new(200, "OK"); - r.header("Content-Type", content_type); - r.header("Content-Length", &body.len().to_string()); - r.body = body; - r - } - - /// 404 Not Found. - pub fn not_found() -> Self { - let body = "

    404 Not Found

    "; - let mut r = Self::new(404, "Not Found"); - r.header("Content-Type", "text/html; charset=utf-8"); - r.header("Content-Length", &body.len().to_string()); - r.body = body.as_bytes().to_vec(); - r - } - - /// 500 Internal Server Error. - pub fn internal_error(msg: &str) -> Self { - let body = format!("

    500 Internal Server Error

    {}

    ", msg); - let mut r = Self::new(500, "Internal Server Error"); - r.header("Content-Type", "text/html; charset=utf-8"); - r.header("Content-Length", &body.len().to_string()); - r.body = body.into_bytes(); - r - } - - /// Add a header. - pub fn header(&mut self, key: &str, value: &str) -> &mut Self { - self.headers.push((key.to_string(), value.to_string())); - self - } - - /// Serialize the response to bytes for writing to a socket. - pub fn to_bytes(&self) -> Vec { - let mut buf = Vec::with_capacity(256 + self.body.len()); - - // Status line. - buf.extend_from_slice( - format!("HTTP/1.1 {} {}\r\n", self.status, self.status_text).as_bytes(), - ); - - // Headers. - for (key, value) in &self.headers { - buf.extend_from_slice(format!("{}: {}\r\n", key, value).as_bytes()); - } - - // End of headers. - buf.extend_from_slice(b"\r\n"); - - // Body. - buf.extend_from_slice(&self.body); - - buf - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn html_response() { - let r = Response::html("

    Hello

    ".into()); - let bytes = r.to_bytes(); - let s = String::from_utf8(bytes).unwrap(); - assert!(s.starts_with("HTTP/1.1 200 OK\r\n")); - assert!(s.contains("Content-Type: text/html")); - assert!(s.contains("

    Hello

    ")); - } - - #[test] - fn not_found_response() { - let r = Response::not_found(); - assert_eq!(r.status, 404); - let bytes = r.to_bytes(); - let s = String::from_utf8(bytes).unwrap(); - assert!(s.contains("404 Not Found")); - } -} diff --git a/reference/crates/wo-index/Cargo.toml b/reference/crates/wo-index/Cargo.toml deleted file mode 100644 index 2362ce7..0000000 --- a/reference/crates/wo-index/Cargo.toml +++ /dev/null @@ -1,13 +0,0 @@ -[package] -name = "wo-index" -version = "0.1.0" -edition = "2021" - -[dependencies] -wo-model = { path = "../wo-model" } -libc = { workspace = true } -bincode = { workspace = true } -serde = { workspace = true } - -[dev-dependencies] -tempfile = "3" diff --git a/reference/crates/wo-index/src/date.rs b/reference/crates/wo-index/src/date.rs deleted file mode 100644 index 22c4ed3..0000000 --- a/reference/crates/wo-index/src/date.rs +++ /dev/null @@ -1,194 +0,0 @@ -use std::io; -use std::path::Path; - -/// Sorted index mapping timestamps to byte offsets in a .seg file. -/// -/// On-disk format: -/// ```text -/// [8 bytes] entry_count: u64 LE -/// For each entry (sorted by timestamp ascending): -/// [8 bytes] timestamp: i64 LE -/// [8 bytes] offset: u64 LE -/// ``` -const ENTRY_SIZE: usize = 16; // i64 + u64 -const HEADER_SIZE: usize = 8; - -pub struct DateIndex { - data: Vec, - count: usize, -} - -impl DateIndex { - /// Build a date index. Entries are sorted by timestamp before writing. - pub fn build(path: &Path, entries: &mut [(i64, u64)]) -> io::Result<()> { - entries.sort_by_key(|e| e.0); - - let file_size = HEADER_SIZE + entries.len() * ENTRY_SIZE; - let mut data = vec![0u8; file_size]; - - data[0..8].copy_from_slice(&(entries.len() as u64).to_le_bytes()); - - for (i, &(ts, offset)) in entries.iter().enumerate() { - let base = HEADER_SIZE + i * ENTRY_SIZE; - data[base..base + 8].copy_from_slice(&ts.to_le_bytes()); - data[base + 8..base + 16].copy_from_slice(&offset.to_le_bytes()); - } - - std::fs::write(path, &data)?; - Ok(()) - } - - /// Open a date index from disk. - pub fn open(path: &Path) -> io::Result { - let data = std::fs::read(path)?; - if data.len() < HEADER_SIZE { - return Err(io::Error::new(io::ErrorKind::InvalidData, "date.idx too small")); - } - let count = u64::from_le_bytes(data[0..8].try_into().unwrap()) as usize; - Ok(Self { data, count }) - } - - /// Get offsets for all entries with timestamps in [start, end]. - pub fn range(&self, start: i64, end: i64) -> Vec { - let lo = self.lower_bound(start); - let hi = self.upper_bound(end); - - (lo..hi).map(|i| self.offset_at(i)).collect() - } - - /// Get offsets for the N most recent entries (highest timestamps). - pub fn latest(&self, n: usize) -> Vec { - let start = self.count.saturating_sub(n); - (start..self.count) - .rev() - .map(|i| self.offset_at(i)) - .collect() - } - - /// Number of entries. - pub fn len(&self) -> usize { - self.count - } - - pub fn is_empty(&self) -> bool { - self.count == 0 - } - - fn timestamp_at(&self, i: usize) -> i64 { - let base = HEADER_SIZE + i * ENTRY_SIZE; - i64::from_le_bytes(self.data[base..base + 8].try_into().unwrap()) - } - - fn offset_at(&self, i: usize) -> u64 { - let base = HEADER_SIZE + i * ENTRY_SIZE; - u64::from_le_bytes(self.data[base + 8..base + 16].try_into().unwrap()) - } - - /// Binary search: first index where timestamp >= target. - fn lower_bound(&self, target: i64) -> usize { - let (mut lo, mut hi) = (0usize, self.count); - while lo < hi { - let mid = lo + (hi - lo) / 2; - if self.timestamp_at(mid) < target { - lo = mid + 1; - } else { - hi = mid; - } - } - lo - } - - /// Binary search: first index where timestamp > target. - fn upper_bound(&self, target: i64) -> usize { - let (mut lo, mut hi) = (0usize, self.count); - while lo < hi { - let mid = lo + (hi - lo) / 2; - if self.timestamp_at(mid) <= target { - lo = mid + 1; - } else { - hi = mid; - } - } - lo - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn build_and_range_query() { - let tmp = tempfile::tempdir().unwrap(); - let path = tmp.path().join("date.idx"); - - let mut entries = vec![ - (1000i64, 100u64), - (2000, 200), - (3000, 300), - (4000, 400), - (5000, 500), - ]; - - DateIndex::build(&path, &mut entries).unwrap(); - let idx = DateIndex::open(&path).unwrap(); - - assert_eq!(idx.len(), 5); - - // Full range. - let all = idx.range(0, 9999); - assert_eq!(all, vec![100, 200, 300, 400, 500]); - - // Partial range. - let mid = idx.range(2000, 4000); - assert_eq!(mid, vec![200, 300, 400]); - - // Single. - let one = idx.range(3000, 3000); - assert_eq!(one, vec![300]); - - // Empty range. - let none = idx.range(6000, 9000); - assert!(none.is_empty()); - } - - #[test] - fn latest_entries() { - let tmp = tempfile::tempdir().unwrap(); - let path = tmp.path().join("date.idx"); - - let mut entries = vec![ - (1000i64, 100u64), - (2000, 200), - (3000, 300), - ]; - - DateIndex::build(&path, &mut entries).unwrap(); - let idx = DateIndex::open(&path).unwrap(); - - let top2 = idx.latest(2); - assert_eq!(top2, vec![300, 200]); - - let top10 = idx.latest(10); - assert_eq!(top10, vec![300, 200, 100]); - } - - #[test] - fn unsorted_input() { - let tmp = tempfile::tempdir().unwrap(); - let path = tmp.path().join("date.idx"); - - let mut entries = vec![ - (5000i64, 500u64), - (1000, 100), - (3000, 300), - ]; - - DateIndex::build(&path, &mut entries).unwrap(); - let idx = DateIndex::open(&path).unwrap(); - - // Should be sorted on disk. - let all = idx.range(0, 9999); - assert_eq!(all, vec![100, 300, 500]); - } -} diff --git a/reference/crates/wo-index/src/lib.rs b/reference/crates/wo-index/src/lib.rs deleted file mode 100644 index 3a57343..0000000 --- a/reference/crates/wo-index/src/lib.rs +++ /dev/null @@ -1,40 +0,0 @@ -mod title; -mod date; -mod tags; - -pub use title::TitleIndex; -pub use date::DateIndex; -pub use tags::TagIndex; - -use std::io; -use std::path::Path; - -/// Rebuild all three index files from an iterator of article metadata. -/// -/// Each entry is `(sys_title, published_on_timestamp, tags, seg_offset)`. -pub fn rebuild_all( - index_dir: &Path, - entries: &[(String, i64, Vec, u64)], -) -> io::Result<()> { - std::fs::create_dir_all(index_dir)?; - - // Build title index. - let title_entries: Vec<(&str, u64)> = entries.iter().map(|e| (e.0.as_str(), e.3)).collect(); - TitleIndex::build(&index_dir.join("title.idx"), &title_entries)?; - - // Build date index. - let mut date_entries: Vec<(i64, u64)> = entries.iter().map(|e| (e.1, e.3)).collect(); - DateIndex::build(&index_dir.join("date.idx"), &mut date_entries)?; - - // Build tags index. - let mut tag_map: std::collections::HashMap> = std::collections::HashMap::new(); - for entry in entries { - for tag in &entry.2 { - tag_map.entry(tag.clone()).or_default().push(entry.3); - } - } - let tag_entries: Vec<(String, Vec)> = tag_map.into_iter().collect(); - TagIndex::build(&index_dir.join("tags.idx"), &tag_entries)?; - - Ok(()) -} diff --git a/reference/crates/wo-index/src/tags.rs b/reference/crates/wo-index/src/tags.rs deleted file mode 100644 index 5b3e113..0000000 --- a/reference/crates/wo-index/src/tags.rs +++ /dev/null @@ -1,97 +0,0 @@ -use std::collections::HashMap; -use std::io; -use std::path::Path; - -use serde::{Deserialize, Serialize}; - -/// Inverted index mapping tag strings to lists of .seg byte offsets. -/// -/// Serialized to disk using bincode for simplicity (article count is -/// small enough that loading the full index into memory is fine). -#[derive(Debug, Serialize, Deserialize)] -struct TagIndexData { - tags: HashMap>, -} - -pub struct TagIndex { - tags: HashMap>, -} - -impl TagIndex { - /// Build a tags index from a list of (tag, offsets) pairs. - pub fn build(path: &Path, entries: &[(String, Vec)]) -> io::Result<()> { - let tags: HashMap> = entries.iter().cloned().collect(); - let data = TagIndexData { tags }; - let bytes = bincode::serialize(&data).map_err(|e| { - io::Error::new(io::ErrorKind::InvalidData, format!("bincode: {}", e)) - })?; - std::fs::write(path, bytes)?; - Ok(()) - } - - /// Open a tags index from disk. - pub fn open(path: &Path) -> io::Result { - let bytes = std::fs::read(path)?; - let data: TagIndexData = bincode::deserialize(&bytes).map_err(|e| { - io::Error::new(io::ErrorKind::InvalidData, format!("bincode: {}", e)) - })?; - Ok(Self { tags: data.tags }) - } - - /// Get all offsets for articles tagged with the given tag. - pub fn get(&self, tag: &str) -> Option<&[u64]> { - self.tags.get(tag).map(|v| v.as_slice()) - } - - /// All tag names in the index. - pub fn tags(&self) -> Vec<&str> { - self.tags.keys().map(|s| s.as_str()).collect() - } - - /// Number of distinct tags. - pub fn len(&self) -> usize { - self.tags.len() - } - - pub fn is_empty(&self) -> bool { - self.tags.is_empty() - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn build_and_lookup() { - let tmp = tempfile::tempdir().unwrap(); - let path = tmp.path().join("tags.idx"); - - let entries = vec![ - ("rust".to_string(), vec![100u64, 200]), - ("linux".to_string(), vec![300]), - ("tutorial".to_string(), vec![100, 300, 400]), - ]; - - TagIndex::build(&path, &entries).unwrap(); - let idx = TagIndex::open(&path).unwrap(); - - assert_eq!(idx.len(), 3); - assert_eq!(idx.get("rust"), Some(vec![100u64, 200].as_slice())); - assert_eq!(idx.get("linux"), Some(vec![300u64].as_slice())); - assert_eq!(idx.get("tutorial"), Some(vec![100u64, 300, 400].as_slice())); - assert_eq!(idx.get("nonexistent"), None); - } - - #[test] - fn empty_index() { - let tmp = tempfile::tempdir().unwrap(); - let path = tmp.path().join("tags.idx"); - - TagIndex::build(&path, &[]).unwrap(); - let idx = TagIndex::open(&path).unwrap(); - - assert!(idx.is_empty()); - assert_eq!(idx.get("anything"), None); - } -} diff --git a/reference/crates/wo-index/src/title.rs b/reference/crates/wo-index/src/title.rs deleted file mode 100644 index 8a4b71f..0000000 --- a/reference/crates/wo-index/src/title.rs +++ /dev/null @@ -1,183 +0,0 @@ -use std::collections::hash_map::DefaultHasher; -use std::hash::{Hash, Hasher}; -use std::io; -use std::path::Path; - -/// On-disk hash index mapping `sys_title` (string) to byte offset in a .seg file. -/// -/// Uses open addressing with linear probing. The table is sized at 2x the entry -/// count (load factor 0.5) to keep collision chains short. -/// -/// On-disk format: -/// ```text -/// [8 bytes] table_size: u64 LE (number of slots) -/// [8 bytes] entry_count: u64 LE -/// For each slot (table_size slots): -/// [8 bytes] offset: u64 LE (0 = empty, otherwise seg offset + 1) -/// [2 bytes] key_len: u16 LE -/// [128 bytes] key_data: zero-padded sys_title -/// Total slot size: 138 bytes -/// ``` -const SLOT_SIZE: usize = 8 + 2 + 128; // offset + key_len + key_data -const HEADER_SIZE: usize = 16; // table_size + entry_count -const MAX_KEY_LEN: usize = 128; - -pub struct TitleIndex { - data: Vec, - table_size: u64, -} - -impl TitleIndex { - /// Build a title index file from a list of (sys_title, seg_offset) pairs. - pub fn build(path: &Path, entries: &[(&str, u64)]) -> io::Result<()> { - let table_size = (entries.len() * 2).max(16) as u64; - let file_size = HEADER_SIZE + (table_size as usize) * SLOT_SIZE; - - let mut data = vec![0u8; file_size]; - - // Write header. - data[0..8].copy_from_slice(&table_size.to_le_bytes()); - data[8..16].copy_from_slice(&(entries.len() as u64).to_le_bytes()); - - // Insert entries. - for &(key, offset) in entries { - let slot = Self::find_empty_slot(&data, table_size, key); - Self::write_slot(&mut data, slot, key, offset); - } - - std::fs::write(path, &data)?; - Ok(()) - } - - /// Open a title index from disk (loads into memory). - pub fn open(path: &Path) -> io::Result { - let data = std::fs::read(path)?; - if data.len() < HEADER_SIZE { - return Err(io::Error::new(io::ErrorKind::InvalidData, "title.idx too small")); - } - let table_size = u64::from_le_bytes(data[0..8].try_into().unwrap()); - Ok(Self { data, table_size }) - } - - /// Look up a sys_title, returning the seg file byte offset if found. - pub fn get(&self, sys_title: &str) -> Option { - let mut slot = Self::hash_key(sys_title) % self.table_size; - - for _ in 0..self.table_size { - let (stored_offset, stored_key) = self.read_slot(slot as usize); - - if stored_offset == 0 { - return None; // empty slot, key not found - } - - if stored_key == sys_title { - return Some(stored_offset - 1); // stored as offset + 1 - } - - slot = (slot + 1) % self.table_size; - } - - None - } - - /// Number of entries in the index. - pub fn len(&self) -> usize { - u64::from_le_bytes(self.data[8..16].try_into().unwrap()) as usize - } - - pub fn is_empty(&self) -> bool { - self.len() == 0 - } - - fn hash_key(key: &str) -> u64 { - let mut hasher = DefaultHasher::new(); - key.hash(&mut hasher); - hasher.finish() - } - - fn find_empty_slot(data: &[u8], table_size: u64, key: &str) -> usize { - let mut slot = (Self::hash_key(key) % table_size) as usize; - loop { - let base = HEADER_SIZE + slot * SLOT_SIZE; - let stored_offset = u64::from_le_bytes(data[base..base + 8].try_into().unwrap()); - if stored_offset == 0 { - return slot; - } - slot = (slot + 1) % table_size as usize; - } - } - - fn write_slot(data: &mut [u8], slot: usize, key: &str, offset: u64) { - let base = HEADER_SIZE + slot * SLOT_SIZE; - let stored_offset = offset + 1; // +1 so that 0 means empty - data[base..base + 8].copy_from_slice(&stored_offset.to_le_bytes()); - - let key_bytes = key.as_bytes(); - let key_len = key_bytes.len().min(MAX_KEY_LEN) as u16; - data[base + 8..base + 10].copy_from_slice(&key_len.to_le_bytes()); - data[base + 10..base + 10 + key_len as usize].copy_from_slice(&key_bytes[..key_len as usize]); - } - - fn read_slot(&self, slot: usize) -> (u64, String) { - let base = HEADER_SIZE + slot * SLOT_SIZE; - let stored_offset = u64::from_le_bytes(self.data[base..base + 8].try_into().unwrap()); - let key_len = u16::from_le_bytes(self.data[base + 8..base + 10].try_into().unwrap()) as usize; - let key = String::from_utf8_lossy(&self.data[base + 10..base + 10 + key_len]).to_string(); - (stored_offset, key) - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn build_and_lookup() { - let tmp = tempfile::tempdir().unwrap(); - let path = tmp.path().join("title.idx"); - - let entries = vec![ - ("linux-misc", 32u64), - ("aws-lambda-pulumi", 500), - ("rust-patterns", 1200), - ]; - - TitleIndex::build(&path, &entries).unwrap(); - let idx = TitleIndex::open(&path).unwrap(); - - assert_eq!(idx.len(), 3); - assert_eq!(idx.get("linux-misc"), Some(32)); - assert_eq!(idx.get("aws-lambda-pulumi"), Some(500)); - assert_eq!(idx.get("rust-patterns"), Some(1200)); - assert_eq!(idx.get("nonexistent"), None); - } - - #[test] - fn empty_index() { - let tmp = tempfile::tempdir().unwrap(); - let path = tmp.path().join("title.idx"); - - TitleIndex::build(&path, &[]).unwrap(); - let idx = TitleIndex::open(&path).unwrap(); - - assert!(idx.is_empty()); - assert_eq!(idx.get("anything"), None); - } - - #[test] - fn many_entries() { - let tmp = tempfile::tempdir().unwrap(); - let path = tmp.path().join("title.idx"); - - let keys: Vec = (0..100).map(|i| format!("article-{}", i)).collect(); - let entries: Vec<(&str, u64)> = keys.iter().enumerate().map(|(i, k)| (k.as_str(), i as u64 * 100)).collect(); - - TitleIndex::build(&path, &entries).unwrap(); - let idx = TitleIndex::open(&path).unwrap(); - - assert_eq!(idx.len(), 100); - for (i, key) in keys.iter().enumerate() { - assert_eq!(idx.get(key), Some(i as u64 * 100)); - } - } -} diff --git a/reference/crates/wo-md/Cargo.toml b/reference/crates/wo-md/Cargo.toml deleted file mode 100644 index 4247786..0000000 --- a/reference/crates/wo-md/Cargo.toml +++ /dev/null @@ -1,6 +0,0 @@ -[package] -name = "wo-md" -version = "0.1.0" -edition = "2021" - -[dependencies] diff --git a/reference/crates/wo-md/src/block.rs b/reference/crates/wo-md/src/block.rs deleted file mode 100644 index 24dd774..0000000 --- a/reference/crates/wo-md/src/block.rs +++ /dev/null @@ -1,324 +0,0 @@ -use crate::highlight::highlight; -use crate::inline::markdown_to_html; - -/// Parse a full markdown document into HTML. -/// -/// Handles block-level elements (headings, paragraphs, code fences, lists, -/// blockquotes, images) and delegates inline formatting to `markdown_to_html`. -pub fn markdown_to_html_block(md: &str) -> String { - let lines: Vec<&str> = md.lines().collect(); - let len = lines.len(); - let mut html = String::with_capacity(md.len() * 2); - let mut i = 0; - - while i < len { - let line = lines[i]; - let trimmed = line.trim(); - - // Skip blank lines. - if trimmed.is_empty() { - i += 1; - continue; - } - - // Fenced code block: ```lang ... ``` - if trimmed.starts_with("```") { - let lang = trimmed[3..].trim(); - i += 1; - let mut code = String::new(); - while i < len && !lines[i].trim().starts_with("```") { - if !code.is_empty() { - code.push('\n'); - } - code.push_str(lines[i]); - i += 1; - } - if i < len { - i += 1; // skip closing ``` - } - - if lang.is_empty() { - html.push_str("
    ");
    -                html.push_str(&escape_html(&code));
    -                html.push_str("
    \n"); - } else { - html.push_str(&format!("
    ", escape_html(lang)));
    -                html.push_str(&highlight(&code, lang));
    -                html.push_str("
    \n"); - } - continue; - } - - // Heading: # ... ###### - if trimmed.starts_with('#') { - let level = trimmed.chars().take_while(|&c| c == '#').count().min(6); - let text = trimmed[level..].trim(); - html.push_str(&format!( - "{1}\n", - level, - markdown_to_html(text) - )); - i += 1; - continue; - } - - // Blockquote: > text - if trimmed.starts_with('>') { - let mut quote_lines = Vec::new(); - while i < len && lines[i].trim().starts_with('>') { - let content = lines[i].trim().strip_prefix('>').unwrap_or("").trim(); - quote_lines.push(content); - i += 1; - } - html.push_str("
    "); - html.push_str(&markdown_to_html("e_lines.join(" "))); - html.push_str("
    \n"); - continue; - } - - // Unordered list: - item or * item - if (trimmed.starts_with("- ") || trimmed.starts_with("* ")) - && !trimmed.starts_with("---") - { - html.push_str("
      \n"); - while i < len { - let lt = lines[i].trim(); - if lt.starts_with("- ") || lt.starts_with("* ") { - let text = <[2..]; - html.push_str(&format!("
    • {}
    • \n", markdown_to_html(text))); - i += 1; - } else if lt.is_empty() { - i += 1; - break; - } else { - break; - } - } - html.push_str("
    \n"); - continue; - } - - // Ordered list: 1. item - if trimmed.len() > 2 && trimmed.as_bytes()[0].is_ascii_digit() { - if let Some(rest) = strip_ordered_prefix(trimmed) { - html.push_str("
      \n"); - html.push_str(&format!("
    1. {}
    2. \n", markdown_to_html(rest))); - i += 1; - while i < len { - let lt = lines[i].trim(); - if let Some(rest) = strip_ordered_prefix(lt) { - html.push_str(&format!("
    3. {}
    4. \n", markdown_to_html(rest))); - i += 1; - } else if lt.is_empty() { - i += 1; - break; - } else { - break; - } - } - html.push_str("
    \n"); - continue; - } - } - - // Image: ![alt](url) - if trimmed.starts_with("![") { - if let Some((alt, url)) = parse_image(trimmed) { - html.push_str(&format!( - "\"{}\"\n", - escape_html(url), - escape_html(alt) - )); - i += 1; - continue; - } - } - - // Horizontal rule: --- or *** - if trimmed == "---" || trimmed == "***" || trimmed == "___" { - html.push_str("
    \n"); - i += 1; - continue; - } - - // Paragraph: collect consecutive non-blank, non-block lines. - let mut para_lines = Vec::new(); - while i < len { - let lt = lines[i].trim(); - if lt.is_empty() - || lt.starts_with('#') - || lt.starts_with("```") - || lt.starts_with('>') - || lt == "---" - || lt == "***" - || lt == "___" - { - break; - } - // Check if next line starts a list - if (lt.starts_with("- ") || lt.starts_with("* ")) && !lt.starts_with("---") { - break; - } - if strip_ordered_prefix(lt).is_some() && para_lines.is_empty() { - break; - } - para_lines.push(lt); - i += 1; - } - if !para_lines.is_empty() { - html.push_str("

    "); - html.push_str(&markdown_to_html(¶_lines.join(" "))); - html.push_str("

    \n"); - } - } - - html -} - -fn escape_html(s: &str) -> String { - s.replace('&', "&") - .replace('<', "<") - .replace('>', ">") - .replace('"', """) -} - -fn strip_ordered_prefix(s: &str) -> Option<&str> { - let dot_pos = s.find(". ")?; - if dot_pos > 0 && s[..dot_pos].chars().all(|c| c.is_ascii_digit()) { - Some(&s[dot_pos + 2..]) - } else { - None - } -} - -fn parse_image(s: &str) -> Option<(&str, &str)> { - // ![alt](url) - let alt_start = s.find("![")? + 2; - let alt_end = s[alt_start..].find(']')? + alt_start; - let url_start = s[alt_end..].find('(')? + alt_end + 1; - let url_end = s[url_start..].find(')')? + url_start; - Some((&s[alt_start..alt_end], &s[url_start..url_end])) -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn headings() { - let md = "# Title\n\n## Subtitle\n\n### Third"; - let html = markdown_to_html_block(md); - assert!(html.contains("

    Title

    ")); - assert!(html.contains("

    Subtitle

    ")); - assert!(html.contains("

    Third

    ")); - } - - #[test] - fn paragraphs() { - let md = "First paragraph.\n\nSecond paragraph."; - let html = markdown_to_html_block(md); - assert!(html.contains("

    First paragraph.

    ")); - assert!(html.contains("

    Second paragraph.

    ")); - } - - #[test] - fn code_fence_with_language() { - let md = "```rust\nfn main() {\n let x = 42;\n}\n```"; - let html = markdown_to_html_block(md); - assert!(html.contains("
    "));
    -        assert!(html.contains("fn"));
    -        assert!(html.contains("42"));
    -        assert!(html.contains("
    ")); - } - - #[test] - fn code_fence_no_language() { - let md = "```\nplain code\n```"; - let html = markdown_to_html_block(md); - assert!(html.contains("
    plain code
    ")); - } - - #[test] - fn unordered_list() { - let md = "- Item one\n- Item two\n- Item three"; - let html = markdown_to_html_block(md); - assert!(html.contains("
      ")); - assert!(html.contains("
    • Item one
    • ")); - assert!(html.contains("
    • Item two
    • ")); - assert!(html.contains("
    ")); - } - - #[test] - fn ordered_list() { - let md = "1. First\n2. Second\n3. Third"; - let html = markdown_to_html_block(md); - assert!(html.contains("
      ")); - assert!(html.contains("
    1. First
    2. ")); - assert!(html.contains("
    3. Third
    4. ")); - assert!(html.contains("
    ")); - } - - #[test] - fn blockquote() { - let md = "> This is a quote\n> spanning two lines"; - let html = markdown_to_html_block(md); - assert!(html.contains("
    This is a quote spanning two lines
    ")); - } - - #[test] - fn image() { - let md = "![Alt text](https://example.com/img.png)"; - let html = markdown_to_html_block(md); - assert!(html.contains("\"Alt")); - } - - #[test] - fn horizontal_rule() { - let md = "Before\n\n---\n\nAfter"; - let html = markdown_to_html_block(md); - assert!(html.contains("
    ")); - } - - #[test] - fn inline_formatting_in_paragraphs() { - let md = "Use **bold** and `code` in a paragraph."; - let html = markdown_to_html_block(md); - assert!(html.contains("bold")); - assert!(html.contains("code")); - } - - #[test] - fn full_article() { - let md = "\ -# Getting Started - -Welcome to the guide. - -## Installation - -Install with cargo: - -```bash -cargo install writeonce -``` - -## Features - -- Fast rendering -- Zero dependencies -- **Bold** feature - -> Note: this is a quote. - -![Logo](logo.png) -"; - let html = markdown_to_html_block(md); - assert!(html.contains("

    Getting Started

    ")); - assert!(html.contains("

    Installation

    ")); - assert!(html.contains("

    Install with cargo:

    ")); - assert!(html.contains("
    "));
    -        assert!(html.contains("
  • Fast rendering
  • ")); - assert!(html.contains("
    ")); - assert!(html.contains("` tokens. Pair with code-theme.css. -pub fn highlight(code: &str, language: &str) -> String { - let keywords = keywords_for(language); - let mut result = String::with_capacity(code.len() * 2); - let chars: Vec = code.chars().collect(); - let len = chars.len(); - let mut i = 0; - - while i < len { - // String literals (double or single quotes). - if chars[i] == '"' || chars[i] == '\'' { - let quote = chars[i]; - let start = i; - i += 1; - while i < len && chars[i] != quote { - if chars[i] == '\\' { i += 1; } // skip escaped char - i += 1; - } - if i < len { i += 1; } // closing quote - let s: String = chars[start..i].iter().collect(); - result.push_str(&format!("{}", escape(&s))); - continue; - } - - // Line comments. - if i + 1 < len && chars[i] == '/' && chars[i + 1] == '/' { - let start = i; - while i < len && chars[i] != '\n' { i += 1; } - let s: String = chars[start..i].iter().collect(); - result.push_str(&format!("{}", escape(&s))); - continue; - } - - // Hash comments (bash, yaml, etc.). - if chars[i] == '#' && (language == "bash" || language == "yaml" || language == "python") { - let start = i; - while i < len && chars[i] != '\n' { i += 1; } - let s: String = chars[start..i].iter().collect(); - result.push_str(&format!("{}", escape(&s))); - continue; - } - - // Numbers. - if chars[i].is_ascii_digit() { - let start = i; - while i < len && (chars[i].is_ascii_alphanumeric() || chars[i] == '.') { i += 1; } - let s: String = chars[start..i].iter().collect(); - result.push_str(&format!("{}", escape(&s))); - continue; - } - - // Identifiers / keywords. - if chars[i].is_ascii_alphabetic() || chars[i] == '_' { - let start = i; - while i < len && (chars[i].is_ascii_alphanumeric() || chars[i] == '_') { i += 1; } - let word: String = chars[start..i].iter().collect(); - if keywords.contains(&word.as_str()) { - result.push_str(&format!("{}", escape(&word))); - } else { - result.push_str(&escape(&word)); - } - continue; - } - - // Everything else. - result.push_str(&escape(&chars[i].to_string())); - i += 1; - } - - result -} - -fn escape(s: &str) -> String { - s.replace('&', "&") - .replace('<', "<") - .replace('>', ">") -} - -fn keywords_for(language: &str) -> &'static [&'static str] { - match language { - "rust" => &[ - "fn", "let", "mut", "const", "struct", "enum", "impl", "trait", "pub", "use", - "mod", "crate", "self", "super", "return", "if", "else", "match", "for", "while", - "loop", "break", "continue", "where", "type", "as", "in", "ref", "move", - "async", "await", "unsafe", "extern", "dyn", "true", "false", - ], - "go" => &[ - "func", "var", "const", "type", "struct", "interface", "map", "chan", - "package", "import", "return", "if", "else", "for", "range", "switch", - "case", "default", "go", "defer", "select", "true", "false", "nil", - ], - "bash" | "sh" => &[ - "if", "then", "else", "elif", "fi", "for", "while", "do", "done", - "case", "esac", "function", "return", "export", "local", "echo", - "in", "true", "false", - ], - "yaml" => &["true", "false", "null", "yes", "no"], - "json" => &["true", "false", "null"], - "python" => &[ - "def", "class", "if", "elif", "else", "for", "while", "return", - "import", "from", "as", "with", "try", "except", "finally", - "raise", "pass", "lambda", "yield", "True", "False", "None", "in", - "not", "and", "or", "is", "async", "await", - ], - _ => &[], - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn highlight_rust() { - let code = "fn main() {\n let x = 42;\n}"; - let html = highlight(code, "rust"); - assert!(html.contains("fn")); - assert!(html.contains("let")); - assert!(html.contains("42")); - } - - #[test] - fn highlight_string() { - let code = r#"let s = "hello";"#; - let html = highlight(code, "rust"); - assert!(html.contains("\"hello\"")); - } - - #[test] - fn highlight_comment() { - let code = "// this is a comment\nlet x = 1;"; - let html = highlight(code, "rust"); - assert!(html.contains("// this is a comment")); - } - - #[test] - fn highlight_bash() { - let code = "# comment\nexport PATH=/usr/bin"; - let html = highlight(code, "bash"); - assert!(html.contains("# comment")); - assert!(html.contains("export")); - } - - #[test] - fn escapes_html() { - let code = "fn compare(a: T, b: T) {}"; - let html = highlight(code, "rust"); - assert!(html.contains("<")); - assert!(html.contains(">")); - assert!(!html.contains("")); - } -} diff --git a/reference/crates/wo-md/src/inline.rs b/reference/crates/wo-md/src/inline.rs deleted file mode 100644 index 458b1a8..0000000 --- a/reference/crates/wo-md/src/inline.rs +++ /dev/null @@ -1,171 +0,0 @@ -/// Convert inline markdown to HTML. -/// -/// Handles: **bold**, *italic*, `code`, [text](url), and HTML entity escaping. -/// Does not handle block-level elements (headers, lists, etc.) — those are -/// already structured in the article JSON. -pub fn markdown_to_html(text: &str) -> String { - let mut result = String::with_capacity(text.len() * 2); - let chars: Vec = text.chars().collect(); - let len = chars.len(); - let mut i = 0; - - while i < len { - match chars[i] { - // HTML entity escaping. - '&' => { result.push_str("&"); i += 1; } - '<' => { result.push_str("<"); i += 1; } - '>' => { result.push_str(">"); i += 1; } - - // **bold** or *italic* - '*' => { - if i + 1 < len && chars[i + 1] == '*' { - // **bold** - if let Some(end) = find_closing(&chars, i + 2, "**") { - result.push_str(""); - let inner: String = chars[i + 2..end].iter().collect(); - result.push_str(&escape_html(&inner)); - result.push_str(""); - i = end + 2; - } else { - result.push('*'); - i += 1; - } - } else { - // *italic* - if let Some(end) = find_closing_char(&chars, i + 1, '*') { - result.push_str(""); - let inner: String = chars[i + 1..end].iter().collect(); - result.push_str(&escape_html(&inner)); - result.push_str(""); - i = end + 1; - } else { - result.push('*'); - i += 1; - } - } - } - - // `inline code` - '`' => { - if let Some(end) = find_closing_char(&chars, i + 1, '`') { - result.push_str(""); - let inner: String = chars[i + 1..end].iter().collect(); - result.push_str(&escape_html(&inner)); - result.push_str(""); - i = end + 1; - } else { - result.push('`'); - i += 1; - } - } - - // [text](url) - '[' => { - if let Some((text_end, url_start, url_end)) = parse_link(&chars, i) { - let link_text: String = chars[i + 1..text_end].iter().collect(); - let url: String = chars[url_start..url_end].iter().collect(); - result.push_str(&format!( - "{}", - escape_html(&url), - escape_html(&link_text) - )); - i = url_end + 1; - } else { - result.push('['); - i += 1; - } - } - - c => { - result.push(c); - i += 1; - } - } - } - - result -} - -fn escape_html(s: &str) -> String { - s.replace('&', "&") - .replace('<', "<") - .replace('>', ">") - .replace('"', """) -} - -fn find_closing(chars: &[char], start: usize, marker: &str) -> Option { - let marker_chars: Vec = marker.chars().collect(); - let mlen = marker_chars.len(); - for i in start..chars.len().saturating_sub(mlen - 1) { - if chars[i..i + mlen] == marker_chars[..] { - return Some(i); - } - } - None -} - -fn find_closing_char(chars: &[char], start: usize, marker: char) -> Option { - for i in start..chars.len() { - if chars[i] == marker { - return Some(i); - } - } - None -} - -fn parse_link(chars: &[char], start: usize) -> Option<(usize, usize, usize)> { - // [text](url) - let text_end = find_closing_char(chars, start + 1, ']')?; - if text_end + 1 >= chars.len() || chars[text_end + 1] != '(' { - return None; - } - let url_start = text_end + 2; - let url_end = find_closing_char(chars, url_start, ')')?; - Some((text_end, url_start, url_end)) -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn plain_text() { - assert_eq!(markdown_to_html("hello world"), "hello world"); - } - - #[test] - fn bold() { - assert_eq!(markdown_to_html("**bold**"), "bold"); - } - - #[test] - fn italic() { - assert_eq!(markdown_to_html("*italic*"), "italic"); - } - - #[test] - fn inline_code() { - assert_eq!(markdown_to_html("`code`"), "code"); - } - - #[test] - fn link() { - assert_eq!( - markdown_to_html("[click](https://example.com)"), - "click" - ); - } - - #[test] - fn mixed() { - assert_eq!( - markdown_to_html("Use **Rust** with `cargo` for [docs](https://doc.rust-lang.org)"), - "Use Rust with cargo for docs" - ); - } - - #[test] - fn html_escaping() { - assert_eq!(markdown_to_html(""), "<script>alert('xss')</script>"); - } -} diff --git a/reference/crates/wo-md/src/lib.rs b/reference/crates/wo-md/src/lib.rs deleted file mode 100644 index 4efb475..0000000 --- a/reference/crates/wo-md/src/lib.rs +++ /dev/null @@ -1,7 +0,0 @@ -mod block; -mod inline; -mod highlight; - -pub use block::markdown_to_html_block; -pub use inline::markdown_to_html; -pub use highlight::highlight; diff --git a/reference/crates/wo-model/Cargo.toml b/reference/crates/wo-model/Cargo.toml deleted file mode 100644 index 6a94256..0000000 --- a/reference/crates/wo-model/Cargo.toml +++ /dev/null @@ -1,13 +0,0 @@ -[package] -name = "wo-model" -version = "0.1.0" -edition = "2021" - -[dependencies] -serde = { workspace = true } -serde_json = { workspace = true } -bincode = { workspace = true } -wo-md = { path = "../wo-md" } - -[dev-dependencies] -tempfile = "3" diff --git a/reference/crates/wo-model/src/article.rs b/reference/crates/wo-model/src/article.rs deleted file mode 100644 index d650652..0000000 --- a/reference/crates/wo-model/src/article.rs +++ /dev/null @@ -1,177 +0,0 @@ -use serde::{Deserialize, Serialize}; - -/// Markdown-first article: minimal metadata + pre-rendered HTML content. -/// -/// The `sys_title` field is the primary key — a URL-safe slug -/// used for lookups and routing (e.g., "linux-misc"). -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] -pub struct Article { - pub sys_title: String, - pub title: String, - pub published: bool, - pub author: String, - #[serde(default)] - pub tags: Vec, - #[serde(default)] - pub published_on: Option, - /// Pre-rendered HTML from the .md file. Populated by ContentLoader. - #[serde(default)] - pub content_html: String, -} - -// --- Legacy types for backwards compatibility with old JSON format --- - -/// Legacy article format (nested JSON with sections/codes/etc.). -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct LegacyArticle { - pub title: String, - pub sys_title: String, - pub published: bool, - pub content: LegacyArticleContent, - #[serde(default)] - pub do_aws_sync: Option, - #[serde(default)] - pub published_on: Option, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct LegacyArticleContent { - pub author: String, - pub content: LegacyArticleBody, - #[serde(default, rename = "publishedOn")] - pub published_on: Option, - #[serde(default)] - pub references: Vec, - #[serde(default)] - pub tags: Vec, - #[serde(default)] - pub title: Option, - #[serde(default)] - pub systitle: Option, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct LegacyArticleBody { - #[serde(default)] - pub sections: Vec, - #[serde(default)] - pub codes: Vec, - #[serde(default)] - pub images: Vec, - #[serde(default)] - pub img: Option, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct LegacySection { - pub heading: String, - #[serde(default)] - pub paragraphs: Vec, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct LegacyReference { - #[serde(default, rename = "dateAccessed")] - pub date_accessed: Option, - #[serde(default)] - pub title: Option, - #[serde(default)] - pub url: Option, -} - -impl LegacyArticle { - /// Convert a legacy article to the new format by building HTML from sections. - pub fn to_article(&self) -> Article { - let mut html = String::new(); - for section in &self.content.content.sections { - html.push_str(&format!("

    {}

    \n", section.heading)); - for para in §ion.paragraphs { - if !para.is_empty() { - html.push_str(&format!("

    {}

    \n", wo_md::markdown_to_html(para))); - } - } - } - - Article { - sys_title: self.sys_title.clone(), - title: self.title.clone(), - published: self.published, - author: self.content.author.clone(), - tags: self.content.tags.clone(), - published_on: self.published_on.or(self.content.published_on), - content_html: html, - } - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn deserialize_new_format() { - let json = r#"{ - "sys_title": "test-article", - "title": "Test Article", - "published": true, - "author": "Author", - "tags": ["test"], - "published_on": 1000 - }"#; - - let article: Article = serde_json::from_str(json).unwrap(); - assert_eq!(article.sys_title, "test-article"); - assert_eq!(article.author, "Author"); - assert_eq!(article.tags, vec!["test"]); - assert!(article.published); - assert_eq!(article.content_html, ""); - } - - #[test] - fn round_trip_bincode() { - let article = Article { - sys_title: "test".into(), - title: "Test".into(), - published: true, - author: "Author".into(), - tags: vec!["rust".into()], - published_on: Some(1000), - content_html: "

    Hello

    ".into(), - }; - - let bytes = bincode::serialize(&article).unwrap(); - let deserialized: Article = bincode::deserialize(&bytes).unwrap(); - assert_eq!(article, deserialized); - } - - #[test] - fn legacy_to_article() { - let json = r#"{ - "title": "Legacy Article", - "sys_title": "legacy", - "published": true, - "content": { - "author": "Author", - "content": { - "sections": [ - {"heading": "Intro", "paragraphs": ["Hello **world**."]} - ], - "codes": [], - "images": [] - }, - "tags": ["test"], - "publishedOn": 1000 - }, - "published_on": 2000 - }"#; - - let legacy: LegacyArticle = serde_json::from_str(json).unwrap(); - let article = legacy.to_article(); - assert_eq!(article.sys_title, "legacy"); - assert_eq!(article.author, "Author"); - assert_eq!(article.tags, vec!["test"]); - assert_eq!(article.published_on, Some(2000)); - assert!(article.content_html.contains("

    Intro

    ")); - assert!(article.content_html.contains("world")); - } -} diff --git a/reference/crates/wo-model/src/lib.rs b/reference/crates/wo-model/src/lib.rs deleted file mode 100644 index a0b1f63..0000000 --- a/reference/crates/wo-model/src/lib.rs +++ /dev/null @@ -1,5 +0,0 @@ -mod article; -mod loader; - -pub use article::*; -pub use loader::ContentLoader; diff --git a/reference/crates/wo-model/src/loader.rs b/reference/crates/wo-model/src/loader.rs deleted file mode 100644 index 9de19be..0000000 --- a/reference/crates/wo-model/src/loader.rs +++ /dev/null @@ -1,249 +0,0 @@ -use std::fs; -use std::io; -use std::path::Path; - -use crate::{Article, LegacyArticle}; - -/// Walks a content directory and loads all articles. -/// -/// Supports two formats: -/// - **New**: minimal `.json` metadata + `.md` file for content -/// - **Legacy**: full JSON with nested sections/codes (falls back if no `.md`) -/// -/// ```text -/// content_dir/ -/// article-slug/ -/// article-slug.json # metadata -/// article-slug.md # content (new format) -/// ``` -pub struct ContentLoader; - -impl ContentLoader { - /// Load all articles from the given content directory. - pub fn load_all(content_dir: &Path) -> io::Result> { - let mut articles = Vec::new(); - - let entries = fs::read_dir(content_dir)?; - for entry in entries { - let entry = entry?; - let path = entry.path(); - - if !path.is_dir() { - continue; - } - - match Self::load_from_dir(&path) { - Ok(article) => articles.push(article), - Err(e) => { - eprintln!( - "wo-model: skipping {:?}: {}", - path.file_name().unwrap_or_default(), - e - ); - } - } - } - - articles.sort_by(|a, b| { - b.published_on - .unwrap_or(0) - .cmp(&a.published_on.unwrap_or(0)) - }); - - Ok(articles) - } - - /// Load a single article from a directory. - /// - /// Tries new format first (minimal JSON + .md), falls back to legacy JSON. - pub fn load_from_dir(dir: &Path) -> io::Result
    { - let json_file = Self::find_json_file(dir)?; - let json_contents = fs::read_to_string(&json_file)?; - - // Check if a .md file exists alongside the JSON. - let md_file = Self::find_md_file(dir); - - if let Some(md_path) = md_file { - // New format: minimal JSON + markdown file. - // Try parsing as new format first, then fall back to legacy. - let mut article: Article = - if let Ok(a) = serde_json::from_str::
    (&json_contents) { - a - } else { - let legacy: LegacyArticle = - serde_json::from_str(&json_contents).map_err(|e| { - io::Error::new( - io::ErrorKind::InvalidData, - format!("{}: {}", json_file.display(), e), - ) - })?; - legacy.to_article() - }; - - let md_content = fs::read_to_string(&md_path)?; - article.content_html = wo_md::markdown_to_html_block(&md_content); - Ok(article) - } else { - // Legacy format: full JSON with sections, no .md file. - let legacy: LegacyArticle = serde_json::from_str(&json_contents).map_err(|e| { - io::Error::new( - io::ErrorKind::InvalidData, - format!("{}: {}", json_file.display(), e), - ) - })?; - Ok(legacy.to_article()) - } - } - - /// Load a single article from a specific JSON file path. - pub fn load_from_file(path: &Path) -> io::Result
    { - let dir = path.parent().unwrap_or(Path::new(".")); - Self::load_from_dir(dir) - } - - fn find_json_file(dir: &Path) -> io::Result { - for entry in fs::read_dir(dir)? { - let entry = entry?; - let path = entry.path(); - if path.extension().and_then(|e| e.to_str()) == Some("json") { - return Ok(path); - } - } - Err(io::Error::new( - io::ErrorKind::NotFound, - format!("no .json file in {}", dir.display()), - )) - } - - fn find_md_file(dir: &Path) -> Option { - let entries = fs::read_dir(dir).ok()?; - for entry in entries { - let entry = entry.ok()?; - let path = entry.path(); - if path.extension().and_then(|e| e.to_str()) == Some("md") { - return Some(path); - } - } - None - } -} - -#[cfg(test)] -mod tests { - use super::*; - use std::fs; - - /// Create a new-format article: minimal JSON + .md file. - fn create_new_article(base: &Path, sys_title: &str, published: bool, tags: &[&str]) { - let dir = base.join(sys_title); - fs::create_dir_all(&dir).unwrap(); - - let tags_json: Vec = tags.iter().map(|t| format!("\"{}\"", t)).collect(); - let json = format!( - r#"{{ - "sys_title": "{}", - "title": "{}", - "published": {}, - "author": "Test Author", - "tags": [{}], - "published_on": 2000 - }}"#, - sys_title, sys_title, published, tags_json.join(", ") - ); - fs::write(dir.join(format!("{}.json", sys_title)), json).unwrap(); - - let md = format!("# {}\n\nThis is the **content** of the article.\n\n```rust\nfn main() {{}}\n```\n", sys_title); - fs::write(dir.join(format!("{}.md", sys_title)), md).unwrap(); - } - - /// Create a legacy-format article: full JSON, no .md file. - fn create_legacy_article(base: &Path, sys_title: &str, published: bool, tags: &[&str]) { - let dir = base.join(sys_title); - fs::create_dir_all(&dir).unwrap(); - - let tags_json: Vec = tags.iter().map(|t| format!("\"{}\"", t)).collect(); - let json = format!( - r#"{{ - "title": "{}", - "sys_title": "{}", - "published": {}, - "content": {{ - "author": "Test Author", - "content": {{ - "sections": [{{ "heading": "Intro", "paragraphs": ["Hello **world**."] }}], - "codes": [], - "images": [] - }}, - "tags": [{}], - "publishedOn": 1000 - }}, - "published_on": 2000 - }}"#, - sys_title, sys_title, published, tags_json.join(", ") - ); - fs::write(dir.join(format!("{}.json", sys_title)), json).unwrap(); - } - - #[test] - fn load_new_format() { - let tmp = tempfile::tempdir().unwrap(); - create_new_article(tmp.path(), "my-article", true, &["rust"]); - - let article = ContentLoader::load_from_dir(&tmp.path().join("my-article")).unwrap(); - assert_eq!(article.sys_title, "my-article"); - assert_eq!(article.author, "Test Author"); - assert_eq!(article.tags, vec!["rust"]); - assert!(article.content_html.contains("

    my-article

    ")); - assert!(article.content_html.contains("content")); - assert!(article.content_html.contains("
    Intro"));
    -        assert!(article.content_html.contains("world"));
    -    }
    -
    -    #[test]
    -    fn load_all_mixed() {
    -        let tmp = tempfile::tempdir().unwrap();
    -        create_new_article(tmp.path(), "new-art", true, &["rust"]);
    -        create_legacy_article(tmp.path(), "old-art", true, &["go"]);
    -
    -        let articles = ContentLoader::load_all(tmp.path()).unwrap();
    -        assert_eq!(articles.len(), 2);
    -
    -        let new = articles.iter().find(|a| a.sys_title == "new-art").unwrap();
    -        assert!(new.content_html.contains("

    ")); - - let old = articles.iter().find(|a| a.sys_title == "old-art").unwrap(); - assert!(old.content_html.contains("

    Intro

    ")); - } - - #[test] - fn skip_non_directory_entries() { - let tmp = tempfile::tempdir().unwrap(); - create_new_article(tmp.path(), "valid-article", true, &[]); - fs::write(tmp.path().join("README.md"), "not an article").unwrap(); - - let articles = ContentLoader::load_all(tmp.path()).unwrap(); - assert_eq!(articles.len(), 1); - } - - #[test] - fn skip_directory_without_json() { - let tmp = tempfile::tempdir().unwrap(); - create_new_article(tmp.path(), "valid-article", true, &[]); - fs::create_dir_all(tmp.path().join("images")).unwrap(); - fs::write(tmp.path().join("images/photo.png"), b"fake image").unwrap(); - - let articles = ContentLoader::load_all(tmp.path()).unwrap(); - assert_eq!(articles.len(), 1); - } -} diff --git a/reference/crates/wo-route/Cargo.toml b/reference/crates/wo-route/Cargo.toml deleted file mode 100644 index 636e3e9..0000000 --- a/reference/crates/wo-route/Cargo.toml +++ /dev/null @@ -1,7 +0,0 @@ -[package] -name = "wo-route" -version = "0.1.0" -edition = "2021" - -[dependencies] -wo-http = { path = "../wo-http" } diff --git a/reference/crates/wo-route/src/lib.rs b/reference/crates/wo-route/src/lib.rs deleted file mode 100644 index 83a08bc..0000000 --- a/reference/crates/wo-route/src/lib.rs +++ /dev/null @@ -1,5 +0,0 @@ -mod pattern; -mod router; - -pub use pattern::{Pattern, Segment}; -pub use router::{RouteParams, Router}; diff --git a/reference/crates/wo-route/src/pattern.rs b/reference/crates/wo-route/src/pattern.rs deleted file mode 100644 index 62234bd..0000000 --- a/reference/crates/wo-route/src/pattern.rs +++ /dev/null @@ -1,146 +0,0 @@ -/// A compiled URL pattern segment. -#[derive(Debug, Clone, PartialEq)] -pub enum Segment { - /// Exact literal match (e.g., "blog"). - Literal(String), - /// Named parameter (e.g., ":sys_title"). - Param(String), - /// Wildcard matching the rest of the path (e.g., "*path"). - Wildcard(String), -} - -/// A compiled URL pattern like "/blog/:sys_title" or "/static/*path". -#[derive(Debug, Clone)] -pub struct Pattern { - pub segments: Vec, -} - -impl Pattern { - /// Compile a pattern string into segments. - /// - /// - `/blog/:sys_title` → `[Literal("blog"), Param("sys_title")]` - /// - `/static/*path` → `[Literal("static"), Wildcard("path")]` - /// - `/` → `[]` - pub fn compile(pattern: &str) -> Self { - let segments = pattern - .trim_start_matches('/') - .split('/') - .filter(|s| !s.is_empty()) - .map(|s| { - if let Some(name) = s.strip_prefix(':') { - Segment::Param(name.to_string()) - } else if let Some(name) = s.strip_prefix('*') { - Segment::Wildcard(name.to_string()) - } else { - Segment::Literal(s.to_string()) - } - }) - .collect(); - - Self { segments } - } - - /// Try to match a URL path against this pattern. - /// - /// Returns `Some(params)` if the path matches, where params is a list - /// of `(name, value)` pairs for any `:param` or `*wildcard` segments. - pub fn matches(&self, path: &str) -> Option> { - let path_segments: Vec<&str> = path - .trim_start_matches('/') - .split('/') - .filter(|s| !s.is_empty()) - .collect(); - - let mut params = Vec::new(); - let mut pi = 0; // path segment index - - for seg in &self.segments { - match seg { - Segment::Literal(lit) => { - if pi >= path_segments.len() || path_segments[pi] != lit.as_str() { - return None; - } - pi += 1; - } - Segment::Param(name) => { - if pi >= path_segments.len() { - return None; - } - params.push((name.clone(), path_segments[pi].to_string())); - pi += 1; - } - Segment::Wildcard(name) => { - if pi >= path_segments.len() { - return None; - } - let rest = path_segments[pi..].join("/"); - params.push((name.clone(), rest)); - return Some(params); - } - } - } - - // All pattern segments consumed; path must also be fully consumed. - if pi == path_segments.len() { - Some(params) - } else { - None - } - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn root_path() { - let p = Pattern::compile("/"); - assert!(p.matches("/").is_some()); - assert!(p.matches("/blog").is_none()); - } - - #[test] - fn literal_path() { - let p = Pattern::compile("/about"); - assert!(p.matches("/about").is_some()); - assert!(p.matches("/contact").is_none()); - assert!(p.matches("/about/extra").is_none()); - } - - #[test] - fn param_extraction() { - let p = Pattern::compile("/blog/:sys_title"); - let params = p.matches("/blog/linux-misc").unwrap(); - assert_eq!(params, vec![("sys_title".into(), "linux-misc".into())]); - - assert!(p.matches("/blog").is_none()); - assert!(p.matches("/blog/linux-misc/extra").is_none()); - } - - #[test] - fn wildcard() { - let p = Pattern::compile("/static/*path"); - let params = p.matches("/static/styles/main.css").unwrap(); - assert_eq!(params, vec![("path".into(), "styles/main.css".into())]); - - let params = p.matches("/static/logo.png").unwrap(); - assert_eq!(params, vec![("path".into(), "logo.png".into())]); - - assert!(p.matches("/static").is_none()); - } - - #[test] - fn multi_segment() { - let p = Pattern::compile("/tag/:tag"); - let params = p.matches("/tag/rust").unwrap(); - assert_eq!(params, vec![("tag".into(), "rust".into())]); - } - - #[test] - fn no_match() { - let p = Pattern::compile("/blog/:sys_title"); - assert!(p.matches("/about").is_none()); - assert!(p.matches("/").is_none()); - } -} diff --git a/reference/crates/wo-route/src/router.rs b/reference/crates/wo-route/src/router.rs deleted file mode 100644 index f940168..0000000 --- a/reference/crates/wo-route/src/router.rs +++ /dev/null @@ -1,127 +0,0 @@ -use std::collections::HashMap; - -use wo_http::request::{Method, Request}; - -use crate::pattern::Pattern; - -/// Extracted route parameters. -#[derive(Debug, Clone, Default)] -pub struct RouteParams { - params: HashMap, -} - -impl RouteParams { - pub fn get(&self, key: &str) -> Option<&str> { - self.params.get(key).map(|s| s.as_str()) - } - - pub fn from_pairs(pairs: Vec<(String, String)>) -> Self { - Self { - params: pairs.into_iter().collect(), - } - } -} - -/// A route entry: method + pattern + handler name. -struct Route { - method: Method, - pattern: Pattern, - handler: String, -} - -/// URL router that matches requests to named handlers. -pub struct Router { - routes: Vec, -} - -impl Router { - pub fn new() -> Self { - Self { routes: Vec::new() } - } - - /// Add a route. Handler is a string name that the caller maps to a function. - pub fn add(&mut self, method: Method, pattern: &str, handler: &str) { - self.routes.push(Route { - method, - pattern: Pattern::compile(pattern), - handler: handler.to_string(), - }); - } - - /// Match a request to a route. Returns the handler name and extracted params. - pub fn dispatch(&self, request: &Request) -> Option<(String, RouteParams)> { - for route in &self.routes { - if route.method != request.method { - continue; - } - if let Some(pairs) = route.pattern.matches(&request.path) { - return Some(( - route.handler.clone(), - RouteParams::from_pairs(pairs), - )); - } - } - None - } -} - -#[cfg(test)] -mod tests { - use super::*; - - fn make_request(method: Method, path: &str) -> Request { - Request { - method, - path: path.to_string(), - query: None, - headers: HashMap::new(), - } - } - - #[test] - fn dispatch_routes() { - let mut router = Router::new(); - router.add(Method::Get, "/", "home"); - router.add(Method::Get, "/blog/:sys_title", "article"); - router.add(Method::Get, "/about", "about"); - router.add(Method::Get, "/tag/:tag", "tag_listing"); - router.add(Method::Get, "/static/*path", "static_file"); - - // Home. - let (handler, _) = router.dispatch(&make_request(Method::Get, "/")).unwrap(); - assert_eq!(handler, "home"); - - // Article. - let (handler, params) = router - .dispatch(&make_request(Method::Get, "/blog/linux-misc")) - .unwrap(); - assert_eq!(handler, "article"); - assert_eq!(params.get("sys_title"), Some("linux-misc")); - - // About. - let (handler, _) = router.dispatch(&make_request(Method::Get, "/about")).unwrap(); - assert_eq!(handler, "about"); - - // Tag. - let (handler, params) = router - .dispatch(&make_request(Method::Get, "/tag/rust")) - .unwrap(); - assert_eq!(handler, "tag_listing"); - assert_eq!(params.get("tag"), Some("rust")); - - // Static. - let (handler, params) = router - .dispatch(&make_request(Method::Get, "/static/styles/main.css")) - .unwrap(); - assert_eq!(handler, "static_file"); - assert_eq!(params.get("path"), Some("styles/main.css")); - - // No match. - assert!(router - .dispatch(&make_request(Method::Get, "/nonexistent")) - .is_none()); - - // Wrong method. - assert!(router.dispatch(&make_request(Method::Post, "/")).is_none()); - } -} diff --git a/reference/crates/wo-rt/Cargo.toml b/reference/crates/wo-rt/Cargo.toml deleted file mode 100644 index 7aa3b6f..0000000 --- a/reference/crates/wo-rt/Cargo.toml +++ /dev/null @@ -1,28 +0,0 @@ -[package] -name = "wo-rt" -version = "0.1.0" -edition = "2021" - -[dependencies] -wo-model = { path = "../wo-model" } -wo-store = { path = "../wo-store" } -wo-watch = { path = "../wo-watch" } -wo-event = { path = "../wo-event" } -wo-sub = { path = "../wo-sub" } -wo-http = { path = "../wo-http" } -wo-route = { path = "../wo-route" } -wo-htmlx = { path = "../wo-htmlx" } -wo-md = { path = "../wo-md" } -wo-serve = { path = "../wo-serve" } -libc = { workspace = true } -serde = { workspace = true } -serde_json = { workspace = true } -bincode = { workspace = true } - -[[bin]] -name = "writeonce" -path = "src/main.rs" - -[dev-dependencies] -tempfile = "3" -bincode = { workspace = true } diff --git a/reference/crates/wo-rt/src/config.rs b/reference/crates/wo-rt/src/config.rs deleted file mode 100644 index 3430078..0000000 --- a/reference/crates/wo-rt/src/config.rs +++ /dev/null @@ -1,31 +0,0 @@ -use std::path::PathBuf; - -/// Runtime configuration. -#[derive(Debug, Clone)] -pub struct Config { - /// Path to the content directory (JSON + MD source files). - pub content_dir: PathBuf, - /// Path to the data directory (.seg + .idx derived files). - pub data_dir: PathBuf, - /// Path to the templates directory (.htmlx files). - pub templates_dir: PathBuf, - /// Path to the static assets directory (CSS, images). - pub static_dir: PathBuf, - /// HTTP bind address (e.g., "0.0.0.0:3000"). - pub bind_addr: String, - /// Force a full rebuild on startup (ignore existing data/). - pub rebuild_on_start: bool, -} - -impl Config { - pub fn new(content_dir: impl Into, data_dir: impl Into) -> Self { - Self { - content_dir: content_dir.into(), - data_dir: data_dir.into(), - templates_dir: PathBuf::from("templates"), - static_dir: PathBuf::from("static"), - bind_addr: "0.0.0.0:3000".into(), - rebuild_on_start: false, - } - } -} diff --git a/reference/crates/wo-rt/src/handlers.rs b/reference/crates/wo-rt/src/handlers.rs deleted file mode 100644 index c40038e..0000000 --- a/reference/crates/wo-rt/src/handlers.rs +++ /dev/null @@ -1,130 +0,0 @@ -use wo_htmlx::{self, Value}; -use wo_http::response::Response; -use wo_route::RouteParams; -use wo_store::Store; -use wo_htmlx::TemplateRegistry; - -/// Render the homepage: list of published articles. -pub fn handle_home(store: &Store, templates: &TemplateRegistry) -> Response { - let articles = match store.list_published(0, 20) { - Ok(a) => a, - Err(e) => return Response::internal_error(&e.to_string()), - }; - - let articles_val: Vec = articles - .iter() - .map(|a| { - let json = serde_json::to_value(a).unwrap_or_default(); - Value::from_json(&json) - }) - .collect(); - - let mut ctx = std::collections::BTreeMap::new(); - ctx.insert("articles".into(), Value::List(articles_val)); - ctx.insert("page_title".into(), Value::String("writeonce".into())); - let context = Value::Object(ctx); - - render_page("home", &context, templates) -} - -/// Render a single article page. -pub fn handle_article(params: &RouteParams, store: &Store, templates: &TemplateRegistry) -> Response { - let sys_title = match params.get("sys_title") { - Some(t) => t, - None => return Response::not_found(), - }; - - let article = match store.get_by_title(sys_title) { - Ok(Some(a)) => a, - Ok(None) => return Response::not_found(), - Err(e) => return Response::internal_error(&e.to_string()), - }; - - let json = serde_json::to_value(&article).unwrap_or_default(); - let mut ctx = std::collections::BTreeMap::new(); - ctx.insert("article".into(), Value::from_json(&json)); - ctx.insert("page_title".into(), Value::String(article.title.clone())); - let context = Value::Object(ctx); - - render_page("article", &context, templates) -} - -/// Render a tag listing page. -pub fn handle_tag(params: &RouteParams, store: &Store, templates: &TemplateRegistry) -> Response { - let tag = match params.get("tag") { - Some(t) => t, - None => return Response::not_found(), - }; - - let articles = match store.list_by_tag(tag) { - Ok(a) => a, - Err(e) => return Response::internal_error(&e.to_string()), - }; - - let articles_val: Vec = articles - .iter() - .map(|a| { - let json = serde_json::to_value(a).unwrap_or_default(); - Value::from_json(&json) - }) - .collect(); - - let mut ctx = std::collections::BTreeMap::new(); - ctx.insert("articles".into(), Value::List(articles_val)); - ctx.insert("page_title".into(), Value::String(format!("tag: {}", tag))); - ctx.insert("tag".into(), Value::String(tag.to_string())); - let context = Value::Object(ctx); - - render_page("home", &context, templates) -} - -/// Render a static page (about, contact). -pub fn handle_static_page(name: &str, templates: &TemplateRegistry) -> Response { - let mut ctx = std::collections::BTreeMap::new(); - ctx.insert("page_title".into(), Value::String(name.to_string())); - let context = Value::Object(ctx); - - render_page(name, &context, templates) -} - -/// Render a page template composed with layout, header, and footer. -fn render_page(template_name: &str, context: &Value, templates: &TemplateRegistry) -> Response { - let page_nodes = match templates.get(template_name) { - Some(nodes) => nodes, - None => return Response::not_found(), - }; - - let partials = templates.partials(); - - // Render the page content. - let page_html = wo_htmlx::render(page_nodes, context, partials); - - // Compose with layout if it exists. - let html = if let Some(layout_nodes) = templates.get("layout") { - // Inject page content and header/footer. - let header_html = templates - .get("header") - .map(|n| wo_htmlx::render(n, context, partials)) - .unwrap_or_default(); - let footer_html = templates - .get("footer") - .map(|n| wo_htmlx::render(n, context, partials)) - .unwrap_or_default(); - - let mut layout_ctx = std::collections::BTreeMap::new(); - layout_ctx.insert("content".into(), Value::String(page_html)); - layout_ctx.insert("header".into(), Value::String(header_html)); - layout_ctx.insert("footer".into(), Value::String(footer_html)); - if let Value::Object(map) = context { - for (k, v) in map { - layout_ctx.insert(k.clone(), v.clone()); - } - } - let layout_context = Value::Object(layout_ctx); - wo_htmlx::render(layout_nodes, &layout_context, partials) - } else { - page_html - }; - - Response::html(html) -} diff --git a/reference/crates/wo-rt/src/lib.rs b/reference/crates/wo-rt/src/lib.rs deleted file mode 100644 index a58caa8..0000000 --- a/reference/crates/wo-rt/src/lib.rs +++ /dev/null @@ -1,6 +0,0 @@ -mod config; -mod handlers; -mod runtime; - -pub use config::Config; -pub use runtime::{Runtime, RuntimeHandle}; diff --git a/reference/crates/wo-rt/src/main.rs b/reference/crates/wo-rt/src/main.rs deleted file mode 100644 index 5b1ed7d..0000000 --- a/reference/crates/wo-rt/src/main.rs +++ /dev/null @@ -1,34 +0,0 @@ -use std::path::PathBuf; -use wo_rt::{Config, Runtime}; - -fn main() { - let content_dir = PathBuf::from("content"); - let data_dir = PathBuf::from("data"); - - let config = Config { - content_dir, - data_dir, - templates_dir: PathBuf::from("templates"), - static_dir: PathBuf::from("static"), - bind_addr: "0.0.0.0:3000".into(), - rebuild_on_start: true, - }; - - let mut rt = match Runtime::new(&config) { - Ok(rt) => rt, - Err(e) => { - eprintln!("failed to start: {}", e); - std::process::exit(1); - } - }; - - eprintln!("writeonce started — content={} data={}", - config.content_dir.display(), config.data_dir.display()); - - if let Err(e) = rt.run() { - eprintln!("runtime error: {}", e); - std::process::exit(1); - } - - eprintln!("writeonce shut down"); -} diff --git a/reference/crates/wo-rt/src/runtime.rs b/reference/crates/wo-rt/src/runtime.rs deleted file mode 100644 index 0d32bf0..0000000 --- a/reference/crates/wo-rt/src/runtime.rs +++ /dev/null @@ -1,481 +0,0 @@ -use std::collections::HashMap; -use std::io; -use std::os::unix::io::RawFd; -use std::sync::atomic::{AtomicBool, Ordering}; -use std::sync::Arc; -use std::time::Duration; - -use wo_event::{EventLoop, Interest, SignalFd, TimerFd, Token}; -use wo_htmlx::TemplateRegistry; -use wo_http::connection::{Connection, State}; -use wo_http::listener::TcpListener; -use wo_http::request::Method; -use wo_http::response::Response; -use wo_route::{RouteParams, Router}; -use wo_store::Store; -use wo_sub::{ChangeType, Subscription, SubscriptionManager}; -use wo_watch::{ContentChange, ContentWatcher}; - -use crate::handlers; -use crate::Config; - -// Well-known tokens. -const TOKEN_WATCHER: Token = 1; -const TOKEN_SIGNAL: Token = 2; -const TOKEN_TIMER: Token = 3; -const TOKEN_NOTIFY: Token = 4; -const TOKEN_HTTP_LISTENER: Token = 5; -// Token ranges. -const TOKEN_HTTP_BASE: Token = 10000; -const TOKEN_HTTP_MAX: Token = 19999; -const TOKEN_SUB_BASE: Token = 20000; - -/// The writeonce runtime: single process, single event loop, all fds on one epoll. -pub struct Runtime { - event_loop: EventLoop, - store: Store, - watcher: ContentWatcher, - subscriptions: SubscriptionManager, - signal_fd: SignalFd, - timer_fd: TimerFd, - running: Arc, - // HTTP - listener: Option, - connections: HashMap, - next_http_token: Token, - router: Router, - templates: TemplateRegistry, - static_dir: std::path::PathBuf, - // Subscribed browser connections. - next_sub_token: Token, - sub_token_to_fd: HashMap, -} - -/// Handle returned by `Runtime::start()` for external interaction. -pub struct RuntimeHandle { - running: Arc, -} - -impl RuntimeHandle { - pub fn shutdown(&self) { - self.running.store(false, Ordering::SeqCst); - } -} - -impl Runtime { - pub fn new(config: &Config) -> io::Result { - let event_loop = EventLoop::new()?; - - let store = if config.rebuild_on_start { - let mut s = Store::open(&config.content_dir, &config.data_dir)?; - s.rebuild()?; - s - } else { - Store::open(&config.content_dir, &config.data_dir)? - }; - - let watcher = ContentWatcher::new(&config.content_dir)?; - let subscriptions = SubscriptionManager::new()?; - let signal_fd = SignalFd::new()?; - let timer_fd = TimerFd::new()?; - timer_fd.set(Duration::from_secs(60), Duration::from_secs(60))?; - - // Register system fds. - event_loop.register(watcher.fd(), Interest::Readable, TOKEN_WATCHER)?; - event_loop.register(signal_fd.fd(), Interest::Readable, TOKEN_SIGNAL)?; - event_loop.register(timer_fd.fd(), Interest::Readable, TOKEN_TIMER)?; - event_loop.register(subscriptions.notify_fd(), Interest::Readable, TOKEN_NOTIFY)?; - - // HTTP listener (optional — may not bind in test mode). - let listener = if !config.bind_addr.is_empty() { - match TcpListener::bind(&config.bind_addr) { - Ok(l) => { - event_loop.register(l.fd(), Interest::Readable, TOKEN_HTTP_LISTENER)?; - Some(l) - } - Err(e) => { - eprintln!("wo-rt: failed to bind {}: {}", config.bind_addr, e); - None - } - } - } else { - None - }; - - // Load templates. - let templates = TemplateRegistry::load(&config.templates_dir)?; - - // Build router. - let mut router = Router::new(); - router.add(Method::Get, "/", "home"); - router.add(Method::Get, "/blog/:sys_title", "article"); - router.add(Method::Get, "/about", "about"); - router.add(Method::Get, "/contact", "contact"); - router.add(Method::Get, "/tag/:tag", "tag_listing"); - router.add(Method::Get, "/static/*path", "static_file"); - - Ok(Self { - event_loop, - store, - watcher, - subscriptions, - signal_fd, - timer_fd, - running: Arc::new(AtomicBool::new(false)), - listener, - connections: HashMap::new(), - next_http_token: TOKEN_HTTP_BASE, - router, - templates, - static_dir: config.static_dir.clone(), - next_sub_token: TOKEN_SUB_BASE, - sub_token_to_fd: HashMap::new(), - }) - } - - pub fn run(&mut self) -> io::Result<()> { - self.running.store(true, Ordering::SeqCst); - - if let Some(ref listener) = self.listener { - let (_, port) = listener.local_addr()?; - eprintln!("writeonce listening on port {}", port); - } - - while self.running.load(Ordering::SeqCst) { - let events = self.event_loop.poll(Some(Duration::from_millis(500)))?; - - for event in events { - match event.token { - TOKEN_WATCHER if event.readable => { - self.handle_watcher_event()?; - } - TOKEN_SIGNAL if event.readable => { - let _ = self.signal_fd.read(); - self.running.store(false, Ordering::SeqCst); - } - TOKEN_TIMER if event.readable => { - let _ = self.timer_fd.read(); - } - TOKEN_NOTIFY if event.readable => { - // Notifications already written by SubscriptionManager. - } - TOKEN_HTTP_LISTENER if event.readable => { - self.handle_accept()?; - } - token if token >= TOKEN_SUB_BASE => { - // Subscribed connection. - if event.hangup || event.error { - if let Some(&fd) = self.sub_token_to_fd.get(&token) { - self.subscriptions.unsubscribe(fd); - let _ = self.event_loop.deregister(fd); - self.sub_token_to_fd.remove(&token); - // Don't close fd here — Connection owns it. - } - } - } - token if token >= TOKEN_HTTP_BASE => { - self.handle_http_event(token, &event)?; - } - _ => {} - } - } - - // Clean up completed connections. - let done_tokens: Vec = self - .connections - .iter() - .filter(|(_, c)| c.state == State::Done) - .map(|(&t, _)| t) - .collect(); - for token in done_tokens { - if let Some(conn) = self.connections.remove(&token) { - let _ = self.event_loop.deregister(conn.fd); - } - } - } - - Ok(()) - } - - pub fn handle(&self) -> RuntimeHandle { - RuntimeHandle { - running: self.running.clone(), - } - } - - pub fn store(&self) -> &Store { - &self.store - } - - pub fn store_mut(&mut self) -> &mut Store { - &mut self.store - } - - /// Subscribe a fd (binary format, for internal use). - pub fn subscribe(&mut self, fd: RawFd, query: Subscription) -> io::Result { - let token = self.next_sub_token; - self.next_sub_token += 1; - self.event_loop.register(fd, Interest::Readable, token)?; - self.subscriptions.subscribe(fd, query); - self.sub_token_to_fd.insert(token, fd); - Ok(token) - } - - fn handle_accept(&mut self) -> io::Result<()> { - let listener = match &self.listener { - Some(l) => l, - None => return Ok(()), - }; - - // Accept all pending connections. - while let Some(client_fd) = listener.accept()? { - if self.next_http_token > TOKEN_HTTP_MAX { - self.next_http_token = TOKEN_HTTP_BASE; // recycle tokens - } - let token = self.next_http_token; - self.next_http_token += 1; - - self.event_loop - .register(client_fd, Interest::Readable, token)?; - self.connections.insert(token, Connection::new(client_fd)); - } - - Ok(()) - } - - fn handle_http_event(&mut self, token: Token, event: &wo_event::Event) -> io::Result<()> { - if event.hangup || event.error { - if let Some(conn) = self.connections.remove(&token) { - let _ = self.event_loop.deregister(conn.fd); - } - return Ok(()); - } - - // Read phase: parse the request without holding mutable borrow during dispatch. - let conn = match self.connections.get_mut(&token) { - Some(c) => c, - None => return Ok(()), - }; - - match conn.state { - State::ReadingRequest if event.readable => { - let open = conn.read()?; - if !open { - conn.set_done(); - return Ok(()); - } - - if let Some(request) = conn.try_parse() { - // Dispatch the handler (borrows self immutably via store/templates). - let dispatch_result = self.router.dispatch(&request); - let response = match dispatch_result { - Some((ref handler, ref params)) => { - dispatch_handler(handler, params, &self.store, &self.templates, &self.static_dir) - } - None => Response::not_found(), - }; - - // Now get the connection again to set the response. - let conn = self.connections.get_mut(&token).unwrap(); - conn.set_response(&response); - if conn.write()? { - conn.set_done(); - } else { - let fd = conn.fd; - self.event_loop.modify(fd, Interest::Writable, token)?; - } - } - } - State::WritingResponse if event.writable => { - if conn.write()? { - conn.set_done(); - } - } - _ => {} - } - - Ok(()) - } - - fn handle_watcher_event(&mut self) -> io::Result<()> { - let changes = self.watcher.process_events()?; - - for change in &changes { - match change { - ContentChange::Created(sys_title) | ContentChange::Modified(sys_title) => { - self.store.rebuild()?; - let version = self.store.article_version(sys_title).unwrap_or(0); - let tags = self - .store - .get_by_title(sys_title)? - .map(|a| a.tags.clone()) - .unwrap_or_default(); - let change_type = match change { - ContentChange::Created(_) => ChangeType::Created, - _ => ChangeType::Updated, - }; - self.subscriptions.notify(sys_title, &tags, change_type, version)?; - } - ContentChange::Deleted(sys_title) => { - let version = self.store.article_version(sys_title).unwrap_or(0); - self.subscriptions.notify(sys_title, &[], ChangeType::Deleted, version)?; - self.store.rebuild()?; - } - } - } - - Ok(()) - } -} - -fn dispatch_handler( - handler: &str, - params: &RouteParams, - store: &Store, - templates: &TemplateRegistry, - static_dir: &std::path::Path, -) -> Response { - match handler { - "home" => handlers::handle_home(store, templates), - "article" => handlers::handle_article(params, store, templates), - "tag_listing" => handlers::handle_tag(params, store, templates), - "about" => handlers::handle_static_page("about", templates), - "contact" => handlers::handle_static_page("contact", templates), - "static_file" => { - let path = params.get("path").unwrap_or(""); - wo_serve::send_file(static_dir, path) - } - _ => Response::not_found(), - } -} - -#[cfg(test)] -mod tests { - use super::*; - use std::fs; - - fn create_test_article(base: &std::path::Path, sys_title: &str, published: bool) { - let dir = base.join(sys_title); - fs::create_dir_all(&dir).unwrap(); - let json = format!( - r#"{{ - "title": "{}", - "sys_title": "{}", - "published": {}, - "content": {{ - "author": "Author", - "content": {{ - "sections": [{{ "heading": "Intro", "paragraphs": ["Hello."] }}], - "codes": [], - "images": [] - }}, - "tags": ["test"], - "publishedOn": 1000 - }}, - "published_on": 1000 - }}"#, - sys_title, sys_title, published - ); - fs::write(dir.join(format!("{}.json", sys_title)), json).unwrap(); - } - - fn test_config(content: &std::path::Path, data: &std::path::Path) -> Config { - Config { - bind_addr: String::new(), // no HTTP in tests - ..Config::new(content, data) - } - } - - #[test] - fn runtime_creates_and_queries() { - let tmp = tempfile::tempdir().unwrap(); - let content = tmp.path().join("content"); - let data = tmp.path().join("data"); - - create_test_article(&content, "art-one", true); - create_test_article(&content, "art-two", true); - - let config = test_config(&content, &data); - let rt = Runtime::new(&config).unwrap(); - - let art = rt.store().get_by_title("art-one").unwrap().unwrap(); - assert_eq!(art.sys_title, "art-one"); - assert_eq!(rt.store().count_published().unwrap(), 2); - } - - #[test] - fn runtime_subscribe_and_notify() { - let tmp = tempfile::tempdir().unwrap(); - let content = tmp.path().join("content"); - let data = tmp.path().join("data"); - - create_test_article(&content, "art-one", true); - - let config = test_config(&content, &data); - let mut rt = Runtime::new(&config).unwrap(); - - let mut fds = [0i32; 2]; - assert_eq!(unsafe { libc::pipe2(fds.as_mut_ptr(), libc::O_NONBLOCK) }, 0); - let (read_fd, write_fd) = (fds[0], fds[1]); - - rt.subscribe(write_fd, Subscription::ByTitle("art-one".into())).unwrap(); - - rt.subscriptions - .notify("art-one", &["test".into()], ChangeType::Updated, 1) - .unwrap(); - - let mut len_buf = [0u8; 4]; - let ret = unsafe { libc::read(read_fd, len_buf.as_mut_ptr() as *mut libc::c_void, 4) }; - assert_eq!(ret, 4); - let len = u32::from_le_bytes(len_buf) as usize; - - let mut payload = vec![0u8; len]; - let ret = unsafe { libc::read(read_fd, payload.as_mut_ptr() as *mut libc::c_void, len) }; - assert_eq!(ret as usize, len); - - let notification: wo_sub::Notification = bincode::deserialize(&payload).unwrap(); - assert_eq!(notification.sys_title, "art-one"); - - unsafe { libc::close(read_fd); libc::close(write_fd); } - } - - #[test] - fn runtime_shutdown_via_handle() { - let tmp = tempfile::tempdir().unwrap(); - let content = tmp.path().join("content"); - let data = tmp.path().join("data"); - fs::create_dir_all(&content).unwrap(); - - let config = test_config(&content, &data); - let mut rt = Runtime::new(&config).unwrap(); - let handle = rt.handle(); - - std::thread::spawn(move || { - std::thread::sleep(Duration::from_millis(100)); - handle.shutdown(); - }); - - rt.run().unwrap(); - } - - #[test] - fn runtime_rebuild_on_start() { - let tmp = tempfile::tempdir().unwrap(); - let content = tmp.path().join("content"); - let data = tmp.path().join("data"); - - create_test_article(&content, "art-one", true); - - let config = test_config(&content, &data); - let rt = Runtime::new(&config).unwrap(); - assert_eq!(rt.store().count_published().unwrap(), 1); - drop(rt); - - let config = Config { - rebuild_on_start: true, - ..test_config(&content, &data) - }; - let rt = Runtime::new(&config).unwrap(); - assert_eq!(rt.store().count_published().unwrap(), 1); - } -} diff --git a/reference/crates/wo-seg/Cargo.toml b/reference/crates/wo-seg/Cargo.toml deleted file mode 100644 index 5fc0b8e..0000000 --- a/reference/crates/wo-seg/Cargo.toml +++ /dev/null @@ -1,13 +0,0 @@ -[package] -name = "wo-seg" -version = "0.1.0" -edition = "2021" - -[dependencies] -wo-model = { path = "../wo-model" } -serde = { workspace = true } -bincode = { workspace = true } -libc = { workspace = true } - -[dev-dependencies] -tempfile = "3" diff --git a/reference/crates/wo-seg/src/header.rs b/reference/crates/wo-seg/src/header.rs deleted file mode 100644 index 03786fd..0000000 --- a/reference/crates/wo-seg/src/header.rs +++ /dev/null @@ -1,111 +0,0 @@ -use std::io; - -/// Magic bytes: "WOSF" (WriteOnce Segment File). -pub const MAGIC: [u8; 4] = *b"WOSF"; - -/// Segment file version. -pub const VERSION: u16 = 1; - -/// Fixed header size in bytes. -pub const HEADER_SIZE: u64 = 32; - -/// The segment file header, stored at byte 0 of every .seg file. -/// -/// Layout (32 bytes): -/// ```text -/// [0..4] magic: b"WOSF" -/// [4..6] version: u16 LE -/// [6..8] flags: u16 LE (reserved) -/// [8..16] record_count: u64 LE -/// [16..24] data_start: u64 LE -/// [24..32] reserved: 8 bytes -/// ``` -#[derive(Debug, Clone, Copy, PartialEq)] -pub struct SegHeader { - pub version: u16, - pub flags: u16, - pub record_count: u64, - pub data_start: u64, -} - -impl SegHeader { - /// Create a new header with default values. - pub fn new() -> Self { - Self { - version: VERSION, - flags: 0, - record_count: 0, - data_start: HEADER_SIZE, - } - } - - /// Serialize the header into a 32-byte buffer. - pub fn to_bytes(&self) -> [u8; 32] { - let mut buf = [0u8; 32]; - buf[0..4].copy_from_slice(&MAGIC); - buf[4..6].copy_from_slice(&self.version.to_le_bytes()); - buf[6..8].copy_from_slice(&self.flags.to_le_bytes()); - buf[8..16].copy_from_slice(&self.record_count.to_le_bytes()); - buf[16..24].copy_from_slice(&self.data_start.to_le_bytes()); - // [24..32] reserved, stays zero - buf - } - - /// Parse a header from a 32-byte buffer. - pub fn from_bytes(buf: &[u8; 32]) -> io::Result { - if &buf[0..4] != &MAGIC { - return Err(io::Error::new( - io::ErrorKind::InvalidData, - format!( - "invalid magic bytes: expected {:?}, got {:?}", - MAGIC, - &buf[0..4] - ), - )); - } - - let version = u16::from_le_bytes([buf[4], buf[5]]); - if version != VERSION { - return Err(io::Error::new( - io::ErrorKind::InvalidData, - format!("unsupported version: {}", version), - )); - } - - let flags = u16::from_le_bytes([buf[6], buf[7]]); - let record_count = u64::from_le_bytes(buf[8..16].try_into().unwrap()); - let data_start = u64::from_le_bytes(buf[16..24].try_into().unwrap()); - - Ok(Self { - version, - flags, - record_count, - data_start, - }) - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn round_trip() { - let header = SegHeader { - version: VERSION, - flags: 0, - record_count: 42, - data_start: HEADER_SIZE, - }; - let bytes = header.to_bytes(); - let parsed = SegHeader::from_bytes(&bytes).unwrap(); - assert_eq!(header, parsed); - } - - #[test] - fn bad_magic() { - let mut bytes = SegHeader::new().to_bytes(); - bytes[0] = b'X'; - assert!(SegHeader::from_bytes(&bytes).is_err()); - } -} diff --git a/reference/crates/wo-seg/src/lib.rs b/reference/crates/wo-seg/src/lib.rs deleted file mode 100644 index 233ad6e..0000000 --- a/reference/crates/wo-seg/src/lib.rs +++ /dev/null @@ -1,14 +0,0 @@ -mod header; -mod reader; -mod writer; - -pub use header::{SegHeader, HEADER_SIZE, MAGIC}; -pub use reader::SegReader; -pub use writer::SegWriter; - -/// Record flags. -pub const FLAG_ACTIVE: u8 = 0x00; -pub const FLAG_TOMBSTONED: u8 = 0x01; - -/// Size of the per-record header: 4 bytes length + 1 byte flags. -pub const RECORD_HEADER_SIZE: usize = 5; diff --git a/reference/crates/wo-seg/src/reader.rs b/reference/crates/wo-seg/src/reader.rs deleted file mode 100644 index 31a7c04..0000000 --- a/reference/crates/wo-seg/src/reader.rs +++ /dev/null @@ -1,221 +0,0 @@ -use std::fs::File; -use std::io; -use std::os::unix::io::AsRawFd; -use std::path::Path; - -use wo_model::Article; - -use crate::header::SegHeader; -use crate::{FLAG_TOMBSTONED, RECORD_HEADER_SIZE}; - -/// Reads articles from a .seg file. -pub struct SegReader { - file: File, - header: SegHeader, -} - -impl SegReader { - /// Open an existing .seg file and validate its header. - pub fn open(path: &Path) -> io::Result { - let file = File::open(path)?; - let mut header_buf = [0u8; 32]; - pread_exact(&file, &mut header_buf, 0)?; - let header = SegHeader::from_bytes(&header_buf)?; - - Ok(Self { file, header }) - } - - /// Read a single article record at the given byte offset. - /// - /// Returns `None` if the record is tombstoned. - pub fn read_at(&self, offset: u64) -> io::Result> { - // Read the record header: [u32 length][u8 flags] - let mut rec_header = [0u8; RECORD_HEADER_SIZE]; - pread_exact(&self.file, &mut rec_header, offset)?; - - let payload_len = u32::from_le_bytes(rec_header[0..4].try_into().unwrap()) as usize; - let flags = rec_header[4]; - - if flags == FLAG_TOMBSTONED { - return Ok(None); - } - - // Read the payload. - let mut payload = vec![0u8; payload_len]; - pread_exact(&self.file, &mut payload, offset + RECORD_HEADER_SIZE as u64)?; - - let article: Article = bincode::deserialize(&payload).map_err(|e| { - io::Error::new(io::ErrorKind::InvalidData, format!("bincode: {}", e)) - })?; - - Ok(Some(article)) - } - - /// Iterate all active (non-tombstoned) records. - /// - /// Returns `(byte_offset, Article)` pairs. - pub fn iter(&self) -> io::Result> { - let mut results = Vec::new(); - let mut pos = self.header.data_start; - - for _ in 0..self.header.record_count { - // Read record header. - let mut rec_header = [0u8; RECORD_HEADER_SIZE]; - pread_exact(&self.file, &mut rec_header, pos)?; - - let payload_len = u32::from_le_bytes(rec_header[0..4].try_into().unwrap()) as usize; - let flags = rec_header[4]; - let record_offset = pos; - - pos += RECORD_HEADER_SIZE as u64 + payload_len as u64; - - if flags == FLAG_TOMBSTONED { - continue; - } - - let mut payload = vec![0u8; payload_len]; - pread_exact( - &self.file, - &mut payload, - record_offset + RECORD_HEADER_SIZE as u64, - )?; - - let article: Article = bincode::deserialize(&payload).map_err(|e| { - io::Error::new(io::ErrorKind::InvalidData, format!("bincode: {}", e)) - })?; - - results.push((record_offset, article)); - } - - Ok(results) - } - - /// Return the parsed file header. - pub fn header(&self) -> &SegHeader { - &self.header - } -} - -/// Positional read using pread(2). Does not modify the file offset. -fn pread_exact(file: &File, buf: &mut [u8], offset: u64) -> io::Result<()> { - let fd = file.as_raw_fd(); - let mut read = 0usize; - while read < buf.len() { - let ret = unsafe { - libc::pread( - fd, - buf[read..].as_mut_ptr() as *mut libc::c_void, - buf.len() - read, - (offset + read as u64) as libc::off_t, - ) - }; - if ret < 0 { - return Err(io::Error::last_os_error()); - } - if ret == 0 { - return Err(io::Error::new( - io::ErrorKind::UnexpectedEof, - "pread returned 0", - )); - } - read += ret as usize; - } - Ok(()) -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::SegWriter; - use wo_model::*; - - fn sample_article(sys_title: &str) -> Article { - Article { - sys_title: sys_title.to_string(), - title: format!("Title: {}", sys_title), - published: true, - author: "Author".into(), - tags: vec!["test".into()], - published_on: Some(2000), - content_html: "

    Hello world.

    ".into(), - } - } - - #[test] - fn write_and_read_single() { - let tmp = tempfile::tempdir().unwrap(); - let path = tmp.path().join("test.seg"); - - let article = sample_article("test-one"); - - let offset = { - let mut writer = SegWriter::create(&path).unwrap(); - writer.append(&article).unwrap() - }; - - let reader = SegReader::open(&path).unwrap(); - assert_eq!(reader.header().record_count, 1); - - let loaded = reader.read_at(offset).unwrap().unwrap(); - assert_eq!(loaded.sys_title, "test-one"); - assert_eq!(loaded, article); - } - - #[test] - fn write_and_read_multiple() { - let tmp = tempfile::tempdir().unwrap(); - let path = tmp.path().join("multi.seg"); - - let articles: Vec
    = (0..5).map(|i| sample_article(&format!("art-{}", i))).collect(); - - let offsets: Vec = { - let mut writer = SegWriter::create(&path).unwrap(); - articles.iter().map(|a| writer.append(a).unwrap()).collect() - }; - - let reader = SegReader::open(&path).unwrap(); - assert_eq!(reader.header().record_count, 5); - - // Read each by offset. - for (i, offset) in offsets.iter().enumerate() { - let loaded = reader.read_at(*offset).unwrap().unwrap(); - assert_eq!(loaded.sys_title, format!("art-{}", i)); - } - - // Iterate all. - let all = reader.iter().unwrap(); - assert_eq!(all.len(), 5); - } - - #[test] - fn tombstone_record() { - let tmp = tempfile::tempdir().unwrap(); - let path = tmp.path().join("tomb.seg"); - - let offset = { - let mut writer = SegWriter::create(&path).unwrap(); - let o = writer.append(&sample_article("to-delete")).unwrap(); - writer.append(&sample_article("to-keep")).unwrap(); - writer.tombstone(o).unwrap(); - o - }; - - let reader = SegReader::open(&path).unwrap(); - - // Direct read returns None for tombstoned. - assert!(reader.read_at(offset).unwrap().is_none()); - - // Iter skips tombstoned. - let all = reader.iter().unwrap(); - assert_eq!(all.len(), 1); - assert_eq!(all[0].1.sys_title, "to-keep"); - } - - #[test] - fn corrupted_header() { - let tmp = tempfile::tempdir().unwrap(); - let path = tmp.path().join("bad.seg"); - std::fs::write(&path, b"not a segment file at all!!!!!xx").unwrap(); - assert!(SegReader::open(&path).is_err()); - } -} diff --git a/reference/crates/wo-seg/src/writer.rs b/reference/crates/wo-seg/src/writer.rs deleted file mode 100644 index acdb772..0000000 --- a/reference/crates/wo-seg/src/writer.rs +++ /dev/null @@ -1,129 +0,0 @@ -use std::fs::{File, OpenOptions}; -use std::io; -use std::os::unix::io::AsRawFd; -use std::path::Path; - -use wo_model::Article; - -use crate::header::{SegHeader, HEADER_SIZE}; -use crate::{FLAG_ACTIVE, RECORD_HEADER_SIZE}; - -/// Writes articles to a .seg file. -/// -/// Records are appended sequentially. Each append returns the byte offset -/// of the record, which can be stored in an index for direct access. -pub struct SegWriter { - file: File, - header: SegHeader, - /// Current write position (end of file). - pos: u64, -} - -impl SegWriter { - /// Create a new .seg file at the given path. - /// - /// Writes the initial header and optionally pre-allocates disk space - /// using `fallocate` to reduce fragmentation. - pub fn create(path: &Path) -> io::Result { - let file = OpenOptions::new() - .read(true) - .write(true) - .create(true) - .truncate(true) - .open(path)?; - - let header = SegHeader::new(); - // Write the header at position 0. - pwrite_all(&file, &header.to_bytes(), 0)?; - - // Pre-allocate 1 MB to reduce fragmentation. - let _ = fallocate_safe(&file, HEADER_SIZE as i64, 1024 * 1024); - - Ok(Self { - file, - header, - pos: HEADER_SIZE, - }) - } - - /// Append an article as an active record. - /// - /// Returns the byte offset of the record start (the position of the - /// length prefix), which can be used for direct reads via `SegReader::read_at`. - pub fn append(&mut self, article: &Article) -> io::Result { - let payload = bincode::serialize(article).map_err(|e| { - io::Error::new(io::ErrorKind::InvalidData, format!("bincode: {}", e)) - })?; - - let record_offset = self.pos; - let payload_len = payload.len() as u32; - - // Build record: [u32 length][u8 flags][payload] - let mut record = Vec::with_capacity(RECORD_HEADER_SIZE + payload.len()); - record.extend_from_slice(&payload_len.to_le_bytes()); - record.push(FLAG_ACTIVE); - record.extend_from_slice(&payload); - - pwrite_all(&self.file, &record, record_offset)?; - - self.pos += record.len() as u64; - self.header.record_count += 1; - - // Update the header with the new record count. - pwrite_all(&self.file, &self.header.to_bytes(), 0)?; - - Ok(record_offset) - } - - /// Tombstone a record at the given offset. - /// - /// Sets the flags byte to `FLAG_TOMBSTONED` without modifying the payload. - pub fn tombstone(&self, record_offset: u64) -> io::Result<()> { - let flags_offset = record_offset + 4; // skip the u32 length - pwrite_all(&self.file, &[crate::FLAG_TOMBSTONED], flags_offset) - } - - /// Return the current header (record count, etc.). - pub fn header(&self) -> &SegHeader { - &self.header - } - - /// Sync file data and metadata to disk. - pub fn sync(&self) -> io::Result<()> { - self.file.sync_all() - } -} - -/// Positional write using pwrite(2). Does not modify the file offset. -fn pwrite_all(file: &File, buf: &[u8], offset: u64) -> io::Result<()> { - let fd = file.as_raw_fd(); - let mut written = 0usize; - while written < buf.len() { - let ret = unsafe { - libc::pwrite( - fd, - buf[written..].as_ptr() as *const libc::c_void, - buf.len() - written, - (offset + written as u64) as libc::off_t, - ) - }; - if ret < 0 { - return Err(io::Error::last_os_error()); - } - if ret == 0 { - return Err(io::Error::new(io::ErrorKind::WriteZero, "pwrite returned 0")); - } - written += ret as usize; - } - Ok(()) -} - -/// Best-effort fallocate. Non-fatal if unsupported. -fn fallocate_safe(file: &File, offset: i64, len: i64) -> io::Result<()> { - let ret = unsafe { libc::fallocate(file.as_raw_fd(), 0, offset, len) }; - if ret < 0 { - Err(io::Error::last_os_error()) - } else { - Ok(()) - } -} diff --git a/reference/crates/wo-serve/Cargo.toml b/reference/crates/wo-serve/Cargo.toml deleted file mode 100644 index 505049c..0000000 --- a/reference/crates/wo-serve/Cargo.toml +++ /dev/null @@ -1,11 +0,0 @@ -[package] -name = "wo-serve" -version = "0.1.0" -edition = "2021" - -[dependencies] -wo-http = { path = "../wo-http" } -libc = { workspace = true } - -[dev-dependencies] -tempfile = "3" diff --git a/reference/crates/wo-serve/src/lib.rs b/reference/crates/wo-serve/src/lib.rs deleted file mode 100644 index 5a91e67..0000000 --- a/reference/crates/wo-serve/src/lib.rs +++ /dev/null @@ -1,7 +0,0 @@ -mod mime; -mod resolve; -mod sendfile; - -pub use mime::content_type_for; -pub use resolve::resolve_path; -pub use sendfile::send_file; diff --git a/reference/crates/wo-serve/src/mime.rs b/reference/crates/wo-serve/src/mime.rs deleted file mode 100644 index 3b347aa..0000000 --- a/reference/crates/wo-serve/src/mime.rs +++ /dev/null @@ -1,44 +0,0 @@ -/// Map a file extension to its HTTP Content-Type. -pub fn content_type_for(path: &str) -> &'static str { - let ext = path.rsplit('.').next().unwrap_or(""); - match ext { - "html" | "htmlx" => "text/html; charset=utf-8", - "css" => "text/css; charset=utf-8", - "js" => "application/javascript; charset=utf-8", - "json" => "application/json; charset=utf-8", - "png" => "image/png", - "jpg" | "jpeg" => "image/jpeg", - "gif" => "image/gif", - "svg" => "image/svg+xml", - "ico" => "image/x-icon", - "woff" => "font/woff", - "woff2" => "font/woff2", - "ttf" => "font/ttf", - "txt" => "text/plain; charset=utf-8", - "xml" => "application/xml; charset=utf-8", - _ => "application/octet-stream", - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn known_types() { - assert_eq!(content_type_for("main.css"), "text/css; charset=utf-8"); - assert_eq!(content_type_for("logo.png"), "image/png"); - assert_eq!(content_type_for("app.js"), "application/javascript; charset=utf-8"); - assert_eq!(content_type_for("favicon.ico"), "image/x-icon"); - } - - #[test] - fn unknown_type() { - assert_eq!(content_type_for("file.xyz"), "application/octet-stream"); - } - - #[test] - fn nested_path() { - assert_eq!(content_type_for("styles/code-theme.css"), "text/css; charset=utf-8"); - } -} diff --git a/reference/crates/wo-serve/src/resolve.rs b/reference/crates/wo-serve/src/resolve.rs deleted file mode 100644 index 5c5b788..0000000 --- a/reference/crates/wo-serve/src/resolve.rs +++ /dev/null @@ -1,80 +0,0 @@ -use std::io; -use std::path::{Path, PathBuf}; - -/// Resolve a request path relative to a static directory. -/// -/// Returns the absolute path to the file, or an error if: -/// - The path contains `..` (directory traversal) -/// - The resolved path escapes the base directory -/// - The file doesn't exist -pub fn resolve_path(base: &Path, request_path: &str) -> io::Result { - // Reject any path with `..` - if request_path.contains("..") { - return Err(io::Error::new( - io::ErrorKind::PermissionDenied, - "path traversal rejected", - )); - } - - let clean = request_path.trim_start_matches('/'); - let resolved = base.join(clean); - - // Canonicalize and verify it's still under base. - let canonical = resolved.canonicalize().map_err(|_| { - io::Error::new(io::ErrorKind::NotFound, "file not found") - })?; - - let canonical_base = base.canonicalize().map_err(|_| { - io::Error::new(io::ErrorKind::NotFound, "base directory not found") - })?; - - if !canonical.starts_with(&canonical_base) { - return Err(io::Error::new( - io::ErrorKind::PermissionDenied, - "path traversal rejected", - )); - } - - if !canonical.is_file() { - return Err(io::Error::new(io::ErrorKind::NotFound, "not a file")); - } - - Ok(canonical) -} - -#[cfg(test)] -mod tests { - use super::*; - use std::fs; - - #[test] - fn valid_path() { - let tmp = tempfile::tempdir().unwrap(); - fs::write(tmp.path().join("test.css"), "body {}").unwrap(); - - let path = resolve_path(tmp.path(), "test.css").unwrap(); - assert!(path.ends_with("test.css")); - } - - #[test] - fn nested_path() { - let tmp = tempfile::tempdir().unwrap(); - fs::create_dir_all(tmp.path().join("styles")).unwrap(); - fs::write(tmp.path().join("styles/main.css"), "body {}").unwrap(); - - let path = resolve_path(tmp.path(), "styles/main.css").unwrap(); - assert!(path.ends_with("styles/main.css")); - } - - #[test] - fn reject_traversal() { - let tmp = tempfile::tempdir().unwrap(); - assert!(resolve_path(tmp.path(), "../etc/passwd").is_err()); - } - - #[test] - fn reject_nonexistent() { - let tmp = tempfile::tempdir().unwrap(); - assert!(resolve_path(tmp.path(), "nonexistent.css").is_err()); - } -} diff --git a/reference/crates/wo-serve/src/sendfile.rs b/reference/crates/wo-serve/src/sendfile.rs deleted file mode 100644 index aadcdc3..0000000 --- a/reference/crates/wo-serve/src/sendfile.rs +++ /dev/null @@ -1,109 +0,0 @@ -use std::fs; -use std::io; -use std::os::unix::io::RawFd; -use std::path::Path; - -use wo_http::response::Response; - -use crate::mime::content_type_for; -use crate::resolve::resolve_path; - -/// Serve a static file. Returns a Response with the file contents. -/// -/// For small files, reads into memory and returns a normal Response. -/// For zero-copy serving via sendfile, use `send_file_zero_copy` instead. -pub fn send_file(static_dir: &Path, request_path: &str) -> Response { - let file_path = match resolve_path(static_dir, request_path) { - Ok(p) => p, - Err(e) => { - return match e.kind() { - io::ErrorKind::NotFound => Response::not_found(), - io::ErrorKind::PermissionDenied => { - Response::new(403, "Forbidden") - } - _ => Response::internal_error(&e.to_string()), - }; - } - }; - - let body = match fs::read(&file_path) { - Ok(b) => b, - Err(_) => return Response::internal_error("failed to read file"), - }; - - let ct = content_type_for(file_path.to_str().unwrap_or("")); - let mut response = Response::ok(body, ct); - response.header("Cache-Control", "public, max-age=3600"); - response -} - -/// Zero-copy file serving using sendfile(2). -/// -/// Sends the file directly from the page cache to the socket fd. -/// Returns the number of bytes sent, or an error. -pub fn sendfile_to_fd(socket_fd: RawFd, file_path: &Path) -> io::Result { - let file = fs::File::open(file_path)?; - let file_fd = { - use std::os::unix::io::AsRawFd; - file.as_raw_fd() - }; - - let metadata = file.metadata()?; - let file_size = metadata.len() as usize; - - let mut offset: libc::off_t = 0; - let mut sent = 0usize; - - while sent < file_size { - let n = unsafe { - libc::sendfile( - socket_fd, - file_fd, - &mut offset, - file_size - sent, - ) - }; - if n < 0 { - let err = io::Error::last_os_error(); - if err.raw_os_error() == Some(libc::EAGAIN) { - break; // partial send, caller should retry on EPOLLOUT - } - return Err(err); - } - if n == 0 { - break; - } - sent += n as usize; - } - - Ok(sent) -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn serve_existing_file() { - let tmp = tempfile::tempdir().unwrap(); - fs::write(tmp.path().join("test.css"), "body { color: red; }").unwrap(); - - let response = send_file(tmp.path(), "test.css"); - assert_eq!(response.status, 200); - assert!(String::from_utf8_lossy(&response.body).contains("color: red")); - } - - #[test] - fn serve_nonexistent_file() { - let tmp = tempfile::tempdir().unwrap(); - let response = send_file(tmp.path(), "nope.css"); - assert_eq!(response.status, 404); - } - - #[test] - fn serve_traversal_attempt() { - let tmp = tempfile::tempdir().unwrap(); - let response = send_file(tmp.path(), "../etc/passwd"); - assert_eq!(response.status, 403); - } -} diff --git a/reference/crates/wo-store/Cargo.toml b/reference/crates/wo-store/Cargo.toml deleted file mode 100644 index 3a4015a..0000000 --- a/reference/crates/wo-store/Cargo.toml +++ /dev/null @@ -1,13 +0,0 @@ -[package] -name = "wo-store" -version = "0.1.0" -edition = "2021" - -[dependencies] -wo-model = { path = "../wo-model" } -wo-seg = { path = "../wo-seg" } -wo-index = { path = "../wo-index" } -serde_json = { workspace = true } - -[dev-dependencies] -tempfile = "3" diff --git a/reference/crates/wo-store/src/lib.rs b/reference/crates/wo-store/src/lib.rs deleted file mode 100644 index 803a52c..0000000 --- a/reference/crates/wo-store/src/lib.rs +++ /dev/null @@ -1,355 +0,0 @@ -use std::collections::HashMap; -use std::io; -use std::path::{Path, PathBuf}; - -use wo_index::{DateIndex, TagIndex, TitleIndex}; -use wo_model::{Article, ContentLoader}; -use wo_seg::{SegReader, SegWriter}; - -/// Unified storage engine composing .seg files and indexes. -/// -/// Provides the query API that the rest of the system uses. -/// Handles cold-start (rebuild from content/) and incremental ingestion. -pub struct Store { - seg_path: PathBuf, - index_dir: PathBuf, - content_dir: PathBuf, - title_idx: TitleIndex, - date_idx: DateIndex, - tag_idx: TagIndex, - /// Monotonic version counter per sys_title (for subscription diffs). - versions: HashMap, - version_counter: u64, -} - -impl Store { - /// Open an existing data directory, or cold-start from content/. - /// - /// If `data_dir` contains valid .seg and index files, opens them. - /// Otherwise, rebuilds everything from `content_dir`. - pub fn open(content_dir: &Path, data_dir: &Path) -> io::Result { - let seg_path = data_dir.join("articles.seg"); - let index_dir = data_dir.join("index"); - - if seg_path.exists() && index_dir.join("title.idx").exists() { - Self::open_existing(content_dir, &seg_path, &index_dir) - } else { - let mut store = Self::init_empty(content_dir, &seg_path, &index_dir)?; - store.rebuild()?; - Ok(store) - } - } - - fn open_existing( - content_dir: &Path, - seg_path: &Path, - index_dir: &Path, - ) -> io::Result { - let title_idx = TitleIndex::open(&index_dir.join("title.idx"))?; - let date_idx = DateIndex::open(&index_dir.join("date.idx"))?; - let tag_idx = TagIndex::open(&index_dir.join("tags.idx"))?; - - // Build version map from existing seg records. - let reader = SegReader::open(seg_path)?; - let mut versions = HashMap::new(); - let mut version_counter = 0u64; - for (_, article) in reader.iter()? { - version_counter += 1; - versions.insert(article.sys_title.clone(), version_counter); - } - - Ok(Self { - seg_path: seg_path.to_path_buf(), - index_dir: index_dir.to_path_buf(), - content_dir: content_dir.to_path_buf(), - title_idx, - date_idx, - tag_idx, - versions, - version_counter, - }) - } - - fn init_empty( - content_dir: &Path, - seg_path: &Path, - index_dir: &Path, - ) -> io::Result { - std::fs::create_dir_all(seg_path.parent().unwrap())?; - std::fs::create_dir_all(index_dir)?; - - // Create empty seg file. - let _writer = SegWriter::create(seg_path)?; - - // Create empty indexes. - TitleIndex::build(&index_dir.join("title.idx"), &[])?; - DateIndex::build(&index_dir.join("date.idx"), &mut [])?; - TagIndex::build(&index_dir.join("tags.idx"), &[])?; - - let title_idx = TitleIndex::open(&index_dir.join("title.idx"))?; - let date_idx = DateIndex::open(&index_dir.join("date.idx"))?; - let tag_idx = TagIndex::open(&index_dir.join("tags.idx"))?; - - Ok(Self { - seg_path: seg_path.to_path_buf(), - index_dir: index_dir.to_path_buf(), - content_dir: content_dir.to_path_buf(), - title_idx, - date_idx, - tag_idx, - versions: HashMap::new(), - version_counter: 0, - }) - } - - /// Full rebuild: load all articles from content/, rewrite .seg and indexes. - pub fn rebuild(&mut self) -> io::Result<()> { - let articles = ContentLoader::load_all(&self.content_dir)?; - - // Write all articles to a new .seg file. - let mut writer = SegWriter::create(&self.seg_path)?; - let mut index_entries: Vec<(String, i64, Vec, u64)> = Vec::new(); - - self.versions.clear(); - self.version_counter = 0; - - for article in &articles { - let offset = writer.append(article)?; - let timestamp = article.published_on.unwrap_or(0); - - index_entries.push(( - article.sys_title.clone(), - timestamp, - article.tags.clone(), - offset, - )); - - self.version_counter += 1; - self.versions - .insert(article.sys_title.clone(), self.version_counter); - } - - writer.sync()?; - - // Rebuild all indexes. - wo_index::rebuild_all(&self.index_dir, &index_entries)?; - - // Reload indexes. - self.title_idx = TitleIndex::open(&self.index_dir.join("title.idx"))?; - self.date_idx = DateIndex::open(&self.index_dir.join("date.idx"))?; - self.tag_idx = TagIndex::open(&self.index_dir.join("tags.idx"))?; - - Ok(()) - } - - /// Look up a single article by sys_title. - pub fn get_by_title(&self, sys_title: &str) -> io::Result> { - match self.title_idx.get(sys_title) { - Some(offset) => { - let reader = SegReader::open(&self.seg_path)?; - reader.read_at(offset) - } - None => Ok(None), - } - } - - /// List published articles, most recent first, with pagination. - pub fn list_published(&self, skip: usize, limit: usize) -> io::Result> { - let offsets = self.date_idx.latest(skip + limit); - let reader = SegReader::open(&self.seg_path)?; - - let mut articles = Vec::new(); - for &offset in offsets.iter().skip(skip).take(limit) { - if let Some(article) = reader.read_at(offset)? { - if article.published { - articles.push(article); - } - } - } - Ok(articles) - } - - /// List all articles with a given tag. - pub fn list_by_tag(&self, tag: &str) -> io::Result> { - let offsets = match self.tag_idx.get(tag) { - Some(offsets) => offsets, - None => return Ok(vec![]), - }; - - let reader = SegReader::open(&self.seg_path)?; - let mut articles = Vec::new(); - for &offset in offsets { - if let Some(article) = reader.read_at(offset)? { - articles.push(article); - } - } - Ok(articles) - } - - /// List articles published within a timestamp range. - pub fn list_by_date_range(&self, start: i64, end: i64) -> io::Result> { - let offsets = self.date_idx.range(start, end); - let reader = SegReader::open(&self.seg_path)?; - - let mut articles = Vec::new(); - for offset in offsets { - if let Some(article) = reader.read_at(offset)? { - articles.push(article); - } - } - Ok(articles) - } - - /// Count of published articles. - pub fn count_published(&self) -> io::Result { - // For now, iterate and count. With small article counts this is fine. - let reader = SegReader::open(&self.seg_path)?; - let count = reader - .iter()? - .into_iter() - .filter(|(_, a)| a.published) - .count(); - Ok(count) - } - - /// Ingest a single article from a JSON file path. - /// - /// Appends to .seg and triggers a full index rebuild. - /// Returns the sys_title of the ingested article. - pub fn ingest_article(&mut self, json_path: &Path) -> io::Result { - let article = ContentLoader::load_from_file(json_path)?; - let sys_title = article.sys_title.clone(); - - // For simplicity, rebuild the entire store. - // A future optimization can do incremental append + index update. - self.rebuild()?; - - Ok(sys_title) - } - - /// Get the current version number for a sys_title. - pub fn article_version(&self, sys_title: &str) -> Option { - self.versions.get(sys_title).copied() - } - - /// Get a reference to the content directory path. - pub fn content_dir(&self) -> &Path { - &self.content_dir - } -} - -#[cfg(test)] -mod tests { - use super::*; - use std::fs; - - fn create_test_article(base: &Path, sys_title: &str, published: bool, tags: &[&str], ts: i64) { - let dir = base.join(sys_title); - fs::create_dir_all(&dir).unwrap(); - - let tags_json: Vec = tags.iter().map(|t| format!("\"{}\"", t)).collect(); - let json = format!( - r#"{{ - "title": "{}", - "sys_title": "{}", - "published": {}, - "content": {{ - "author": "Author", - "content": {{ - "sections": [{{ "heading": "Intro", "paragraphs": ["Hello."] }}], - "codes": [], - "images": [] - }}, - "tags": [{}], - "publishedOn": {} - }}, - "published_on": {} - }}"#, - sys_title, - sys_title, - published, - tags_json.join(", "), - ts, - ts - ); - fs::write(dir.join(format!("{}.json", sys_title)), json).unwrap(); - } - - #[test] - fn cold_start_and_queries() { - let tmp = tempfile::tempdir().unwrap(); - let content = tmp.path().join("content"); - let data = tmp.path().join("data"); - - create_test_article(&content, "art-one", true, &["rust"], 1000); - create_test_article(&content, "art-two", true, &["rust", "linux"], 2000); - create_test_article(&content, "art-three", false, &["draft"], 3000); - create_test_article(&content, "art-four", true, &["linux"], 4000); - - let store = Store::open(&content, &data).unwrap(); - - // Title lookup. - let art = store.get_by_title("art-one").unwrap().unwrap(); - assert_eq!(art.sys_title, "art-one"); - - assert!(store.get_by_title("nonexistent").unwrap().is_none()); - - // Tag query. - let rust_articles = store.list_by_tag("rust").unwrap(); - assert_eq!(rust_articles.len(), 2); - - let linux_articles = store.list_by_tag("linux").unwrap(); - assert_eq!(linux_articles.len(), 2); - - // Date range. - let range = store.list_by_date_range(1500, 3500).unwrap(); - assert_eq!(range.len(), 2); // art-two(2000) and art-three(3000) - - // Published count. - let count = store.count_published().unwrap(); - assert_eq!(count, 3); - - // Version tracking. - assert!(store.article_version("art-one").is_some()); - assert!(store.article_version("nonexistent").is_none()); - } - - #[test] - fn rebuild_after_delete() { - let tmp = tempfile::tempdir().unwrap(); - let content = tmp.path().join("content"); - let data = tmp.path().join("data"); - - create_test_article(&content, "art-one", true, &["test"], 1000); - - // First open: cold start. - let store = Store::open(&content, &data).unwrap(); - assert_eq!(store.count_published().unwrap(), 1); - drop(store); - - // Delete data/ and reopen: should rebuild. - fs::remove_dir_all(&data).unwrap(); - let store = Store::open(&content, &data).unwrap(); - assert_eq!(store.count_published().unwrap(), 1); - } - - #[test] - fn reopen_existing() { - let tmp = tempfile::tempdir().unwrap(); - let content = tmp.path().join("content"); - let data = tmp.path().join("data"); - - create_test_article(&content, "art-one", true, &["test"], 1000); - - // First open: cold start builds .seg + indexes. - let store = Store::open(&content, &data).unwrap(); - assert_eq!(store.count_published().unwrap(), 1); - drop(store); - - // Second open: loads existing files, no rebuild. - let store = Store::open(&content, &data).unwrap(); - assert_eq!(store.count_published().unwrap(), 1); - let art = store.get_by_title("art-one").unwrap().unwrap(); - assert_eq!(art.sys_title, "art-one"); - } -} diff --git a/reference/crates/wo-sub/Cargo.toml b/reference/crates/wo-sub/Cargo.toml deleted file mode 100644 index b98d483..0000000 --- a/reference/crates/wo-sub/Cargo.toml +++ /dev/null @@ -1,16 +0,0 @@ -[package] -name = "wo-sub" -version = "0.1.0" -edition = "2021" - -[dependencies] -wo-model = { path = "../wo-model" } -wo-store = { path = "../wo-store" } -wo-event = { path = "../wo-event" } -libc = { workspace = true } -bincode = { workspace = true } -serde = { workspace = true } -serde_json = { workspace = true } - -[dev-dependencies] -tempfile = "3" diff --git a/reference/crates/wo-sub/src/lib.rs b/reference/crates/wo-sub/src/lib.rs deleted file mode 100644 index e5527d4..0000000 --- a/reference/crates/wo-sub/src/lib.rs +++ /dev/null @@ -1,470 +0,0 @@ -use std::collections::HashMap; -use std::io; -use std::os::unix::io::RawFd; - -use serde::{Deserialize, Serialize}; -use wo_event::EventFd; - -/// A subscription query pattern. -#[derive(Debug, Clone, PartialEq)] -pub enum Subscription { - /// Subscribe to changes for a single article by sys_title. - ByTitle(String), - /// Subscribe to changes for all articles with a given tag. - ByTag(String), - /// Subscribe to all content changes. - All, -} - -/// The type of content change. -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] -pub enum ChangeType { - Created, - Updated, - Deleted, -} - -/// Notification payload written to subscriber fds. -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] -pub struct Notification { - pub change_type: ChangeType, - pub sys_title: String, - pub version: u64, -} - -/// Wire format for notification payloads. -#[derive(Debug, Clone, Copy, PartialEq)] -pub enum FdFormat { - /// Length-prefixed bincode (for internal subscribers). - Binary, - /// Length-prefixed JSON (for browser socket fds). - Json, -} - -/// Manages subscriptions and delivers notifications to subscriber fds. -/// -/// Subscribers register interest via `subscribe()` with a query pattern. -/// When content changes, `notify()` evaluates which subscriptions match -/// and writes a length-prefixed notification to each matching fd. -pub struct SubscriptionManager { - /// sys_title → list of subscriber fds. - by_title: HashMap>, - /// tag → list of subscriber fds. - by_tag: HashMap>, - /// Fds subscribed to all changes. - global: Vec, - /// fd → list of subscriptions (for cleanup on disconnect). - fd_registry: HashMap>, - /// fd → wire format. - fd_format: HashMap, - /// EventFd for signaling the event loop that notifications are pending. - notify_efd: EventFd, -} - -impl SubscriptionManager { - /// Create a new subscription manager. - pub fn new() -> io::Result { - Ok(Self { - by_title: HashMap::new(), - by_tag: HashMap::new(), - global: Vec::new(), - fd_registry: HashMap::new(), - fd_format: HashMap::new(), - notify_efd: EventFd::new()?, - }) - } - - /// The eventfd for signaling the event loop. - pub fn notify_fd(&self) -> RawFd { - self.notify_efd.fd() - } - - /// Register a subscription for the given fd (binary format). - pub fn subscribe(&mut self, fd: RawFd, query: Subscription) { - self.subscribe_with_format(fd, query, FdFormat::Binary); - } - - /// Register a subscription for the given fd (JSON format for browsers). - pub fn subscribe_json(&mut self, fd: RawFd, query: Subscription) { - self.subscribe_with_format(fd, query, FdFormat::Json); - } - - fn subscribe_with_format(&mut self, fd: RawFd, query: Subscription, format: FdFormat) { - match &query { - Subscription::ByTitle(title) => { - self.by_title.entry(title.clone()).or_default().push(fd); - } - Subscription::ByTag(tag) => { - self.by_tag.entry(tag.clone()).or_default().push(fd); - } - Subscription::All => { - self.global.push(fd); - } - } - self.fd_registry.entry(fd).or_default().push(query); - self.fd_format.insert(fd, format); - } - - /// Remove all subscriptions for a fd (e.g., on EPOLLHUP). - pub fn unsubscribe(&mut self, fd: RawFd) { - self.fd_format.remove(&fd); - if let Some(subs) = self.fd_registry.remove(&fd) { - for sub in subs { - match sub { - Subscription::ByTitle(title) => { - if let Some(fds) = self.by_title.get_mut(&title) { - fds.retain(|&f| f != fd); - } - } - Subscription::ByTag(tag) => { - if let Some(fds) = self.by_tag.get_mut(&tag) { - fds.retain(|&f| f != fd); - } - } - Subscription::All => { - self.global.retain(|&f| f != fd); - } - } - } - } - } - - /// Notify subscribers about a content change. - /// - /// Evaluates which subscriptions match the changed sys_title and tags, - /// then writes a length-prefixed notification to each matching fd. - pub fn notify( - &self, - sys_title: &str, - tags: &[String], - change_type: ChangeType, - version: u64, - ) -> io::Result { - let notification = Notification { - change_type, - sys_title: sys_title.to_string(), - version, - }; - - // Pre-serialize both formats (only actually used if there are subscribers). - let bincode_payload = bincode::serialize(¬ification).map_err(|e| { - io::Error::new(io::ErrorKind::InvalidData, format!("bincode: {}", e)) - })?; - let json_payload = serde_json::to_vec(¬ification).map_err(|e| { - io::Error::new(io::ErrorKind::InvalidData, format!("json: {}", e)) - })?; - - // Build wire formats: [u32 length][payload] - let bincode_wire = build_wire(&bincode_payload); - let json_wire = build_wire(&json_payload); - - // Collect all fds that should receive this notification. - let mut target_fds: Vec = Vec::new(); - - // Title subscribers. - if let Some(fds) = self.by_title.get(sys_title) { - target_fds.extend(fds); - } - - // Tag subscribers. - for tag in tags { - if let Some(fds) = self.by_tag.get(tag) { - target_fds.extend(fds); - } - } - - // Global subscribers. - target_fds.extend(&self.global); - - // Deduplicate. - target_fds.sort_unstable(); - target_fds.dedup(); - - let mut sent = 0; - for &fd in &target_fds { - let format = self.fd_format.get(&fd).copied().unwrap_or(FdFormat::Binary); - let wire = match format { - FdFormat::Binary => &bincode_wire, - FdFormat::Json => &json_wire, - }; - if write_all_fd(fd, wire).is_ok() { - sent += 1; - } - } - - // Signal the event loop. - if sent > 0 { - let _ = self.notify_efd.write(1); - } - - Ok(sent) - } - - /// Number of active subscriber fds. - pub fn subscriber_count(&self) -> usize { - self.fd_registry.len() - } -} - -fn build_wire(payload: &[u8]) -> Vec { - let len = payload.len() as u32; - let mut wire = Vec::with_capacity(4 + payload.len()); - wire.extend_from_slice(&len.to_le_bytes()); - wire.extend_from_slice(payload); - wire -} - -/// Write all bytes to a raw fd. -fn write_all_fd(fd: RawFd, buf: &[u8]) -> io::Result<()> { - let mut written = 0; - while written < buf.len() { - let ret = unsafe { - libc::write( - fd, - buf[written..].as_ptr() as *const libc::c_void, - buf.len() - written, - ) - }; - if ret < 0 { - return Err(io::Error::last_os_error()); - } - if ret == 0 { - return Err(io::Error::new(io::ErrorKind::WriteZero, "write returned 0")); - } - written += ret as usize; - } - Ok(()) -} - -/// Convenience macro for compile-time subscription wiring. -/// -/// ```ignore -/// register!(sub_manager, fd, ByTitle("linux-misc")); -/// register!(sub_manager, fd, ByTag("rust")); -/// register!(sub_manager, fd, All); -/// ``` -#[macro_export] -macro_rules! register { - ($mgr:expr, $fd:expr, ByTitle($title:expr)) => { - $mgr.subscribe($fd, $crate::Subscription::ByTitle($title.into())) - }; - ($mgr:expr, $fd:expr, ByTag($tag:expr)) => { - $mgr.subscribe($fd, $crate::Subscription::ByTag($tag.into())) - }; - ($mgr:expr, $fd:expr, All) => { - $mgr.subscribe($fd, $crate::Subscription::All) - }; -} - -#[cfg(test)] -mod tests { - use super::*; - - /// Create a pipe and return (read_fd, write_fd). - fn make_pipe() -> (RawFd, RawFd) { - let mut fds = [0i32; 2]; - assert_eq!(unsafe { libc::pipe2(fds.as_mut_ptr(), libc::O_NONBLOCK) }, 0); - (fds[0], fds[1]) - } - - fn read_notification(fd: RawFd) -> Notification { - let mut len_buf = [0u8; 4]; - let ret = unsafe { libc::read(fd, len_buf.as_mut_ptr() as *mut libc::c_void, 4) }; - assert!(ret == 4, "failed to read length prefix"); - let len = u32::from_le_bytes(len_buf) as usize; - - let mut payload = vec![0u8; len]; - let ret = unsafe { libc::read(fd, payload.as_mut_ptr() as *mut libc::c_void, len) }; - assert_eq!(ret as usize, len); - - bincode::deserialize(&payload).unwrap() - } - - fn close_fd(fd: RawFd) { - unsafe { libc::close(fd) }; - } - - #[test] - fn title_subscription() { - let mut mgr = SubscriptionManager::new().unwrap(); - - let (r1, w1) = make_pipe(); - let (r2, w2) = make_pipe(); - - mgr.subscribe(w1, Subscription::ByTitle("art-one".into())); - mgr.subscribe(w2, Subscription::ByTitle("art-one".into())); - - let sent = mgr - .notify("art-one", &[], ChangeType::Updated, 1) - .unwrap(); - assert_eq!(sent, 2); - - let n1 = read_notification(r1); - assert_eq!(n1.sys_title, "art-one"); - assert_eq!(n1.change_type, ChangeType::Updated); - assert_eq!(n1.version, 1); - - let n2 = read_notification(r2); - assert_eq!(n2.sys_title, "art-one"); - - // Unrelated title: no notifications. - let sent = mgr - .notify("art-two", &[], ChangeType::Created, 2) - .unwrap(); - assert_eq!(sent, 0); - - close_fd(r1); close_fd(w1); - close_fd(r2); close_fd(w2); - } - - #[test] - fn tag_subscription() { - let mut mgr = SubscriptionManager::new().unwrap(); - - let (r, w) = make_pipe(); - mgr.subscribe(w, Subscription::ByTag("rust".into())); - - let sent = mgr - .notify("art-one", &["rust".into(), "linux".into()], ChangeType::Updated, 1) - .unwrap(); - assert_eq!(sent, 1); - - let n = read_notification(r); - assert_eq!(n.sys_title, "art-one"); - - // No "rust" tag: no notification. - let sent = mgr - .notify("art-two", &["python".into()], ChangeType::Created, 2) - .unwrap(); - assert_eq!(sent, 0); - - close_fd(r); close_fd(w); - } - - #[test] - fn global_subscription() { - let mut mgr = SubscriptionManager::new().unwrap(); - - let (r, w) = make_pipe(); - mgr.subscribe(w, Subscription::All); - - let sent = mgr.notify("anything", &[], ChangeType::Deleted, 5).unwrap(); - assert_eq!(sent, 1); - - let n = read_notification(r); - assert_eq!(n.change_type, ChangeType::Deleted); - - close_fd(r); close_fd(w); - } - - #[test] - fn unsubscribe_removes_fd() { - let mut mgr = SubscriptionManager::new().unwrap(); - - let (r, w) = make_pipe(); - mgr.subscribe(w, Subscription::ByTitle("art-one".into())); - mgr.subscribe(w, Subscription::All); - assert_eq!(mgr.subscriber_count(), 1); - - mgr.unsubscribe(w); - assert_eq!(mgr.subscriber_count(), 0); - - let sent = mgr.notify("art-one", &[], ChangeType::Updated, 1).unwrap(); - assert_eq!(sent, 0); - - close_fd(r); close_fd(w); - } - - #[test] - fn dedup_notifications() { - let mut mgr = SubscriptionManager::new().unwrap(); - - let (r, w) = make_pipe(); - // Same fd subscribed to both title AND tag — should only get one notification. - mgr.subscribe(w, Subscription::ByTitle("art-one".into())); - mgr.subscribe(w, Subscription::ByTag("rust".into())); - - let sent = mgr - .notify("art-one", &["rust".into()], ChangeType::Updated, 1) - .unwrap(); - assert_eq!(sent, 1); // deduped - - let n = read_notification(r); - assert_eq!(n.sys_title, "art-one"); - - close_fd(r); close_fd(w); - } - - #[test] - fn json_format_subscription() { - let mut mgr = SubscriptionManager::new().unwrap(); - - let (r, w) = make_pipe(); - mgr.subscribe_json(w, Subscription::ByTitle("art-one".into())); - - mgr.notify("art-one", &[], ChangeType::Updated, 1).unwrap(); - - // Read length-prefixed JSON. - let mut len_buf = [0u8; 4]; - let ret = unsafe { libc::read(r, len_buf.as_mut_ptr() as *mut libc::c_void, 4) }; - assert_eq!(ret, 4); - let len = u32::from_le_bytes(len_buf) as usize; - - let mut payload = vec![0u8; len]; - let ret = unsafe { libc::read(r, payload.as_mut_ptr() as *mut libc::c_void, len) }; - assert_eq!(ret as usize, len); - - // Should be valid JSON. - let json: serde_json::Value = serde_json::from_slice(&payload).unwrap(); - assert_eq!(json["sys_title"], "art-one"); - assert_eq!(json["change_type"], "Updated"); - assert_eq!(json["version"], 1); - - close_fd(r); close_fd(w); - } - - #[test] - fn mixed_format_subscribers() { - let mut mgr = SubscriptionManager::new().unwrap(); - - let (r_bin, w_bin) = make_pipe(); - let (r_json, w_json) = make_pipe(); - - mgr.subscribe(w_bin, Subscription::ByTitle("art-one".into())); - mgr.subscribe_json(w_json, Subscription::ByTitle("art-one".into())); - - let sent = mgr.notify("art-one", &[], ChangeType::Created, 5).unwrap(); - assert_eq!(sent, 2); - - // Binary subscriber gets bincode. - let n = read_notification(r_bin); - assert_eq!(n.sys_title, "art-one"); - assert_eq!(n.version, 5); - - // JSON subscriber gets JSON. - let mut len_buf = [0u8; 4]; - unsafe { libc::read(r_json, len_buf.as_mut_ptr() as *mut libc::c_void, 4) }; - let len = u32::from_le_bytes(len_buf) as usize; - let mut payload = vec![0u8; len]; - unsafe { libc::read(r_json, payload.as_mut_ptr() as *mut libc::c_void, len) }; - let json: serde_json::Value = serde_json::from_slice(&payload).unwrap(); - assert_eq!(json["sys_title"], "art-one"); - - close_fd(r_bin); close_fd(w_bin); - close_fd(r_json); close_fd(w_json); - } - - #[test] - fn register_macro() { - let mut mgr = SubscriptionManager::new().unwrap(); - let (r, w) = make_pipe(); - - register!(mgr, w, ByTitle("test-article")); - register!(mgr, w, ByTag("rust")); - register!(mgr, w, All); - - assert_eq!(mgr.subscriber_count(), 1); - - close_fd(r); close_fd(w); - } -} diff --git a/reference/crates/wo-watch/Cargo.toml b/reference/crates/wo-watch/Cargo.toml deleted file mode 100644 index 977b7d2..0000000 --- a/reference/crates/wo-watch/Cargo.toml +++ /dev/null @@ -1,11 +0,0 @@ -[package] -name = "wo-watch" -version = "0.1.0" -edition = "2021" - -[dependencies] -wo-store = { path = "../wo-store" } -libc = { workspace = true } - -[dev-dependencies] -tempfile = "3" diff --git a/reference/crates/wo-watch/src/lib.rs b/reference/crates/wo-watch/src/lib.rs deleted file mode 100644 index 5dc55e1..0000000 --- a/reference/crates/wo-watch/src/lib.rs +++ /dev/null @@ -1,280 +0,0 @@ -use std::collections::HashMap; -use std::io; -use std::os::unix::io::RawFd; -use std::path::{Path, PathBuf}; - -/// A content change detected by the watcher. -#[derive(Debug, Clone, PartialEq)] -pub enum ContentChange { - Created(String), // sys_title - Modified(String), // sys_title - Deleted(String), // sys_title -} - -/// Watches a content directory for article file changes using inotify. -/// -/// Expects the directory structure: -/// ```text -/// content_dir/ -/// {sys_title}/ -/// {sys_title}.json -/// ``` -pub struct ContentWatcher { - inotify_fd: RawFd, - /// Maps inotify watch descriptor → directory path. - wd_to_path: HashMap, - /// Maps directory path → watch descriptor. - path_to_wd: HashMap, - content_dir: PathBuf, -} - -const EVENT_MASK: u32 = - (libc::IN_CREATE | libc::IN_MODIFY | libc::IN_DELETE | libc::IN_MOVED_TO | libc::IN_MOVED_FROM) - as u32; - -impl ContentWatcher { - /// Create a new watcher on the given content directory. - /// - /// Adds inotify watches on the content dir itself and all subdirectories. - pub fn new(content_dir: &Path) -> io::Result { - let fd = unsafe { libc::inotify_init1(libc::IN_NONBLOCK | libc::IN_CLOEXEC) }; - if fd < 0 { - return Err(io::Error::last_os_error()); - } - - let mut watcher = Self { - inotify_fd: fd, - wd_to_path: HashMap::new(), - path_to_wd: HashMap::new(), - content_dir: content_dir.to_path_buf(), - }; - - // Watch the root content directory (for new article dirs). - watcher.add_watch(content_dir)?; - - // Watch each existing subdirectory. - if content_dir.exists() { - for entry in std::fs::read_dir(content_dir)? { - let entry = entry?; - if entry.path().is_dir() { - watcher.add_watch(&entry.path())?; - } - } - } - - Ok(watcher) - } - - /// The inotify file descriptor, for registration on an event loop. - pub fn fd(&self) -> RawFd { - self.inotify_fd - } - - /// Read and process pending inotify events. - /// - /// Returns a list of content changes. Call this when the event loop - /// signals that the inotify fd is readable. - pub fn process_events(&mut self) -> io::Result> { - let mut buf = [0u8; 4096]; - let n = unsafe { - libc::read( - self.inotify_fd, - buf.as_mut_ptr() as *mut libc::c_void, - buf.len(), - ) - }; - - if n < 0 { - let err = io::Error::last_os_error(); - if err.raw_os_error() == Some(libc::EAGAIN) { - return Ok(vec![]); - } - return Err(err); - } - - let mut changes = Vec::new(); - let mut offset = 0usize; - - while offset < n as usize { - let event = unsafe { &*(buf.as_ptr().add(offset) as *const libc::inotify_event) }; - let name_len = event.len as usize; - let name = if name_len > 0 { - let name_ptr = unsafe { buf.as_ptr().add(offset + std::mem::size_of::()) }; - let name_bytes = unsafe { std::slice::from_raw_parts(name_ptr, name_len) }; - let end = name_bytes.iter().position(|&b| b == 0).unwrap_or(name_len); - Some(String::from_utf8_lossy(&name_bytes[..end]).to_string()) - } else { - None - }; - - offset += std::mem::size_of::() + name_len; - - let Some(name) = name else { continue }; - let mask = event.mask; - - // Event on the root content directory: subdirectory created/deleted. - if let Some(dir_path) = self.wd_to_path.get(&event.wd) { - let dir_path = dir_path.clone(); - - if dir_path == self.content_dir { - let sub_path = self.content_dir.join(&name); - if (mask & libc::IN_CREATE as u32) != 0 || (mask & libc::IN_MOVED_TO as u32) != 0 { - if sub_path.is_dir() { - let _ = self.add_watch(&sub_path); - changes.push(ContentChange::Created(name.clone())); - } - } - if (mask & libc::IN_DELETE as u32) != 0 || (mask & libc::IN_MOVED_FROM as u32) != 0 { - self.remove_watch(&sub_path); - changes.push(ContentChange::Deleted(name)); - } - } else { - // Event inside an article subdirectory. - if name.ends_with(".json") || name.ends_with(".md") { - let sys_title = dir_path - .file_name() - .unwrap() - .to_string_lossy() - .to_string(); - - if (mask & libc::IN_MODIFY as u32) != 0 { - changes.push(ContentChange::Modified(sys_title)); - } else if (mask & libc::IN_CREATE as u32) != 0 - || (mask & libc::IN_MOVED_TO as u32) != 0 - { - changes.push(ContentChange::Created(sys_title)); - } else if (mask & libc::IN_DELETE as u32) != 0 - || (mask & libc::IN_MOVED_FROM as u32) != 0 - { - changes.push(ContentChange::Deleted(sys_title)); - } - } - } - } - } - - // Deduplicate: keep only the last change per sys_title. - let mut seen = HashMap::new(); - for change in changes { - let key = match &change { - ContentChange::Created(t) | ContentChange::Modified(t) | ContentChange::Deleted(t) => { - t.clone() - } - }; - seen.insert(key, change); - } - - Ok(seen.into_values().collect()) - } - - fn add_watch(&mut self, path: &Path) -> io::Result<()> { - let c_path = std::ffi::CString::new(path.to_str().unwrap_or("")).map_err(|_| { - io::Error::new(io::ErrorKind::InvalidInput, "invalid path for inotify") - })?; - - let wd = unsafe { libc::inotify_add_watch(self.inotify_fd, c_path.as_ptr(), EVENT_MASK) }; - if wd < 0 { - return Err(io::Error::last_os_error()); - } - - self.wd_to_path.insert(wd, path.to_path_buf()); - self.path_to_wd.insert(path.to_path_buf(), wd); - Ok(()) - } - - fn remove_watch(&mut self, path: &Path) { - if let Some(wd) = self.path_to_wd.remove(path) { - unsafe { libc::inotify_rm_watch(self.inotify_fd, wd) }; - self.wd_to_path.remove(&wd); - } - } -} - -impl Drop for ContentWatcher { - fn drop(&mut self) { - unsafe { libc::close(self.inotify_fd) }; - } -} - -#[cfg(test)] -mod tests { - use super::*; - use std::fs; - - #[test] - fn detect_json_modification() { - let tmp = tempfile::tempdir().unwrap(); - let content = tmp.path().join("content"); - let art_dir = content.join("my-article"); - fs::create_dir_all(&art_dir).unwrap(); - fs::write(art_dir.join("my-article.json"), r#"{"test": true}"#).unwrap(); - - let mut watcher = ContentWatcher::new(&content).unwrap(); - - // Modify the file. - fs::write(art_dir.join("my-article.json"), r#"{"test": false}"#).unwrap(); - - // Give inotify a moment. - std::thread::sleep(std::time::Duration::from_millis(50)); - - let changes = watcher.process_events().unwrap(); - assert!(!changes.is_empty()); - assert!(changes.iter().any(|c| matches!(c, ContentChange::Modified(t) if t == "my-article"))); - } - - #[test] - fn detect_new_directory() { - let tmp = tempfile::tempdir().unwrap(); - let content = tmp.path().join("content"); - fs::create_dir_all(&content).unwrap(); - - let mut watcher = ContentWatcher::new(&content).unwrap(); - - // Create a new article directory. - let art_dir = content.join("new-article"); - fs::create_dir_all(&art_dir).unwrap(); - - std::thread::sleep(std::time::Duration::from_millis(50)); - - let changes = watcher.process_events().unwrap(); - assert!(changes.iter().any(|c| matches!(c, ContentChange::Created(t) if t == "new-article"))); - } - - #[test] - fn detect_json_delete() { - let tmp = tempfile::tempdir().unwrap(); - let content = tmp.path().join("content"); - let art_dir = content.join("delete-me"); - fs::create_dir_all(&art_dir).unwrap(); - fs::write(art_dir.join("delete-me.json"), "{}").unwrap(); - - let mut watcher = ContentWatcher::new(&content).unwrap(); - - // Delete the JSON file. - fs::remove_file(art_dir.join("delete-me.json")).unwrap(); - - std::thread::sleep(std::time::Duration::from_millis(50)); - - let changes = watcher.process_events().unwrap(); - assert!(changes.iter().any(|c| matches!(c, ContentChange::Deleted(t) if t == "delete-me"))); - } - - #[test] - fn ignore_non_json_files() { - let tmp = tempfile::tempdir().unwrap(); - let content = tmp.path().join("content"); - let art_dir = content.join("my-article"); - fs::create_dir_all(&art_dir).unwrap(); - - let mut watcher = ContentWatcher::new(&content).unwrap(); - - // Create a non-JSON file. - fs::write(art_dir.join("notes.txt"), "not json").unwrap(); - - std::thread::sleep(std::time::Duration::from_millis(50)); - - let changes = watcher.process_events().unwrap(); - // Non-JSON files should not trigger Modified events. - assert!(changes.iter().all(|c| !matches!(c, ContentChange::Modified(_)))); - } -} diff --git a/reference/rest/README.md b/reference/rest/README.md deleted file mode 100644 index c1544fb..0000000 --- a/reference/rest/README.md +++ /dev/null @@ -1,92 +0,0 @@ -# `reference/rest/` — HTTP test files for the `.wo` runtime - -`.rest` (or `.http`) is the plain-text HTTP-request format supported by the two main editor HTTP clients: - -- **VS Code** — install [REST Client](https://marketplace.visualstudio.com/items?itemName=humao.rest-client) (`humao.rest-client`) and click "Send Request" above any block. -- **JetBrains IDEs** (IntelliJ, WebStorm, RustRover, Goland) — built-in HTTP Client recognises `.rest` and `.http` natively. - -Both clients understand: - -- `### ...` block separators -- `@var = value` document-level variables referenced as `{{var}}` -- `# @name foo` on a request, whose response fields are later reachable as `{{foo.response.body.id}}` — useful for threading auto-generated ids from `create` responses into later `get`/`patch`/`delete` calls - -## Files - -| File | Against | What it exercises | -| --- | --- | --- | -| [`blog.rest`](./blog.rest) | `docs/examples/blog/` | Full CRUD on Article/Comment, read-only on Author/Tag (matches the sample's `expose` lists). End-to-end flow: create article → list → get by id → PATCH title → PATCH embedded doc → DELETE draft → verify final state. | -| [`ecommerce.rest`](./ecommerce.rest) | `docs/examples/ecommerce/` | What Stage 2 currently serves for the ecommerce sample: read-only Product/Order/Customer lists, 405s for non-exposed create endpoints, 501s for Stage 3 stubs. Documents the shape of Stage 3/4 endpoints (`fn checkout`, LIVE subscribe, `/me`) even though they're not wired yet. | - -## Running - -```bash -# one terminal — start the runtime -cargo run --bin wo -- run docs/examples/blog -# [wo] listening on http://127.0.0.1:8080 - -# another terminal — or just open the .rest file in VS Code/JetBrains and click -``` - -Override the port via `WO_LISTEN`: - -```bash -WO_LISTEN=127.0.0.1:9000 cargo run --bin wo -- run docs/examples/blog -``` - -…and update the `@host` line at the top of the `.rest` file to match. - -## Without an editor (just `curl`) - -Each `.rest` block maps directly to `curl`. Some examples: - -```bash -# Runtime info -curl http://127.0.0.1:8080/ - -# List -curl http://127.0.0.1:8080/api/articles - -# Create — server assigns `id` automatically -curl -X POST http://127.0.0.1:8080/api/articles \ - -H "Content-Type: application/json" \ - -d '{ - "slug": "hello-writeonce", - "title": "Hello, writeonce", - "author": 1, - "published": true, - "meta": { "excerpt": "first post", "body_md": "# hi" } - }' - -# Get by id -curl http://127.0.0.1:8080/api/articles/1 - -# Partial update -curl -X PATCH http://127.0.0.1:8080/api/articles/1 \ - -H "Content-Type: application/json" \ - -d '{ "title": "Updated" }' - -# Delete -curl -X DELETE http://127.0.0.1:8080/api/articles/1 # 204 on success - -# Stage 3 stub -curl -i http://127.0.0.1:8080/api/articles/live # 501 Not Implemented -``` - -For a scripted smoke run against the blog sample, the top-to-bottom `curl` sequence that exactly mirrors `blog.rest` is in [`docs/examples/blog/README.md`](../../docs/examples/blog/README.md). - -## Expected-status cheat sheet - -Every block in the `.rest` files ends its description with the expected HTTP status. Quick legend: - -| Status | Meaning in this prototype | -| --- | --- | -| `200` | OK — list / get / update succeeded | -| `201` | Created — new row, `id` in the response body | -| `204` | No Content — delete succeeded | -| `400` | Bad JSON body | -| `404` | No such row, OR no method at all is attached to the path (e.g. `/api/customers` when the `service rest` block doesn't `expose` any collection-root op) | -| `405` | Method not allowed — the path is registered for a *different* method (e.g. POST against `/api/products` when only `list` is exposed, so GET is attached but POST isn't) | -| `501` | Not Implemented — Stage 3+ feature (LIVE subscriptions, `/me`, transactional fns) | - -A `405` is a *feature* of the sample — it confirms the `expose` list in the `.wo` file is being honoured. A `501` is a Stage marker — the runtime acknowledges the shape but hasn't wired the handler yet. diff --git a/reference/rest/blog.rest b/reference/rest/blog.rest deleted file mode 100644 index aebb0de..0000000 --- a/reference/rest/blog.rest +++ /dev/null @@ -1,181 +0,0 @@ -############################################################################### -# blog.rest — exercise the `.wo` runtime against docs/examples/blog/ -# -# Start the server first: -# cargo run --bin wo -- run docs/examples/blog -# -# Then in VS Code (REST Client extension) or JetBrains (HTTP Client): -# click "Send Request" on each block, top to bottom -# -# Response back-references (# @name foo → {{foo.response.body.id}}) are -# supported by both clients — later blocks pick up ids minted by earlier -# blocks automatically. For `curl` equivalents, see reference/rest/README.md. -############################################################################### - -@host = http://127.0.0.1:8080 - - -### Runtime info — expected 200 -GET {{host}}/ - -### Liveness probe — expected 200 "ok" -GET {{host}}/healthz - - -############################################################################### -# Author — exposes: list, get, me, subscribe -############################################################################### - -### List authors (empty on fresh boot) — expected 200 [] -GET {{host}}/api/authors - -### Author create is NOT exposed in the blog sample (expose list, get, me, subscribe) -# Expected 405 — method not allowed. The sample expects authors seeded by -# `on startup do: seed_admin()` in app.wo. Stage 2 doesn't run startup hooks -# yet, so the list above will be empty. -POST {{host}}/api/authors -Content-Type: application/json - -{ "email": "alice@example.com", "handle": "alice", "display": "Alice" } - -### /me — Stage 3 session layer; expected 501 -GET {{host}}/api/authors/me - -### LIVE subscribe — Stage 3; expected 501 -GET {{host}}/api/authors/live - - -############################################################################### -# Article — exposes: list, get, create, update, delete, subscribe -############################################################################### - -### Create an article — expected 201 -# @name createArticle -POST {{host}}/api/articles -Content-Type: application/json - -{ - "slug": "hello-writeonce", - "title": "Hello, writeonce", - "author": 1, - "published": true, - "meta": { - "excerpt": "First post on the new runtime.", - "body_md": "# Hi\n\nHello from the `.wo` runtime. The server, the database, and this HTTP API are all one binary.\n" - } -} - -### Create a second article (draft) — expected 201 -# @name createDraft -POST {{host}}/api/articles -Content-Type: application/json - -{ - "slug": "second-draft", - "title": "Second Post (draft)", - "author": 1, - "published": false, - "meta": { "excerpt": "", "body_md": "WIP." } -} - -### List articles — expected 200 with 2 rows -GET {{host}}/api/articles - -### Get one article by id — expected 200 -GET {{host}}/api/articles/{{createArticle.response.body.id}} - -### Partial update (PATCH) — change just the title — expected 200 -PATCH {{host}}/api/articles/{{createArticle.response.body.id}} -Content-Type: application/json - -{ "title": "Hi, writeonce!" } - -### Partial update of an embedded-document field — expected 200 -# Note: Stage 2's PATCH does a shallow merge at the top level. -# To change `meta.excerpt` alone you re-send the whole `meta` object. -PATCH {{host}}/api/articles/{{createArticle.response.body.id}} -Content-Type: application/json - -{ - "meta": { "excerpt": "Updated excerpt.", "body_md": "# Hi\n\nUpdated body." } -} - -### Publish the draft — expected 200 (`on update` trigger that sets -### `published_at` is Stage 3+; Stage 2 just records the bool change). -PATCH {{host}}/api/articles/{{createDraft.response.body.id}} -Content-Type: application/json - -{ "published": true } - -### Delete the draft — expected 204 No Content -DELETE {{host}}/api/articles/{{createDraft.response.body.id}} - -### Get the deleted id — expected 404 -GET {{host}}/api/articles/{{createDraft.response.body.id}} - -### LIVE subscribe (Article) — Stage 3; expected 501 -GET {{host}}/api/articles/live - - -############################################################################### -# Tag — exposes: list, get, subscribe -############################################################################### - -### List tags — expected 200 [] (create is not exposed) -GET {{host}}/api/tags - -### Tag create NOT exposed (expose list, get, subscribe) — expected 405 -POST {{host}}/api/tags -Content-Type: application/json - -{ "slug": "rust", "label": "Rust" } - -### LIVE subscribe (Tag) — Stage 3; expected 501 -GET {{host}}/api/tags/live - - -############################################################################### -# Comment — exposes: list, get, create, update, delete, subscribe -############################################################################### - -### Create a comment — expected 201 -# @name createComment -POST {{host}}/api/comments -Content-Type: application/json - -{ - "article": {{createArticle.response.body.id}}, - "author": 1, - "body": "Nice post. Runs on one binary which is still weird to me." -} - -### List comments — expected 200 with 1 row -GET {{host}}/api/comments - -### Get one comment — expected 200 -GET {{host}}/api/comments/{{createComment.response.body.id}} - -### Update comment body — expected 200 (the `on update when old.body != new.body -### do set self.edited_at = now()` trigger is Stage 3+; edited_at stays unset) -PATCH {{host}}/api/comments/{{createComment.response.body.id}} -Content-Type: application/json - -{ "body": "Edited: really, one binary? Neat." } - -### Delete comment — expected 204 -DELETE {{host}}/api/comments/{{createComment.response.body.id}} - -### LIVE subscribe (Comment) — Stage 3; expected 501 -GET {{host}}/api/comments/live - - -############################################################################### -# Final state — should show the updated article still there, no drafts, -# no comments (all deleted above). -############################################################################### - -### Final article list — expected 200 with 1 row -GET {{host}}/api/articles - -### Final comment list — expected 200 [] -GET {{host}}/api/comments diff --git a/reference/rest/ecommerce.rest b/reference/rest/ecommerce.rest deleted file mode 100644 index 6285e79..0000000 --- a/reference/rest/ecommerce.rest +++ /dev/null @@ -1,142 +0,0 @@ -############################################################################### -# ecommerce.rest — exercise the `.wo` runtime against docs/examples/ecommerce/ -# -# Start the server first: -# cargo run --bin wo -- run docs/examples/ecommerce -# -# The ecommerce sample is heavier on features that land in later stages: -# * orders are minted by `fn checkout(customer, product, qty) in txn snapshot` -# — transactional functions are a Stage 3/4 addition. Stage 2 does not -# register `/api/fn/checkout` yet; the block below documents that. -# * customer/product/order rows are seeded by `on startup do: seed()` in -# app.wo — startup hooks are also Stage 3+. Lists start empty. -# * Order-status lifecycle triggers (`on update when old.status != Paid ...`) -# are Stage 3+. -# -# This file therefore focuses on what Stage 2 *does* serve — route wiring, -# empty-list reads, method-not-allowed for non-exposed operations, and the -# Stage-3 stubs that respond 501. It doubles as a living spec for what the -# ecommerce sample should behave like once Stage 3+ lands. -############################################################################### - -@host = http://127.0.0.1:8080 - - -### Runtime info — expected 200 -GET {{host}}/ - -### Liveness probe — expected 200 "ok" -GET {{host}}/healthz - - -############################################################################### -# Product — exposes: list, get, subscribe -# Stage 2 does NOT expose create — admin console is expected to seed inventory. -############################################################################### - -### List products — expected 200 [] (no startup seed yet) -GET {{host}}/api/products - -### Product create NOT exposed — expected 405 -POST {{host}}/api/products -Content-Type: application/json - -{ - "sku": "SKU-WIDGET", - "name": "Widget", - "price": 1999, - "meta": { "description": "A widget.", "images": [], "attributes": { "colour": "blue" } }, - "inventory": { "on_hand": 50, "reserved": 0, "reorder_at": 10 } -} - -### Get by id — expected 404 (nothing exists) -GET {{host}}/api/products/1 - -### LIVE subscribe — Stage 3; expected 501 -GET {{host}}/api/products/live - - -############################################################################### -# Customer — exposes: get, me, update, subscribe -# Customer create is gated by admin/self-signup flows outside this sample's -# scope. For now the list endpoint is not exposed either. -############################################################################### - -### List NOT exposed — expected 404 -# Customer's `expose get, me, update, subscribe` has nothing at the collection -# root, so no route is registered at `/api/customers` at all. The server -# returns 404 "no route" rather than 405 "method not allowed". -GET {{host}}/api/customers - -### Customer create NOT exposed — expected 404 -# Same reason: no method attached to /api/customers. -POST {{host}}/api/customers -Content-Type: application/json - -{ "email": "carol@shop.test", "name": "Carol", "role": "Customer" } - -### Get by id — expected 404 (nothing exists) -GET {{host}}/api/customers/1 - -### Update by id — would work if the customer existed; expected 404 -PATCH {{host}}/api/customers/1 -Content-Type: application/json - -{ "name": "Carol Updated" } - -### /me — Stage 3 session layer; expected 501 -GET {{host}}/api/customers/me - -### LIVE subscribe — Stage 3; expected 501 -GET {{host}}/api/customers/live - - -############################################################################### -# Order — exposes: list, get, subscribe -# Orders are created by `fn checkout(...)` (see logic/checkout.wo), not via -# POST. Stage 2 does not register transactional-fn endpoints, so the list is -# empty until Stage 3/4 brings them online. -############################################################################### - -### List orders — expected 200 [] -GET {{host}}/api/orders - -### Order create NOT exposed (use fn checkout) — expected 405 -POST {{host}}/api/orders -Content-Type: application/json - -{ "customer": 1, "status": "Pending", "line_items": [] } - -### LIVE subscribe — same WebSocket the `##ui #admin-orders` board opens in -### Stage 6. For now the endpoint responds 501. This is the single most -### requested Stage 3 endpoint for this sample. -GET {{host}}/api/orders/live - - -############################################################################### -# fn checkout — deferred to Stage 3/4 (transactional functions) -# -# When transactional-fn endpoints land, this block becomes the canonical -# cross-paradigm ACID test: one call updates the product's inventory doc, -# inserts an Order row, creates a Purchase graph edge, and threads the new -# order id through all three stores inside one BEGIN ... COMMIT. -# -# See docs/examples/ecommerce/logic/checkout.wo and -# docs/runtime/database/05-go-sdk.md "Checkout from Go without codegen". -############################################################################### - -### Stage 2 returns 404 — the route isn't registered. -POST {{host}}/api/fn/checkout -Content-Type: application/json - -{ "customer": 1, "product": 1, "qty": 2 } - - -############################################################################### -# Purchase (link type) — no `service rest` block declared -# Purchase edges are created by fn checkout and read via -# `Customer.purchased` traversal (Stage 3+ query language). -############################################################################### - -### Purchase list NOT exposed — expected 404 (no route registered) -GET {{host}}/api/purchases diff --git a/reference/writeonce-api/.dockerignore b/reference/writeonce-api/.dockerignore deleted file mode 100644 index fec6a17..0000000 --- a/reference/writeonce-api/.dockerignore +++ /dev/null @@ -1,38 +0,0 @@ -# Ignore target directory (where build artifacts are stored) -target/ - -# Ignore Cargo.lock if you want to use the latest dependencies -# Uncomment the line below if you want to ignore it -# Cargo.lock - -# Ignore build files -**/*.rs.bk -**/*.rlib - -# Ignore any local configuration files -*.env -*.local - -# Ignore any files that are not part of the Rust project -*.md -*.txt - -# Ignore version control directories -.git/ -.gitignore - -# Ignore IDE/editor configuration files -.vscode/ -.idea/ -*.sublime-workspace -*.sublime-project -*.swp -*.DS_Store - -# Ignore any backup files -*~ - - -# Diesel CLI generated schema.rs -diesel.toml -**/migrations/**/diesel_schema.lock \ No newline at end of file diff --git a/reference/writeonce-api/.env.sample b/reference/writeonce-api/.env.sample deleted file mode 100644 index 26c374b..0000000 --- a/reference/writeonce-api/.env.sample +++ /dev/null @@ -1,4 +0,0 @@ -PORT= -DATABASE_URL= -API_ACCESS_TOKEN= -AWS_INFRA_BASE_URL= \ No newline at end of file diff --git a/reference/writeonce-api/.gitignore b/reference/writeonce-api/.gitignore deleted file mode 100644 index e4c7d90..0000000 --- a/reference/writeonce-api/.gitignore +++ /dev/null @@ -1,50 +0,0 @@ -# Ignore target directory (generated build files) -target/ - -# Ignore intermediate files from rustc compiler -*.rs.bk - -# Cargo-specific files -Cargo.lock -/.cargo/ - -# Output files and logs from Cargo -**/*.log - -# Ignore binaries and libraries (compiled executables or libraries) -*.exe -*.exe~ -*.out -*.o -*.a -*.so -*.dylib -*.dll - -# Ignore dependency build artifacts -/.rustup/ -/.git/target/ -/.git/target/** - -# macOS specific files -.DS_Store - -# Windows specific files -Thumbs.db -ehthumbs.db -desktop.ini -$RECYCLE.BIN/ - -# Linux specific files -*~ - -# IDE/editor specific files (optional) -/.idea/ -/.vscode/ -/*.iml - -# Other tooling-specific files (optional) -/node_modules/ - -.env -!.env.sample \ No newline at end of file diff --git a/reference/writeonce-api/.gitlab-ci.yml b/reference/writeonce-api/.gitlab-ci.yml deleted file mode 100644 index c3bf098..0000000 --- a/reference/writeonce-api/.gitlab-ci.yml +++ /dev/null @@ -1,44 +0,0 @@ -stages: - - build - - push - - deploy - -build: - stage: build - image: docker:20.10.7 - services: - - docker:20.10.7-dind - before_script: - - echo "Global setup before script" - - chmod +x ./infra/00-build-image.sh - script: - - ./infra/00-build-image.sh - -push: - stage: push - image: docker:20.10.7 - services: - - docker:20.10.7-dind - before_script: - - chmod +x ./infra/02-docker-login.sh - - chmod +x ./infra/03-upload-image.sh - script: - - infra/02-docker-login.sh - - infra/03-upload-image.sh - only: - - main - -deploy: - stage: deploy - image: docker:20.10.7 - services: - - docker:20.10.7-dind - before_script: - - chmod +x ./infra/04-ssh-setup.sh - - chmod +x ./infra/05-deploy.sh - script: - - echo "Docker set docker auth config" - - infra/04-ssh-setup.sh - - infra/05-deploy.sh - only: - - main \ No newline at end of file diff --git a/reference/writeonce-api/Cargo.toml b/reference/writeonce-api/Cargo.toml deleted file mode 100644 index 8084061..0000000 --- a/reference/writeonce-api/Cargo.toml +++ /dev/null @@ -1,51 +0,0 @@ - -#[target.aarch64-unknown-linux-gnu] -#image = "ghcr.io/cross-rs/aarch64-unknown-linux-gnu:main" -#dockerfile = "Dockerfile.cross" -#linker = "aarch64-linux-gnu-gcc" - -[env] -#OPENSSL_STATIC = "1" -#OPENSSL_DIR = "/usr/aarch64-linux-gnu/" -[package] -name = "writeonce-manage-article-api" -version = "0.1.0" -edition = "2021" - -[[bin]] -name = "writeonce-manage-article-api" -path = "src/main.rs" - -[[bin]] -name = "lambda-post-article" -path = "src/lambda/post_article.rs" - -[dependencies] -actix-web = "4.0" -warp = "0.3" -serde = { version = "1.0", features = ["derive"] } -serde_json = "1.0" -dotenv = "0.15" -reqwest = { version = "0.11", default-features = false, features = ["json","rustls-tls"] } -url = "2" -chrono = "0.4" -actix-files = "0.6" -############ Diesel ################# -diesel = { version = "2.2.0", default-features = false, features = ["postgres","serde_json","r2d2"] } -diesel_migrations = "2.2.0" -r2d2 = "0.8" -thiserror = "1.0" -lazy_static = "1.4" -#openssl = { version = "0.10", features = ["vendored"] } -##### Lambda ########## -tokio = "1.43.0" -lambda_runtime = "0.13.0" - -#### AWS #### -aws-config = { version = "1", features = ["behavior-version-latest"] } -aws-sdk-s3 = "1" -aws-sdk-ec2 = "1" -aws-smithy-types = "1" - - - diff --git a/reference/writeonce-api/Cross.toml b/reference/writeonce-api/Cross.toml deleted file mode 100644 index 3792aeb..0000000 --- a/reference/writeonce-api/Cross.toml +++ /dev/null @@ -1,5 +0,0 @@ -[build] -default-target = "aarch64-unknown-linux-gnu" - -[target.aarch64-unknown-linux-gnu] -image = "cross-aarch64:latest" diff --git a/reference/writeonce-api/Dockerfile b/reference/writeonce-api/Dockerfile deleted file mode 100644 index ae826df..0000000 --- a/reference/writeonce-api/Dockerfile +++ /dev/null @@ -1,66 +0,0 @@ -# Declare ARG in global scope -ARG VERSION=1.82.0 - -FROM rust:${VERSION} AS base -WORKDIR /usr/src/app - -FROM base as build - -# Install build dependencies - -RUN apt-get update && apt-get install -y \ - curl \ - build-essential \ - pkg-config \ - libssl-dev \ - && rm -rf /var/lib/apt/lists/* - - -# Install and configure sccache to speed up compilation -RUN cargo install sccache -ENV RUSTC_WRAPPER="sccache" - - -# Cache dependencies to speed up builds -COPY Cargo.toml ./ -RUN mkdir src && echo "fn main() {}" > src/main.rs -RUN cargo build --release && rm -rf target/release/deps/* - -COPY . . - -RUN cargo build --release --bin writeonce-manage-article-api - -# Stage 2: Create a minimal runtime image -FROM debian:bookworm-slim as runtime - -# Install necessary runtime dependencies -RUN apt-get update && apt-get install -y \ - ca-certificates \ - libc6 \ - libpq5 \ - && rm -rf /var/lib/apt/lists/* - - -COPY --from=build /usr/src/app/target/release/writeonce-manage-article-api /usr/local/bin/app -COPY doc /usr/local/share/doc - -ARG PORT -ARG AWS_INFRA_BASE_URL -ARG DATABASE_URL -ARG API_ACCESS_TOKEN -ARG API_ACCESS_ADMIN_TOKEN -RUN echo $PORT - -ENV PORT=$PORT -ENV AWS_INFRA_BASE_URL=$AWS_INFRA_BASE_URL -ENV API_ACCESS_TOKEN=$API_ACCESS_TOKEN -ENV API_ACCESS_ADMIN_TOKEN=$API_ACCESS_ADMIN_TOKEN -ENV DATABASE_URL=$DATABASE_URL - -# Expose the application port -EXPOSE ${PORT} - -WORKDIR /usr/local/share - -# Run the application -CMD ["app"] diff --git a/reference/writeonce-api/Dockerfile.cross b/reference/writeonce-api/Dockerfile.cross deleted file mode 100644 index 8a7a8b0..0000000 --- a/reference/writeonce-api/Dockerfile.cross +++ /dev/null @@ -1,16 +0,0 @@ -FROM ghcr.io/cross-rs/aarch64-unknown-linux-gnu:main - -ENV PKG_CONFIG_ALLOW_CROSS=1 -ENV PKG_CONFIG_PATH=/usr/lib/aarch64-linux-gnu/pkgconfig - - -RUN dpkg --add-architecture arm64 && \ - apt-get update && \ - apt-get install -y \ - curl \ - build-essential \ - pkg-config \ - libssl-dev \ - libpq-dev:arm64 \ - && rm -rf /var/lib/apt/lists/* - diff --git a/reference/writeonce-api/DockerfileLambda b/reference/writeonce-api/DockerfileLambda deleted file mode 100644 index 0440736..0000000 --- a/reference/writeonce-api/DockerfileLambda +++ /dev/null @@ -1,38 +0,0 @@ -# Stage 1: Build the Rust application using the official Rust image -FROM rust:bullseye AS builder - -# Install required dependencies for cargo-lambda and Zig (if needed) -RUN apt-get update && apt-get install -y \ - curl \ - build-essential \ - gcc \ - libc-dev \ - pkg-config \ - libssl-dev \ - gcc-aarch64-linux-gnu \ - && rm -rf /var/lib/apt/lists/* - -# Install Zig (only if needed for cross-compiling or complex setups) -RUN curl -sSf https://ziglang.org/builds/zig-linux-x86_64-0.15.0-dev.377+f01833e03.tar.xz | tar -xJf - -C /usr/local/bin --strip-components=1 - -RUN rustup target add aarch64-unknown-linux-gnu -# Install cargo-lambda via cargo for building AWS Lambda-compatible binaries -RUN cargo install cargo-lambda - -# Create a working directory for the project -WORKDIR /app - -# Copy the entire Rust project into the container -COPY . . - -# Build the Rust application with cargo-lambda, targeting Lambda-compatible format -RUN cargo lambda build --release --bin lambda-post-article --arm64 - -# Copy the Lambda-compatible binary from the build stage into the Lambda runtime directory -RUN cd target/lambda/lambda-post-article && zip function.zip bootstrap - -# Stage 2: Extract just the zip artifact -FROM alpine:latest AS artifact - -# Copy only the zip file out of the builder -COPY --from=builder /app/target/lambda/lambda-post-article/function.zip /function.zip diff --git a/reference/writeonce-api/Makefile b/reference/writeonce-api/Makefile deleted file mode 100644 index b8875ab..0000000 --- a/reference/writeonce-api/Makefile +++ /dev/null @@ -1,27 +0,0 @@ -# Variables -CARGO=cargo -BUILD_DIR=target/lambda -BINARIES=lambda-post-article - -# Default target -all: build - -# Build all Lambda functions -build: - @for bin in $(BINARIES); do \ - $(CARGO) lambda build --release --bin $$bin --output-format zip; \ - done - -# Clean up build -clean: - $(CARGO) clean - -start-emulator: - $(CARGO) lambda start & - - -run-post: - $(CARGO) lambda invoke lambda-post-article --data-file events/post.json - -dev-run-post: start-emulator run-post - diff --git a/reference/writeonce-api/README.md b/reference/writeonce-api/README.md deleted file mode 100644 index 8d3406c..0000000 --- a/reference/writeonce-api/README.md +++ /dev/null @@ -1,25 +0,0 @@ -# Manage Articles API - -writeonce - -The **Manage Articles API** allows you to create, retrieve, and manage blog articles. This API is built to help with managing articles on a blog platform, allowing operations like fetching articles by title, pagination, and article creation. - -## Base URL - -api.writeonce.de - -## Tech Stack - -The **Manage Articles API** is built using the following technologies: - -- **Rust**: The backend of the API is built with the Rust programming language for high performance and reliability. -- **Diesel**: Diesel is used as the ORM (Object Relational Mapper) to interact with the database. -- **PostgreSQL**: The database used for storing articles and related data. - -## Cross build - -CROSS_CONTAINER_ENGINE=docker cross build \ - --bin writeonce-manage-article-api \ - --release \ - --target aarch64-unknown-linux-gnu \ - --verbose diff --git a/reference/writeonce-api/diesel.toml b/reference/writeonce-api/diesel.toml deleted file mode 100644 index 43ab6c5..0000000 --- a/reference/writeonce-api/diesel.toml +++ /dev/null @@ -1,9 +0,0 @@ -# For documentation on how to configure this file, -# see https://diesel.rs/guides/configuring-diesel-cli - -[print_schema] -file = "src/schema.rs" -custom_type_derives = ["diesel::query_builder::QueryId", "Clone"] - -[migrations_directory] -dir = "/home/shoney/manage-articles-api/migrations" diff --git a/reference/writeonce-api/doc/index.html b/reference/writeonce-api/doc/index.html deleted file mode 100644 index ed9bc28..0000000 --- a/reference/writeonce-api/doc/index.html +++ /dev/null @@ -1,42 +0,0 @@ - - - - - - - Swagger UI - - - - - -
    - - - - \ No newline at end of file diff --git a/reference/writeonce-api/doc/openapi.json b/reference/writeonce-api/doc/openapi.json deleted file mode 100644 index 5d11e3c..0000000 --- a/reference/writeonce-api/doc/openapi.json +++ /dev/null @@ -1,520 +0,0 @@ -{ - "openapi": "3.0.3", - "info": { - "title": "Manage Articles API", - "termsOfService": "", - "contact": { - "email": "shoney.john@outlook.com" - }, - "host": "api.writeonce.de", - "version": "0.0.1", - "summary": "Blog manage API", - "description": "🚀 **WriteOnce API Server**\n\nSource code available on [GitLab](https://git.writeonce.de/shoney/writeonce-manage-article-api).\n\n🔐 **Authorization:**\nYou can use the API key `REDACTED_TOKEN` to test the authorization filters (except for POST requests).\n\n📦 **S3 + AWS Lambda Integration:**\nWhen JSON documents are saved to an S3 bucket, an AWS Lambda function is triggered automatically. This function then makes a POST request to this API endpoint to create a new article.\n\n💡 View the [AWS Lambda function code](https://git.writeonce.de/shoney/writeonce-function)." - }, - "security": [ - { - "BearerAuth": [] - } - ], - "paths": { - "/article/title/{sys_title}": { - "get": { - "summary": "Fetch Articles by page route blog title name", - "operationId": "getArticleByTitle", - "security": [ - { - "BearerAuth": [] - } - ], - "parameters": [ - { - "name": "sys_title", - "in": "path", - "required": true, - "description": "Specify the system title of the article", - "schema": { - "type": "string", - "example": "gitlab-runner-with-kubernetes-executor" - } - } - ], - "responses": { - "200": { - "description": "Articles found", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Article" - } - } - } - }, - "404": { - "description": "Article not found", - "content": { - "text/plain": { - "example": "Article not found" - } - } - }, - "401": { - "description": "Unauthorized", - "content": { - "text/plain": { - "example": "Unauthorized" - } - } - } - } - } - }, - "/articles/{skip}/{limit}": { - "get": { - "summary": "Fetch Articles Pagination", - "operationId": "getArticleByPage", - "security": [ - { - "BearerAuth": [] - } - ], - "parameters": [ - { - "name": "skip", - "in": "path", - "required": true, - "description": "The lower range of Article pages", - "schema": { - "type": "integer", - "example": 0 - } - }, - { - "name": "limit", - "in": "path", - "required": true, - "description": "The limit range of Article pages", - "schema": { - "type": "integer", - "example": 0 - } - } - ], - "responses": { - "200": { - "description": "Articles found", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Article" - } - } - } - }, - "404": { - "description": "Article not found", - "content": { - "text/plain": { - "example": "Article not found" - } - } - }, - "401": { - "description": "Unauthorized", - "content": { - "text/plain": { - "example": "Unauthorized" - } - } - } - } - } - }, - "/article/{id}": { - "get": { - "summary": "Get article by ID", - "operationId": "getArticleById", - "security": [ - { - "BearerAuth": [] - } - ], - "parameters": [ - { - "name": "id", - "in": "path", - "required": true, - "description": "The ID of the article to retrieve.", - "schema": { - "type": "integer", - "example": 1 - } - } - ], - "responses": { - "200": { - "description": "Article found", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Article" - } - } - } - }, - "404": { - "description": "Article not found", - "content": { - "text/plain": { - "example": "Article not found" - } - } - }, - "401": { - "description": "Unauthorized", - "content": { - "text/plain": { - "example": "Unauthorized" - } - } - } - } - } - }, - "/article/": { - "post": { - "summary": "Create a new Article", - "operationId": "saveArticle", - "requestBody": { - "description": "Save a new article", - "required": true, - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "title": { - "type": "string", - "example": "Gitlab Runner with Kubernetes Executor" - }, - "sys_title": { - "type": "string", - "example": "gitlab-runner-with-kubernetes-executor" - }, - "published": { - "type": "boolean", - "example": true - }, - "content": { - "type": "object", - "properties": { - "author": { - "type": "string", - "example": "Shoney Arickathil" - }, - "content": { - "type": "object", - "properties": { - "codes": { - "type": "array", - "items": { - "type": "object", - "properties": { - "language": { - "type": "string", - "example": "yaml" - }, - "paragraphIndex": { - "type": "integer", - "example": 0 - }, - "sectionIndex": { - "type": "integer", - "example": 1 - }, - "snippet": { - "type": "string", - "example": "gitlab-runnergitlab-runner-deploy/gitlab-runner-with-kubernetes-executor" - }, - "title": { - "type": "string", - "example": "gitlab runner deploy" - } - } - } - }, - "images": { - "type": "array", - "items": { - "type": "object", - "properties": {} - } - }, - "img": { - "type": "object", - "properties": { - "caption": { - "type": "string", - "example": "Gitlab runner with kubernetes executor." - }, - "path": { - "type": "string", - "example": "https://writeonce-filecloud-bucket.s3.eu-central-1.amazonaws.com/images/kubernetes.png" - } - } - }, - "sections": { - "type": "array", - "items": { - "type": "object", - "properties": { - "heading": { - "type": "string", - "example": "Introduction" - }, - "paragraphs": { - "type": "array", - "items": { - "type": "string", - "example": "Deploying a Gitlab runner using kubernetes is a great option to overcome the limitations of other gitlab runner executor such as docker and docker machine." - } - } - } - } - } - } - }, - "publishedOn": { - "type": "integer", - "example": 1737414886 - }, - "references": { - "type": "array", - "items": { - "type": "object", - "properties": { - "dateAccessed": { - "type": "integer", - "example": 1737414886 - }, - "title": { - "type": "string", - "example": "Gitlab Kubernetes executor" - }, - "url": { - "type": "string", - "example": "https://docs.gitlab.com/runner/executors/kubernetes/" - } - } - } - }, - "systitle": { - "type": "string", - "example": "gitlab-runner-with-kubernetes-executor" - }, - "tags": { - "type": "array", - "items": { - "type": "string", - "example": ["Kubernetes", "Gitlab runner"] - } - }, - "title": { - "type": "string", - "example": "Gitlab Runner with Kubernetes Executor" - } - } - }, - "do_aws_sync": { - "type": "boolean", - "example": false - } - } - } - } - } - }, - "responses": { - "201": { - "description": "created successfully", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "message": { - "type": "string", - "example": "" - }, - "articleId": { - "type": "integer", - "example": "2" - } - } - } - } - } - }, - "400": { - "description": "Bad request" - }, - "500": { - "description": "Internal server error" - } - } - } - } - }, - "components": { - "securitySchemes": { - "BearerAuth": { - "type": "http", - "scheme": "bearer", - "bearerFormat": "JWT" - } - }, - "schemas": { - "Article": { - "type": "object", - "properties": { - "title": { - "type": "string", - "example": "Gitlab Runner with Kubernetes Executor" - }, - "sys_title": { - "type": "string", - "example": "gitlab-runner-with-kubernetes-executor" - }, - "published": { - "type": "boolean", - "example": true - }, - "content": { - "type": "object", - "properties": { - "author": { - "type": "string", - "example": "Shoney Arickathil" - }, - "content": { - "type": "object", - "properties": { - "codes": { - "type": "array", - "items": { - "type": "object", - "properties": { - "language": { - "type": "string", - "example": "yaml" - }, - "paragraphIndex": { - "type": "integer", - "example": 0 - }, - "sectionIndex": { - "type": "integer", - "example": 1 - }, - "snippet": { - "type": "string", - "example": "gitlab-runnergitlab-runner-deploy/gitlab-runner-with-kubernetes-executor" - }, - "title": { - "type": "string", - "example": "gitlab runner deploy" - } - } - } - }, - "images": { - "type": "array", - "items": { - "type": "object", - "properties": {} - } - }, - "img": { - "type": "object", - "properties": { - "caption": { - "type": "string", - "example": "Gitlab runner with kubernetes executor." - }, - "path": { - "type": "string", - "example": "https://writeonce-filecloud-bucket.s3.eu-central-1.amazonaws.com/images/kubernetes.png" - } - } - }, - "sections": { - "type": "array", - "items": { - "type": "object", - "properties": { - "heading": { - "type": "string", - "example": "Introduction" - }, - "paragraphs": { - "type": "array", - "items": { - "type": "string", - "example": "Deploying a Gitlab runner using kubernetes is a great option to overcome the limitations of other gitlab runner executor such as docker and docker machine." - } - } - } - } - } - } - }, - "publishedOn": { - "type": "integer", - "example": 1737414886 - }, - "references": { - "type": "array", - "items": { - "type": "object", - "properties": { - "dateAccessed": { - "type": "integer", - "example": 1737414886 - }, - "title": { - "type": "string", - "example": "Gitlab Kubernetes executor" - }, - "url": { - "type": "string", - "example": "https://docs.gitlab.com/runner/executors/kubernetes/" - } - } - } - }, - "systitle": { - "type": "string", - "example": "gitlab-runner-with-kubernetes-executor" - }, - "tags": { - "type": "array", - "items": { - "type": "string", - "example": ["Kubernetes", "Gitlab runner"] - } - }, - "title": { - "type": "string", - "example": "Gitlab Runner with Kubernetes Executor" - } - } - }, - "do_aws_sync": { - "type": "boolean", - "example": false - } - } - } - } - } -} - diff --git a/reference/writeonce-api/docker-compose.yml b/reference/writeonce-api/docker-compose.yml deleted file mode 100644 index bdfbc97..0000000 --- a/reference/writeonce-api/docker-compose.yml +++ /dev/null @@ -1,53 +0,0 @@ -services: - writeonce-postgress-db: - image: postgres:17.0 - - container_name: writeonce-postgress-db - restart: always - # set shared memory limit when using docker-compose - shm_size: 128mb - # or set shared memory limit when deploy via swarm stack - #volumes: - # - type: tmpfs - # target: /dev/shm - # tmpfs: - # size: 134217728 # 128*2^20 bytes = 128Mb - environment: - POSTGRES_USER: ${POSTGRES_USER} - POSTGRES_PASSWORD: ${POSTGRES_PASSWORD} - POSTGRES_DB: ${POSTGRES_DB} - DB_USER: ${DB_USER} - DB_PASSWORD: ${DB_PASSWORD} - DB_NAME: ${DB_NAME} - volumes: - - ./postgress/:/docker-entrypoint-initdb.d - healthcheck: - test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER}"] - interval: 10s - timeout: 5s - retries: 5 - networks: - - writeonce-network - - writeonce-manage-article-api: - image: registry.writeonce.de/shoney/writeonce-manage-article-api - - container_name: writeonce-manage-article-api - ports: - - ${PORT}:${PORT} - depends_on: - writeonce-postgress-db: - condition: service_healthy - environment: - - AWS_INFRA_BASE_URL=${AWS_INFRA_BASE_URL} - - PORT=${PORT} - - API_ACCESS_TOKEN=${API_ACCESS_TOKEN} - - API_ACCESS_ADMIN_TOKEN=${API_ACCESS_ADMIN_TOKEN} - - DATABASE_URL=${DATABASE_URL} - restart: always - networks: - - writeonce-network - -networks: - writeonce-network: - external: true diff --git a/reference/writeonce-api/events/post.json b/reference/writeonce-api/events/post.json deleted file mode 100644 index a4b59e6..0000000 --- a/reference/writeonce-api/events/post.json +++ /dev/null @@ -1,51 +0,0 @@ -{ - "content": { - "author": "Shoney Arickathil", - "content": { - "codes": [ - { - "language": "yaml", - "paragraphIndex": 0, - "sectionIndex": 1, - "snippet": "gitlab-runnergitlab-runner-deploy/gitlab-runner-with-kubernetes-executor", - "title": "gitlab runner deploy" - } - ], - "images": [ - {} - ], - "img": { - "caption": "Gitlab runner with kubernetes executor.", - "path": "https://writeonce-filecloud-bucket.s3.eu-central-1.amazonaws.com/images/kubernetes.png" - }, - "sections": [ - { - "heading": "Introduction", - "paragraphs": [ - "Deploying a Gitlab runner using kubernetes is a great option to overcome the limitations of other gitlab runner executor such as docker and docker machine." - ] - } - ] - }, - "publishedOn": 1737414886, - "references": [ - { - "dateAccessed": 1737414886, - "title": "Gitlab Kubernetes executor", - "url": "https://docs.gitlab.com/runner/executors/kubernetes/" - } - ], - "systitle": "gitlab-runner-with-kubernetes-executor", - "tags": [ - [ - "Kubernetes", - "Gitlab runner" - ] - ], - "title": "Gitlab Runner with Kubernetes Executor" - }, - "do_aws_sync": false, - "published": true, - "sys_title": "gitlab-runner-with-kubernetes-executor", - "title": "Gitlab Runner with Kubernetes Executor" - } \ No newline at end of file diff --git a/reference/writeonce-api/infra/00-build-image.sh b/reference/writeonce-api/infra/00-build-image.sh deleted file mode 100644 index d4212eb..0000000 --- a/reference/writeonce-api/infra/00-build-image.sh +++ /dev/null @@ -1,16 +0,0 @@ -#!/bin/sh - -if [ -z "$API_PORT" ] || [ -z "$AWS_INFRA_BASE_URL" ] || [ -z "$DATABASE_URL" ] || [ -z "$API_ACCESS_TOKEN" ] || [ -z "$API_ACCESS_ADMIN_TOKEN" ]; then - echo "Error: Missing required environment variables" - exit 1 -fi - - echo PORT="$API_PORT" - -docker build \ - --build-arg PORT="$API_PORT" \ - --build-arg AWS_INFRA_BASE_URL="$AWS_INFRA_BASE_URL" \ - --build-arg DATABASE_URL="$DATABASE_URL" \ - --build-arg API_ACCESS_TOKEN="$API_ACCESS_TOKEN" \ - --build-arg API_ACCESS_ADMIN_TOKEN="$API_ACCESS_ADMIN_TOKEN" \ - -t "$CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA" . \ No newline at end of file diff --git a/reference/writeonce-api/infra/02-docker-login.sh b/reference/writeonce-api/infra/02-docker-login.sh deleted file mode 100644 index 20ac47c..0000000 --- a/reference/writeonce-api/infra/02-docker-login.sh +++ /dev/null @@ -1,3 +0,0 @@ -#!/bin/sh - -docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY \ No newline at end of file diff --git a/reference/writeonce-api/infra/03-upload-image.sh b/reference/writeonce-api/infra/03-upload-image.sh deleted file mode 100644 index c6ddb29..0000000 --- a/reference/writeonce-api/infra/03-upload-image.sh +++ /dev/null @@ -1,2 +0,0 @@ -#!/bin/sh -docker push "$CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA" \ No newline at end of file diff --git a/reference/writeonce-api/infra/04-ssh-setup.sh b/reference/writeonce-api/infra/04-ssh-setup.sh deleted file mode 100644 index 5b01bea..0000000 --- a/reference/writeonce-api/infra/04-ssh-setup.sh +++ /dev/null @@ -1,7 +0,0 @@ -#!/bin/sh - -set -e # Exit immediately if a command exits with a non-zero status -apk add --no-cache openssh-client -echo "$SSH_PRIVATE_KEY" > /tmp/remote-login-key.pem -chmod 600 /tmp/remote-login-key.pem # Restrict permissions -ssh-keyscan -H "$SERVER_DNS_OR_IP" >> /tmp/known_hosts 2>/dev/null \ No newline at end of file diff --git a/reference/writeonce-api/infra/05-deploy.sh b/reference/writeonce-api/infra/05-deploy.sh deleted file mode 100644 index 3333f58..0000000 --- a/reference/writeonce-api/infra/05-deploy.sh +++ /dev/null @@ -1,8 +0,0 @@ -#!/bin/sh -ssh -v -i /tmp/remote-login-key.pem -o StrictHostKeyChecking=no -o UserKnownHostsFile=/tmp/known_hosts "$SERVER_USER@$SERVER_DNS_OR_IP" << EOF - - docker pull "$CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA" - docker stop "$CI_PROJECT_NAME" || true # Stop any running instance - docker rm "$CI_PROJECT_NAME" || true # Remove stopped instance - docker run -d --name "$CI_PROJECT_NAME" -p $API_PORT:$API_PORT --network writeonce-network "$CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA" -EOF diff --git a/reference/writeonce-api/infra/aurora-serverless/Pulumi.prod.yaml b/reference/writeonce-api/infra/aurora-serverless/Pulumi.prod.yaml deleted file mode 100644 index 790bee4..0000000 --- a/reference/writeonce-api/infra/aurora-serverless/Pulumi.prod.yaml +++ /dev/null @@ -1,4 +0,0 @@ -encryptionsalt: v1:whjKntLcNLo=:v1:0Sj2AsnGmgeoQI/N:9qEbPRTmbfQ6NDhuJAM7PIA6YX2r5A== -config: - aws:region: eu-central-1 - aws:profile: writeonce-iac diff --git a/reference/writeonce-api/infra/aurora-serverless/Pulumi.yaml b/reference/writeonce-api/infra/aurora-serverless/Pulumi.yaml deleted file mode 100644 index f22d3c8..0000000 --- a/reference/writeonce-api/infra/aurora-serverless/Pulumi.yaml +++ /dev/null @@ -1,7 +0,0 @@ -name: aurora-serverless -description: A minimal AWS Go Pulumi program -runtime: go -config: - pulumi:tags: - value: - pulumi:template: aws-go diff --git a/reference/writeonce-api/infra/aurora-serverless/README.md b/reference/writeonce-api/infra/aurora-serverless/README.md deleted file mode 100644 index 9f340c9..0000000 --- a/reference/writeonce-api/infra/aurora-serverless/README.md +++ /dev/null @@ -1,3 +0,0 @@ -## Reference - -https://www.pulumi.com/answers/deploying-aws-aurora-serverless-v2-with-pulumi/ \ No newline at end of file diff --git a/reference/writeonce-api/infra/aurora-serverless/go.mod b/reference/writeonce-api/infra/aurora-serverless/go.mod deleted file mode 100644 index 6fa5e91..0000000 --- a/reference/writeonce-api/infra/aurora-serverless/go.mod +++ /dev/null @@ -1,94 +0,0 @@ -module aurora-serverless - -go 1.22 - -toolchain go1.24.1 - -require ( - github.com/pulumi/pulumi-aws/sdk/v6 v6.77.1 - github.com/pulumi/pulumi/sdk/v3 v3.160.0 -) - -require github.com/BurntSushi/toml v1.2.1 // indirect - -require ( - dario.cat/mergo v1.0.0 // indirect - github.com/Microsoft/go-winio v0.6.1 // indirect - github.com/ProtonMail/go-crypto v1.1.3 // indirect - github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da // indirect - github.com/agext/levenshtein v1.2.3 // indirect - github.com/apparentlymart/go-textseg/v13 v13.0.0 // indirect - github.com/atotto/clipboard v0.1.4 // indirect - github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect - github.com/blang/semver v3.5.1+incompatible // indirect - github.com/charmbracelet/bubbles v0.16.1 // indirect - github.com/charmbracelet/bubbletea v0.25.0 // indirect - github.com/charmbracelet/lipgloss v0.7.1 // indirect - github.com/cheggaaa/pb v1.0.29 // indirect - github.com/cloudflare/circl v1.3.7 // indirect - github.com/containerd/console v1.0.4-0.20230313162750-1ae8d489ac81 // indirect - github.com/cyphar/filepath-securejoin v0.3.6 // indirect - github.com/djherbis/times v1.5.0 // indirect - github.com/emirpasic/gods v1.18.1 // indirect - github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect - github.com/go-git/go-billy/v5 v5.6.1 // indirect - github.com/go-git/go-git/v5 v5.13.1 // indirect - github.com/gogo/protobuf v1.3.2 // indirect - github.com/golang/glog v1.2.4 // indirect - github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect - github.com/google/uuid v1.6.0 // indirect - github.com/grpc-ecosystem/grpc-opentracing v0.0.0-20180507213350-8e809c8a8645 // indirect - github.com/hashicorp/errwrap v1.1.0 // indirect - github.com/hashicorp/go-multierror v1.1.1 // indirect - github.com/hashicorp/hcl/v2 v2.17.0 // indirect - github.com/inconshreveable/mousetrap v1.1.0 // indirect - github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect - github.com/kevinburke/ssh_config v1.2.0 // indirect - github.com/lucasb-eyer/go-colorful v1.2.0 // indirect - github.com/mattn/go-isatty v0.0.20 // indirect - github.com/mattn/go-localereader v0.0.1 // indirect - github.com/mattn/go-runewidth v0.0.15 // indirect - github.com/mitchellh/go-ps v1.0.0 // indirect - github.com/mitchellh/go-wordwrap v1.0.1 // indirect - github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 // indirect - github.com/muesli/cancelreader v0.2.2 // indirect - github.com/muesli/reflow v0.3.0 // indirect - github.com/muesli/termenv v0.15.2 // indirect - github.com/opentracing/basictracer-go v1.1.0 // indirect - github.com/opentracing/opentracing-go v1.2.0 // indirect - github.com/pgavlin/fx v0.1.6 // indirect - github.com/pjbgf/sha1cd v0.3.0 // indirect - github.com/pkg/errors v0.9.1 // indirect - github.com/pkg/term v1.1.0 // indirect - github.com/pulumi/appdash v0.0.0-20231130102222-75f619a67231 // indirect - github.com/pulumi/esc v0.9.1 // indirect - github.com/rivo/uniseg v0.4.4 // indirect - github.com/rogpeppe/go-internal v1.12.0 // indirect - github.com/sabhiram/go-gitignore v0.0.0-20210923224102-525f6e181f06 // indirect - github.com/santhosh-tekuri/jsonschema/v5 v5.0.0 // indirect - github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 // indirect - github.com/skeema/knownhosts v1.3.0 // indirect - github.com/spf13/cobra v1.8.0 // indirect - github.com/spf13/pflag v1.0.5 // indirect - github.com/texttheater/golang-levenshtein v1.0.1 // indirect - github.com/uber/jaeger-client-go v2.30.0+incompatible // indirect - github.com/uber/jaeger-lib v2.4.1+incompatible // indirect - github.com/xanzy/ssh-agent v0.3.3 // indirect - github.com/zclconf/go-cty v1.13.2 // indirect - go.uber.org/atomic v1.9.0 // indirect - golang.org/x/crypto v0.33.0 // indirect - golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 // indirect - golang.org/x/mod v0.19.0 // indirect - golang.org/x/net v0.35.0 // indirect - golang.org/x/sync v0.11.0 // indirect - golang.org/x/sys v0.30.0 // indirect - golang.org/x/term v0.29.0 // indirect - golang.org/x/text v0.22.0 // indirect - golang.org/x/tools v0.23.0 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20240311173647-c811ad7063a7 // indirect - google.golang.org/grpc v1.63.2 // indirect - google.golang.org/protobuf v1.34.0 // indirect - gopkg.in/warnings.v0 v0.1.2 // indirect - gopkg.in/yaml.v3 v3.0.1 // indirect - lukechampine.com/frand v1.4.2 // indirect -) diff --git a/reference/writeonce-api/infra/aurora-serverless/go.sum b/reference/writeonce-api/infra/aurora-serverless/go.sum deleted file mode 100644 index fa79a5a..0000000 --- a/reference/writeonce-api/infra/aurora-serverless/go.sum +++ /dev/null @@ -1,283 +0,0 @@ -dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk= -dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= -github.com/BurntSushi/toml v1.2.1 h1:9F2/+DoOYIOksmaJFPw1tGFy1eDnIJXg+UHjuD8lTak= -github.com/BurntSushi/toml v1.2.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= -github.com/HdrHistogram/hdrhistogram-go v1.1.2 h1:5IcZpTvzydCQeHzK4Ef/D5rrSqwxob0t8PQPMybUNFM= -github.com/HdrHistogram/hdrhistogram-go v1.1.2/go.mod h1:yDgFjdqOqDEKOvasDdhWNXYg9BVp4O+o5f6V/ehm6Oo= -github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY= -github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow= -github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM= -github.com/ProtonMail/go-crypto v1.1.3 h1:nRBOetoydLeUb4nHajyO2bKqMLfWQ/ZPwkXqXxPxCFk= -github.com/ProtonMail/go-crypto v1.1.3/go.mod h1:rA3QumHc/FZ8pAHreoekgiAbzpNsfQAosU5td4SnOrE= -github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da h1:KjTM2ks9d14ZYCvmHS9iAKVt9AyzRSqNU1qabPih5BY= -github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da/go.mod h1:eHEWzANqSiWQsof+nXEI9bUVUyV6F53Fp89EuCh2EAA= -github.com/agext/levenshtein v1.2.3 h1:YB2fHEn0UJagG8T1rrWknE3ZQzWM06O8AMAatNn7lmo= -github.com/agext/levenshtein v1.2.3/go.mod h1:JEDfjyjHDjOF/1e4FlBE/PkbqA9OfWu2ki2W0IB5558= -github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8= -github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4= -github.com/apparentlymart/go-textseg/v13 v13.0.0 h1:Y+KvPE1NYz0xl601PVImeQfFyEy6iT90AvPUL1NNfNw= -github.com/apparentlymart/go-textseg/v13 v13.0.0/go.mod h1:ZK2fH7c4NqDTLtiYLvIkEghdlcqw7yxLeM89kiTRPUo= -github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= -github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= -github.com/atotto/clipboard v0.1.4 h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z4= -github.com/atotto/clipboard v0.1.4/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI= -github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k= -github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8= -github.com/blang/semver v3.5.1+incompatible h1:cQNTCjp13qL8KC3Nbxr/y2Bqb63oX6wdnnjpJbkM4JQ= -github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk= -github.com/charmbracelet/bubbles v0.16.1 h1:6uzpAAaT9ZqKssntbvZMlksWHruQLNxg49H5WdeuYSY= -github.com/charmbracelet/bubbles v0.16.1/go.mod h1:2QCp9LFlEsBQMvIYERr7Ww2H2bA7xen1idUDIzm/+Xc= -github.com/charmbracelet/bubbletea v0.25.0 h1:bAfwk7jRz7FKFl9RzlIULPkStffg5k6pNt5dywy4TcM= -github.com/charmbracelet/bubbletea v0.25.0/go.mod h1:EN3QDR1T5ZdWmdfDzYcqOCAps45+QIJbLOBxmVNWNNg= -github.com/charmbracelet/lipgloss v0.7.1 h1:17WMwi7N1b1rVWOjMT+rCh7sQkvDU75B2hbZpc5Kc1E= -github.com/charmbracelet/lipgloss v0.7.1/go.mod h1:yG0k3giv8Qj8edTCbbg6AlQ5e8KNWpFujkNawKNhE2c= -github.com/cheggaaa/pb v1.0.29 h1:FckUN5ngEk2LpvuG0fw1GEFx6LtyY2pWI/Z2QgCnEYo= -github.com/cheggaaa/pb v1.0.29/go.mod h1:W40334L7FMC5JKWldsTWbdGjLo0RxUKK73K+TuPxX30= -github.com/cloudflare/circl v1.3.7 h1:qlCDlTPz2n9fu58M0Nh1J/JzcFpfgkFHHX3O35r5vcU= -github.com/cloudflare/circl v1.3.7/go.mod h1:sRTcRWXGLrKw6yIGJ+l7amYJFfAXbZG0kBSc8r4zxgA= -github.com/containerd/console v1.0.4-0.20230313162750-1ae8d489ac81 h1:q2hJAaP1k2wIvVRd/hEHD7lacgqrCPS+k8g1MndzfWY= -github.com/containerd/console v1.0.4-0.20230313162750-1ae8d489ac81/go.mod h1:YynlIjWYF8myEu6sdkwKIvGQq+cOckRm6So2avqoYAk= -github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= -github.com/cyphar/filepath-securejoin v0.3.6 h1:4d9N5ykBnSp5Xn2JkhocYDkOpURL/18CYMpo6xB9uWM= -github.com/cyphar/filepath-securejoin v0.3.6/go.mod h1:Sdj7gXlvMcPZsbhwhQ33GguGLDGQL7h7bg04C/+u9jI= -github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= -github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/djherbis/times v1.5.0 h1:79myA211VwPhFTqUk8xehWrsEO+zcIZj0zT8mXPVARU= -github.com/djherbis/times v1.5.0/go.mod h1:5q7FDLvbNg1L/KaBmPcWlVR9NmoKo3+ucqUA3ijQhA0= -github.com/elazarl/goproxy v1.2.3 h1:xwIyKHbaP5yfT6O9KIeYJR5549MXRQkoQMRXGztz8YQ= -github.com/elazarl/goproxy v1.2.3/go.mod h1:YfEbZtqP4AetfO6d40vWchF3znWX7C7Vd6ZMfdL8z64= -github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc= -github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ= -github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU= -github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM= -github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE= -github.com/gliderlabs/ssh v0.3.8 h1:a4YXD1V7xMF9g5nTkdfnja3Sxy1PVDCj1Zg4Wb8vY6c= -github.com/gliderlabs/ssh v0.3.8/go.mod h1:xYoytBv1sV0aL3CavoDuJIQNURXkkfPA/wxQ1pL1fAU= -github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI= -github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmSxCcxctByoQdvwPiA7DTK7jaaFDBTtu0ic= -github.com/go-git/go-billy/v5 v5.6.1 h1:u+dcrgaguSSkbjzHwelEjc0Yj300NUevrrPphk/SoRA= -github.com/go-git/go-billy/v5 v5.6.1/go.mod h1:0AsLr1z2+Uksi4NlElmMblP5rPcDZNRCD8ujZCRR2BE= -github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399 h1:eMje31YglSBqCdIqdhKBW8lokaMrL3uTkpGYlE2OOT4= -github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399/go.mod h1:1OCfN199q1Jm3HZlxleg+Dw/mwps2Wbk9frAWm+4FII= -github.com/go-git/go-git/v5 v5.13.1 h1:DAQ9APonnlvSWpvolXWIuV6Q6zXy2wHbN4cVlNR5Q+M= -github.com/go-git/go-git/v5 v5.13.1/go.mod h1:qryJB4cSBoq3FRoBRf5A77joojuBcmPJ0qu3XXXVixc= -github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= -github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= -github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= -github.com/golang/glog v1.2.4 h1:CNNw5U8lSiiBk7druxtSHHTsRWcxKoac6kZKm2peBBc= -github.com/golang/glog v1.2.4/go.mod h1:6AhwSGph0fcJtXVM/PEHPqZlFeoLxhs7/t5UDAwmO+w= -github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= -github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= -github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= -github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= -github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= -github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= -github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/grpc-ecosystem/grpc-opentracing v0.0.0-20180507213350-8e809c8a8645 h1:MJG/KsmcqMwFAkh8mTnAwhyKoB+sTAnY4CACC110tbU= -github.com/grpc-ecosystem/grpc-opentracing v0.0.0-20180507213350-8e809c8a8645/go.mod h1:6iZfnjpejD4L/4DwD7NryNaJyCQdzwWwH2MWhCA90Kw= -github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= -github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= -github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= -github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= -github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= -github.com/hashicorp/hcl/v2 v2.17.0 h1:z1XvSUyXd1HP10U4lrLg5e0JMVz6CPaJvAgxM0KNZVY= -github.com/hashicorp/hcl/v2 v2.17.0/go.mod h1:gJyW2PTShkJqQBKpAmPO3yxMxIuoXkOF2TpqXzrQyx4= -github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= -github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= -github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A= -github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo= -github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4= -github.com/kevinburke/ssh_config v1.2.0/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM= -github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= -github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= -github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= -github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= -github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= -github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= -github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= -github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= -github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= -github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= -github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY= -github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= -github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= -github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= -github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= -github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= -github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE= -github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= -github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= -github.com/mattn/go-localereader v0.0.1 h1:ygSAOl7ZXTx4RdPYinUpg6W99U8jWvWi9Ye2JC/oIi4= -github.com/mattn/go-localereader v0.0.1/go.mod h1:8fBrzywKY7BI3czFoHkuzRoWE9C+EiG4R1k4Cjx5p88= -github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= -github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk= -github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U= -github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= -github.com/mitchellh/go-ps v1.0.0 h1:i6ampVEEF4wQFF+bkYfwYgY+F/uYJDktmvLPf7qIgjc= -github.com/mitchellh/go-ps v1.0.0/go.mod h1:J4lOc8z8yJs6vUwklHw2XEIiT4z4C40KtWVN3nvg8Pg= -github.com/mitchellh/go-wordwrap v1.0.1 h1:TLuKupo69TCn6TQSyGxwI1EblZZEsQ0vMlAFQflz0v0= -github.com/mitchellh/go-wordwrap v1.0.1/go.mod h1:R62XHJLzvMFRBbcrT7m7WgmE1eOyTSsCt+hzestvNj0= -github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 h1:ZK8zHtRHOkbHy6Mmr5D264iyp3TiX5OmNcI5cIARiQI= -github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6/go.mod h1:CJlz5H+gyd6CUWT45Oy4q24RdLyn7Md9Vj2/ldJBSIo= -github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA= -github.com/muesli/cancelreader v0.2.2/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo= -github.com/muesli/reflow v0.3.0 h1:IFsN6K9NfGtjeggFP+68I4chLZV2yIKsXJFNZ+eWh6s= -github.com/muesli/reflow v0.3.0/go.mod h1:pbwTDkVPibjO2kyvBQRBxTWEEGDGq0FlB1BIKtnHY/8= -github.com/muesli/termenv v0.15.2 h1:GohcuySI0QmI3wN8Ok9PtKGkgkFIk7y6Vpb5PvrY+Wo= -github.com/muesli/termenv v0.15.2/go.mod h1:Epx+iuz8sNs7mNKhxzH4fWXGNpZwUaJKRS1noLXviQ8= -github.com/onsi/gomega v1.34.1 h1:EUMJIKUjM8sKjYbtxQI9A4z2o+rruxnzNvpknOXie6k= -github.com/onsi/gomega v1.34.1/go.mod h1:kU1QgUvBDLXBJq618Xvm2LUX6rSAfRaFRTcdOeDLwwY= -github.com/opentracing/basictracer-go v1.1.0 h1:Oa1fTSBvAl8pa3U+IJYqrKm0NALwH9OsgwOqDv4xJW0= -github.com/opentracing/basictracer-go v1.1.0/go.mod h1:V2HZueSJEp879yv285Aap1BS69fQMD+MNP1mRs6mBQc= -github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= -github.com/opentracing/opentracing-go v1.2.0 h1:uEJPy/1a5RIPAJ0Ov+OIO8OxWu77jEv+1B0VhjKrZUs= -github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc= -github.com/pgavlin/fx v0.1.6 h1:r9jEg69DhNoCd3Xh0+5mIbdbS3PqWrVWujkY76MFRTU= -github.com/pgavlin/fx v0.1.6/go.mod h1:KWZJ6fqBBSh8GxHYqwYCf3rYE7Gp2p0N8tJp8xv9u9M= -github.com/pjbgf/sha1cd v0.3.0 h1:4D5XXmUUBUl/xQ6IjCkEAbqXskkq/4O7LmGn0AqMDs4= -github.com/pjbgf/sha1cd v0.3.0/go.mod h1:nZ1rrWOcGJ5uZgEEVL1VUM9iRQiZvWdbZjkKyFzPPsI= -github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= -github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pkg/term v1.1.0 h1:xIAAdCMh3QIAy+5FrE8Ad8XoDhEU4ufwbaSozViP9kk= -github.com/pkg/term v1.1.0/go.mod h1:E25nymQcrSllhX42Ok8MRm1+hyBdHY0dCeiKZ9jpNGw= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= -github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/pulumi/appdash v0.0.0-20231130102222-75f619a67231 h1:vkHw5I/plNdTr435cARxCW6q9gc0S/Yxz7Mkd38pOb0= -github.com/pulumi/appdash v0.0.0-20231130102222-75f619a67231/go.mod h1:murToZ2N9hNJzewjHBgfFdXhZKjY3z5cYC1VXk+lbFE= -github.com/pulumi/esc v0.9.1 h1:HH5eEv8sgyxSpY5a8yePyqFXzA8cvBvapfH8457+mIs= -github.com/pulumi/esc v0.9.1/go.mod h1:oEJ6bOsjYlQUpjf70GiX+CXn3VBmpwFDxUTlmtUN84c= -github.com/pulumi/pulumi-aws/sdk/v6 v6.77.1 h1:9LCY0Nn8SdZIlBqB7PlL3Uz2fU00VUDiXlbW36THaRE= -github.com/pulumi/pulumi-aws/sdk/v6 v6.77.1/go.mod h1:zhyKkaiAn464Y5bKmeqAMdX1ZFroxYzOX9JlI483nJY= -github.com/pulumi/pulumi/sdk/v3 v3.160.0 h1:OxeATnIEqWyu3KpTeLUmyJdZaSEE6yX7ECvfGhByb9w= -github.com/pulumi/pulumi/sdk/v3 v3.160.0/go.mod h1:YEbbl0N7eVsgfsL7h5215dDf8GBSe4AnRon7Ya/KIVc= -github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= -github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= -github.com/rivo/uniseg v0.4.4 h1:8TfxU8dW6PdqD27gjM8MVNuicgxIjxpm4K7x4jp8sis= -github.com/rivo/uniseg v0.4.4/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= -github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= -github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= -github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= -github.com/sabhiram/go-gitignore v0.0.0-20210923224102-525f6e181f06 h1:OkMGxebDjyw0ULyrTYWeN0UNCCkmCWfjPnIA2W6oviI= -github.com/sabhiram/go-gitignore v0.0.0-20210923224102-525f6e181f06/go.mod h1:+ePHsJ1keEjQtpvf9HHw0f4ZeJ0TLRsxhunSI2hYJSs= -github.com/santhosh-tekuri/jsonschema/v5 v5.0.0 h1:TToq11gyfNlrMFZiYujSekIsPd9AmsA2Bj/iv+s4JHE= -github.com/santhosh-tekuri/jsonschema/v5 v5.0.0/go.mod h1:FKdcjfQW6rpZSnxxUvEA5H/cDPdvJ/SZJQLWWXWGrZ0= -github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 h1:n661drycOFuPLCN3Uc8sB6B/s6Z4t2xvBgU1htSHuq8= -github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4= -github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= -github.com/skeema/knownhosts v1.3.0 h1:AM+y0rI04VksttfwjkSTNQorvGqmwATnvnAHpSgc0LY= -github.com/skeema/knownhosts v1.3.0/go.mod h1:sPINvnADmT/qYH1kfv+ePMmOBTH6Tbl7b5LvTDjFK7M= -github.com/spf13/cobra v1.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0= -github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyhcho= -github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= -github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= -github.com/stretchr/objx v0.1.0 h1:4G4v2dO3VZwixGIRoQ5Lfboy6nUhCyYzaqnIAPPhYs4= -github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= -github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= -github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= -github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= -github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= -github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= -github.com/texttheater/golang-levenshtein v1.0.1 h1:+cRNoVrfiwufQPhoMzB6N0Yf/Mqajr6t1lOv8GyGE2U= -github.com/texttheater/golang-levenshtein v1.0.1/go.mod h1:PYAKrbF5sAiq9wd+H82hs7gNaen0CplQ9uvm6+enD/8= -github.com/uber/jaeger-client-go v2.30.0+incompatible h1:D6wyKGCecFaSRUpo8lCVbaOOb6ThwMmTEbhRwtKR97o= -github.com/uber/jaeger-client-go v2.30.0+incompatible/go.mod h1:WVhlPFC8FDjOFMMWRy2pZqQJSXxYSwNYOkTr/Z6d3Kk= -github.com/uber/jaeger-lib v2.4.1+incompatible h1:td4jdvLcExb4cBISKIpHuGoVXh+dVKhn2Um6rjCsSsg= -github.com/uber/jaeger-lib v2.4.1+incompatible/go.mod h1:ComeNDZlWwrWnDv8aPp0Ba6+uUTzImX/AauajbLI56U= -github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM= -github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw= -github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/zclconf/go-cty v1.13.2 h1:4GvrUxe/QUDYuJKAav4EYqdM47/kZa672LwmXFmEKT0= -github.com/zclconf/go-cty v1.13.2/go.mod h1:YKQzy/7pZ7iq2jNFzy5go57xdxdWoLLpaEp4u238AE0= -go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE= -go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= -golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= -golang.org/x/crypto v0.33.0 h1:IOBPskki6Lysi0lo9qQvbxiQ+FvsCC/YWOecCHAixus= -golang.org/x/crypto v0.33.0/go.mod h1:bVdXmD7IV/4GdElGPozy6U7lWdRXA4qyRVGJV57uQ5M= -golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 h1:2dVuKD2vS7b0QIHQbpyTISPd0LeHDbnYEryqj5Q1ug8= -golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY= -golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= -golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= -golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.19.0 h1:fEdghXQSo20giMthA7cd28ZC+jts4amQ3YMXiP5oMQ8= -golang.org/x/mod v0.19.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= -golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200421231249-e086a090c8fd/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.35.0 h1:T5GQRQb2y08kTAByq9L4/bz8cipCdA8FbRTXewonqY8= -golang.org/x/net v0.35.0/go.mod h1:EglIi67kWsHKlRzzVMUD93VMSWGFOMSZgxFjparz1Qk= -golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.11.0 h1:GGz8+XQP4FvTTrjZPzNKTMFtSXH80RAzG+5ghFPgK9w= -golang.org/x/sync v0.11.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= -golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200909081042-eff7692f9009/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc= -golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/term v0.29.0 h1:L6pJp37ocefwRRtYPKSWOWzOtWSxVajvz2ldH/xi3iU= -golang.org/x/term v0.29.0/go.mod h1:6bl4lRlvVuDgSf3179VpIxBF0o10JUpXWOnI7nErv7s= -golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM= -golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY= -golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.23.0 h1:SGsXPZ+2l4JsgaCKkx+FQ9YZ5XEtA1GZYuoDjenLjvg= -golang.org/x/tools v0.23.0/go.mod h1:pnu6ufv6vQkll6szChhK3C3L/ruaIv5eBeztNG8wtsI= -golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240311173647-c811ad7063a7 h1:8EeVk1VKMD+GD/neyEHGmz7pFblqPjHoi+PGQIlLx2s= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240311173647-c811ad7063a7/go.mod h1:WtryC6hu0hhx87FDGxWCDptyssuo68sk10vYjF+T9fY= -google.golang.org/grpc v1.63.2 h1:MUeiw1B2maTVZthpU5xvASfTh3LDbxHd6IJ6QQVU+xM= -google.golang.org/grpc v1.63.2/go.mod h1:WAX/8DgncnokcFUldAxq7GeB5DXHDbMF+lLvDomNkRA= -google.golang.org/protobuf v1.34.0 h1:Qo/qEd2RZPCf2nKuorzksSknv0d3ERwp1vFG38gSmH4= -google.golang.org/protobuf v1.34.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= -gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= -gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME= -gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= -gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= -gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= -gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= -gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -lukechampine.com/frand v1.4.2 h1:RzFIpOvkMXuPMBb9maa4ND4wjBn71E1Jpf8BzJHMaVw= -lukechampine.com/frand v1.4.2/go.mod h1:4S/TM2ZgrKejMcKMbeLjISpJMO+/eZ1zu3vYX9dtj3s= -pgregory.net/rapid v0.6.1 h1:4eyrDxyht86tT4Ztm+kvlyNBLIk071gR+ZQdhphc9dQ= -pgregory.net/rapid v0.6.1/go.mod h1:PY5XlDGj0+V1FCq0o192FdRhpKHGTRIWBgqjDBTrq04= diff --git a/reference/writeonce-api/infra/aurora-serverless/main.go b/reference/writeonce-api/infra/aurora-serverless/main.go deleted file mode 100644 index b94eeb7..0000000 --- a/reference/writeonce-api/infra/aurora-serverless/main.go +++ /dev/null @@ -1,269 +0,0 @@ -package main - -import ( - "github.com/pulumi/pulumi-aws/sdk/v6/go/aws/ec2" - "github.com/pulumi/pulumi-aws/sdk/v6/go/aws/iam" - "github.com/pulumi/pulumi-aws/sdk/v6/go/aws/lambda" - "github.com/pulumi/pulumi-aws/sdk/v6/go/aws/rds" - "github.com/pulumi/pulumi/sdk/v3/go/pulumi" -) - -func vpc(ctx *pulumi.Context) (*ec2.Vpc, error) { - - return ec2.NewVpc(ctx, "writeonce-vpc", &ec2.VpcArgs{ - CidrBlock: pulumi.String("10.0.0.0/16"), - InstanceTenancy: pulumi.String("default"), - Tags: pulumi.StringMap{ - "dbFor": pulumi.String("blogs"), - }, - }) -} - -func securityGroup(ctx *pulumi.Context, vpc *ec2.Vpc) (*ec2.SecurityGroup, error) { - - return ec2.NewSecurityGroup(ctx, "blog-db-sg", &ec2.SecurityGroupArgs{ - VpcId: vpc.ID(), - Description: pulumi.String("Allow DB access"), - Ingress: ec2.SecurityGroupIngressArray{ - &ec2.SecurityGroupIngressArgs{ - Protocol: pulumi.String("tcp"), - FromPort: pulumi.Int(5432), - ToPort: pulumi.Int(5432), - CidrBlocks: pulumi.StringArray{vpc.CidrBlock}, - }, - }, - Egress: ec2.SecurityGroupEgressArray{ - &ec2.SecurityGroupEgressArgs{ - Protocol: pulumi.String("-1"), - FromPort: pulumi.Int(0), - ToPort: pulumi.Int(0), - CidrBlocks: pulumi.StringArray{pulumi.String("0.0.0.0/0")}, - }, - }, - Tags: pulumi.StringMap{ - "Name": pulumi.String("blog-db-sg"), - }, - }) -} - -func subnetA(ctx *pulumi.Context, vpc *ec2.Vpc) (*ec2.Subnet, error) { - - return ec2.NewSubnet(ctx, "aurora-subnet-a", &ec2.SubnetArgs{ - VpcId: vpc.ID(), - CidrBlock: pulumi.String("10.0.1.0/24"), - AvailabilityZone: pulumi.String("eu-central-1a"), - Tags: pulumi.StringMap{ - "Name": pulumi.String("aurora-subnet-a"), - }, - }) -} -func subnetB(ctx *pulumi.Context, vpc *ec2.Vpc) (*ec2.Subnet, error) { - - return ec2.NewSubnet(ctx, "aurora-subnet-b", &ec2.SubnetArgs{ - VpcId: vpc.ID(), - CidrBlock: pulumi.String("10.0.2.0/24"), - AvailabilityZone: pulumi.String("eu-central-1b"), - Tags: pulumi.StringMap{ - "Name": pulumi.String("aurora-subnet-b"), - }, - }) -} - -func cluster(ctx *pulumi.Context, sg *ec2.SecurityGroup, subnets []*ec2.Subnet) (*rds.Cluster, error) { - subnetIds := pulumi.StringArray{} - for _, s := range subnets { - subnetIds = append(subnetIds, s.ID()) - } - - subnetGroup, err := rds.NewSubnetGroup(ctx, "aurora-subnet-group", &rds.SubnetGroupArgs{ - SubnetIds: subnetIds, - Tags: pulumi.StringMap{ - "Name": pulumi.String("aurora-db-subnet-group"), - }, - }) - if err != nil { - return nil, err - } - - result, err := rds.NewCluster(ctx, "postgresql", &rds.ClusterArgs{ - ClusterIdentifier: pulumi.String("aurora-cluster"), - Engine: pulumi.String(rds.EngineTypeAuroraPostgresql), - EngineMode: pulumi.String(rds.EngineModeProvisioned), - EngineVersion: pulumi.String("16.6"), - DatabaseName: pulumi.String("writeonce"), - MasterUsername: pulumi.String("writeonce"), - MasterPassword: pulumi.String("writeonce"), - VpcSecurityGroupIds: pulumi.StringArray{ - sg.ID().ToStringOutput(), - }, - DbSubnetGroupName: subnetGroup.Name, - StorageEncrypted: pulumi.Bool(true), - Serverlessv2ScalingConfiguration: &rds.ClusterServerlessv2ScalingConfigurationArgs{ - MaxCapacity: pulumi.Float64(1), - MinCapacity: pulumi.Float64(0), - SecondsUntilAutoPause: pulumi.Int(3600), - }, - SkipFinalSnapshot: pulumi.Bool(true), - }) - if err != nil { - return nil, err - } - _, err = rds.NewClusterInstance(ctx, "postgresql-instance", &rds.ClusterInstanceArgs{ - ClusterIdentifier: result.ID(), - InstanceClass: pulumi.String("db.serverless"), - Engine: result.Engine, - EngineVersion: result.EngineVersion, - }) - if err != nil { - return nil, err - } - return result, nil - -} - -func lambdaFunction(ctx *pulumi.Context, cluster *rds.Cluster, securityGroup *ec2.SecurityGroup, subnets []*ec2.Subnet) error { - - subnetIds := pulumi.StringArray{} - for _, s := range subnets { - subnetIds = append(subnetIds, s.ID()) - } - - layer, err := lambda.NewLayerVersion(ctx, "pg_libpq-layer", &lambda.LayerVersionArgs{ - Code: pulumi.NewFileArchive("layer/libpq-layer.zip"), - LayerName: pulumi.String("libpq-layer"), - CompatibleRuntimes: pulumi.StringArray{ - pulumi.String("provided.al2023"), - }, - }) - if err != nil { - return err - } - // Create an IAM role. - role, err := iam.NewRole(ctx, "task-exec-role", &iam.RoleArgs{ - AssumeRolePolicy: pulumi.String(`{ - "Version": "2012-10-17", - "Statement": [{ - "Sid": "", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com" - }, - "Action": "sts:AssumeRole" - }] - }`), - }) - if err != nil { - return err - } - - // change later - funcPolicy, err := iam.NewRolePolicy(ctx, "post-article-aurora-pgsql-policy", &iam.RolePolicyArgs{ - Role: role.Name, - Policy: pulumi.String(`{ - "Version": "2012-10-17", - "Statement": [{ - "Effect": "Allow", - "Action": [ - "logs:CreateLogGroup", - "logs:CreateLogStream", - "logs:PutLogEvents" - ], - "Resource": "arn:aws:logs:*:*:*" - }, - { - "Effect": "Allow", - "Action": [ - "ec2:CreateNetworkInterface", - "ec2:DescribeNetworkInterfaces", - "ec2:DeleteNetworkInterface" - ], - "Resource": "*" - }, - { - "Effect": "Allow", - "Action": [ - "rds-db:connect", - "secretsmanager:GetSecretValue" - ], - "Resource": "*" - }] - }`), - }) - if err != nil { - return err - } - - // Set arguments for constructing the function resource. - args := &lambda.FunctionArgs{ - Handler: pulumi.String("bootstrap"), - Role: role.Arn, - Runtime: pulumi.String("provided.al2023"), - Code: pulumi.NewFileArchive("../../target/lambda/lambda-post-article/bootstrap.zip"), - Environment: &lambda.FunctionEnvironmentArgs{ - Variables: pulumi.StringMap{ - "DATABASE_URL": cluster.Endpoint, - }, - }, - VpcConfig: &lambda.FunctionVpcConfigArgs{ - SecurityGroupIds: pulumi.StringArray{ - securityGroup.ID(), - }, - SubnetIds: subnetIds, - }, - Layers: pulumi.StringArray{ - layer.Arn, - }, - } - - // Create the lambda using the args. - function, err := lambda.NewFunction( - ctx, - "post-article-aurora-pgsql-function", - args, - pulumi.DependsOn([]pulumi.Resource{funcPolicy}), - ) - if err != nil { - return err - } - // Export the lambda ARN. - ctx.Export("lambda", function.Arn) - - return nil - -} - -func main() { - pulumi.Run(func(ctx *pulumi.Context) error { - - vpc, err := vpc(ctx) - if err != nil { - return err - } - - sg, err := securityGroup(ctx, vpc) - if err != nil { - return err - } - - subA, err := subnetA(ctx, vpc) - if err != nil { - return err - } - subB, err := subnetB(ctx, vpc) - if err != nil { - return err - } - - cluster, err := cluster(ctx, sg, []*ec2.Subnet{subA, subB}) - if err != nil { - return err - } - lambdaFunction(ctx, cluster, sg, []*ec2.Subnet{subA, subB}) - ctx.Export("vpcId", vpc.ID()) - ctx.Export("securityGroupId", sg.ID()) - ctx.Export("clusterEndpoint", cluster.Endpoint) - - return nil - - }) -} diff --git a/reference/writeonce-api/migrations/.keep b/reference/writeonce-api/migrations/.keep deleted file mode 100644 index e69de29..0000000 diff --git a/reference/writeonce-api/migrations/00000000000000_diesel_initial_setup/down.sql b/reference/writeonce-api/migrations/00000000000000_diesel_initial_setup/down.sql deleted file mode 100644 index a9f5260..0000000 --- a/reference/writeonce-api/migrations/00000000000000_diesel_initial_setup/down.sql +++ /dev/null @@ -1,6 +0,0 @@ --- This file was automatically created by Diesel to setup helper functions --- and other internal bookkeeping. This file is safe to edit, any future --- changes will be added to existing projects as new migrations. - -DROP FUNCTION IF EXISTS diesel_manage_updated_at(_tbl regclass); -DROP FUNCTION IF EXISTS diesel_set_updated_at(); diff --git a/reference/writeonce-api/migrations/00000000000000_diesel_initial_setup/up.sql b/reference/writeonce-api/migrations/00000000000000_diesel_initial_setup/up.sql deleted file mode 100644 index d68895b..0000000 --- a/reference/writeonce-api/migrations/00000000000000_diesel_initial_setup/up.sql +++ /dev/null @@ -1,36 +0,0 @@ --- This file was automatically created by Diesel to setup helper functions --- and other internal bookkeeping. This file is safe to edit, any future --- changes will be added to existing projects as new migrations. - - - - --- Sets up a trigger for the given table to automatically set a column called --- `updated_at` whenever the row is modified (unless `updated_at` was included --- in the modified columns) --- --- # Example --- --- ```sql --- CREATE TABLE users (id SERIAL PRIMARY KEY, updated_at TIMESTAMP NOT NULL DEFAULT NOW()); --- --- SELECT diesel_manage_updated_at('users'); --- ``` -CREATE OR REPLACE FUNCTION diesel_manage_updated_at(_tbl regclass) RETURNS VOID AS $$ -BEGIN - EXECUTE format('CREATE TRIGGER set_updated_at BEFORE UPDATE ON %s - FOR EACH ROW EXECUTE PROCEDURE diesel_set_updated_at()', _tbl); -END; -$$ LANGUAGE plpgsql; - -CREATE OR REPLACE FUNCTION diesel_set_updated_at() RETURNS trigger AS $$ -BEGIN - IF ( - NEW IS DISTINCT FROM OLD AND - NEW.updated_at IS NOT DISTINCT FROM OLD.updated_at - ) THEN - NEW.updated_at := current_timestamp; - END IF; - RETURN NEW; -END; -$$ LANGUAGE plpgsql; diff --git a/reference/writeonce-api/migrations/2024-10-26-110007_create_articles/down.sql b/reference/writeonce-api/migrations/2024-10-26-110007_create_articles/down.sql deleted file mode 100644 index 98b4e57..0000000 --- a/reference/writeonce-api/migrations/2024-10-26-110007_create_articles/down.sql +++ /dev/null @@ -1,2 +0,0 @@ --- This file should undo anything in `up.sql` -DROP TABLE articles \ No newline at end of file diff --git a/reference/writeonce-api/migrations/2024-10-26-110007_create_articles/up.sql b/reference/writeonce-api/migrations/2024-10-26-110007_create_articles/up.sql deleted file mode 100644 index 29f315a..0000000 --- a/reference/writeonce-api/migrations/2024-10-26-110007_create_articles/up.sql +++ /dev/null @@ -1,7 +0,0 @@ --- Your SQL goes here -CREATE TABLE articles ( - id SERIAL PRIMARY KEY, - title VARCHAR NOT NULL, - sys_title VARCHAR NOT NULL, - published BOOLEAN NOT NULL DEFAULT FALSE -) \ No newline at end of file diff --git a/reference/writeonce-api/migrations/2024-10-27-130201_add_articles_content/down.sql b/reference/writeonce-api/migrations/2024-10-27-130201_add_articles_content/down.sql deleted file mode 100644 index 5654b8a..0000000 --- a/reference/writeonce-api/migrations/2024-10-27-130201_add_articles_content/down.sql +++ /dev/null @@ -1,3 +0,0 @@ --- This file should undo anything in `up.sql` -ALTER TABLE public.articles -DROP COLUMN content; \ No newline at end of file diff --git a/reference/writeonce-api/migrations/2024-10-27-130201_add_articles_content/up.sql b/reference/writeonce-api/migrations/2024-10-27-130201_add_articles_content/up.sql deleted file mode 100644 index 0c61aef..0000000 --- a/reference/writeonce-api/migrations/2024-10-27-130201_add_articles_content/up.sql +++ /dev/null @@ -1,3 +0,0 @@ --- Your SQL goes here -ALTER TABLE public.articles -ADD COLUMN content JSONB; \ No newline at end of file diff --git a/reference/writeonce-api/migrations/2024-10-27-131600_add_articles_do_aws_sync/down.sql b/reference/writeonce-api/migrations/2024-10-27-131600_add_articles_do_aws_sync/down.sql deleted file mode 100644 index 1b183fe..0000000 --- a/reference/writeonce-api/migrations/2024-10-27-131600_add_articles_do_aws_sync/down.sql +++ /dev/null @@ -1,3 +0,0 @@ --- This file should undo anything in `up.sql` -ALTER TABLE public.articles -DROP COLUMN do_aws_sync; \ No newline at end of file diff --git a/reference/writeonce-api/migrations/2024-10-27-131600_add_articles_do_aws_sync/up.sql b/reference/writeonce-api/migrations/2024-10-27-131600_add_articles_do_aws_sync/up.sql deleted file mode 100644 index 4ed43b0..0000000 --- a/reference/writeonce-api/migrations/2024-10-27-131600_add_articles_do_aws_sync/up.sql +++ /dev/null @@ -1,3 +0,0 @@ --- Your SQL goes here -ALTER TABLE public.articles -ADD COLUMN do_aws_sync BOOLEAN; \ No newline at end of file diff --git a/reference/writeonce-api/migrations/2024-11-12-090859_add_sys_title_contrainst/down.sql b/reference/writeonce-api/migrations/2024-11-12-090859_add_sys_title_contrainst/down.sql deleted file mode 100644 index 4ccb790..0000000 --- a/reference/writeonce-api/migrations/2024-11-12-090859_add_sys_title_contrainst/down.sql +++ /dev/null @@ -1,5 +0,0 @@ --- This file should undo anything in `up.sql` -ALTER TABLE articles DROP CONSTRAINT articles_pkey; - -ALTER TABLE articles -ADD CONSTRAINT articles_pkey PRIMARY KEY (id); \ No newline at end of file diff --git a/reference/writeonce-api/migrations/2024-11-12-090859_add_sys_title_contrainst/up.sql b/reference/writeonce-api/migrations/2024-11-12-090859_add_sys_title_contrainst/up.sql deleted file mode 100644 index beb4a94..0000000 --- a/reference/writeonce-api/migrations/2024-11-12-090859_add_sys_title_contrainst/up.sql +++ /dev/null @@ -1,5 +0,0 @@ --- Your SQL goes here -ALTER TABLE articles DROP CONSTRAINT articles_pkey; - -ALTER TABLE articles -ADD CONSTRAINT articles_pkey PRIMARY KEY (id, sys_title); \ No newline at end of file diff --git a/reference/writeonce-api/migrations/2024-11-12-101350_add_sys_title_updated_contrainst/down.sql b/reference/writeonce-api/migrations/2024-11-12-101350_add_sys_title_updated_contrainst/down.sql deleted file mode 100644 index 8828f7f..0000000 --- a/reference/writeonce-api/migrations/2024-11-12-101350_add_sys_title_updated_contrainst/down.sql +++ /dev/null @@ -1,5 +0,0 @@ --- This file should undo anything in `up.sql` -ALTER TABLE articles DROP CONSTRAINT articles_pkey; - -ALTER TABLE articles -ADD CONSTRAINT articles_pkey PRIMARY KEY (id, sys_title); \ No newline at end of file diff --git a/reference/writeonce-api/migrations/2024-11-12-101350_add_sys_title_updated_contrainst/up.sql b/reference/writeonce-api/migrations/2024-11-12-101350_add_sys_title_updated_contrainst/up.sql deleted file mode 100644 index af8f37f..0000000 --- a/reference/writeonce-api/migrations/2024-11-12-101350_add_sys_title_updated_contrainst/up.sql +++ /dev/null @@ -1,5 +0,0 @@ --- Your SQL goes here -ALTER TABLE articles DROP CONSTRAINT articles_pkey; - -ALTER TABLE articles -ADD CONSTRAINT articles_pkey PRIMARY KEY (sys_title); \ No newline at end of file diff --git a/reference/writeonce-api/migrations/2024-11-12-154958_add_time_stamp/down.sql b/reference/writeonce-api/migrations/2024-11-12-154958_add_time_stamp/down.sql deleted file mode 100644 index 819a7b7..0000000 --- a/reference/writeonce-api/migrations/2024-11-12-154958_add_time_stamp/down.sql +++ /dev/null @@ -1,3 +0,0 @@ --- This file should undo anything in `up.sql` -ALTER TABLE public.articles -DROP COLUMN published_on; \ No newline at end of file diff --git a/reference/writeonce-api/migrations/2024-11-12-154958_add_time_stamp/up.sql b/reference/writeonce-api/migrations/2024-11-12-154958_add_time_stamp/up.sql deleted file mode 100644 index f5daf73..0000000 --- a/reference/writeonce-api/migrations/2024-11-12-154958_add_time_stamp/up.sql +++ /dev/null @@ -1,3 +0,0 @@ --- Your SQL goes here -ALTER TABLE public.articles -ADD COLUMN published_on BIGINT DEFAULT EXTRACT(EPOCH FROM CURRENT_TIMESTAMP); diff --git a/reference/writeonce-api/postgress/init.sql b/reference/writeonce-api/postgress/init.sql deleted file mode 100644 index f2063f6..0000000 --- a/reference/writeonce-api/postgress/init.sql +++ /dev/null @@ -1,3 +0,0 @@ -CREATE ROLE WRITEONCE_DB_ADMIN WITH LOGIN PASSWORD 'adminpassword'; -CREATE DATABASE WRITEONCE_DB OWNER admin; -GRANT ALL PRIVILEGES ON DATABASE WRITEONCE_DB TO admin; \ No newline at end of file diff --git a/reference/writeonce-api/src/api/article.rs b/reference/writeonce-api/src/api/article.rs deleted file mode 100644 index b98031b..0000000 --- a/reference/writeonce-api/src/api/article.rs +++ /dev/null @@ -1,183 +0,0 @@ -use crate::api::valid; -use crate::config; -use crate::models::*; -use crate::services::aws::get_s3_article; -use crate::services::aws_s3; -use actix_web::{delete, get, post, web, HttpRequest, HttpResponse, Responder}; - -#[get("/article/{id}")] -pub async fn get_article_by_id(req: HttpRequest, id: web::Path) -> impl Responder { - if let Err(response) = valid::validate_token(&req).await { - return response; // Return Unauthorized response - } - - match Article::find(id.into_inner()) { - Ok(article) => { - // Return the article in the response - HttpResponse::Ok().json(article) - } - Err(_) => { - // Handle the case when the article is not found - HttpResponse::NotFound().body("Article not found") - } - } -} - -#[delete("/article/{id}")] -pub async fn remove_article_by_id(req: HttpRequest, id: web::Path) -> impl Responder { - if let Err(response) = valid::validate_admin_token(&req).await { - return response; // Return Unauthorized response - } - - match Article::remove(id.into_inner()) { - Ok(article) => { - // Return the article in the response - HttpResponse::Ok().json(article) - } - Err(_) => { - // Handle the case when the article is not found - HttpResponse::NotFound().body("Article not found") - } - } -} - -#[get("/article/title/{sys_title}")] -pub async fn get_article_by_title( - req: HttpRequest, - sys_title: web::Path, -) -> impl Responder { - if let Err(response) = valid::validate_token(&req).await { - return response; // Return Unauthorized response - } - - match Article::get_article_by_title(sys_title.into_inner()) { - Ok(article) => { - // Return the article in the response - HttpResponse::Ok().json(article) - } - Err(_) => { - // Handle the case when the article is not found - HttpResponse::NotFound().body("Article not found") - } - } -} - -#[get("/articles/count")] -pub async fn get_articles_total_count(req: HttpRequest) -> impl Responder { - if let Err(response) = valid::validate_token(&req).await { - return response; // Return Unauthorized response - } - - match Article::get_total_articles_count() { - Ok(result) => { - // Return the article in the response - HttpResponse::Ok().json(result) - } - Err(_) => { - // Handle the case when the article is not found - HttpResponse::NotFound().body("Article not found") - } - } -} - -#[get("/articles/{skip}/{limit}")] -pub async fn get_articles_pagination( - req: HttpRequest, - path: web::Path<(i64, i64)>, -) -> impl Responder { - if let Err(response) = valid::validate_token(&req).await { - return response; - } - - let (skip, limit) = path.into_inner(); - - match Article::get_articles_pagination(skip, limit) { - Ok(articles) => { - // Return the article in the response - HttpResponse::Ok().json(articles) - } - Err(_) => { - // Handle the case when the article is not found - HttpResponse::NotFound().body("Articles not found") - } - } -} - -#[post("/article/")] -pub async fn upsert(req: HttpRequest, body: web::Json) -> impl Responder { - if let Err(response) = valid::validate_admin_token(&req).await { - return response; // Return Unauthorized response - } - let mut new_article = body.into_inner(); - - if new_article.do_aws_sync.unwrap_or(false) { - match get_s3_article(new_article.sys_title.clone()).await { - Ok(content) => { - new_article.content = Some(content); - } - Err(_) => {} - }; - } - - match Article::upsert(new_article) { - Ok(article) => { - // Return the article in the response - HttpResponse::Ok().json(article) - } - Err(_) => { - // Handle the case when the article is not found - HttpResponse::NotFound().body("Failed to Insert") - } - } -} - -#[get("/s3article/{sys_title}")] -pub async fn get_article_by_file_name(req: HttpRequest, path: web::Path) -> impl Responder { - if let Err(response) = valid::validate_token(&req).await { - return response; // Return Unauthorized response - } - let file_name = format!("{}/content.json", path.into_inner()); // Append .json to the file name - - let client = aws_s3::init_s3_client().await.unwrap(); - let bucket = config::get_aws_bucket_name(); - match aws_s3::read_json_from_s3(&client, &bucket, &file_name).await { - Ok(json_value) => { - // Return the JSON response - HttpResponse::Ok() - .content_type("application/json") // Set content type - .json(json_value) // Return the JSON response - } - Err(e) => { - // Handle error, return a 404 or 500 response as appropriate - eprintln!("Error reading from S3: {}", e); - HttpResponse::InternalServerError().body("Error reading the article") - } - } -} - -#[get("/s3markdown/{file_name}/{sys_title}")] -pub async fn get_markdown_by_file_name( - req: HttpRequest, - path: web::Path<(String, String)>, -) -> impl Responder { - if let Err(response) = valid::validate_token(&req).await { - return response; // Return Unauthorized response - } - let (file_name, sys_title) = path.into_inner(); - let s3_path = format!("{}/{}.md", sys_title, file_name); - - let client = aws_s3::init_s3_client().await.unwrap(); - let bucket = config::get_aws_bucket_name(); - match aws_s3::read_markdown_from_s3(&client, &bucket, &s3_path).await { - Ok(contents) => { - HttpResponse::Ok() - .content_type("text/plain") // Set content type - .body(contents) - } - Err(e) => { - // Handle error, return a 404 or 500 response as appropriate - eprintln!("Error reading from S3: {}", e); - HttpResponse::InternalServerError().body("Error reading the markdown") - } - } -} diff --git a/reference/writeonce-api/src/api/image.rs b/reference/writeonce-api/src/api/image.rs deleted file mode 100644 index 7a13053..0000000 --- a/reference/writeonce-api/src/api/image.rs +++ /dev/null @@ -1,29 +0,0 @@ -use actix_web::{get,web, HttpResponse,HttpRequest, Responder}; -use crate::services::aws_s3; -use crate::config; -use crate::api::valid; - - -#[get("/image/{sys_title}/{image_name}")] -pub async fn get_image_by_sys_title_file_name( req: HttpRequest,path: web::Path<(String,String)>) -> impl Responder { - if let Err(response) = valid::validate_token(&req).await { - return response; // Return Unauthorized response - } - let (sys_title, image_name) = path.into_inner(); - let s3_path = format!("{}/images/{}.png",sys_title, image_name); - - let client = aws_s3::init_s3_client().await.unwrap(); - let bucket = config::get_aws_bucket_name(); - match aws_s3::image_from_s3(&client, &bucket, &s3_path).await { - Ok((content_type, bytes)) => { - HttpResponse::Ok() - .content_type(content_type) // Set content type - .body(bytes) // Return the JSON response - } - Err(e) => { - // Handle error, return a 404 or 500 response as appropriate - eprintln!("Error reading from S3: {}", e); - HttpResponse::InternalServerError().body("Error reading the article") - } - } -} diff --git a/reference/writeonce-api/src/api/mod.rs b/reference/writeonce-api/src/api/mod.rs deleted file mode 100644 index 6a640ae..0000000 --- a/reference/writeonce-api/src/api/mod.rs +++ /dev/null @@ -1,19 +0,0 @@ -pub mod article; -pub mod spec; -pub mod valid; -pub mod image; -use actix_web::web; - -pub fn init_routes(cfg: &mut web::ServiceConfig) { - cfg.service(article::get_article_by_id); - cfg.service(article::upsert); - cfg.service(article::get_articles_pagination); - cfg.service(article::get_articles_total_count); - cfg.service(article::get_article_by_title); - cfg.service(article::remove_article_by_id); - cfg.service(spec::openapi_spec); - cfg.service(spec::swagger_ui); - cfg.service(article::get_article_by_file_name); - cfg.service(article::get_markdown_by_file_name); - cfg.service(image::get_image_by_sys_title_file_name); -} diff --git a/reference/writeonce-api/src/api/spec.rs b/reference/writeonce-api/src/api/spec.rs deleted file mode 100644 index b571428..0000000 --- a/reference/writeonce-api/src/api/spec.rs +++ /dev/null @@ -1,81 +0,0 @@ -use std::{env, fs}; - -use actix_web::{get,HttpResponse, Responder}; -use serde_json::{from_str, Value}; -// Serve the Swagger UI HTML page - -#[get("/open-api-spec")] -// Read the OpenAPI spec from an external JSON file -async fn openapi_spec() -> impl Responder { - // File path to the OpenAPI spec (can be an absolute or relative path) - let current_dir = env::current_dir().expect("Failed to get current directory"); - println!("{}", ¤t_dir.display()); - let file_path = current_dir.join("doc/openapi.json"); - - // Read the file contents asynchronously - match fs::read_to_string(file_path) { - Ok(content) => { - // Parse the JSON content into a Value - match from_str::(&content) { - Ok(json) => HttpResponse::Ok().json(json), // Return the parsed JSON as response - Err(_) => HttpResponse::InternalServerError().body("Error parsing OpenAPI JSON"), - } - }, - Err(_) => HttpResponse::InternalServerError().body("Error reading OpenAPI JSON file"), - } -} - -#[get("/doc")] -pub async fn swagger_ui() -> impl Responder { - HttpResponse::Ok() - .content_type("text/html") - .body(r#" - - - - - - Manage Blog API - - - - -
    - - - - "#) -} \ No newline at end of file diff --git a/reference/writeonce-api/src/api/valid.rs b/reference/writeonce-api/src/api/valid.rs deleted file mode 100644 index 326b7b1..0000000 --- a/reference/writeonce-api/src/api/valid.rs +++ /dev/null @@ -1,33 +0,0 @@ -use actix_web::{HttpResponse,HttpRequest}; -use crate::config; -pub async fn validate_token(req: &HttpRequest) -> Result<(), HttpResponse> { - // Get the token from the authorization header - if let Some(auth_header) = req.headers().get("Authorization") { - if let Ok(token) = auth_header.to_str() { - - let api_token = config::get_api_access_token(); - if token.trim_start_matches("Bearer ") == api_token { - return Ok(()); - } - } - } - - // If token is invalid or not present, return Unauthorized - Err(HttpResponse::Unauthorized().finish()) -} - -pub async fn validate_admin_token(req: &HttpRequest) -> Result<(), HttpResponse> { - // Get the token from the authorization header - if let Some(auth_header) = req.headers().get("Authorization") { - if let Ok(token) = auth_header.to_str() { - - let api_token = config::get_api_access_admin_token(); - if token.trim_start_matches("Bearer ") == api_token { - return Ok(()); - } - } - } - - // If token is invalid or not present, return Unauthorized - Err(HttpResponse::Unauthorized().finish()) -} \ No newline at end of file diff --git a/reference/writeonce-api/src/config/mod.rs b/reference/writeonce-api/src/config/mod.rs deleted file mode 100644 index 128e83c..0000000 --- a/reference/writeonce-api/src/config/mod.rs +++ /dev/null @@ -1,53 +0,0 @@ -// src/config/mod.rs -use std::env; - -pub fn init_config() { - dotenv::dotenv().ok(); // Load environment variables from a .env file - env::set_var("RUST_LOG", "actix_web=info"); -} - - -pub fn get_server_port() -> u16 { - // Try to read the `PORT` variable from the environment - // Default to 8080 if `PORT` is not set - env::var("PORT") - .unwrap_or_else(|_| "3001".to_string()) - .parse() - .expect("PORT must be a valid u16") -} - - pub fn get_api_access_token() -> String { - return env::var("API_ACCESS_TOKEN") - .expect("API_ACCESS_TOKEN must be set") - } - - pub fn get_api_access_admin_token() -> String { - return env::var("API_ACCESS_ADMIN_TOKEN") - .expect("API_ACCESS_POST_TOKEN must be set") - } - - pub fn get_aws_infra_base_url() -> String { - return env::var("AWS_INFRA_BASE_URL") - .expect("AWS_INFRA_BASE_URL must be set") - } - -pub fn get_aws_region() -> String { - return env::var("AWS_REGION") - .expect("AWS_REGION must be set") -} - -pub fn get_aws_acess_key_id() -> String { - return env::var("AWS_ACCESS_KEY_ID") - .expect("AWS_ACCESS_KEY_ID must be set") - } - - pub fn get_aws_secret_acess_key_id() -> String { - return env::var("AWS_SECRET_ACCESS_KEY") - .expect("AWS_SECRET_ACCESS_KEY must be set") - } - - pub fn get_aws_bucket_name() -> String { - return env::var("AWS_BUCKET_NAME") - .expect("AWS_BUCKET_NAME must be set") - } - diff --git a/reference/writeonce-api/src/db.rs b/reference/writeonce-api/src/db.rs deleted file mode 100644 index ba15d82..0000000 --- a/reference/writeonce-api/src/db.rs +++ /dev/null @@ -1,27 +0,0 @@ -use crate::error_handler::CustomError; -use diesel::pg::PgConnection; -use diesel::r2d2::ConnectionManager; -use diesel_migrations::{embed_migrations, EmbeddedMigrations, MigrationHarness}; -use lazy_static::lazy_static; -use r2d2; -use std::env; -type Pool = r2d2::Pool>; -pub type DbConnection = r2d2::PooledConnection>; - -lazy_static! { - static ref POOL: Pool = { - let db_url = env::var("DATABASE_URL").expect("Database url not set"); - let manager = ConnectionManager::::new(db_url); - Pool::new(manager).expect("Failed to create db pool") - }; -} -pub fn init() { - lazy_static::initialize(&POOL); - let mut conn = connection().expect("Failed to get db connection"); - pub const MIGRATIONS: EmbeddedMigrations = embed_migrations!("./migrations"); - conn.run_pending_migrations(MIGRATIONS).expect("failed to migrate"); -} -pub fn connection() -> Result { - POOL.get() - .map_err(|e| CustomError::new(500, format!("Failed getting db connection: {}", e))) -} \ No newline at end of file diff --git a/reference/writeonce-api/src/error_handler.rs b/reference/writeonce-api/src/error_handler.rs deleted file mode 100644 index c2c9b87..0000000 --- a/reference/writeonce-api/src/error_handler.rs +++ /dev/null @@ -1,44 +0,0 @@ -use thiserror::Error; -use diesel::result::Error as DieselError; - -#[derive(Debug, Error)] -pub enum CustomError { - #[error("An I/O error occurred: {0}")] - Io(#[from] std::io::Error), - - #[error("A parsing error occurred: {0}")] - Parse(String), - - #[error("A network error occurred: {0}")] - Network(String), - - #[error("An unknown error occurred.")] - Unknown, - - #[error("An unknown error occurred.")] - New(i32, String), - - #[error("Diesel error: {0}")] - DieselError(#[from] DieselError), -} - - -impl CustomError { - - - pub fn new(err_num:i32,value: impl Into) -> Self { - CustomError::New(err_num,value.into()) - } - - pub fn from_json_error(err:serde_json::Error) -> Self { - CustomError::Parse(format!("json conversion error: {}", err)) - - } - - pub fn from_request_error(err:reqwest::Error) -> Self { - CustomError::Parse(format!("json conversion error: {}", err)) - - } - - } - diff --git a/reference/writeonce-api/src/lambda/post_article.rs b/reference/writeonce-api/src/lambda/post_article.rs deleted file mode 100644 index 6ef204b..0000000 --- a/reference/writeonce-api/src/lambda/post_article.rs +++ /dev/null @@ -1,39 +0,0 @@ -use writeonce_manage_article_api::models::*; -use lambda_runtime::{service_fn, Error, LambdaEvent}; -use serde_json::{json, Value}; - -async fn handler(event: LambdaEvent) -> Result { - let payload = event.payload; - - let new_article: NewArticle = match serde_json::from_value(payload) { - Ok(val) => val, - Err(err) => { - return Ok(json!({ - "status": "error", - "message": format!("Invalid payload: {}", err) - })); - } - }; - - match Article::upsert(new_article) { - Ok(article) => { - - return Ok(json!({ - "status": "success", - "data": article - })); - }, - Err(err) =>{ - return Ok(json!({ - "status": "error", - "message": format!("Invalid payload: {}", err) - })); - - } - } -} - -#[tokio::main] -async fn main() -> Result<(), Error> { - lambda_runtime::run(service_fn(handler)).await -} \ No newline at end of file diff --git a/reference/writeonce-api/src/lib.rs b/reference/writeonce-api/src/lib.rs deleted file mode 100644 index 37dabe0..0000000 --- a/reference/writeonce-api/src/lib.rs +++ /dev/null @@ -1,8 +0,0 @@ -pub mod services; -pub mod error_handler; -pub mod db; -pub mod schema; -pub mod models; - -pub mod api; -pub mod config; \ No newline at end of file diff --git a/reference/writeonce-api/src/main.rs b/reference/writeonce-api/src/main.rs deleted file mode 100644 index 82351d4..0000000 --- a/reference/writeonce-api/src/main.rs +++ /dev/null @@ -1,23 +0,0 @@ -use actix_web::{App, HttpServer}; -use writeonce_manage_article_api::db; -use writeonce_manage_article_api::api; -use writeonce_manage_article_api::config; - - - -#[actix_web::main] -async fn main() -> std::io::Result<()>{ - - config::init_config(); - db::init(); - let port = config::get_server_port(); - println!("Starting server on port {port}"); - HttpServer::new(|| { - App::new() - .configure(api::init_routes) - }) - .bind(("0.0.0.0", port))? - .run() - .await -} - diff --git a/reference/writeonce-api/src/models.rs b/reference/writeonce-api/src/models.rs deleted file mode 100644 index 4ddabe0..0000000 --- a/reference/writeonce-api/src/models.rs +++ /dev/null @@ -1,141 +0,0 @@ -use diesel::{dsl::{count_star, delete}, prelude::*}; -use crate::error_handler::CustomError; -use crate::schema::articles; -use crate::db; -use serde::{Serialize, Deserialize}; -use serde_json::Value; -use diesel::insert_into; -use chrono::Utc; - -#[derive(Serialize, Deserialize,Queryable, AsChangeset, Insertable)] -#[diesel(table_name = articles)] -pub struct NewArticle { - pub title: String, - pub sys_title: String, - pub published: bool, - pub content: Option, - pub do_aws_sync: Option, - pub published_on: Option, -} - -#[derive(Serialize, Deserialize,Queryable,AsChangeset, Selectable)] -#[diesel(table_name = articles)] -#[diesel(check_for_backend(diesel::pg::Pg))] -pub struct Article { - pub id: i32, - pub title: String, - pub sys_title: String, - pub published: bool, - pub content: Option, - pub do_aws_sync: Option, - pub published_on: Option, -} - -#[derive(Serialize, Deserialize,Queryable, Selectable)] -#[diesel(table_name = articles)] -#[diesel(check_for_backend(diesel::pg::Pg))] -pub struct ArticleContent { - pub id: i32, - pub title: String, - pub sys_title: String, - pub content: Option -} - -#[derive(Serialize)] -pub struct CountResponse { - count: i64, -} - -#[derive(Serialize)] -pub struct RemoveResponse { - msg: String, -} - -impl NewArticle { - - fn new(data: NewArticle) -> Self { - let now_utc = Utc::now(); - let cet_offset = chrono::FixedOffset::east_opt(1 * 3600).unwrap(); // +1 hour for CET - let now_cet = now_utc.with_timezone(&cet_offset); - let unix_timestamp_cet = now_cet.timestamp(); - - Self { - published_on : Some(unix_timestamp_cet), - ..data - } - - } - -} - -impl Article { - - pub fn find(_id: i32) -> Result { - let mut conn = db::connection()?; - let article = articles::table.filter(articles::id.eq(_id)).first(&mut conn)?; - Ok(article) - } - - pub fn remove(_id: i32) -> Result { - let mut conn = db::connection()?; - let _ = delete(articles::dsl::articles.filter(articles::id.eq(_id))).execute(&mut conn); - let response = RemoveResponse { msg: "removed article".to_string() }; - Ok(response) - } - - pub fn upsert(new_article: NewArticle) -> Result { - let conn = &mut db::connection()?; - - // Convert to CET by specifying the timezone offset (+1 hour) - let insert_article = NewArticle::new(new_article); - let article = insert_into(articles::table) - .values(&insert_article) - .on_conflict(articles::dsl::sys_title) - .do_update() - .set(( - articles::title.eq(&insert_article.title), - articles::sys_title.eq(&insert_article.sys_title), - articles::published.eq(insert_article.published), - articles::content.eq(insert_article.content.clone()), - articles::published_on.eq(insert_article.published_on), - articles::do_aws_sync.eq(insert_article.do_aws_sync), - )) - .get_result(conn)?; - - Ok(article) - - } - pub fn get_total_articles_count() -> Result { - let conn = &mut db::connection()?; - - let total_count = articles::table - .select(count_star()) // Use count_star to count all rows - .filter(articles::published.eq(true)) - .first::(conn)?; - - let response = CountResponse { count: total_count }; - - Ok(response) - } - - pub fn get_articles_pagination(skip: i64, limit: i64) -> Result, CustomError> { - - let conn = &mut db::connection()?; - let results = articles::table - .select((articles::id, articles::title,articles::sys_title, articles::content)) - .limit(limit) - .offset(skip) - .filter(articles::published.eq(true)) - .load::(conn)?; - - Ok(results) - } - - pub fn get_article_by_title(sys_title: String) -> Result { - - let mut conn = db::connection()?; - let article = articles::table.filter(articles::sys_title.eq(sys_title)).first(&mut conn)?; - Ok(article) - } - -} diff --git a/reference/writeonce-api/src/schema.rs b/reference/writeonce-api/src/schema.rs deleted file mode 100644 index 3e2d01d..0000000 --- a/reference/writeonce-api/src/schema.rs +++ /dev/null @@ -1,13 +0,0 @@ -// @generated automatically by Diesel CLI. - -diesel::table! { - articles (sys_title) { - id -> Int4, - title -> Varchar, - sys_title -> Varchar, - published -> Bool, - content -> Nullable, - do_aws_sync -> Nullable, - published_on -> Nullable, - } -} diff --git a/reference/writeonce-api/src/services/aws.rs b/reference/writeonce-api/src/services/aws.rs deleted file mode 100644 index f2f0aa2..0000000 --- a/reference/writeonce-api/src/services/aws.rs +++ /dev/null @@ -1,28 +0,0 @@ -use crate::config; -use crate::error_handler::CustomError; -use reqwest::{Client, Url}; -use serde_json::Value; - -pub async fn get_s3_article(sys_title: String) -> Result { - let client = Client::new(); - - let aws_url = config::get_aws_infra_base_url(); - let base_url = Url::parse(&aws_url).unwrap(); - - let token = config::get_api_access_token(); - let url = base_url.join(&format!("/article/{}", sys_title)).unwrap(); - - let response = client - .get(url) - .bearer_auth(token) - .send() - .await - .map_err(CustomError::from_request_error)?; - - let json_value = response - .json() - .await - .map_err(CustomError::from_request_error)?; - - Ok(json_value) -} diff --git a/reference/writeonce-api/src/services/aws_s3.rs b/reference/writeonce-api/src/services/aws_s3.rs deleted file mode 100644 index 3e08f0f..0000000 --- a/reference/writeonce-api/src/services/aws_s3.rs +++ /dev/null @@ -1,102 +0,0 @@ -use crate::services::error::S3ExampleError; -use actix_web::web::Bytes; -use aws_config::BehaviorVersion; -use aws_sdk_s3 as s3; -use serde_json::{Error, Value}; - -pub async fn init_s3_client() -> Result { - let config = aws_config::defaults(BehaviorVersion::latest()).load().await; - - // Create the S3 client - let s3_client = s3::Client::new(&config); - - Ok(s3_client) -} - -pub async fn download_object( - client: &s3::Client, - bucket_name: &str, - key: &str, -) -> Result { - client - .get_object() - .bucket(bucket_name) - .key(key) - .send() - .await - .map_err(S3ExampleError::from) -} - -/// Reads a JSON file from the specified S3 bucket and key. -pub async fn read_json_from_s3( - client: &s3::Client, - bucket: &str, - key: &str, -) -> Result { - // Get the object from S3 - // let object = download_object(client, bucket, key).await?; - - let result = client - .get_object() - .bucket(bucket) - .key(key) - .response_content_type("application/json") - .send() - .await?; - - let bytes = result - .body - .collect() - .await - .map_err(S3ExampleError::from_byte_stream_error)? - .into_bytes(); - let response = std::str::from_utf8(&bytes).map_err(S3ExampleError::from_utf_error)?; - let json_value = serde_json::from_str(&response).map_err(S3ExampleError::from_json_error)?; - - Ok(json_value) -} - -pub async fn image_from_s3( - client: &s3::Client, - bucket: &str, - key: &str, -) -> Result<(String, Bytes), S3ExampleError> { - let result = client.get_object().bucket(bucket).key(key).send().await?; - - let content_type = result - .content_type() - .unwrap_or("application/octet-stream") - .to_string(); - - let bytes = result - .body - .collect() - .await - .map_err(S3ExampleError::from_byte_stream_error)? - .into_bytes(); - Ok((content_type, bytes)) -} - -pub async fn read_markdown_from_s3( - client: &s3::Client, - bucket: &str, - key: &str, -) -> Result { - let result = client - .get_object() - .bucket(bucket) - .key(key) - .response_content_type("text/plain") - .send() - .await?; - - let bytes = result - .body - .collect() - .await - .map_err(S3ExampleError::from_byte_stream_error)? - .into_bytes(); - let response = String::from_utf8(bytes.to_vec()).map_err(S3ExampleError::from_utf8_error)?; - - Ok(response) -} diff --git a/reference/writeonce-api/src/services/error.rs b/reference/writeonce-api/src/services/error.rs deleted file mode 100644 index dfd5cae..0000000 --- a/reference/writeonce-api/src/services/error.rs +++ /dev/null @@ -1,67 +0,0 @@ -use aws_smithy_types::byte_stream::error::Error as AWSByteStream; -use std::{str::Utf8Error, string::FromUtf8Error}; - -/// S3ExampleError provides a From impl to extract -/// client-specific error details. This serves as a consistent backup to handling -/// specific service errors, depending on what is needed by the scenario. -/// It is used throughout the code examples for the AWS SDK for Rust. - -#[derive(Debug)] -pub struct S3ExampleError(String); -impl S3ExampleError { - pub fn new(value: impl Into) -> Self { - S3ExampleError(value.into()) - } - - pub fn add_message(self, message: impl Into) -> Self { - S3ExampleError(format!("{}: {}", message.into(), self.0)) - } -} - -impl From for S3ExampleError { - fn from(value: T) -> Self { - S3ExampleError(format!( - "{}: {}", - value - .code() - .map(String::from) - .unwrap_or("unknown code".into()), - value - .message() - .map(String::from) - .unwrap_or("missing reason".into()), - )) - } -} - -impl S3ExampleError { - pub fn from_utf8_error(err: FromUtf8Error) -> Self { - S3ExampleError(format!("UTF-8 conversion error: {}", err)) - } -} - -impl S3ExampleError { - pub fn from_byte_stream_error(err: AWSByteStream) -> Self { - S3ExampleError(format!("UTF-8 conversion error: {}", err)) - } -} - -impl S3ExampleError { - pub fn from_utf_error(err: Utf8Error) -> Self { - S3ExampleError(format!("UTF-8 conversion error: {}", err)) - } -} - -impl S3ExampleError { - pub fn from_json_error(err: serde_json::Error) -> Self { - S3ExampleError(format!("json conversion error: {}", err)) - } -} - -impl std::error::Error for S3ExampleError {} - -impl std::fmt::Display for S3ExampleError { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{}", self.0) - } -} diff --git a/reference/writeonce-api/src/services/mod.rs b/reference/writeonce-api/src/services/mod.rs deleted file mode 100644 index f7c8a43..0000000 --- a/reference/writeonce-api/src/services/mod.rs +++ /dev/null @@ -1,3 +0,0 @@ -pub mod aws; -pub mod aws_s3; -pub mod error; diff --git a/reference/writeonce-app/.dockerignore b/reference/writeonce-app/.dockerignore deleted file mode 100644 index eb98894..0000000 --- a/reference/writeonce-app/.dockerignore +++ /dev/null @@ -1,42 +0,0 @@ -.git -.editorconfig -/.vscode/* -/node_modules -/e2e -/docs -.gitignore -*.zip -/dist -Dockerfile.build - -# Node.js dependencies -node_modules -npm-debug.log -yarn-error.log - -# Build output -dist -build -out -*.log - -# Development files -src/**/*.spec.ts -src/**/*.test.ts - -# IDE and OS-specific files -.idea -.vscode -*.swp -*.swo -.DS_Store -Thumbs.db - -# Temporary files -temp -tmp -*.tmp - -# Docker-related files -Dockerfile* -.dockerignore diff --git a/reference/writeonce-app/.editorconfig b/reference/writeonce-app/.editorconfig deleted file mode 100644 index 59d9a3a..0000000 --- a/reference/writeonce-app/.editorconfig +++ /dev/null @@ -1,16 +0,0 @@ -# Editor configuration, see https://editorconfig.org -root = true - -[*] -charset = utf-8 -indent_style = space -indent_size = 2 -insert_final_newline = true -trim_trailing_whitespace = true - -[*.ts] -quote_type = single - -[*.md] -max_line_length = off -trim_trailing_whitespace = false diff --git a/reference/writeonce-app/.gitignore b/reference/writeonce-app/.gitignore deleted file mode 100644 index 9b3484a..0000000 --- a/reference/writeonce-app/.gitignore +++ /dev/null @@ -1,48 +0,0 @@ -# See http://help.github.com/ignore-files/ for more about ignoring files. -# Compiled output -/dist -/tmp -/out-tsc -/bazel-out - -# Node -/node_modules -npm-debug.log -yarn-error.log - -# IDEs and editors -.idea/ -.project -.classpath -.c9/ -*.launch -.settings/ -*.sublime-workspace - -# Visual Studio Code -.vscode/* -!.vscode/settings.json -!.vscode/tasks.json -!.vscode/launch.json -!.vscode/extensions.json -.history/* - -# Miscellaneous -/.angular/cache -.sass-cache/ -/connect.lock -/coverage -/libpeerconnection.log -testem.log -/typings - -# System files -.DS_Store -Thumbs.db -package-lock.json -obj/ - - -*.pem -*.sh -!deploy.sh \ No newline at end of file diff --git a/reference/writeonce-app/.gitlab-ci.yml b/reference/writeonce-app/.gitlab-ci.yml deleted file mode 100644 index 5e05dd5..0000000 --- a/reference/writeonce-app/.gitlab-ci.yml +++ /dev/null @@ -1,47 +0,0 @@ -stages: - - build - - push - - deploy -variables: - IMAGE_NAME: $CI_REGISTRY_IMAGE:$CI_COMMIT_REF_SLUG - -build: - stage: build - image: docker:20.10.7 - services: - - docker:20.10.7-dind - before_script: - - echo "Setting up Docker environment" - script: - - docker info # Verify Docker is running - - docker build -t ${IMAGE_NAME} . - -push: - stage: push - image: docker:20.10.7 - services: - - docker:20.10.7-dind - before_script: - - echo "Logging into Docker registry" - script: - - docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY - - docker push ${IMAGE_NAME} - only: - - main - -deploy: - stage: deploy - image: docker:20.10.7 - services: - - docker:20.10.7-dind - before_script: - - echo "Deploy artifacts on web server" - script: - - eval $(ssh-agent -s) - - echo "$SSH_PRIVATE_KEY" | tr -d '\r' | ssh-add - - - docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY - - docker pull ${IMAGE_NAME} - - docker run --rm ${IMAGE_NAME} sh -c "tar -czf - -C browser . " | ssh -o StrictHostKeyChecking=no deployer@writeonce.de 'docker exec -i writeonce-webserver tar -xzf - -C /usr/share/nginx/html/' - - ssh-agent -k - only: - - main \ No newline at end of file diff --git a/reference/writeonce-app/.nvmrc b/reference/writeonce-app/.nvmrc deleted file mode 100644 index 17719ce..0000000 --- a/reference/writeonce-app/.nvmrc +++ /dev/null @@ -1 +0,0 @@ -18.20.4 diff --git a/reference/writeonce-app/.vscode/extensions.json b/reference/writeonce-app/.vscode/extensions.json deleted file mode 100644 index 77b3745..0000000 --- a/reference/writeonce-app/.vscode/extensions.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - // For more information, visit: https://go.microsoft.com/fwlink/?linkid=827846 - "recommendations": ["angular.ng-template"] -} diff --git a/reference/writeonce-app/.vscode/launch.json b/reference/writeonce-app/.vscode/launch.json deleted file mode 100644 index 100a1d4..0000000 --- a/reference/writeonce-app/.vscode/launch.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "version": "0.2.0", - "configurations": [ - { - "type": "edge", - "request": "launch", - "name": "localhost (Edge)", - "url": "https://localhost:4200", - "webRoot": "${workspaceFolder}" - }, - { - "type": "chrome", - "request": "launch", - "name": "localhost (Chrome)", - "url": "http://localhost:4200", - "webRoot": "${workspaceFolder}" - } - ] -} diff --git a/reference/writeonce-app/.vscode/tasks.json b/reference/writeonce-app/.vscode/tasks.json deleted file mode 100644 index a298b5b..0000000 --- a/reference/writeonce-app/.vscode/tasks.json +++ /dev/null @@ -1,42 +0,0 @@ -{ - // For more information, visit: https://go.microsoft.com/fwlink/?LinkId=733558 - "version": "2.0.0", - "tasks": [ - { - "type": "npm", - "script": "start", - "isBackground": true, - "problemMatcher": { - "owner": "typescript", - "pattern": "$tsc", - "background": { - "activeOnStart": true, - "beginsPattern": { - "regexp": "(.*?)" - }, - "endsPattern": { - "regexp": "bundle generation complete" - } - } - } - }, - { - "type": "npm", - "script": "test", - "isBackground": true, - "problemMatcher": { - "owner": "typescript", - "pattern": "$tsc", - "background": { - "activeOnStart": true, - "beginsPattern": { - "regexp": "(.*?)" - }, - "endsPattern": { - "regexp": "bundle generation complete" - } - } - } - } - ] -} diff --git a/reference/writeonce-app/Dockerfile b/reference/writeonce-app/Dockerfile deleted file mode 100644 index bf28730..0000000 --- a/reference/writeonce-app/Dockerfile +++ /dev/null @@ -1,24 +0,0 @@ -# Stage 1: Compile and Build angular codebase - -# Use official node image as the base image -FROM node:18.19.0-alpine AS build - -# Set the working directory -WORKDIR /app - -COPY package*.json ./ - -# Install all the dependencies -RUN npm install -COPY . . -# Generate the build of the application -RUN npm run build --prod --output=dist - -# Stage 2: Create a minimal image with only the dist folder -FROM alpine:latest - -# Set the working directory in the new minimal image -WORKDIR /app - -# Copy the built application from the previous stage -COPY --from=build /app/dist ./ \ No newline at end of file diff --git a/reference/writeonce-app/Dockerfile-Dev b/reference/writeonce-app/Dockerfile-Dev deleted file mode 100644 index f71bcbe..0000000 --- a/reference/writeonce-app/Dockerfile-Dev +++ /dev/null @@ -1,13 +0,0 @@ -FROM node:18 as base - -WORKDIR /app - -RUN npm install -g @angular/cli - -COPY package.json ./ - -# Install all the dependencies -RUN npm install -COPY . . -EXPOSE 4200 -CMD ["npm","run","devdoc"] diff --git a/reference/writeonce-app/README.md b/reference/writeonce-app/README.md deleted file mode 100644 index 49ccc3d..0000000 --- a/reference/writeonce-app/README.md +++ /dev/null @@ -1,41 +0,0 @@ -# Writeonce Frontend - -This project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 17.2.3. - -## Development server - -Run `ng serve` for a dev server. Navigate to `http://localhost:4200/`. The application will automatically reload if you change any of the source files. - -## Code scaffolding - -Run `ng generate component component-name` to generate a new component. You can also use `ng generate directive|pipe|service|class|guard|interface|enum|module`. - -## Build - -Run `ng build` to build the project. The build artifacts will be stored in the `dist/` directory. - -## Running unit tests - -Run `ng test` to execute the unit tests via [Karma](https://karma-runner.github.io). - -## Running end-to-end tests - -Run `ng e2e` to execute the end-to-end tests via a platform of your choice. To use this command, you need to first add a package that implements end-to-end testing capabilities. - -## Further help - -To get more help on the Angular CLI use `ng help` or go check out the [Angular CLI Overview and Command Reference](https://angular.io/cli) page. - -## References - -[nginx ssl](https://www.youtube.com/watch?v=R5d-hN9UtpU) - -[SSL] (https://www.digitalocean.com/community/tutorials/how-to-secure-a-containerized-node-js-application-with-nginx-let-s-encrypt-and-docker-compose)ba - - -## Linux -``` -sudo chcon system_u:object_r:usr_t:s0 ./svc.sh -sudo chcon system_u:object_r:usr_t:s0 ./runsvc.sh -docker run -it --rm -p 5200:4200 -v ${PWD}/src:/usr/scr/app/src --name writeonce writeonce:dev -``` \ No newline at end of file diff --git a/reference/writeonce-app/UI_ENHANCEMENT.md b/reference/writeonce-app/UI_ENHANCEMENT.md deleted file mode 100644 index a080887..0000000 --- a/reference/writeonce-app/UI_ENHANCEMENT.md +++ /dev/null @@ -1,216 +0,0 @@ -# UI Enhancement - Professional Blog Website - -## Overview -This document details the UI improvements made to transform the WriteOnce blog into a professional blog website with dark mode support. - -## Changes Summary - -### 1. Dark Mode Infrastructure - -#### Tailwind Configuration -- **File**: `tailwind.config.js` -- **Change**: Added `darkMode: 'class'` to enable class-based dark mode - -#### Theme Service -- **File**: `src/app/theme.service.ts` (NEW) -- **Features**: - - Signal-based state management - - Toggle between light/dark themes - - Persists preference to localStorage - - Checks system preference on first load - - Applies theme to `` element - -#### Index HTML -- **File**: `src/index.html` -- **Changes**: - - Added inline script for theme initialization (prevents flash) - - Added transition classes to body for smooth theme changes - - Default: `bg-slate-50 dark:bg-slate-900` - ---- - -### 2. Header Component - -#### Header Template -- **File**: `src/app/header/header.component.html` -- **Features**: - - Logo with brand name - - Responsive navigation (desktop: horizontal, mobile: hamburger menu) - - Dark mode toggle button (sun/moon icons) - - Sticky positioning - - Smooth transitions - -#### Navigation Links -- Home (`/`) -- Projects (`/projects`) -- About (`/about`) -- Contact (`/contact`) - -#### Header Logic -- **File**: `src/app/header/header.component.ts` -- **Features**: - - Inject ThemeService - - Mobile menu toggle state - - Methods: `toggleTheme()`, `toggleMobileMenu()`, `closeMobileMenu()` - ---- - -### 3. Home Page - -#### Hero Section -- **Location**: `src/app/home/home.component.html` -- **Features**: - - Gradient background (teal) - - Welcome title and tagline - - Centered layout - -#### Article Cards Grid -- **Component**: `src/app/article/summary-card/summary-card.component.html` -- **Features**: - - 3-column responsive grid (1 on mobile, 2 on tablet, 3 on desktop) - - Card hover effects with shadow - - Tag badges with teal colors - - Title with hover state - - Line-clamp for introduction text - -#### Pagination -- Improved styling with rounded buttons -- Theme-aware colors - ---- - -### 4. Article Detail Page - -#### Article Container -- **File**: `src/app/article/article.component.html` -- **Features**: - - Max-width container for readability - - Card-based layout with shadow - - Header with title, author, and date - - Improved spacing - -#### Article Section -- **File**: `src/app/article/article-section/article-section.component.html` -- **Changes**: - - Dark mode text colors - - Better heading hierarchy (h2 instead of h1) - - Increased line height for readability - -#### Image Caption -- **File**: `src/app/article/article-img-caption/article-img-caption.component.html` -- **Changes**: - - Rounded corners - - Shadow effect - - Dark mode caption text - -#### References -- **File**: `src/app/article/article-references/article-references.component.html` -- **Changes**: - - Dark mode background - - Teal link colors - - Better spacing - -#### Signature -- **File**: `src/app/article/article-signature/article-signature.component.html` -- **Changes**: - - Border separator - - Dark mode text colors - ---- - -### 5. Footer Component - -- **File**: `src/app/footer/footer.component.html` -- **Features**: - - 3-column grid layout (About, Quick Links, Connect) - - Profile image and bio - - Social links (Email, Phone, LinkedIn) - - Copyright notice - - Dark mode styling - ---- - -### 6. App Component Layout - -- **File**: `src/app/app.component.html` -- **Features**: - - Full viewport height (`min-h-screen`) - - Flex column layout - - Theme-aware background colors - - Smooth transitions - ---- - -## Color Palette - -### Light Mode -| Element | Color Class | -|---------|-------------| -| Background | `bg-slate-50` | -| Card Background | `bg-white` | -| Primary Text | `text-slate-800` | -| Secondary Text | `text-slate-600` | -| Accent | `text-teal-600` / `bg-teal-600` | -| Border | `border-slate-200` | - -### Dark Mode -| Element | Color Class | -|---------|-------------| -| Background | `bg-slate-900` | -| Card Background | `bg-slate-800` | -| Primary Text | `text-white` | -| Secondary Text | `text-slate-400` | -| Accent | `text-teal-400` / `bg-teal-500` | -| Border | `border-slate-700` | - ---- - -## File Changes Summary - -| File | Action | -|------|--------| -| `tailwind.config.js` | Modified - Added darkMode | -| `src/index.html` | Modified - Theme init script | -| `src/app/theme.service.ts` | Created - Theme management | -| `src/app/app.module.ts` | Modified - Added Router imports | -| `src/app/app.component.html` | Modified - Layout styling | -| `src/app/header/header.component.html` | Modified - Nav + toggle | -| `src/app/header/header.component.ts` | Modified - Toggle logic | -| `src/app/home/home.component.html` | Modified - Hero + grid | -| `src/app/article/summary-card/summary-card.component.html` | Modified - Card design | -| `src/app/article/article.component.html` | Modified - Article layout | -| `src/app/article/article-section/article-section.component.html` | Modified - Dark mode | -| `src/app/article/article-img-caption/article-img-caption.component.html` | Modified - Styling | -| `src/app/article/article-references/article-references.component.html` | Modified - Dark mode | -| `src/app/article/article-signature/article-signature.component.html` | Modified - Dark mode | -| `src/app/footer/footer.component.html` | Modified - Modern layout | - ---- - -## Dependencies - -No new dependencies added. Uses existing: -- Tailwind CSS -- Font Awesome (via @fortawesome) -- Angular Router - ---- - -## Browser Support - -- Chrome/Edge 90+ -- Firefox 90+ -- Safari 14+ -- Supports system dark mode preference -- localStorage for theme persistence - ---- - -## Future Improvements - -- [ ] Add search functionality -- [ ] Add table of contents for articles -- [ ] Add related articles section -- [ ] Add social share buttons -- [ ] Add reading time estimate -- [ ] Add article categories/filtering diff --git a/reference/writeonce-app/angular.json b/reference/writeonce-app/angular.json deleted file mode 100644 index bf8e7b5..0000000 --- a/reference/writeonce-app/angular.json +++ /dev/null @@ -1,139 +0,0 @@ -{ - "$schema": "./node_modules/@angular/cli/lib/config/schema.json", - "version": 1, - "newProjectRoot": "projects", - "projects": { - "writeonce-frontend": { - "projectType": "application", - "schematics": { - "@schematics/angular:component": { - "standalone": false - }, - "@schematics/angular:directive": { - "standalone": false - }, - "@schematics/angular:pipe": { - "standalone": false - } - }, - "root": "", - "sourceRoot": "src", - "prefix": "app", - "architect": { - "build": { - "builder": "@angular-devkit/build-angular:application", - "options": { - "outputPath": "dist", - "index": "src/index.html", - "browser": "src/main.ts", - "polyfills": [ - "zone.js" - ], - "tsConfig": "tsconfig.app.json", - "assets": [ - "src/favicon.ico", - "src/assets", - "src/web.config" - ], - "styles": [ - "src/styles.css", - "node_modules/prismjs/themes/prism.css" - ], - "scripts": [ - "node_modules/prismjs/prism.js", - "node_modules/prismjs/components/prism-typescript.js", - "node_modules/prismjs/components/prism-javascript.js", - "node_modules/prismjs/components/prism-bash.js", - "node_modules/prismjs/components/prism-yaml.js", - "node_modules/prismjs/components/prism-toml.js", - "node_modules/prismjs/components/prism-sql.js", - "node_modules/prismjs/components/prism-nginx.js", - "node_modules/prismjs/components/prism-http.js", - "node_modules/prismjs/components/prism-rust.js", - "node_modules/prismjs/components/prism-docker.js", - "node_modules/prismjs/components/prism-python.js" - ] - }, - "configurations": { - "production": { - "budgets": [ - { - "type": "initial", - "maximumWarning": "500kb", - "maximumError": "1mb" - }, - { - "type": "anyComponentStyle", - "maximumWarning": "2kb", - "maximumError": "4kb" - } - ], - "outputHashing": "all", - "fileReplacements": [ - { - "replace": "src/environments/environment.ts", - "with": "src/environments/environment.prod.ts" - } - ] - }, - "development": { - "optimization": false, - "extractLicenses": false, - "sourceMap": true, - "fileReplacements": [ - { - "replace": "src/environments/environment.prod.ts", - "with": "src/environments/environment.ts" - } - ] - } - }, - "defaultConfiguration": "production" - }, - "serve": { - "builder": "@angular-devkit/build-angular:dev-server", - "configurations": { - "production": { - "buildTarget": "writeonce-frontend:build:production" - }, - "development": { - "buildTarget": "writeonce-frontend:build:development" - } - }, - "defaultConfiguration": "development", - "options": { - "proxyConfig": "src/proxy.conf.js" - } - }, - "extract-i18n": { - "builder": "@angular-devkit/build-angular:extract-i18n", - "options": { - "buildTarget": "writeonce-frontend:build" - } - }, - "test": { - "builder": "@angular-devkit/build-angular:karma", - "options": { - "polyfills": [ - "zone.js", - "zone.js/testing" - ], - "tsConfig": "tsconfig.spec.json", - "assets": [ - "src/favicon.ico", - "src/assets" - ], - "styles": [ - "src/styles.css" - ], - "scripts": [], - "karmaConfig": "karma.conf.js" - } - } - } - } - }, - "cli": { - "analytics": false - } -} \ No newline at end of file diff --git a/reference/writeonce-app/aspnetcore-https.js b/reference/writeonce-app/aspnetcore-https.js deleted file mode 100644 index 764331a..0000000 --- a/reference/writeonce-app/aspnetcore-https.js +++ /dev/null @@ -1,33 +0,0 @@ -// This script sets up HTTPS for the application using the ASP.NET Core HTTPS certificate -const fs = require('fs'); -const spawn = require('child_process').spawn; -const path = require('path'); - -const baseFolder = - process.env.APPDATA !== undefined && process.env.APPDATA !== '' - ? `${process.env.APPDATA}/ASP.NET/https` - : `${process.env.HOME}/.aspnet/https`; - -const certificateArg = process.argv.map(arg => arg.match(/--name=(?.+)/i)).filter(Boolean)[0]; -const certificateName = certificateArg ? certificateArg.groups.value : process.env.npm_package_name; - -if (!certificateName) { - console.error('Invalid certificate name. Run this script in the context of an npm/yarn script or pass --name=<> explicitly.') - process.exit(-1); -} - -const certFilePath = path.join(baseFolder, `${certificateName}.pem`); -const keyFilePath = path.join(baseFolder, `${certificateName}.key`); - -if (!fs.existsSync(certFilePath) || !fs.existsSync(keyFilePath)) { - spawn('dotnet', [ - 'dev-certs', - 'https', - '--export-path', - certFilePath, - '--format', - 'Pem', - '--no-password', - ], { stdio: 'inherit', }) - .on('exit', (code) => process.exit(code)); -} \ No newline at end of file diff --git a/reference/writeonce-app/deploy.sh b/reference/writeonce-app/deploy.sh deleted file mode 100755 index 2f62cc0..0000000 --- a/reference/writeonce-app/deploy.sh +++ /dev/null @@ -1,21 +0,0 @@ -#!/bin/bash - -# Exit on error -set -e - -# Define variables -BUILD_IMAGE="blog-app-build" -NGINX_CONTAINER_NAME="writeonce-webserver" - -# Step 1: Build Angular application -echo "Building Angular application..." -docker build -t $BUILD_IMAGE -f Dockerfile . - -echo "Copying build artifacts..." - docker run --rm $BUILD_IMAGE sh -c "tar -czf - -C browser . " | docker exec -i writeonce-webserver tar -xzf - -C /usr/share/nginx/html/ - - -echo "Restarting Nginx container..." -docker restart $NGINX_CONTAINER_NAME - -echo "Deployment completed successfully." diff --git a/reference/writeonce-app/docker-compose.yml b/reference/writeonce-app/docker-compose.yml deleted file mode 100644 index b044342..0000000 --- a/reference/writeonce-app/docker-compose.yml +++ /dev/null @@ -1,13 +0,0 @@ -services: - writeonce-frontend-dev: - build: - context: . - dockerfile: Dockerfile-Dev - image: writeonce-frontend:dev - container_name: writeonce-frontend-dev - ports: - - 4200:4200 - - volumes: - - .:/app - - /app/node_modules \ No newline at end of file diff --git a/reference/writeonce-app/karma.conf.js b/reference/writeonce-app/karma.conf.js deleted file mode 100644 index efe6efe..0000000 --- a/reference/writeonce-app/karma.conf.js +++ /dev/null @@ -1,44 +0,0 @@ -module.exports = function (config) { - config.set({ - basePath: '', - frameworks: ['jasmine', '@angular-devkit/build-angular'], - plugins: [ - require('karma-jasmine'), - require('karma-chrome-launcher'), - require('karma-jasmine-html-reporter'), - require('karma-coverage'), - require('@angular-devkit/build-angular/plugins/karma') - ], - client: { - jasmine: { - // you can add configuration options for Jasmine here - // the possible options are listed at https://jasmine.github.io/api/edge/Configuration.html - // for example, you can disable the random execution with `random: false` - // or set a specific seed with `seed: 4321` - }, - clearContext: false // leave Jasmine Spec Runner output visible in browser - }, - jasmineHtmlReporter: { - suppressAll: true // removes the duplicated traces - }, - coverageReporter: { - dir: require('path').join(__dirname, './coverage/'), - subdir: '.', - reporters: [ - { type: 'html' }, - { type: 'text-summary' } - ] - }, - reporters: ['progress', 'kjhtml'], - port: 9876, - colors: true, - logLevel: config.LOG_INFO, - autoWatch: true, - browsers: ['Chrome'], - singleRun: false, - restartOnFileChange: true, - listenAddress: 'localhost', - hostname: 'localhost' - }); -}; - diff --git a/reference/writeonce-app/nuget.config b/reference/writeonce-app/nuget.config deleted file mode 100644 index 6548586..0000000 --- a/reference/writeonce-app/nuget.config +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/reference/writeonce-app/package.json b/reference/writeonce-app/package.json deleted file mode 100644 index 49145c9..0000000 --- a/reference/writeonce-app/package.json +++ /dev/null @@ -1,66 +0,0 @@ -{ - "name": "writeonce-frontend", - "version": "0.0.0", - "scripts": { - "ng": "ng", - "dev": "ng serve -o --watch --configuration development", - "devdoc": "ng serve --host 0.0.0.0 --watch --configuration development", - "dstart": "ng serve --port=5200 --host 0.0.0.0", - "start": "ng serve --host 0.0.0.0", - "build": "ng build", - "build:dev": "ng build --watch --deploy-url /my-app/ --base-href /my-app/ --configuration=development", - "watch": "ng build --watch --configuration development", - "test": "ng test", - "build-image": "docker build -t writeonce-frontend .", - "remove-image": "docker rmi writeonce-frontend", - "build-run-container": "docker compose up --build writeonce-frontend", - "run-container": "docker compose up writeonce-frontend", - "remove-container": "docker rm -f writeonce-frontend-container", - "containerize": "npm run build-image && npm run run-container", - "recontainerize": "npm run remove-container && npm run build-run-container", - "reload-nginx": "docker exec writeonce-frontend-container nginx -s reload", - "test-nginx": "docker exec writeonce-frontend-container nginx -t" - }, - "private": true, - "dependencies": { - "@angular/animations": "^18.2.8", - "@angular/cdk": "^17.3.10", - "@angular/common": "^18.2.8", - "@angular/compiler": "^18.2.8", - "@angular/core": "^18.2.8", - "@angular/forms": "^18.2.8", - "@angular/platform-browser": "^18.2.8", - "@angular/platform-browser-dynamic": "^18.2.8", - "@angular/router": "^18.2.8", - "@fortawesome/angular-fontawesome": "^0.10.2", - "@fortawesome/fontawesome-svg-core": "^6.4.0", - "@fortawesome/free-brands-svg-icons": "^6.4.0", - "@fortawesome/free-solid-svg-icons": "^6.4.0", - "jest-editor-support": "*", - "marked": "^12.0.2", - "ngx-markdown": "^18.1.0", - "prismjs": "^1.29.0", - "run-script-os": "*", - "rxjs": "~7.8.0", - "tslib": "^2.3.0", - "zone.js": "~0.14.3" - }, - "devDependencies": { - "@angular-devkit/build-angular": "^18.2.9", - "@angular/cli": "^18.2.9", - "@angular/compiler-cli": "^18.2.8", - "@types/jasmine": "~5.1.0", - "@types/marked": "^6.0.0", - "@types/prismjs": "^1.26.4", - "autoprefixer": "^10.4.19", - "jasmine-core": "~5.1.0", - "karma": "~6.4.0", - "karma-chrome-launcher": "~3.2.0", - "karma-coverage": "~2.2.0", - "karma-jasmine": "~5.1.0", - "karma-jasmine-html-reporter": "~2.1.0", - "postcss": "^8.4.38", - "tailwindcss": "^3.4.3", - "typescript": "~5.5.4" - } -} diff --git a/reference/writeonce-app/src/app/.dockerignore b/reference/writeonce-app/src/app/.dockerignore deleted file mode 100644 index fb95232..0000000 --- a/reference/writeonce-app/src/app/.dockerignore +++ /dev/null @@ -1,8 +0,0 @@ -**/node_modules/ -**/dist -.git -npm-debug.log -.coverage -.coverage.* -.env -.aws \ No newline at end of file diff --git a/reference/writeonce-app/src/app/about/about.component.css b/reference/writeonce-app/src/app/about/about.component.css deleted file mode 100644 index e69de29..0000000 diff --git a/reference/writeonce-app/src/app/about/about.component.html b/reference/writeonce-app/src/app/about/about.component.html deleted file mode 100644 index 6094aa9..0000000 --- a/reference/writeonce-app/src/app/about/about.component.html +++ /dev/null @@ -1 +0,0 @@ -

    about works!

    diff --git a/reference/writeonce-app/src/app/about/about.component.spec.ts b/reference/writeonce-app/src/app/about/about.component.spec.ts deleted file mode 100644 index 2765140..0000000 --- a/reference/writeonce-app/src/app/about/about.component.spec.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { ComponentFixture, TestBed } from '@angular/core/testing'; - -import { AboutComponent } from './about.component'; - -describe('AboutComponent', () => { - let component: AboutComponent; - let fixture: ComponentFixture; - - beforeEach(async () => { - await TestBed.configureTestingModule({ - declarations: [AboutComponent] - }) - .compileComponents(); - - fixture = TestBed.createComponent(AboutComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/reference/writeonce-app/src/app/about/about.component.ts b/reference/writeonce-app/src/app/about/about.component.ts deleted file mode 100644 index 47880cb..0000000 --- a/reference/writeonce-app/src/app/about/about.component.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { Component } from '@angular/core'; - -@Component({ - selector: 'app-about', - templateUrl: './about.component.html', - styleUrl: './about.component.css' -}) -export class AboutComponent { - -} diff --git a/reference/writeonce-app/src/app/app-routing.module.ts b/reference/writeonce-app/src/app/app-routing.module.ts deleted file mode 100644 index 3ad41d4..0000000 --- a/reference/writeonce-app/src/app/app-routing.module.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { NgModule } from '@angular/core'; -import { RouterModule, Routes } from '@angular/router'; -import { ArticleComponent } from './article/article.component'; -import { AboutComponent } from './about/about.component'; -import { ContactComponent } from './contact/contact.component'; -import { HomeComponent } from './home/home.component'; -import { RepositoryListComponent } from './repository-list/repository-list.component'; -const routes: Routes = [ - { path: 'blog/:systitle', component: ArticleComponent }, - { path: 'projects', component: RepositoryListComponent }, - { path: 'about', component: AboutComponent }, - { path: 'contact', component: ContactComponent }, - { path: '', component: HomeComponent }, - // other routes can go here -]; - - -@NgModule({ - imports: [RouterModule.forRoot(routes)], - exports: [RouterModule] -}) -export class AppRoutingModule { } diff --git a/reference/writeonce-app/src/app/app.component.css b/reference/writeonce-app/src/app/app.component.css deleted file mode 100644 index 7a99c93..0000000 --- a/reference/writeonce-app/src/app/app.component.css +++ /dev/null @@ -1,22 +0,0 @@ -:host { - max-width: 1280px; - padding: 2rem; - text-align: center; -} - -tr:nth-child(even) { - background: #F2F2F2; -} - -tr:nth-child(odd) { - background: #FFF; -} - -th, td { - padding-left: 1rem; - padding-right: 1rem; -} - -table { - margin: 0 auto; -} diff --git a/reference/writeonce-app/src/app/app.component.html b/reference/writeonce-app/src/app/app.component.html deleted file mode 100644 index aa103a7..0000000 --- a/reference/writeonce-app/src/app/app.component.html +++ /dev/null @@ -1,9 +0,0 @@ -
    - - -
    - -
    - - -
    diff --git a/reference/writeonce-app/src/app/app.component.spec.ts b/reference/writeonce-app/src/app/app.component.spec.ts deleted file mode 100644 index 80d8ce9..0000000 --- a/reference/writeonce-app/src/app/app.component.spec.ts +++ /dev/null @@ -1,47 +0,0 @@ -import { HttpTestingController, provideHttpClientTesting } from '@angular/common/http/testing'; -import { ComponentFixture, TestBed } from '@angular/core/testing'; -import { AppComponent } from './app.component'; -import { provideHttpClient, withInterceptorsFromDi } from '@angular/common/http'; - -describe('AppComponent', () => { - let component: AppComponent; - let fixture: ComponentFixture; - let httpMock: HttpTestingController; - - beforeEach(async () => { - await TestBed.configureTestingModule({ - declarations: [AppComponent], - imports: [], - providers: [provideHttpClient(withInterceptorsFromDi()), provideHttpClientTesting()] -}).compileComponents(); - }); - - beforeEach(() => { - fixture = TestBed.createComponent(AppComponent); - component = fixture.componentInstance; - httpMock = TestBed.inject(HttpTestingController); - }); - - afterEach(() => { - httpMock.verify(); - }); - - it('should create the app', () => { - expect(component).toBeTruthy(); - }); - - it('should retrieve weather forecasts from the server', () => { - const mockForecasts = [ - { date: '2021-10-01', temperatureC: 20, temperatureF: 68, summary: 'Mild' }, - { date: '2021-10-02', temperatureC: 25, temperatureF: 77, summary: 'Warm' } - ]; - - component.ngOnInit(); - - const req = httpMock.expectOne('/weatherforecast'); - expect(req.request.method).toEqual('GET'); - req.flush(mockForecasts); - - expect(component.forecasts).toEqual(mockForecasts); - }); -}); \ No newline at end of file diff --git a/reference/writeonce-app/src/app/app.component.ts b/reference/writeonce-app/src/app/app.component.ts deleted file mode 100644 index 711cb4f..0000000 --- a/reference/writeonce-app/src/app/app.component.ts +++ /dev/null @@ -1,42 +0,0 @@ -import { HttpClient } from '@angular/common/http'; -import { Component, OnInit } from '@angular/core'; - -interface WeatherForecast { - date: string; - temperatureC: number; - temperatureF: number; - summary: string; -} - -interface UserDetail { -name: string -} - - -@Component({ - selector: 'app-root', - templateUrl: './app.component.html', - styleUrl: './app.component.css' -}) -export class AppComponent implements OnInit { - public users: UserDetail[] = []; - - constructor(private http: HttpClient) {} - - ngOnInit() { - // this.getForecasts(); - } - - // getForecasts() { - // this.http.get('api/v1.0/UserDetail').subscribe( - // (result) => { - // this.users = result; - // }, - // (error) => { - // console.error(error); - // } - // ); - // } - - title = 'Write Once | Blog'; -} diff --git a/reference/writeonce-app/src/app/app.module.ts b/reference/writeonce-app/src/app/app.module.ts deleted file mode 100644 index 5d320a6..0000000 --- a/reference/writeonce-app/src/app/app.module.ts +++ /dev/null @@ -1,63 +0,0 @@ -import { provideHttpClient, withInterceptorsFromDi } from '@angular/common/http'; -import { NgModule } from '@angular/core'; -import { BrowserModule } from '@angular/platform-browser'; -import { RouterLink, RouterLinkActive } from '@angular/router'; - -import { AppRoutingModule } from './app-routing.module'; -import { AppComponent } from './app.component'; -import { NavigationBarComponent } from './navigation-bar/navigation-bar.component'; -import { ArticleComponent } from './article/article.component'; -import { HeaderComponent } from './header/header.component'; -import { FooterComponent } from './footer/footer.component'; -import { AboutComponent } from './about/about.component'; -import { ContactComponent } from './contact/contact.component'; -import { HomeComponent } from './home/home.component'; -import { RepositoryListComponent } from './repository-list/repository-list.component'; -import { FaIconLibrary, FontAwesomeModule } from '@fortawesome/angular-fontawesome'; - -import { faEnvelope, faPhone } from '@fortawesome/free-solid-svg-icons'; -import { faLinkedin as faLinkedinBrands } from '@fortawesome/free-brands-svg-icons'; -import { FormsModule } from '@angular/forms'; -import { ArticleImgCaptionComponent } from './article/article-img-caption/article-img-caption.component'; -import { ArticleSectionComponent } from './article/article-section/article-section.component'; -import { ArticleSignatureComponent } from './article/article-signature/article-signature.component'; -import { ArticleReferencesComponent } from './article/article-references/article-references.component'; -import { ArticleCodeSnippetComponent } from './article/article-code-snippet/article-code-snippet.component'; -import { ScreenShotImageComponent } from './article/screen-shot-image/screen-shot-image.component'; -import { SummaryCardComponent } from './article/summary-card/summary-card.component'; -import { MarkdownModule } from 'ngx-markdown'; - - - -@NgModule({ declarations: [ - AppComponent, - NavigationBarComponent, - ArticleComponent, - HeaderComponent, - FooterComponent, - AboutComponent, - ContactComponent, - HomeComponent, - RepositoryListComponent, - ArticleImgCaptionComponent, - ArticleSectionComponent, - ArticleSignatureComponent, - ArticleReferencesComponent, - ArticleCodeSnippetComponent, - ScreenShotImageComponent, - SummaryCardComponent - ], - bootstrap: [AppComponent], - imports: [BrowserModule, - AppRoutingModule, - FontAwesomeModule, - FormsModule, - RouterLink, - RouterLinkActive, - MarkdownModule.forRoot()], - providers: [provideHttpClient(withInterceptorsFromDi())] }) -export class AppModule { - constructor(library: FaIconLibrary) { - library.addIcons(faEnvelope, faPhone, faLinkedinBrands); - } -} diff --git a/reference/writeonce-app/src/app/article/article-code-snippet/article-code-snippet.component.css b/reference/writeonce-app/src/app/article/article-code-snippet/article-code-snippet.component.css deleted file mode 100644 index e69de29..0000000 diff --git a/reference/writeonce-app/src/app/article/article-code-snippet/article-code-snippet.component.html b/reference/writeonce-app/src/app/article/article-code-snippet/article-code-snippet.component.html deleted file mode 100644 index 7b69db4..0000000 --- a/reference/writeonce-app/src/app/article/article-code-snippet/article-code-snippet.component.html +++ /dev/null @@ -1,22 +0,0 @@ -
    -
    - -
    -
    {{ title }}
    - -
    - - - - - - - - -
    -
    diff --git a/reference/writeonce-app/src/app/article/article-code-snippet/article-code-snippet.component.spec.ts b/reference/writeonce-app/src/app/article/article-code-snippet/article-code-snippet.component.spec.ts deleted file mode 100644 index 77953aa..0000000 --- a/reference/writeonce-app/src/app/article/article-code-snippet/article-code-snippet.component.spec.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { ComponentFixture, TestBed } from '@angular/core/testing'; - -import { ArticleCodeSnippetComponent } from './article-code-snippet.component'; - -describe('ArticleCodeSnippetComponent', () => { - let component: ArticleCodeSnippetComponent; - let fixture: ComponentFixture; - - beforeEach(async () => { - await TestBed.configureTestingModule({ - declarations: [ArticleCodeSnippetComponent] - }) - .compileComponents(); - - fixture = TestBed.createComponent(ArticleCodeSnippetComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/reference/writeonce-app/src/app/article/article-code-snippet/article-code-snippet.component.ts b/reference/writeonce-app/src/app/article/article-code-snippet/article-code-snippet.component.ts deleted file mode 100644 index feac38a..0000000 --- a/reference/writeonce-app/src/app/article/article-code-snippet/article-code-snippet.component.ts +++ /dev/null @@ -1,88 +0,0 @@ -import { Component, AfterViewInit, Input, OnInit } from '@angular/core'; -import { Clipboard } from '@angular/cdk/clipboard'; -import { faCopy } from '@fortawesome/free-solid-svg-icons'; -import { faCheck } from '@fortawesome/free-solid-svg-icons'; -import { ArticleService } from '../../services/article.service'; -import { marked } from 'marked'; -import Prism from 'prismjs'; - -@Component({ - selector: 'app-article-code-snippet', - templateUrl: './article-code-snippet.component.html', - styleUrl: './article-code-snippet.component.css' -}) -export class ArticleCodeSnippetComponent implements OnInit { - - /** - * - */ - @Input() inputMarkdownContentPath: string = ``; - - @Input() title: string = ""; - @Input() codeLang: string = ""; - - public markdownContent: string = ''; - public faCopy = faCopy; - - public copyText: string = ""; - public codeBlock: string = ""; - constructor( - private clipboard: Clipboard, - private articleService: ArticleService - ) { } - ngOnInit(): void { - this.initCopyBtn(); - this.markdownContentPath = this.inputMarkdownContentPath; - this.configureMarkedRenderer(); - } - - // ngAfterViewInit(): void { - // // Highlight the code after view initialization - // (window as any).Prism.highlightAll(); - // } - - configureMarkedRenderer() { - const renderer = new marked.Renderer(); - - // Override the code block renderer to use PrismJS for syntax highlighting - renderer.code = (code: string, language: string) => { - this.codeBlock = code; - const validLanguage = Prism.languages[language] ? language : 'markup'; - const highlightedCode = Prism.highlight(code, Prism.languages[validLanguage], validLanguage); - return `
    ${highlightedCode}
    `; - }; - - marked.setOptions({ - renderer - }); - } - - - private set markdownContentPath(path: string) { - - this.articleService.getMarkdown(path).subscribe(async (data) => { - this.markdownContent = await marked(data); // Convert Markdown to HTML - }); - - } - - copyCode() { - this.clipboard.copy(this.codeBlock); - this.copyText = "Copied!"; - this.faCopy = faCheck; - - setTimeout(() => { - this.initCopyBtn(); - }, 2000); - } - - initCopyBtn() { - - this.copyText = "Copy"; - this.faCopy = faCopy; - - } - - - -} diff --git a/reference/writeonce-app/src/app/article/article-img-caption/article-img-caption.component.css b/reference/writeonce-app/src/app/article/article-img-caption/article-img-caption.component.css deleted file mode 100644 index 8f85c0d..0000000 --- a/reference/writeonce-app/src/app/article/article-img-caption/article-img-caption.component.css +++ /dev/null @@ -1,16 +0,0 @@ -.article-img-caption { - text-align: center; - - } - - .article-img-caption img { - max-width: 100%; - height: 600px; - } - - /* .caption { - font-size: 1.1em; - color: #555; - margin-top: 8px; - } */ - \ No newline at end of file diff --git a/reference/writeonce-app/src/app/article/article-img-caption/article-img-caption.component.html b/reference/writeonce-app/src/app/article/article-img-caption/article-img-caption.component.html deleted file mode 100644 index cf20d9b..0000000 --- a/reference/writeonce-app/src/app/article/article-img-caption/article-img-caption.component.html +++ /dev/null @@ -1,4 +0,0 @@ -
    - -

    {{ caption }}

    -
    diff --git a/reference/writeonce-app/src/app/article/article-img-caption/article-img-caption.component.spec.ts b/reference/writeonce-app/src/app/article/article-img-caption/article-img-caption.component.spec.ts deleted file mode 100644 index 8e0de15..0000000 --- a/reference/writeonce-app/src/app/article/article-img-caption/article-img-caption.component.spec.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { ComponentFixture, TestBed } from '@angular/core/testing'; - -import { ArticleImgCaptionComponent } from './article-img-caption.component'; - -describe('ArticleImgCaptionComponent', () => { - let component: ArticleImgCaptionComponent; - let fixture: ComponentFixture; - - beforeEach(async () => { - await TestBed.configureTestingModule({ - declarations: [ArticleImgCaptionComponent] - }) - .compileComponents(); - - fixture = TestBed.createComponent(ArticleImgCaptionComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/reference/writeonce-app/src/app/article/article-img-caption/article-img-caption.component.ts b/reference/writeonce-app/src/app/article/article-img-caption/article-img-caption.component.ts deleted file mode 100644 index 6c236c1..0000000 --- a/reference/writeonce-app/src/app/article/article-img-caption/article-img-caption.component.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { Component, Input, ViewEncapsulation } from '@angular/core'; - -@Component({ - selector: 'article-img-caption', - templateUrl: './article-img-caption.component.html', - styleUrl: './article-img-caption.component.css', - encapsulation: ViewEncapsulation.None -}) -export class ArticleImgCaptionComponent { - @Input() imgSrc: string = ''; - @Input() caption: string = ''; -} diff --git a/reference/writeonce-app/src/app/article/article-references/article-reference.model.ts b/reference/writeonce-app/src/app/article/article-references/article-reference.model.ts deleted file mode 100644 index c7a30e1..0000000 --- a/reference/writeonce-app/src/app/article/article-references/article-reference.model.ts +++ /dev/null @@ -1,6 +0,0 @@ -export interface ArticleReference { - title: string; - url: string; - dateAccessed: number; - accessedOnDate?: Date; - } \ No newline at end of file diff --git a/reference/writeonce-app/src/app/article/article-references/article-references.component.css b/reference/writeonce-app/src/app/article/article-references/article-references.component.css deleted file mode 100644 index e69de29..0000000 diff --git a/reference/writeonce-app/src/app/article/article-references/article-references.component.html b/reference/writeonce-app/src/app/article/article-references/article-references.component.html deleted file mode 100644 index 3529b50..0000000 --- a/reference/writeonce-app/src/app/article/article-references/article-references.component.html +++ /dev/null @@ -1,12 +0,0 @@ -
    -

    References

    - - -
    - diff --git a/reference/writeonce-app/src/app/article/article-references/article-references.component.spec.ts b/reference/writeonce-app/src/app/article/article-references/article-references.component.spec.ts deleted file mode 100644 index ed4f2e3..0000000 --- a/reference/writeonce-app/src/app/article/article-references/article-references.component.spec.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { ComponentFixture, TestBed } from '@angular/core/testing'; - -import { ArticleReferencesComponent } from './article-references.component'; - -describe('ArticleReferencesComponent', () => { - let component: ArticleReferencesComponent; - let fixture: ComponentFixture; - - beforeEach(async () => { - await TestBed.configureTestingModule({ - declarations: [ArticleReferencesComponent] - }) - .compileComponents(); - - fixture = TestBed.createComponent(ArticleReferencesComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/reference/writeonce-app/src/app/article/article-references/article-references.component.ts b/reference/writeonce-app/src/app/article/article-references/article-references.component.ts deleted file mode 100644 index 0581c50..0000000 --- a/reference/writeonce-app/src/app/article/article-references/article-references.component.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { Component, OnInit, Input } from '@angular/core'; -import { ArticleReference } from './article-reference.model'; - - -@Component({ - selector: 'app-article-references', - templateUrl: './article-references.component.html', - styleUrl: './article-references.component.css' -}) - - -export class ArticleReferencesComponent implements OnInit { - @Input() - references: ArticleReference[] = []; - ngOnInit(): void { - this.references = this.references.map(reference => ({ - ...reference, - accessedOnDate: new Date(reference.dateAccessed * 1000) // Convert Unix timestamp to Date - })); - } - -} diff --git a/reference/writeonce-app/src/app/article/article-section/article-section.component.css b/reference/writeonce-app/src/app/article/article-section/article-section.component.css deleted file mode 100644 index e69de29..0000000 diff --git a/reference/writeonce-app/src/app/article/article-section/article-section.component.html b/reference/writeonce-app/src/app/article/article-section/article-section.component.html deleted file mode 100644 index a8d1e51..0000000 --- a/reference/writeonce-app/src/app/article/article-section/article-section.component.html +++ /dev/null @@ -1,13 +0,0 @@ -
    -

    {{heading}}

    - - - -

    {{ paragraph }}

    - - -
    - - - -
    \ No newline at end of file diff --git a/reference/writeonce-app/src/app/article/article-section/article-section.component.spec.ts b/reference/writeonce-app/src/app/article/article-section/article-section.component.spec.ts deleted file mode 100644 index dac8c42..0000000 --- a/reference/writeonce-app/src/app/article/article-section/article-section.component.spec.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { ComponentFixture, TestBed } from '@angular/core/testing'; - -import { ArticleSectionComponent } from './article-section.component'; - -describe('ArticleSectionComponent', () => { - let component: ArticleSectionComponent; - let fixture: ComponentFixture; - - beforeEach(async () => { - await TestBed.configureTestingModule({ - declarations: [ArticleSectionComponent] - }) - .compileComponents(); - - fixture = TestBed.createComponent(ArticleSectionComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/reference/writeonce-app/src/app/article/article-section/article-section.component.ts b/reference/writeonce-app/src/app/article/article-section/article-section.component.ts deleted file mode 100644 index e6b46ab..0000000 --- a/reference/writeonce-app/src/app/article/article-section/article-section.component.ts +++ /dev/null @@ -1,61 +0,0 @@ -import { Component, Input, ViewChildren, ViewContainerRef, QueryList, AfterViewInit } from '@angular/core'; - -import { ArticleCodeSnippetComponent } from '../article-code-snippet/article-code-snippet.component'; -import { ScreenShotImageComponent } from '../screen-shot-image/screen-shot-image.component'; - -@Component({ - selector: 'app-article-section', - templateUrl: './article-section.component.html', - styleUrl: './article-section.component.css' -}) -export class ArticleSectionComponent implements AfterViewInit { - - @Input() heading: string = ''; - @Input() paragraphs: string [] = []; - @Input() sectionIndex: number= 0; - @Input() codeSnippets: { language: string; snippet: string; title: string; sectionIndex: number, paragraphIndex: number }[] = []; - @Input() images: { path: string; caption: string; sectionIndex: number, paragraphIndex: number }[] = []; - - @ViewChildren('dynamicCodeSnippetComponent', { read: ViewContainerRef }) codeSnippetcontainers!: QueryList; - @ViewChildren('dynamicImageComponent', { read: ViewContainerRef }) ImageContainers!: QueryList; - - constructor() {} - ngAfterViewInit(): void { - this.addCodeSnippets(); - this.addImages(); - } - - addCodeSnippets() { - this.codeSnippets.forEach(snippet => { - if (this.sectionIndex === snippet.sectionIndex) { - const container = this.codeSnippetcontainers.toArray()[snippet.paragraphIndex]; - - if (container){ - const componentRef = container.createComponent(ArticleCodeSnippetComponent); - componentRef.instance.inputMarkdownContentPath = snippet.snippet; - componentRef.instance.codeLang = snippet.language; - componentRef.instance.title = snippet.title; - } - } - - }); - } - - addImages() { - this.images.forEach(image => { - if (this.sectionIndex === image.sectionIndex) { - const container = this.ImageContainers.toArray()[image.paragraphIndex]; - - if (container){ - const componentRef = container.createComponent(ScreenShotImageComponent); - componentRef.instance.imgSrc = image.path; - componentRef.instance.caption = image.caption; - } - - } - - - }); - } - -} diff --git a/reference/writeonce-app/src/app/article/article-signature/article-signature.component.css b/reference/writeonce-app/src/app/article/article-signature/article-signature.component.css deleted file mode 100644 index e69de29..0000000 diff --git a/reference/writeonce-app/src/app/article/article-signature/article-signature.component.html b/reference/writeonce-app/src/app/article/article-signature/article-signature.component.html deleted file mode 100644 index fa979e7..0000000 --- a/reference/writeonce-app/src/app/article/article-signature/article-signature.component.html +++ /dev/null @@ -1,6 +0,0 @@ -
    -

    - Author: {{ authorName }} • - Published: {{ publishedDate | date:'mediumDate' }} -

    -
    diff --git a/reference/writeonce-app/src/app/article/article-signature/article-signature.component.spec.ts b/reference/writeonce-app/src/app/article/article-signature/article-signature.component.spec.ts deleted file mode 100644 index b98dbdc..0000000 --- a/reference/writeonce-app/src/app/article/article-signature/article-signature.component.spec.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { ComponentFixture, TestBed } from '@angular/core/testing'; - -import { ArticleSignatureComponent } from './article-signature.component'; - -describe('ArticleSignatureComponent', () => { - let component: ArticleSignatureComponent; - let fixture: ComponentFixture; - - beforeEach(async () => { - await TestBed.configureTestingModule({ - declarations: [ArticleSignatureComponent] - }) - .compileComponents(); - - fixture = TestBed.createComponent(ArticleSignatureComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/reference/writeonce-app/src/app/article/article-signature/article-signature.component.ts b/reference/writeonce-app/src/app/article/article-signature/article-signature.component.ts deleted file mode 100644 index 6903ea3..0000000 --- a/reference/writeonce-app/src/app/article/article-signature/article-signature.component.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { Component, Input, OnInit } from '@angular/core'; - -@Component({ - selector: 'app-article-signature', - templateUrl: './article-signature.component.html', - styleUrl: './article-signature.component.css' -}) -export class ArticleSignatureComponent implements OnInit{ - publishedDate: Date = new Date(); - ngOnInit(): void { - this.publishedDate = new Date(this.publishedTimestamp * 1000); // Convert to Date object - - } - @Input() authorName: string = ''; - @Input() publishedTimestamp: number =0; -} diff --git a/reference/writeonce-app/src/app/article/article.component.css b/reference/writeonce-app/src/app/article/article.component.css deleted file mode 100644 index e69de29..0000000 diff --git a/reference/writeonce-app/src/app/article/article.component.html b/reference/writeonce-app/src/app/article/article.component.html deleted file mode 100644 index 0dc899c..0000000 --- a/reference/writeonce-app/src/app/article/article.component.html +++ /dev/null @@ -1,22 +0,0 @@ -
    -
    -
    -

    {{ article.title | titlecase }}

    -
    - By {{ article.author }} - {{ article.publishedOn | date:'mediumDate' }} -
    -
    - - - -
    - -
    - - - - -
    -
    \ No newline at end of file diff --git a/reference/writeonce-app/src/app/article/article.component.spec.ts b/reference/writeonce-app/src/app/article/article.component.spec.ts deleted file mode 100644 index 67f47b4..0000000 --- a/reference/writeonce-app/src/app/article/article.component.spec.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { ComponentFixture, TestBed } from '@angular/core/testing'; - -import { ArticleComponent } from './article.component'; - -describe('ArticleComponent', () => { - let component: ArticleComponent; - let fixture: ComponentFixture; - - beforeEach(async () => { - await TestBed.configureTestingModule({ - declarations: [ArticleComponent] - }) - .compileComponents(); - - fixture = TestBed.createComponent(ArticleComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/reference/writeonce-app/src/app/article/article.component.ts b/reference/writeonce-app/src/app/article/article.component.ts deleted file mode 100644 index 7a01db6..0000000 --- a/reference/writeonce-app/src/app/article/article.component.ts +++ /dev/null @@ -1,72 +0,0 @@ -import { AfterViewInit, Component, OnInit } from '@angular/core'; -import { ActivatedRoute } from '@angular/router'; -import { ArticleService } from '../services/article.service'; -import { Meta, Title } from '@angular/platform-browser'; -import { Subscription } from 'rxjs'; -import { ArticleModel, Articles, ArticleContent } from '../models/article'; - -@Component({ - selector: 'app-article', - templateUrl: './article.component.html', - styleUrl: './article.component.css' -}) -export class ArticleComponent implements OnInit, AfterViewInit { - - article: ArticleModel | undefined; - - constructor( - private route: ActivatedRoute, - private articleService: ArticleService, - private meta: Meta, - private headTitle: Title - ) { } - ngAfterViewInit(): void { - this.setMetaTitle() - } - - ngOnInit(): void { - - this.route.paramMap.subscribe(async (params) => { - const title = params.get('systitle'); - if (title) { - await this.loadBlogPost(title); - } - - }); - } - - private setMetaTitle() { - if (this.article) { - this.headTitle.setTitle(this.article.title); - - // Check if the article has tags and update meta keywords - if (this.article.tags && this.article.tags.length > 0) { - const keywords = this.article.tags.join(', '); - this.meta.updateTag({ name: 'keywords', content: keywords }); - } else { - // Optionally, remove the keywords tag if no tags are present - this.meta.removeTag('name="keywords"'); - } - - } - - } - - - - private async loadBlogPost(systitle: string): Promise { - - return new Promise((resolve, reject) => { - const res = this.articleService.getArticlesBySysTitle(systitle).subscribe((resp: ArticleContent) => { - // Find the article by title - this.article = new Articles(resp) - .toArticleModel(); - }); - resolve(res); - - - }); - - } - -} diff --git a/reference/writeonce-app/src/app/article/screen-shot-image/screen-shot-image.component.css b/reference/writeonce-app/src/app/article/screen-shot-image/screen-shot-image.component.css deleted file mode 100644 index e69de29..0000000 diff --git a/reference/writeonce-app/src/app/article/screen-shot-image/screen-shot-image.component.html b/reference/writeonce-app/src/app/article/screen-shot-image/screen-shot-image.component.html deleted file mode 100644 index e2767b9..0000000 --- a/reference/writeonce-app/src/app/article/screen-shot-image/screen-shot-image.component.html +++ /dev/null @@ -1,4 +0,0 @@ -
    - -

    {{ caption }}

    -
    diff --git a/reference/writeonce-app/src/app/article/screen-shot-image/screen-shot-image.component.spec.ts b/reference/writeonce-app/src/app/article/screen-shot-image/screen-shot-image.component.spec.ts deleted file mode 100644 index fa72320..0000000 --- a/reference/writeonce-app/src/app/article/screen-shot-image/screen-shot-image.component.spec.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { ComponentFixture, TestBed } from '@angular/core/testing'; - -import { ScreenShotImageComponent } from './screen-shot-image.component'; - -describe('ScreenShotImageComponent', () => { - let component: ScreenShotImageComponent; - let fixture: ComponentFixture; - - beforeEach(async () => { - await TestBed.configureTestingModule({ - declarations: [ScreenShotImageComponent] - }) - .compileComponents(); - - fixture = TestBed.createComponent(ScreenShotImageComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/reference/writeonce-app/src/app/article/screen-shot-image/screen-shot-image.component.ts b/reference/writeonce-app/src/app/article/screen-shot-image/screen-shot-image.component.ts deleted file mode 100644 index a773d8b..0000000 --- a/reference/writeonce-app/src/app/article/screen-shot-image/screen-shot-image.component.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { Component ,Input } from '@angular/core'; - -@Component({ - selector: 'app-screen-shot-image', - templateUrl: './screen-shot-image.component.html', - styleUrl: './screen-shot-image.component.css' -}) -export class ScreenShotImageComponent { - @Input() imgSrc: string = ''; - @Input() caption: string = ''; - -} diff --git a/reference/writeonce-app/src/app/article/summary-card/summary-card.component.css b/reference/writeonce-app/src/app/article/summary-card/summary-card.component.css deleted file mode 100644 index e69de29..0000000 diff --git a/reference/writeonce-app/src/app/article/summary-card/summary-card.component.html b/reference/writeonce-app/src/app/article/summary-card/summary-card.component.html deleted file mode 100644 index 6e119e4..0000000 --- a/reference/writeonce-app/src/app/article/summary-card/summary-card.component.html +++ /dev/null @@ -1,18 +0,0 @@ - - \ No newline at end of file diff --git a/reference/writeonce-app/src/app/article/summary-card/summary-card.component.spec.ts b/reference/writeonce-app/src/app/article/summary-card/summary-card.component.spec.ts deleted file mode 100644 index c88f3f6..0000000 --- a/reference/writeonce-app/src/app/article/summary-card/summary-card.component.spec.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { ComponentFixture, TestBed } from '@angular/core/testing'; - -import { SummaryCardComponent } from './summary-card.component'; - -describe('SummaryCardComponent', () => { - let component: SummaryCardComponent; - let fixture: ComponentFixture; - - beforeEach(async () => { - await TestBed.configureTestingModule({ - declarations: [SummaryCardComponent] - }) - .compileComponents(); - - fixture = TestBed.createComponent(SummaryCardComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/reference/writeonce-app/src/app/article/summary-card/summary-card.component.ts b/reference/writeonce-app/src/app/article/summary-card/summary-card.component.ts deleted file mode 100644 index 0e3d9ee..0000000 --- a/reference/writeonce-app/src/app/article/summary-card/summary-card.component.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { Component, Input, OnInit } from '@angular/core'; -import { RouterLink } from '@angular/router'; - -@Component({ - selector: 'app-summary-card', - templateUrl: './summary-card.component.html', - styleUrl: './summary-card.component.css' -}) -export class SummaryCardComponent { - @Input() router: string = ''; - @Input() introduction: string = ''; - @Input() title: string = ''; - @Input() tags: string []= []; -} diff --git a/reference/writeonce-app/src/app/contact/contact.component.css b/reference/writeonce-app/src/app/contact/contact.component.css deleted file mode 100644 index e69de29..0000000 diff --git a/reference/writeonce-app/src/app/contact/contact.component.html b/reference/writeonce-app/src/app/contact/contact.component.html deleted file mode 100644 index 9cae746..0000000 --- a/reference/writeonce-app/src/app/contact/contact.component.html +++ /dev/null @@ -1 +0,0 @@ -

    contact works!

    diff --git a/reference/writeonce-app/src/app/contact/contact.component.spec.ts b/reference/writeonce-app/src/app/contact/contact.component.spec.ts deleted file mode 100644 index bf45cee..0000000 --- a/reference/writeonce-app/src/app/contact/contact.component.spec.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { ComponentFixture, TestBed } from '@angular/core/testing'; - -import { ContactComponent } from './contact.component'; - -describe('ContactComponent', () => { - let component: ContactComponent; - let fixture: ComponentFixture; - - beforeEach(async () => { - await TestBed.configureTestingModule({ - declarations: [ContactComponent] - }) - .compileComponents(); - - fixture = TestBed.createComponent(ContactComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/reference/writeonce-app/src/app/contact/contact.component.ts b/reference/writeonce-app/src/app/contact/contact.component.ts deleted file mode 100644 index a953c83..0000000 --- a/reference/writeonce-app/src/app/contact/contact.component.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { Component } from '@angular/core'; - -@Component({ - selector: 'app-contact', - templateUrl: './contact.component.html', - styleUrl: './contact.component.css' -}) -export class ContactComponent { - -} diff --git a/reference/writeonce-app/src/app/footer/footer.component.css b/reference/writeonce-app/src/app/footer/footer.component.css deleted file mode 100644 index e69de29..0000000 diff --git a/reference/writeonce-app/src/app/footer/footer.component.html b/reference/writeonce-app/src/app/footer/footer.component.html deleted file mode 100644 index 38305d4..0000000 --- a/reference/writeonce-app/src/app/footer/footer.component.html +++ /dev/null @@ -1,42 +0,0 @@ -
    -
    -
    -
    - Profile Picture -
    -

    About the Author

    -

    - Hi, I'm Shoney. I share insights on technology, development, and innovation. -

    -
    -
    - -
    -

    Quick Links

    - -
    - -
    -

    Connect

    - -
    -
    - -
    -

    © 2024 WriteOnce. All rights reserved.

    -
    -
    -
    diff --git a/reference/writeonce-app/src/app/footer/footer.component.spec.ts b/reference/writeonce-app/src/app/footer/footer.component.spec.ts deleted file mode 100644 index f3e6953..0000000 --- a/reference/writeonce-app/src/app/footer/footer.component.spec.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { ComponentFixture, TestBed } from '@angular/core/testing'; - -import { FooterComponent } from './footer.component'; - -describe('FooterComponent', () => { - let component: FooterComponent; - let fixture: ComponentFixture; - - beforeEach(async () => { - await TestBed.configureTestingModule({ - declarations: [FooterComponent] - }) - .compileComponents(); - - fixture = TestBed.createComponent(FooterComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/reference/writeonce-app/src/app/footer/footer.component.ts b/reference/writeonce-app/src/app/footer/footer.component.ts deleted file mode 100644 index 679faef..0000000 --- a/reference/writeonce-app/src/app/footer/footer.component.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { Component } from '@angular/core'; -import { faEnvelope, faPhone } from '@fortawesome/free-solid-svg-icons'; -import { faLinkedin as faLinkedinBrands } from '@fortawesome/free-brands-svg-icons'; - - -@Component({ - selector: 'app-footer', - templateUrl: './footer.component.html', - styleUrl: './footer.component.css' -}) -export class FooterComponent { - -} diff --git a/reference/writeonce-app/src/app/gitlab.service.spec.ts b/reference/writeonce-app/src/app/gitlab.service.spec.ts deleted file mode 100644 index 4ddc189..0000000 --- a/reference/writeonce-app/src/app/gitlab.service.spec.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { TestBed } from '@angular/core/testing'; - -import { GitlabService } from './gitlab.service'; - -describe('GitlabService', () => { - let service: GitlabService; - - beforeEach(() => { - TestBed.configureTestingModule({}); - service = TestBed.inject(GitlabService); - }); - - it('should be created', () => { - expect(service).toBeTruthy(); - }); -}); diff --git a/reference/writeonce-app/src/app/gitlab.service.ts b/reference/writeonce-app/src/app/gitlab.service.ts deleted file mode 100644 index 99ec2a5..0000000 --- a/reference/writeonce-app/src/app/gitlab.service.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { Injectable } from '@angular/core'; -import { HttpClient } from '@angular/common/http'; -import { Observable } from 'rxjs'; - -@Injectable({ - providedIn: 'root' -}) -export class GitlabService { - private baseUrl = 'https://git.writeonce.de/api/v4'; // GitLab API base URL - - - constructor(private http: HttpClient) { } - - getProject(id: number): Observable { - return this.http.get(`${this.baseUrl}/projects/${id}`); - } - - getRepositoryTree(id: number): Observable { - return this.http.get(`${this.baseUrl}/projects/${id}/repository/tree`); - } - - getPublicRepositories(): Observable { - return this.http.get(`${this.baseUrl}/projects?visibility=public`); - } - - getRepositoryLanguages(id: number): Observable { - return this.http.get(`${this.baseUrl}/projects/${id}/languages`); - } -} diff --git a/reference/writeonce-app/src/app/header/header.component.css b/reference/writeonce-app/src/app/header/header.component.css deleted file mode 100644 index e69de29..0000000 diff --git a/reference/writeonce-app/src/app/header/header.component.html b/reference/writeonce-app/src/app/header/header.component.html deleted file mode 100644 index e0e66d8..0000000 --- a/reference/writeonce-app/src/app/header/header.component.html +++ /dev/null @@ -1,46 +0,0 @@ -
    -
    -
    - - WriteOnce - - - - - -
    - - - -
    -
    - -
    - -
    -
    -
    \ No newline at end of file diff --git a/reference/writeonce-app/src/app/header/header.component.spec.ts b/reference/writeonce-app/src/app/header/header.component.spec.ts deleted file mode 100644 index 949602e..0000000 --- a/reference/writeonce-app/src/app/header/header.component.spec.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { ComponentFixture, TestBed } from '@angular/core/testing'; - -import { HeaderComponent } from './header.component'; - -describe('HeaderComponent', () => { - let component: HeaderComponent; - let fixture: ComponentFixture; - - beforeEach(async () => { - await TestBed.configureTestingModule({ - declarations: [HeaderComponent] - }) - .compileComponents(); - - fixture = TestBed.createComponent(HeaderComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/reference/writeonce-app/src/app/header/header.component.ts b/reference/writeonce-app/src/app/header/header.component.ts deleted file mode 100644 index e2b3e35..0000000 --- a/reference/writeonce-app/src/app/header/header.component.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { Component, inject } from '@angular/core'; -import { ThemeService } from '../theme.service'; -import { RouterLink, RouterLinkActive } from '@angular/router'; - -@Component({ - selector: 'app-header', - templateUrl: './header.component.html', - styleUrl: './header.component.css' -}) -export class HeaderComponent { - themeService = inject(ThemeService); - isMobileMenuOpen = false; - - toggleTheme(): void { - this.themeService.toggleTheme(); - } - - toggleMobileMenu(): void { - this.isMobileMenuOpen = !this.isMobileMenuOpen; - } - - closeMobileMenu(): void { - this.isMobileMenuOpen = false; - } -} diff --git a/reference/writeonce-app/src/app/home/home.component.css b/reference/writeonce-app/src/app/home/home.component.css deleted file mode 100644 index 8d60255..0000000 --- a/reference/writeonce-app/src/app/home/home.component.css +++ /dev/null @@ -1,8 +0,0 @@ -.flex-1 { - display: flex; - flex-direction: column; - /* min-height: 100vh; Ensures full viewport height */ - } -.content { - flex: 1; /* Takes up all available space */ - } diff --git a/reference/writeonce-app/src/app/home/home.component.html b/reference/writeonce-app/src/app/home/home.component.html deleted file mode 100644 index 37dc035..0000000 --- a/reference/writeonce-app/src/app/home/home.component.html +++ /dev/null @@ -1,49 +0,0 @@ -
    -
    -
    -

    Welcome to WriteOnce

    -

    - Sharing insights on technology, development, and innovation. - Explore our articles and learn something new. -

    -
    -
    - -
    -
    -

    Latest Articles

    -
    - -
    - - -
    - -
    -

    No articles found.

    -
    - -
    - - - Page {{ currentPage }} of {{ totalPages }} - - -
    -
    -
    \ No newline at end of file diff --git a/reference/writeonce-app/src/app/home/home.component.spec.ts b/reference/writeonce-app/src/app/home/home.component.spec.ts deleted file mode 100644 index 6a570bb..0000000 --- a/reference/writeonce-app/src/app/home/home.component.spec.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { ComponentFixture, TestBed } from '@angular/core/testing'; - -import { HomeComponent } from './home.component'; - -describe('HomeComponent', () => { - let component: HomeComponent; - let fixture: ComponentFixture; - - beforeEach(async () => { - await TestBed.configureTestingModule({ - declarations: [HomeComponent] - }) - .compileComponents(); - - fixture = TestBed.createComponent(HomeComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/reference/writeonce-app/src/app/home/home.component.ts b/reference/writeonce-app/src/app/home/home.component.ts deleted file mode 100644 index b4b7d54..0000000 --- a/reference/writeonce-app/src/app/home/home.component.ts +++ /dev/null @@ -1,83 +0,0 @@ -import { Component, OnInit, Input } from '@angular/core'; -import { ArticleService } from '../services/article.service'; -import { ArticleContent, Articles } from '../models/article'; -import { ArticleCodeSnippetComponent } from '../article/article-code-snippet/article-code-snippet.component'; - - -@Component({ - selector: 'app-home', - templateUrl: './home.component.html', - styleUrl: './home.component.css' -}) - - - - -export class HomeComponent implements OnInit { - - @Input() articles : {router:string, introduction: string, title:string,tags: string[]}[] =[]; - searchTerm: string = ''; - filteredArticles : {router:string, introduction: string, title:string, tags: string[]}[] =[]; - paginatedArticles : {router:string, introduction: string, title:string, tags: string[]}[] =[]; - currentPage: number = 1; - itemsPerPage: number = 5; - totalArticles: number = 0; - - constructor( private dbService: ArticleService) { - } - - - async ngOnInit(): Promise { - this.dbService.getArticlesCount().subscribe((resp)=>{ - this.totalArticles = resp.count; - }) - this.updatePagination(); - } - - async getArticlePage(skip: number, limit: number){ - - return this.dbService.getArticlesPagination(skip,limit).subscribe((resp : ArticleContent[]) =>{ - - this.articles = resp.map(r => new Articles(r).toArticlePageModel()); - - }); - - } - - filterArticles() { - if (!this.searchTerm) { - this.filteredArticles = this.articles; - } else { - this.filteredArticles = this.articles.filter(article => - article.title.toLowerCase().includes(this.searchTerm.toLowerCase()) || - article.introduction.toLowerCase().includes(this.searchTerm.toLowerCase()) - ); - } - this.currentPage = 1; // Reset to the first page on new search - this.updatePagination(); - } - - updatePagination() { - const start = (this.currentPage - 1) * this.itemsPerPage; - this.getArticlePage(start,this.itemsPerPage); - } - - nextPage() { - if (this.currentPage < this.totalPages) { - this.currentPage++; - this.updatePagination(); - } - } - - previousPage() { - if (this.currentPage > 1) { - this.currentPage--; - this.updatePagination(); - } - } - - get totalPages() { - return Math.ceil(this.totalArticles / this.itemsPerPage); - } - -} diff --git a/reference/writeonce-app/src/app/models/article.ts b/reference/writeonce-app/src/app/models/article.ts deleted file mode 100644 index fa5e048..0000000 --- a/reference/writeonce-app/src/app/models/article.ts +++ /dev/null @@ -1,120 +0,0 @@ -export interface ArticlePage { - title: string; - router:string; - systitle: string; - introduction :string; - tags : string []; - } - - export interface ArticleModel { - title: string, - content :{ - img: Image; - sections: Section[]; - codes: Code[]; - images: Image[]; - }; - author: string; - publishedOn: number; - references: Reference[]; - tags : string []; - } - -export interface ArticleContent { - id: number; - title: string; - sys_title: string; - content: Content; -} - -export interface Content { - - - content :{ - img: Image; - sections: Section[]; - codes: Code[]; - images: Image[]; - }, - tags : string []; - author: string; - publishedOn: number; - references: Reference[]; -} - -interface Image { - path: string; - caption: string; - sectionIndex: number; - paragraphIndex: number; -} - -interface Section { - heading: string; - paragraphs: string[]; -} - -interface Code { - language: string; - snippet: string; - title: string; - sectionIndex: number; - paragraphIndex: number; -} - -export interface Reference { - title: string; - url: string; - dateAccessed: number; -} - - -export class Articles implements ArticleContent { - id : number; - title: string; - sys_title: string; - content: Content; - author: string; - publishedOn: number; - references: Reference[]; - constructor(data: ArticleContent) { - this.id = data.id - this.title = data.title; - this.sys_title = data.sys_title; - this.content = data.content; - this.author =this.content.author; - this.publishedOn = this.content.publishedOn; - this.references =this.content.references; - } - toArticlePageModel(): ArticlePage { - - const introduction = this.content.content.sections[0].paragraphs[0]; - const tags = this.content.tags; - - - return { - title: this.title, - router : `blog/${this.sys_title}`, - introduction, - systitle: this.sys_title, - tags, - - }; - } - - toArticleModel() : ArticleModel { - - return { - title : this.title, - author: this.content.author, - publishedOn: this.publishedOn, - references: this.content.references, - tags: this.content.tags, - content: this.content.content, - } - - } - -} - - diff --git a/reference/writeonce-app/src/app/navigation-bar/navigation-bar.component.css b/reference/writeonce-app/src/app/navigation-bar/navigation-bar.component.css deleted file mode 100644 index e69de29..0000000 diff --git a/reference/writeonce-app/src/app/navigation-bar/navigation-bar.component.html b/reference/writeonce-app/src/app/navigation-bar/navigation-bar.component.html deleted file mode 100644 index 8b82ab9..0000000 --- a/reference/writeonce-app/src/app/navigation-bar/navigation-bar.component.html +++ /dev/null @@ -1 +0,0 @@ -

    navigation-bar works!

    diff --git a/reference/writeonce-app/src/app/navigation-bar/navigation-bar.component.spec.ts b/reference/writeonce-app/src/app/navigation-bar/navigation-bar.component.spec.ts deleted file mode 100644 index 051313b..0000000 --- a/reference/writeonce-app/src/app/navigation-bar/navigation-bar.component.spec.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { ComponentFixture, TestBed } from '@angular/core/testing'; - -import { NavigationBarComponent } from './navigation-bar.component'; - -describe('NavigationBarComponent', () => { - let component: NavigationBarComponent; - let fixture: ComponentFixture; - - beforeEach(async () => { - await TestBed.configureTestingModule({ - declarations: [NavigationBarComponent] - }) - .compileComponents(); - - fixture = TestBed.createComponent(NavigationBarComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/reference/writeonce-app/src/app/navigation-bar/navigation-bar.component.ts b/reference/writeonce-app/src/app/navigation-bar/navigation-bar.component.ts deleted file mode 100644 index 71b87eb..0000000 --- a/reference/writeonce-app/src/app/navigation-bar/navigation-bar.component.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { Component } from '@angular/core'; - -@Component({ - selector: 'app-navigation-bar', - templateUrl: './navigation-bar.component.html', - styleUrl: './navigation-bar.component.css' -}) -export class NavigationBarComponent { - -} diff --git a/reference/writeonce-app/src/app/repository-list/repository-list.component.css b/reference/writeonce-app/src/app/repository-list/repository-list.component.css deleted file mode 100644 index e69de29..0000000 diff --git a/reference/writeonce-app/src/app/repository-list/repository-list.component.html b/reference/writeonce-app/src/app/repository-list/repository-list.component.html deleted file mode 100644 index b1cadcb..0000000 --- a/reference/writeonce-app/src/app/repository-list/repository-list.component.html +++ /dev/null @@ -1,25 +0,0 @@ -
    -
    - - -
    - -
    -
    -

    {{ entry.key }}

    -
    -
    - -

    {{ repo.name }}

    - - -

    {{ repo.description || 'No description available.' }}

    - - - - View Repository - -
    - -
    -
    \ No newline at end of file diff --git a/reference/writeonce-app/src/app/repository-list/repository-list.component.spec.ts b/reference/writeonce-app/src/app/repository-list/repository-list.component.spec.ts deleted file mode 100644 index 0eb0c97..0000000 --- a/reference/writeonce-app/src/app/repository-list/repository-list.component.spec.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { ComponentFixture, TestBed } from '@angular/core/testing'; - -import { RepositoryListComponent } from './repository-list.component'; - -describe('RepositoryListComponent', () => { - let component: RepositoryListComponent; - let fixture: ComponentFixture; - - beforeEach(async () => { - await TestBed.configureTestingModule({ - declarations: [RepositoryListComponent] - }) - .compileComponents(); - - fixture = TestBed.createComponent(RepositoryListComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/reference/writeonce-app/src/app/repository-list/repository-list.component.ts b/reference/writeonce-app/src/app/repository-list/repository-list.component.ts deleted file mode 100644 index f1b2cb0..0000000 --- a/reference/writeonce-app/src/app/repository-list/repository-list.component.ts +++ /dev/null @@ -1,64 +0,0 @@ -import { Component, OnInit } from '@angular/core'; -import { GitlabService } from '../gitlab.service'; -import { IconDefinition, faJs, faPython, faJava, faHtml5, faCss3, faAngular, faReact, faNodeJs, faDocker, faSquareLastfm } from '@fortawesome/free-brands-svg-icons'; -import { faQuestionCircle } from '@fortawesome/free-solid-svg-icons'; - - -@Component({ - selector: 'app-repository-list', - templateUrl: './repository-list.component.html', - styleUrls: ['./repository-list.component.css'] -}) -export class RepositoryListComponent implements OnInit { - repositories: any[] = []; - // Mapping languages to Font Awesome icons - TECH_ICONS: { [key: string]: IconDefinition } = { - 'JavaScript': faJs, - 'Python': faPython, - 'Java': faJava, - 'HTML': faHtml5, - 'CSS': faCss3, - 'Angular': faAngular, - 'React': faReact, - 'Node.js': faNodeJs, - 'Dockerfile': faDocker - }; - - constructor(private gitlabService: GitlabService) { } - - ngOnInit(): void { - this.loadRepositories(); - } - - loadRepositories(): void { - this.gitlabService.getPublicRepositories().subscribe( - (data) => { - this.repositories = data; - - // Iterate over each repository to get its languages - this.repositories.forEach((r) => { - this.gitlabService.getRepositoryLanguages(r.id).subscribe( - (languagesData) => { - // Assuming you want to store languages for each repository - - r.languages = languagesData; - console.log(languagesData) - }, - (error) => console.error(`Failed to get languages for repository ${r.Id}:`, error) - ); - }); - }, - (error) => console.error(error) - ); - } - - isValid(entry: { key: any, value: any }):boolean{ - - return typeof entry.key === 'string'; - } - - getTechIcon(language: string): IconDefinition { - console.log('hit it') - return this.TECH_ICONS[language] || faQuestionCircle; - } -} diff --git a/reference/writeonce-app/src/app/services/article.service.spec.ts b/reference/writeonce-app/src/app/services/article.service.spec.ts deleted file mode 100644 index 120c0e2..0000000 --- a/reference/writeonce-app/src/app/services/article.service.spec.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { TestBed } from '@angular/core/testing'; - -import { ArticleService } from './article.service'; - -describe('ArticleService', () => { - let service: ArticleService; - - beforeEach(() => { - TestBed.configureTestingModule({}); - service = TestBed.inject(ArticleService); - }); - - it('should be created', () => { - expect(service).toBeTruthy(); - }); -}); diff --git a/reference/writeonce-app/src/app/services/article.service.ts b/reference/writeonce-app/src/app/services/article.service.ts deleted file mode 100644 index 6257a32..0000000 --- a/reference/writeonce-app/src/app/services/article.service.ts +++ /dev/null @@ -1,101 +0,0 @@ -import { Injectable, OnInit } from '@angular/core'; -import { ActivatedRoute } from '@angular/router'; -import { HttpClient, HttpHeaders } from '@angular/common/http'; -import { map, Observable, of, BehaviorSubject } from 'rxjs'; -import { concatMap } from 'rxjs/operators'; -import { environment } from '../../environments/environment'; -import { ArticleContent } from '../models/article'; - -@Injectable({ - providedIn: 'root' -}) -export class ArticleService { - - private dbApiBaseUrl = 'https://api.writeonce.de'; - private awsApiBaseUrl = 'https://api.writeonce.de/aws'; - private headers: HttpHeaders; - private sysTitleSubject = new BehaviorSubject(''); // Default value - private sysTitle: string = ''; - - - constructor(private http: HttpClient, private route: ActivatedRoute) { - - const token = 'REDACTED_TOKEN'; - this.headers = new HttpHeaders({ - 'Authorization': `Bearer ${token}` - }); - - } - getSysTitle(): BehaviorSubject { - this.route.paramMap.subscribe((params) => { - const title = params.get('systitle'); - if (title) { - this.sysTitleSubject.next(title); // Update the BehaviorSubject with the value - } - }); - - return this.sysTitleSubject; // Return the observable - } - - - getArticles(): Observable { - return this.http.get('assets/articles.json'); - } - - getArticlesBySysTitle(systitle: string): Observable { - - if (environment.production) { - return this.http.get(`${this.dbApiBaseUrl}/article/title/${systitle}`, { headers: this.headers }); - - } else { - return this.http.get(`assets/writeonce-articles/${systitle}.json`); - } - - } - - getArticlesPagination(skip: number, limit: number): Observable { - - if (environment.production) { - return this.http.get(`${this.dbApiBaseUrl}/articles/${skip}/${limit}`, { headers: this.headers }); - - } else { - return this.http.get(`assets/writeonce-articles/page.json`); - } - - } - - getArticlesCount(): Observable { - - if (environment.production) { - return this.http.get(`${this.dbApiBaseUrl}/articles/count`, { headers: this.headers }); - - } else { - return of({ "count": 10 }); - } - - } - - - getMarkdown(filePath: string): Observable { - - if (environment.production) { - return this.getSysTitle().pipe( - - concatMap((title) => { - this.sysTitle = title; - return this.http.get(`${this.awsApiBaseUrl}/markdown/${filePath}`, - { - responseType: 'text', - headers: this.headers - }); - }) - - ) - - - } else { - return this.http.get(`assets/writeonce-articles/testreadme.md`, { responseType: 'text' }); - } - } - -} diff --git a/reference/writeonce-app/src/app/theme.service.ts b/reference/writeonce-app/src/app/theme.service.ts deleted file mode 100644 index 0f12cf9..0000000 --- a/reference/writeonce-app/src/app/theme.service.ts +++ /dev/null @@ -1,39 +0,0 @@ -import { Injectable, signal } from '@angular/core'; - -@Injectable({ - providedIn: 'root' -}) -export class ThemeService { - isDarkMode = signal(false); - - constructor() { - this.initializeTheme(); - } - - private initializeTheme(): void { - const storedTheme = localStorage.getItem('theme'); - if (storedTheme) { - this.isDarkMode.set(storedTheme === 'dark'); - } else { - const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches; - this.isDarkMode.set(prefersDark); - } - this.applyTheme(); - } - - toggleTheme(): void { - this.isDarkMode.update(v => !v); - this.applyTheme(); - } - - private applyTheme(): void { - const isDark = this.isDarkMode(); - if (isDark) { - document.documentElement.classList.add('dark'); - localStorage.setItem('theme', 'dark'); - } else { - document.documentElement.classList.remove('dark'); - localStorage.setItem('theme', 'light'); - } - } -} diff --git a/reference/writeonce-app/src/app/types.ts b/reference/writeonce-app/src/app/types.ts deleted file mode 100644 index 9e75ce5..0000000 --- a/reference/writeonce-app/src/app/types.ts +++ /dev/null @@ -1,2 +0,0 @@ -// src/app/types.ts -export type Json = { [key: string]: any }; \ No newline at end of file diff --git a/reference/writeonce-app/src/app/websocket.service.spec.ts b/reference/writeonce-app/src/app/websocket.service.spec.ts deleted file mode 100644 index bf79772..0000000 --- a/reference/writeonce-app/src/app/websocket.service.spec.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { TestBed } from '@angular/core/testing'; - -import { WebsocketService } from './websocket.service'; - -describe('WebsocketService', () => { - let service: WebsocketService; - - beforeEach(() => { - TestBed.configureTestingModule({}); - service = TestBed.inject(WebsocketService); - }); - - it('should be created', () => { - expect(service).toBeTruthy(); - }); -}); diff --git a/reference/writeonce-app/src/app/websocket.service.ts b/reference/writeonce-app/src/app/websocket.service.ts deleted file mode 100644 index 9485fa1..0000000 --- a/reference/writeonce-app/src/app/websocket.service.ts +++ /dev/null @@ -1,50 +0,0 @@ -import { Injectable } from '@angular/core'; - -@Injectable({ - providedIn: 'root', -}) -export class WebsocketService { - private socket: WebSocket; - - constructor() { - this.socket = new WebSocket('wss://api.writeonce.de/ws/'); - this.connect(); - } - - private connect(): void { - // Create the WebSocket connection - - // Set up event listeners - this.socket.onopen = () => { - console.log('WebSocket connection opened'); - }; - - this.socket.onmessage = (event) => { - console.log('Message from server:', event.data); - }; - - this.socket.onerror = (error) => { - console.error('WebSocket error:', error); - }; - - this.socket.onclose = (event) => { - console.log('WebSocket connection closed:', event); - }; - } - - // Optionally, you can add more methods to handle sending and receiving messages - public sendCustomMessage(message: string): void { - if (this.socket.readyState === WebSocket.OPEN) { - this.socket.send(message); - console.log('Custom message sent:', message); - } else { - console.error('WebSocket is not open. Custom message not sent.'); - } - } - - public closeConnection(): void { - if (this.socket) { - this.socket.close(); - } - } -} diff --git a/reference/writeonce-app/src/app/writeoncedb.service.spec.ts b/reference/writeonce-app/src/app/writeoncedb.service.spec.ts deleted file mode 100644 index 9a1d3e9..0000000 --- a/reference/writeonce-app/src/app/writeoncedb.service.spec.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { TestBed } from '@angular/core/testing'; - -import { WriteoncedbService } from './writeoncedb.service'; - -describe('WriteoncedbService', () => { - let service: WriteoncedbService; - - beforeEach(() => { - TestBed.configureTestingModule({}); - service = TestBed.inject(WriteoncedbService); - }); - - it('should be created', () => { - expect(service).toBeTruthy(); - }); -}); diff --git a/reference/writeonce-app/src/app/writeoncedb.service.ts b/reference/writeonce-app/src/app/writeoncedb.service.ts deleted file mode 100644 index bf21c35..0000000 --- a/reference/writeonce-app/src/app/writeoncedb.service.ts +++ /dev/null @@ -1,38 +0,0 @@ -import { Injectable, OnInit } from '@angular/core'; -import { WebsocketService } from './websocket.service'; -import { Json } from './types'; - -@Injectable({ - providedIn: 'root' -}) -export class WriteoncedbService implements OnInit { - private collection: String; - private websocketService: WebsocketService; - - constructor(_collection: String) { - this.collection = _collection; - this.websocketService = new WebsocketService() - } - ngOnInit(): void { - // The WebSocket connection is automatically established when the service is instantiated - } - - insert(document: string,json: Json): void { - const data = JSON.stringify(json); - const message = `insert into ${this.collection} of doc id:${document}; json =${data}.`; - this.websocketService.sendCustomMessage(message); - this.closeConnection(); - } - - closeConnection(): void { - this.websocketService.closeConnection(); - } - - convertTextToDocId(text: string): string { - return text - .toLowerCase() - .trim() - .replace(/[^a-z0-9]+/g, '-') - .replace(/^-+|-+$/g, ''); - } -} diff --git a/reference/writeonce-app/src/assets/.gitkeep b/reference/writeonce-app/src/assets/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/reference/writeonce-app/src/assets/images/auto-scale-gitlab-runner.webp b/reference/writeonce-app/src/assets/images/auto-scale-gitlab-runner.webp deleted file mode 100644 index 6f815082de38a8d10c3b262fa0e762ef396b8f6a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 367684 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
  • mo?BJ9z*^@dgbH9cRKSG#qMU!BMh#;8gaK3x1)e^8d+fNr}Eb4PI5zs zPZ8S9cB>R`;yA3N4I_BU(G}$Yy#*cCcms4E3g6@l$Bk`WKbQJ^X zp6!Oy6=o=%2GnXX~TqqTiAZlC!eESuKHVaTE zuOzAiy`M@?QtN71R!*AaOmQp1T=)n?qVq`3;#rD^2y3ct(l)KRNc6<15Wd5nB%feq;4l`kIm&*#Dbna+49ffbHAy_}@WC(M~k`<&a)*uX7K(Fsr%LEg1cNG`UXc`v+ zE+iiYj@2Vy%B#nP_X%*^B@qUj?U`Og0hRvSMJ(vZD$9E08t!T3MJd~idFN9Iqz!%s zk+B8^#C3EY*(6WO%PRsT-iZ?dtS>BpRw%1{2J-XeL}+ZcD)t507w)eLbW~B>lB$nk zh86|%lFp?OA~u*blVCXl^4P3-t={r+iX&yU!0@$Cc{nQ- zbmX{07ug!MwvKux)uU?cb&qpS-aW*Ijpf5PCwreMLxGGNzsoeRPC*bDQRugvp8^mD zv~(v$s)WQZhdf&#SO>{~A_JgJM7U&MA_{w29_fr#si42gAjR0+vP4bH(c5@&Y4=pk z;1wG+e!W4u4%Us(cx?xZeJwb?5V*fh9joA^PzCRV_pdWbf-fibqnTnJX{k6lYrfzbMXQV3_v2#G|?B^?+Bo!;u$e+G;eJBqBq z2{OMhh#ad^3%EGVUH$Z=-Mji``1-MTaJe6*cY)ufxM2WjAOcBE2cl>^>3217eEI7|qX>fJZ0%+djPmMfQWJLp z;jODGG{Z=Dc)HFw5h7W19_UNSGck|^-}`;@?2fcuyMvDE{zfH4#c z4kZ)TbK_6NOpNdVHg2DXA4#$mHMP zhW$;mV#?Z@P1x>Ls13I4Cn`K=fiaf)RP$}H*Y@Pc*JGGc^&-*n0skZ)5@XSUr^Mtb zQ+&t&q#KBy;x8?dV=k@pE( zpfsMJ=*w+mx5>0(U3iH|8?ny3y0ZhPVOiPBD#>{&-q*R75 zZ29Aro=EWvy;a}*a$In3E*9Y8?27J0tsTDyz6m)^rwaeDmm!j2=OlA;3BB>^Mxu`c z9tfoq7GH#a>8%qIO=7)Q%;GPT6ItWD|BOmkXuqT3HL=7|aFRg62JgY* zFUC_AVOfL7Sm8(2JTsFb1DuUdp3ny(t*T6h>ZflVE>9+;MCI1&Mre3uK0o-G*$6HB z;+<4)q?gq;DvZY?0GJIGc_|}DMuNWIFi65!$HGh~*vy<11ccXv3 z`Dgrt{Uqnnl1y9X41I0tntnAD+;PdOX>ICzIzCxVd>*UL5?K;<3v-s$$G%eIr}&<= zHvoa@D8I$2@aIZ~CaIuY>i;?J$N<9~QjpyV$rD=)H<&Fd@s$|3(V#vS4OFOz?8Q5k z_BLTo0tbhP@FhX)xv8J`YwbLJCkB>Z14tin2F~=fReiGiq63XtcNVni7o*W=AI!v< zhE|LWfaDAgXf`4XS3F0U4wp>**9c($`iyXaXWFkXj*Ng2|G@>{=(gCsE6$zJz)5$T zs=0;&Ym5-}uw^iLX-(o)oRxC-n2g&(&;$@75+KK>wpN?%sExAmJ!=XjgpZyt*;Ptp z_{{pbb9v*OlQ8b5sc1G|suD!0S6=N85=gLhJ42UK!^w(Rl7oswcT#7>ESZvg+P@)n zz8&lJtRlCk79pxbwAkeF;o}V>ELY<{$I6SD%cAui+H(Rv?+N<`lp-Q*_#(5Zbl3zbsAcV3Tl!ZXOq_X^Jhx@ zwTY%nT-R)(p%=p`zVZ_?Z7Q=S95rJFr4!0=Y^ZFKZG$nUTq^Pm z5!}^@C)f&cE74z--J69@J%_a-B=6W~Ou;wj%j}}QUNoys4GcOf_b^Vf>2bP_f;>r* zwLf+jni!xBLJ5K3Z}t@OV53RAo8lWoqe^J*UH?SYxoK0BB0btwmuHeGxr&}ijhhx{ zcy^VQ&}zu+gt+gzxUb$kxb#&8W$Xu@T%Rddm`))8*AfzZBi^=1L(sE#Y=CxPbuGAR z3r84rjuUGi!ER=%Jx(`-)YVZhZkJsxfD^4MII#iCX>`9#_Ujv5)tjWeODR6qA~~$K zesoGaS_ky7+rDe_`r}sLwD?O_aIHowP*Ntj(ISLqjNk0}n5r&FHH<7Q{;yVEnRQ_D z@8&%fPSh4<+2-w-aBqT?BmXAX#HpN4+x31IONTWHm&gh(m`e~GS3GBvE3Gw_*_`$r z`y+8S3cLzfsIRbEPt`J9&4#9A7*Z#!ot%UmqP*(B^d;M6D$(G8h@VnV*&Z?GLKk2} z`cz4~{1YGdA3O3m&Y7Al35gqg*7aO}Sw3-sa&!(xDxbu=vK6T@Q)f;+rSaC)eiEemgvEiOu8=ni0<8af4d4o6aVlZZ0TG~r^TPOYJHtB?ti^p zW?mX8y3c;e1EpTWmC`@~gqn>!-8>vqG+E2qr41dve+ zVCrU^$+c1>{E|~ zyWAbF|3jWk2TgaQ#5JsYW&d}aDk-<8BR;kZ-Fg8 zTo&%vC}DF`s?=97!YeI9nY+5E>-Hul-bgx`wcLghiHaL8Mvf6!a=;VnZh*3Skp^(O z8X7{*HD;W~KIm^lTO&(x+M~LzyThSkqbR}1+G}jT;?VD~Vg#q3;W|y2i z<&Q*v73nT&2~gY zM5x(CTjIX0UVVlCRxtX{GONI?kJ8BjaUC7YdyP{lp)MdjLXP)|m=TChC#w+NB@t!b znXNCp^vU_8oAYS6YW;ew$t1^%`)mFem(o0h6q7aSiq{L>Ftf&ET+e`;W*#3WIE^Ub zgqyvE+XF1yxHsKn!mm`z!aY||fc%ykrb?$7(VtC1?B_NGm1t3ihqC59S4}E+UHsr< zDsA#^@?vM5xhJZo%RY&HmzR3d!eg<)zFWY(FRzn+SgFLYc_p%h7L6U8mdM3I5-VUM zT?d$sro(0JKIuxI|2(kIIQEM#hI@PiGda!`Vp7$Ib0I!{M3U3K`V@(ddZachA*%@`(1J3Mf}jzeQ1X0M?jov=__6dzOG!gb zU&yBkPEC-DOsIrqlxNT|ssFtSd>if)c;C}%jVbP+iL{>(rQNRyIj+@@^DLR1`_$WK zxkJ?c3{4cORjmGzg<3H%fSOT@jf9oby-)0_J~Y1JzT+BN(lgJe#*g;?Umz)=57&XB zZC2+z$NXv8VZQdh*#g!Y*GA<#Tgt4=0Xnb*zG2KMG(0T8Ph zJn8jXDZ%wU&}h7b(N9J2LF;BcckaxSN8x`>fm!EieSWc=P5W8f_w@X4Qt4Tyi5ZYk z!s0QI)0~EA!QoeOH>wZ|`N4^P`but5ak;andgF9Cx*|&6H03AXLe@AE^L51`Y(v#- z>BMbAnAvigt_Ml+Z+>dDXnF6EE|Do4Pc9*bPq~q34n78F(>S>|*}s)4$zT$&a9xX( ztLqNmY-F5HH$v*ipa9>U5O&MpGL-yB7hsSCZ^MLg*7?S;*Lj-pI+y^)ooDze>`*`1jEL;LUuc*(yW2z&3sf>GFAjhGFa#Aw0z zp)cd+q}q4c@^NS9X2P@3Qc2_>=_#$akBypmmEz3o=YCT)H9*8YP6ECbO!?n}H_AG7 zM&^uIH1AOqO%VA;cL%k`$d~!hg>#>#HX4zX*b72 z4dhndicu}cB8d-{wvE;7y%Pu8T+l|HO;tEPs)XiO&Bwu6mtUj50QQDf393urDVbsm zy5VY^UFwA9r*eyl5lQVnlcd8B6VPd6MPtu*?3;S{qmK#in=`jDjz@y@{fLaU| z<#18%(X)7`?$sz{x5af^QNvu%2+ahM3dNww@FnGCOjzqjXhD)BHvGn2@JZ_EaCp2g ztWLL#i!AO&?x6S#x|>*H&UyXWh0eON{+$_jH6ek0SmbOz#4T_NDozQW}F>qrx4@;kZDdDe>9~BSZ{)g>AhL9h}Ad6sGaApTHg`2MC$h zZBe}Y2$$!}hbrXIQ~7;FMdv(Cn|0Hm1oU(ym;qrECK~Nmj^|CR1Ft1`@9)sv> zptKsSAQn(rWpRGtK7?g)ZAxl(GMRBhnqDN#tn)Izn9>a6)eCKX0|Q*^V~6(&#&<9+ zHm)NN5_|f_+~*%aMBXQ`W_`EvG_gPtlD1L_Ji3$<9rHTApM7N|BD7$<3w_rOErZF_ z4pT81MCswl7zpr-rd=C8{#nScVlZ4>J&_a!NU>s>y_jjLA}tYbp!vTleT}20NBpZh zKakz|k!p!HzYDV~M1m#P@J*yNC-AN9tLFlbk)lS$1=*W_2jN5_aK` zV1m+lE=a~Wz-c5qq77H%8;S9E(=%lc;qL~m?4n+U1+=vGT01}XbSIoA(dtM5&4TtT zO8)z@AxNqU_mpSCY179T3rNWoQv&Fwi-g@gbUh4 z<3W%sH1kZ~On0w^p%97^z9vFek~(YlFUmS3E3UKK!pjg!wTd}Y=3QGWl2V)-)TFjF z6P7Crd4pgPzf>gs0>3YJzhK&|w@xml-gWYc0E^7tE?lKij=P`CH+l>MsZ_h;TZzS= z^kCuo6AffZZW1PCp#Bui1C~Nl({-!gj<~>g9u2mhsrisqtHeuLb4r~(FD#Ur8W7)= zhjBRk0yWNMg!h0>qsy$zF|iB%AwLMZN2cBE5wrjdED8HOlqMPhYHaUxWO_ltO8gYZ zVjDAU;Q=o?kLXuJ39`My#SiX^NKY>SBSMFz6aCBmyO^up|H9q;xDv~nh_ex zU{{x7x}$UyubP)+puOa%#PXlvgb=dOHxJ@m5#po)W;Q-TumWJD1wo{cT8)*!KD`P& zwSmo9@BiinD^CN!HiVLN3VKzq=_pLy&1usg-}g0=K2I?_8N%MUO@_9IV9O+_qDwoy z#5=yp^1qI28Cf8Sm5(h9VTJv@lzHLA=g0>if+%}(^PuK=R4m?dZiX2BzArM5Y#*ez zx08WLBly%BBGrf+N*v|dMMyWk3u$O9dDFw~2tC#+SXTUH&eL&@(o{%g2G3Sc*70lY z>&dLCKlmU+!V=93N6VlA+@-sm8P`;i6=SDu_LuM`u*C88edqv` z7!Nibh{~pnq!oN79&$D$M^BMM_`M}>m&NiuJ;O7k_w1 zwOvGb$GQWvb=LlRlNv)qrVl((bSebJ-O>7 zcew;A>9umLKHHNonC16LL!VxiV&KS`(HMa7aQ@QPU(1tOcnDZ^I=C4;-s|8M)U$Cm z3IG@kBW2p8SLV8&8Ry|$2+88J7Qm-z<3Ue>;b;}&66$`4g;1t~8jAcvA@9@Rc-0IK zI)M-A+C(T%Oj~J1b!(m2sXFRC9SWoN_ijyY3QyOq1=sMC>*vVh^Ryu&2W$)3Fo1L# z$DE-v0}F|&-x|+~wj^?-_-1)ER%#Tq6793y_Bs5t$?cEU)DxYy2$@lQHMBV!5#j5P zo~-Hz;m!h^@Tz;P(;+fVgyBX4+c*WB#{cUG{WuzAU#YCfUf>t+PqQ*?hWwita8N2H38EoB||o1 zRp$!|`0z8JCO9;9jP7Yfk#rMIR9orzp@QC8Z8OPSO#ez}f^^c_jWuM1ekH`wyvmh@ zr6wSDd~4mq^b$3CkB8_p_YzO$UK<#D(@d&Zk%t2dq+oqA4tFZ}Ap=E}mqScd^2>3d zDPcUjL0LB+9t?iko$C(AGP~ODi}VTu{1Q{ujW!vI9nze?M*DDqSLCv|=59+oPX0=! z*zb`ttIw?t&d%_-0^M#fS0~#jpvb~QUw~J1;h}2S$uLJ;p3H^^)1SQ9I^(It=u=Go z(2IBVD>HHgL~*5Tpx(=eu^JpF<k3s+QQ=JcuYer%kEUIaHNU;ve^m-$deByV3=I-hBi^qJtH(AT=~ zV^Ae8yL1;*@u$1+js2-vK zjmE4Uo}fdv#T=F#-Bf?_1rDP{bCtpmbPF?S?q@(5 zX9>gVd;I<-6a0=Mm6*`Mu?qxg2%bBWcYxo+qS(zR$N6(3yP}b0Ko08p70H~!7z&Dq z9G0V&vK?dC>@Ka`gXl)AJJ5C;voFja8{hVRHDviARO@N49o#j<<$@!OK>gU|FA-*=>mVSYpG2`O>I@+(`V(jxkYxgcw=HF z-XsU&U4CA|8dRB&JZ|c0($K5V+&!EQa6lh3=`vEeW=pS`tKNs6 zX$i`TAz92)((DG?0wjCz!fQ4sL2##AgnU(Rqm%{Nunx8-4zh|tCW_l5PteDY$*Y+u z%c>y)VE_Tk@{d-s{PG#vxer^n(e7Z^y=M@8( zLbw#;*i--`io)bLRpeWNc2e9)b2zNp5yREOQf=aj+QWzp!OV;4(y(yeCm6a)>7vX8 zcHxgJfQ{HzR4GT^48_+X%6`aEeO9FRhbQRcL(6O-B(iUrgs`vPruF?pbFvu$y@QTT ztO8FymrphR=6vn_Eq?ZOIkEn0w}BvtV!>BYQ0LSbgQrirgwl*u}O%_WGO7x(=>UT@N0Iy7{YCs98Y z`4Woun_=bs28%x)EjyGjj6m4ypDzs8NcpPE1&+%%8bss{^HN`> zCSWc|^IhTblSTZ5&mXwdiu@5{T%X?kqPgjI^cvt{#-mCMi*L;|A>5wcA;DCbZX1rewLASG^8O1KtsM9ppW zyY;@L{bPHNFr;J~x-WDLsb&*cW9P%VncJlw`WENRkulb4aQa6yUhAJ5O|N3dIfZGG$OKIHdMN5dnC%Bwwbk zZJEWXy_Sk?pzCCJenr4z5ZSr`oCf9*qgaY=-)W&B(MwXL-fKJ0a znNAl+a=L3wyW=qOjCnfcV+tCX-gF|N1JIMKYLVv69>(qe8Vzur(Kt>_or_1q)A-;N zt^;H>E4`!rSn$pfP6X^|J`j%q)Er?9$HKXXErOl7cv;F`#{okcLH#M;}+%wnD7gbv40A#lL0)JE;w+en$ZKLbUhL_h{WuXQZ{gtCb~}2`k&-`?4>BUVBW9A6xm0VF$!){jAX$9I7x@sg z_F*F7-}r~JW@BBKs9emArilQ534c_84)Sq(nqcwa&CX>zJM>f?n*)rU>EWMQb1<>L)CPn;o@d?x|!6+l~HiYWI1! zIwxT8eQQM_Q3-mjMbi+xEc(wlP@NKK_wAGrFWQ2Y zh==vAbKSX+qVCDaHh=hJQvb|HYBW^G%(zAJu>g46YYSsot7@~>-<3<5^2d-vhxmWF z=}S_T@c+Kqf+M#M1b0I?uT9&hgVSN`QjkC5GcOgaXhD{l#_jzTjPS}v>*#CQ%$dmn zsWs@DaYnkF?)A*<>-aM%Hzo?m22w3>E?zXWU_~P%2h`xox_n?VdDS*SO$3_4SDSCxCCE4i^g;p22t^ZQ?_kmtElGktti zM$46-{Hy^`q6UwDhtzakKd#Hw_S{GNL%^eV z(e@s%7M{l_`pm|NXvU#i!=M2uN`wqJauPV;qv(mcW~TznDw)K&-@+^Mz9{(8MZEmV{? zN!+c!gLDA(fux~HNW|anS_?lumMmU`=_zXXNxhiF>+I1vIOB8H*WJD)wv9;uqZ_g> z%FBvZAeY9s8&lOv%qoNcoRG<`o-jEauu2gnJg0~tC<1khQ5On0Vc<5RVjIv0&k!cc z>!h)PPB_ijG8ZFoE`CDy>g_GWZM^Ovnz#;H zX9E4>zs4)@Qe237ZBiwGm6^QI5lKb>JGG-7?(0M*qMzG$`8DK3%==BemFFfNd8&*OC81-}n$lH&n#DwN}I;JIJS*cTqPKSDBvut{1I1TZ7T^;J|Ca>k~EVFg- zqhh{lwrcwWIxc7$k+y3Ad0e+nmo|vw1UzD+2N@c}sdS(ruNKQ_#w`t+?8ph$)9ll# zjVQ2XkE!c4?EAT>FI5yXt*M2_Vc=SB#5We3cmPkF z?4%(_YEYF&wJu+*n*Kjzk-IR=Xf?4yI+5UmaZ;2yTilAt?yqF?FA&NYqL<`Hk^8(P zY^_KMO$x}>4=j?Zt&mnPwu+IHsmpaylQa0HcSSgria-SD>gl@fT0s9b+qAeou27W1YThfD`5BEz? z5y!#_kKCQgjyS%o7|+NYz+7~kvDL&U;5k09aqjxmuA){+13*l#WJgjRbViZSscIj5 z{EdOOfvv6v8evY}Br(a{YLGgnCL*(%&q&InI)HGOS~E8>xv|30z|)=``7YxA+e(S#4 z5yx!*`>1&DWg;_jKcQ90@Ebr)f)8+nIKwT+ z`?P}{J$oH0#C~Z?>@M&nn)%vKlo|7YJ#K*Egi-t z=n$tY_M1vvx3|2FJo|N0e(BzfXyJwUC$gveSnQvMM9wmXv!ZVl`%~4lUIWEXjTO0K zUkdEeTUqLoDrlR54qp;A)yqU}>A5l4$l?~oq+$IWiyWZI?mTepQ0L>R&V*VHDbD-= z2`QWCY!9S5F)XGNOl%lfyA4Lyi)(tpjuh&YcA02#rGS(`MC{I{e@Wn3BN-tWzH*q~ z(U4|nMusBKQeU^^K+g9QSZ8+!U*J*k*h>c1owQo zCg~J)$fkMec27!`384a@*0r`;?`Zi(5#RxZ05L$$zu8C)fw>TJ z^k2H2kY%>IA#=e4Vn=L43Q&Rlr&;_O8DeO2Z(rTiD8~;bv)=bh+ zDqlwLHdfNF^F6{-lD~6-ao?o_c_7HLL34Er!Cn^wPr0+^{%F(uAe{}E$2<~6~=WiA{x4(?P@9)Ql!AV^q*gYx`2P`W};srIH34Ud~x z%3(y)kXnZYNWXsjNXA%;t{V^la98GU}Qyjp)O9TD20(KtO2oG8~4L^C@FJ?%5M+-Fx){|zJyt`@*Y-YSzBK9S)*3K z(G>!#z=%^1qyUQZzRzZh)L=2r?KDg5bB&xmdMB@O3qW>2_w}0kyJWj9zxV{zFDzs+ zf<4?ECY{*AP_tpp&%mzQD!I$p-6==EorI$kDgxNkE5qX@VXyHSEOKZx(dyke(l;uqv% zKQL{`IAnZ->D+hB85zK@^hoc`*^&qtby|Aq5&^}(b4b@P7WP}hkf{h#?}ZWU#j}3V zy9+=#(0Ra~m2ErguA%%7%~|8;P4v80)$AIw^|(na>-5L7gtr&W)u5o1}U5b zAP@BXe~?^91o}C@40Wdy2|8+MFdj=huiqJ^tl_23*T}!kWsUH6U=hph8#x-bJWQ5v z<5t8nfi*in-w&)?pG4C{j+fvpL;KHrqp9ncAiXnFj1N_ZS34?izaRSeb_q$DwWj87oZ*dwR7 zRGyvvA|lHqh^hpXl;?AlUUDGp1ka_)r1}{Rkz&eU-=uo z?z&ff4-ZO>$*q#b|BqrjYFUNC=bK>fnzX$)Ar?ceDF*n;rL(0oQvD~9%7lt^`RI`oI7BhT)}wg^QN zDIE47+Noki!8Of_FB6EjI&^?rJ1#-ltL&B1E{9TXCsAi5o(!|OK6j5-EfhEt`&~4h zw&UUiO*s>s1VxN8>9!gx_u}Z}f&#nVF`o&3eq9hgFZtX{bOm1u(X`y>(%C5s#E=B# z_~_U)H(Q24wCiK81di(#`v~cmQR>!*tZE?Ei~Sgw70&V0yXGp>h%cY!*123cbvBb6 zbVo^HULt%T<$vkS2hn}rkrOcYrTVd1X1PCq~|xi4!`R9`Bp)=htKO#Fo|Tq=Yo zSWsN4KJ}gl%%?XyxM^7m-f6s=PA|nj(g$vn~lqgajoY1 zzO=*+k43BNDVjpUz;AD6%#%KVeAL=7WjACo}_3Q0IF;O?vY-Uyhq5Nqr1HPjkMgNT|X zw)N+8kJ?b%nQD7s&i-oaO9xBzQxs6wj3)tw1xH zdnHI(+R$?8)lIoW7mGPE81A@dv}QCSlXxyhwMUsFU&6EqoGL_TJYiTJvW%qz1u2%F zbw_o~CKCqeTJlcZ@R2KiUmmPy)%~Qxp$V&b+l{=;qW$BMG)reTRWR1Ot%(2253&i_ zo4(*B13V+}cdMj|rDUlKA@)U|8V9XlkKa7akKqA(vaK`Z(x79m#(oIW2|4&;<^BM~ zytNz!N`@?azoyG`2Gr?ciz5RcduxkNw8xsY-R2N)C}W;T7?eH=05YKZUef0PoRrfa zLPiD}+R-2Y1FV}q5#zfTOzl0O|Gbe3KBaro#|;V~nL5N%EiwOb9txhs%~B7?Uqh^P zln}Eo+ zvBNnhm?6(s#}5}dlF@J=C$(tzGODiV5`gUj zc~o7&ii@AI;$}Bz-|I*vGNX`VUxujB%L+96?@*xxdnA;g^u&ubHvG4P`VD73Lnw^2 z5<1bCt;>4mAcWrDx|PnshXo{_zVOKTNnyQQ+0i9ptptD7j?BFBfzfdrUX3O(GQrVK+N<9F@28NT$9?8jAwu3-z@q7agnNzXy3^)ne zOgu^#bC>Uq*2o&80?O8NB*64OQ$!89bjGw__vkm!sf9bep;Jj9XT1~r4<@Yc+A}K8 zO~6e2C#P*}I)ieh@E}0*Fux8KAE($eA-_{XLAL?&F8FePwq4x}&@lG$hoR^vdO;*! zePhT~k9bBR`iGu{@%_t?f#6`tb$>Y=>z`k%>y$va;S=roEY@W!mbJ};JQxQ2n?oVK zlBaOdE;-5NAj*LOY@e-KOQ11HQ>xco1t7_Yx*yJ+!+cdL>`nLzQhtMp>vkH7*7POJcdaOup*M#4&D zl*lpNjdlYvC9#kE9aDwmOfZ7^6WruY4qAyOx9;DU(?($64bq-4`9+Z8yz}rF70EO1 z9ZTEAf^y;maRi{i*jEU!q*!)1@8$GP50kFBRop~-7voT~&c%x4wj>lmod}1+2}lD6 zdI04G9AZLMJr2EL`!MFyZ(;C;`@}nKxXd@nNnXM^%1N`!X(v^@c~;w z^59vl_pTY9-NO;J@N=Wwp-(H*7Xa;{(u1%HWXbHI4fJZQq&BYHwCvukp&l_fkky${6U3GCqeT`|b1n?{&@%l~ zIDSGub<+?zW-VFM;D-8XlN+N8IuY~i! zh%Yq)l!QJean1}*9Oes-x+0!k+<%MMvdn^Ly!V{g^#z3!a6C@a?|rQmVx2(^>C6w& zup)UJc#~`Vv0>=?+<<+Zka;uXjVtA=1oYccoihuJRABt9YbNW@MLijnOQF&z=)tfY z1aq^N+B=(lJPMF+ju=oo#xhR8#3#Jcd!7`qUDy`M4xTHxO7h}6Pc<}{~G{tJPhF% zcn7pn!k*z%WK08JDyr?wh<8}V(u|L?9!I7f+4A{p2zKqaG`lU!r*+6rT=vvG#lufEYCdf{E#u^Kpx8DS^!TcU8!2? zfw;<*ZPGD%UE&i)`N7{Ljh_mT!|%Rk{3ywfnewE8&#J0+N8{cKpI?R&b;c+_zmxOL znjO!@umq{MY-spe0K4HGb%MA@`GT;b3i36@Xq$zU*)O7!(~z(Yoe|fiwV(+FDO5?< zAyYV$m-0A~_Tj)ZSFyg&j-S&Ui5Ea$X4ah<(|Z!0_9dp&WcXzO*Fn)g3b8hv=~`K3 zzTnrH8BL>8`IAf>EZ6S5G8A(Ilc|dpdh#v&S7K{})5noz8@=?XDjMPI z00Hov*8Sc{`kUyuw$vt&dwT~iW9r^e>i_k8T;>LpK4Ic1PG8Bo3;4TQd0!hOv492% zO$njG*aq@$DhwejYowYr*80Nhn8^oVnO{d941gsAxZnxkSVnlG9cupGVJbH4 z!gA%I!8tD;I$i(o7-aB^j482f=6p<>QQIT^e)fx}ktvE>q~^}^U# z&AFI{=agRMS0voThHMvGS{r}?$?7gs;YTM_u|b^2ROd6=nR^mjH9Tx{|27n+(8zbj zW}Jp@Y61Ul+2^5)VA|uo@c#1js`YkN7dX$q+aD4crkBxtAe30LVf@81)o0D-oQAmI zqsWjb#0v-UwEeW#Gx8B>^#Lpxt(Zf%ef5LmiB#M+9jOTS!8IZbls;k}ur#)?szP$t zxDdlgTM|=cAP9leD^jNE0t^^_PBPYHE!&1rP+{!1y;nYO9ec8;y8K@o>KP(8O`}lX zI*SDt*vpE0_Ofc9xwxAKhn%Q$;?DyPX)6yXz6?!^yaL952w#R!-$G~WWLDha6q5DF zJwtYmAPjB^^W9V3xm^m!4s_mEpo{6Laq}r94dK2`q@jTrMk0Hkazc-1cXB) zM^RW&&SXVRIY=r-*Zo2iuTVDBo}fO(N&SX@{)Ms}S~CC{@7}_;lCosA!VeKG;Um-7 zj8xK2cof+n6(Cuk^3T2?cDI}~4MnN(P>8n1ZBLqcjT|KoiU`w$F6PrIOtD2)*<*jc zzLM=xBKrn$R3OwUR@TEl*mw2L6ptB82Qglflf}NCENU7~+4L&`fP2 z!OBoHn^&*y*L>GV7zE7W_dUxE#iR>oqeYxn{3+AO(4l;nc z{;_7Vs3d07!xXX&U;a1*oFuTn>3uRCi3=<}gcA0>EhKRd+%yLve&biQn0F5_(T>n( zmJSt-!o#IpLT zDpyjeRH$S>M=TwHq$=~$UOP6QZ(w<0r&C?dybY-*1_C^ zMbu_(GyvSLTI1QG!GyB%{L{ojtdQGr0AFBuBo;p zGB98Jxti`ZKx7C(vPHuY%3gM-7$brqGl$0&lox; zgp^g4>|KnXHS2>9)@W~9??h@sN}3;}H_}=qPp5emNT41~l%j0+Z<9%^>85CpE;Mt} zUn#&2?h>hiL-LQTsj%0_%FLiB#}` zOoqo4e6>hu;)tqyaqbDxKvE<_V=(q0{KexCWO#Fm_jHzs9H&w^{n1hEy~bMVAS=j3 zxVPPc9&+TuWr09P&XHwbl+!5uNX@!n(MI6po zYq!1qTu)EWyc*n-wF1|uEuZxw9Oh~?GiP9k-&QWHMw?m%vf&~1hc}a4UqgUTYI1M2 zxxh{pIz4pwL1=$TeReU(sOtiZi#64ypr z)9%?SSU!R~%kiJKuG8Apo#lsGkXO6&YR>PqB6&t=yu#OtZ8ZiS_6KrpV#ysfiR13zKjzhbUVZ)1l)~5gpQ+ka8uu;v!TwnxfUGS^e%4<>*F5CUcbD&4alYFK)pEX{1_5ss&j<|6X=A_C46Rq zUiP7Rpd>}HgNa2X5zpZ3qZ*INbcx^uQ@;}?u#E@ciNuOLt#wv9vvO;ve5Umux+r_X z!N!`^n>Gx%d(Sn+7Q`HtCg(%5nJu|>Ks?vnga00sb!$kvVSd3NmyCymrA7kA2#Q#M z@;gRfN<@DV#+G>JztdwPiA6kMJ`G-MI+b8xfOpqMk!?q@8b36L72h4rUv*~DCe;xB zCsyKQ-jJIEV{KmZ#hT$m*vw9u95P zfR_^kRwZT;a5ibnrb(q6VXbc^90@|fEEZBxwwrWNhk_)fd)O)(p* zBf;KE&R^uEQdg61cO6n%KPIEdCNTt?Q6YO)hj|P7v^%A+)8hp|dY~E;MTLHwOFR-u zx`cnoVja!dR)praMS$6?&YU>?SjRYHi+oV(j45e0`uXVVa}E=lP>jAZrdyl5O6%anK9x0nHo=P-BHOe zAT>z0u2uLJ2K$h7x_GsOh``boQl8>sS%}YLct1&Gcz-DwAs>|EnJlo05=9+BKca2ujm#I?oeo~ z9O_tr#<~>l-3zj`BH~hy_w{ecundPAmuCh|VY_o|Ohy>dThrH{6MI)CC>4UgKOJkV zJDZ+G+V_qFmyo~A;y$S2GZ5wXj>iffm)!1a1cJIFa)If-VNuzH^#mR14VL5(DWp8@ zj`k23G$SwH<{af z(yz+%KLOdZb2s9e<~@e5H;Wl6XHRP&YUHF`^U~NO9FcEo5p`J3N?@miL*gB(euOFIf@X9&s6 zT|||ywp_x-eL`S$`IKwW-c3PmEPG+;w{mI-#$@CQR_*a6!pOxI5kVabr(Y_0WUHG~ zB)^3Q*tALSBYg4}W#78r7MWqIyCg#{Qwf`Dw84i+3WeVq>)}QjOBTBg&Phh+GeI(Z zbZE$K(2f6JGvjNourYJ>dTRE(w(h;)*T%0wi>%NDyLtF1{4R!Oo;Y%R$@55`hOoJ| z4&~d}qp5KB^(4MQq(?m4Y z5Mk;&W@y(w4}?l%!OA=f=V_k1F{-f4+( zutiln*bVOhw*RQBd81en;Ip=@RcCHQpu5e+t##spxr!lYKaWv@5%oBg%qU>XtRq^| zcH+(_o3zXh$arw|c2)@)^kb+w1$>@^F14V=BC=Yg=RPK-E>zIUq$a=*FA<^A zWlzA5bmUO2=keToUBOgL5LX2sdv6JAqU{hXRoELA_g#spH^Ww{Js4~5lL1AK%StxJ zutY=8;b&ICyD=l2$l_M#*EWp|@o}c>ev%;ip1{hXu!i5~)Oi)!ilI$n(Hw$-ic#<_ z*A)GN^rM8oB4RpWeo25ny9EYNy}sq)d3=7s39Q>KYkez&d@x1xBwwtE22`h$&5bXl z1pnv$jgZ6Fy9q^Ru$P-azVS3kQZ+{HK#@_%AKASEMlD!cGjlhnfVcinbjnjGP)XW4 zo@aB@S8ZTJ^dX0i;>Wmjv+y*$>3B{Tuh$$@8nXrW9}+c1(?n6XO2U(E3!WBaiR^{` zVi2}JxR*oR+G>jMBpG?zT5_$czDMSh+zOf&n7VL%WR>XY(NUr!{RAWw>rUWsFcaXniqjxbq7&nsh zn})~MU9Y@+Lfo&Dbi~%Y+{*NV@zQ5JQ&)l$9Un)iOBES;B!c-xoMF16_Nj|w4VV9r zh7goNO${wKM2_~vUs7hF4Z-6oKkxvV_Q=fq*Yk3mckR`hWPVSG$ctoKB0YXh1(^dY@9m}^3$PDvpao`2a~Ft&<#L4X0|DAf*Qfhm7f+LCc8_dx z_yGIxEfjzjmK*AyGw`(;a2>KWxIj+Tn=5E~jq7tNUC=}`es~h6_wgx5qXV6A9?TYv zmuG}a%%0*^^?V7`-=VF(Qz6|&qDv+8L?;U-gSsx%(O3W&2R{W?EyqhtQyTzgw3J>M zM=jWVEQNnu2;P z@yVZHr>@_2-L740RRxhop|ZzxeIa zg8c?aeNG^I6jVFbmpYd_EYKBU09sI!O`Y)~B&@yyUpLfxBmsPJBmHKu8hhK_Ve`*F z4`P0m<;MPzxX;K8c%&&B5XDH?#>qpmc5VM4t8BVv(UBURfX%tc^Bp5;QJQFq$Hk_Z z$2PrWiUzcjSPjXAJ(zi{!HS~M$>YL7nh|}j(MsYC^YD(!QSPq2__as&_%{k3U_&CY zPSF$*I`1BRDG9z@xc~>K3IcP%7u5FJTz9|_Pf(m;o^dzQ1 z4LuN@9unHWhd^A}r0ze0q-N00@0p^zylM+sXn9SH313hTTCZV_dCHC7tPQ)dPHuqPLHUdf`H2Q|dhKhl3J0gUVjR+Tsi6?Y(<51Z~)>&P_ zycafBtvGV-l-p$bNH$RNRu}}uiviiSwVnQqI2GrE05|N5fcs?Z`14tfy7di-LnM9p z8`{N?`*4f#)aVfsMrpR!f7$SYW0%;?JSm}1pDIMO%x{3bU)QUxle9Y6-^wuxw-KE8 zhl8+rt2{3HUrK(JXzjXb%KH^sLP3@>3zd-R6YuQV4&>I(m%$F~}$apguBi%iS z?Nog<`Omp$0o&zJS=qwi4(WD3&tC&8uELhv%V+HkPjJ~P7IvBkYRvc{l$h+SP9Iu40SJe^{dqZ+xw`szpt*F(&WO5RQ2=3U% z!X+x1@wlojmG+nI?;)M}SnkCXe_8)l3U{p4g3<-!R}6LtX*`;73j}r4zROlsk^3t% zQcgSg?bCsp2(KWkU|SBddyKCB5|z?D7nB ziCx3M788=v_3zL=#Bn?**x>JgGmT(#PPax2bmnigK&Ya{GyLeM_IL)#!_3DM0 z1pR1+rD57lwR{Wfqg&mo&#y#Vc(sl*?1^gJ_7C1GS^c~(o&5eLXll|buMHiRyCc_N zWBnD+K5Z(2AR5YgI2tsM(lu_G*vAFnbkYW-yPszqjU?%qOG|0sYIeUJOg|B6C&ynG zxuUWglY?&rT?a@uTboona;h)@WjbyeU#Rs~6hdw~C_+x$-)<6EpaFCP$Q42DRE2sQ z(<_s-s^BDw4DG_g;YlEh-*@!Wl%y=7Y7CrT^!X4~%?`Vrbi@E!9TKU#NHb&|Iy!Ek z`291BN?K`DQ~<&Smh+iPP#tAw_A;HO^go-$RNg@wUp*Elk0e()?M|>Ph4zFBVeLMlz*f0AxzO1LS`-vN;l? z>}i`&_uZV>Z6M8bO+E$d2d+W)fa_b74^W#uq#ix?OWN{X^c8U_iw!aO%sv>|#^L1u zJj)C2|IdXHC(5V*9ciLnnr4aG80SB3(4mbOB5<^aPj8b*=SU1U7^Sue=~%mltzVLG zzDX%w7$bx|*S$Ws#8mwti~}J^J2=WO#x-MGyuVu*0>{q!!bdpD82J}0+e)LXwo&eK z!(*^4*WesM%K?EdtGe%0@?Zg~c*YOu>G z!)s&TWa!VbAqN?M2M8MN0uTsoc6p3V1l!0bS=z?cIXY(;wQ;Ekbz26bCD0tNWI&sbsIDRwHhM^khEBJAt- zVk~mC3pGRE7u$}nbRgzkXX|L@>48(@#!x&h7%9!f2}R@7>MVYLdD9C8L8&2}Itgof z^@#lCYXH|L*^TRFLCj7${9pP0{NZ2QD*_3>8xDNPrF8&w5TI3G>%%|Sy|;qOazJy4 zJ9>7X{D3G}Eb`B>#Ine|Oh1I*qDzPk0*jxet&hMbt7c|34(x`IZ4J)eFOYc~35~yV z;+qXD^~^I5e3>=&tWXpDxSUlJ2q@^fkqR9(22nxdw7%k(%@Wik7+cA~yK{uJmCD43 zn-A?_6d(T*Vn1?pSem`j#CBvhLd@jUlr5L?iuc&rO$KuJn5pftD^hik%muZv=_Xb& z6f^GydHQOS1BF)=k5?Kq@@Vg$8h!EYx0QPT8eB^A!vG$+kBs7nv&yckVd4Jmc+m54f}xK0zt#( z`=g|%8@_UjWrR=-TMyWopcEq*!%0N?|8(6}zhA=z%`yo{=kZLLv0pvc?X-C&`X^L7 zS=!vcQfeGm@O5hRTNAg23;Rn2FM}dk9=lJXk|KlMU8%ul*Gq)+?wzonPT87aY>Y7* z_20-SRW(swC^0(;ier>PrLj#3c>W4L_HDvVXj7}xCdKF}~ov7|RJv7noMyRjyKj0f-iIXZH}HEWbG2bgEB zr1CZ}vWXJ&D7Q+D5a*o);tfc8lOz>t(u$nupi1fhipZ;o6GduJjBQ3l;XECV^(b{r z$O`yCjbrXIuci2~+d`H7z|FeOPxj3MQao&Zq{UZEqdW~wYlkpiR$d3jkRFg#{M1-< z(njCh9!BL0?>-ah5m*95>{0>U-VI*dMlQXn8&w&BU4$0=h{S^csksw#@M93q0W>1u ztUNDRwsBm<`3N)QC-2?fYYg#0o}V8BRx_01P`J;F;iekKDJ9uH=XOkXfaLf0LK0YVRefSaz?O(xF(7*sO zU0qyoYqklVOP#EBb|xtlyl~`h3l4zLtz-6Jbb$W>&?%(EM1i|ved4&6daeMjhV#j7 z3Anv49n`FKVC}h)&}kYE={MVBjLYhg`pee z8@|eeandVVwo<|*H|kojAx0%kV=ueEKK^gfo9U$Hv_IyaD1P-(+-rlI+!sKdO<6}> zTr9sCj>fB6)+Iy4rt%E0`CCbn9%6ioIg}Q+K`<3HQAoaX?{x2>XFoA zlt)aP=`0r&7{f!ZD+J9of?`3+@m;4}W_w4Ub$`e#z@h-9n8FGFFa? z^d51W1w>pw)QELu{I?iccW*#IPXX-frpNLHu>?%3bvEnVeL~jjjedo+%*%_Hj zTkbJ}Sp+<<0?wSJz_XU<2?=rv>iZ zV#gNK_Xd{6OEyEM2*NbLpe?|Kst26~0j~p>LTR*Bk^OJ>OA;}Qu=@d=RNh^7{bKJ) zSo?I%1R=>4XNDyfeCi)wDh+DwrN`Zp&bq$qh@GlS0~O|R2$jO@J9nI*t4(@j^Z^0s zHSBV)=3rNz-dolhLy1XzSO~le!(+(@I{~E?=Sx!e4${If;yS-tvGs-Jr`3CHyzq#G z3mW+p;IENvmm!1HI19zY3CLBiHdLW@=+!Hk=uOX8mB0w|cYvL&i9u#WpqI;6#hSQE zA$u1UoOn-9iCqof9xjWrA@kmkczTA_ucsC9@N6K+ubgEmf)H>=UdIGp&W?*RZXmw? zD)J*FuodSw-9b?at2XY>L8vpGyJ64K%jUGuzJW(=`%K)*3dh`!fu4E|=zFoP%y&eN zy?rnl#~4J&tl?Ye@rL2Ch`0-X73e@Fj4#!mjDgXm{^q%r=p`dsD(z;`ep)>ILDOsZ zb|6mD&tDO^b#Bk0(*yXz+svj0gu|k)K*cm)g6oegto9P-^xue%G00#R%~N~+o|=7@HTreTa=l00m1{@zb+Y$$y{WrPrl3)?j4-e| zG~XhLi-Af}ofJL0s%SX@I-xG6F$8UiA+mnY6}{ZCI2|4q!7?*j*JLH3eE^${@6iXS zhpSYOp~Z>4BVwTfej&=TC0Q7PM_PI}2L%`Z>3LyR-1&rc9^ko!SzhW#$euOW)d~R$BsCIu2Q0qcQCJ^MO|_G>@2Opx(!mdU2~=Wvl%OMPX7V9S zpuE^z!kUeS>*iN?d}!l@kW5^!L$S(i_VC(N%0NSs$ty2YjIW>QPm6r^af#4KRXB4? zY7*ZbRdSo?2*oW|R=sTbU!5Gr`5(lKC%|cktp?8*J{r8@25P9(dUPi*rM+Gq|RNY-el@Y@*AO09)uZH}h;i=n=#@n}a zNBhTX^7mGsdyPq+&&vu#trr1Bh8hL3$d^wYWAT7s{xUkW`|FsFhXrHJzL1XEB+|DA z<8RAzy5tk3OU}qI?@0tYZe@yCR3vH8sq6+9K(6*`1=F_QYOI5;H24_8B>e>nhK5hV z-2tgLz6(c+PbP3YFCf2dm<8$4VE^1Y+jI;*ZuRK+>$~Lyn!|>uYIx%yBoW(PZBe&R zL-+byrLRHPO&%($a^Vqeqe*2JwO|P}2@t#E{K0W%P0W?T7=-#~@(4?{R?^px)yQsX z%G?WH%=q188-O;3O|Ko4+jB!1>LmD=U!kDK44RugF+$jfD4S5zxh`Z^pKi4a$SLY* z&#Yf!!{Ed<%JTWVE)vVWu&Zi8$5q+=?(c+7-bgTm6#}E^(L__8ox)MJc{Zk#6`3Aa zAqs7co<=FUl;5~|XNhWzD10`chKiWQ8L5+F0%8Q4t8uv>)C>lNw%=WLYnoXfx7*ZN z^=eaMn6~(}yejQZNj%IT!AbTY{rI#j8vtF!feuGKdvs_Oby5z0FbZh}>~s4bC-Mh* zgRm_oa@zao+ZgmWxm9^XRmqY~d5B9VJ*<6KC^a|TgAHh#;NFplo_-a=*u;a=77mak zRBZVk&V`A}&X5!O0T>Np?R4U6kv>;PUyjq%L60)PS8;~mwwfLp9j2fPLDG1yyMs!= zD6?T8RS03{cL!c#9TAC53h4PANe9$IofGn*$MN+^YCKho|AO}5=MM*xU|ol~jlpfI z;(b96W-K;c>Ar?+p1+PdQZgS$+APJ(DvZv1Nx)$(+gOqJ#e7&wGg3*M^DPGta&MsZ zvkyRHZm(!KACjS|rg}$*hd8H4an7;T+>cP`&D&!&PRGgyi=I+}LGiC7U;p7YE_G6b zA#M0lM6e!8|3rmXRWVy2Yh_(jg`F$OK8wtPz!?^7d0_EO`>n_XDR~E1O4Qr5i8YLj zOkw2yVCEwmGgi&l1SdMkL0$U$FKI&98^;;S4T1J}cGJpP!#LURKIR4fMf%hBei)U* z4SZXn8(i*t=GCDD1vnA=A> zGrigKq!SW^c%jz`@ctEjIudh@OF+B@R}>uob1B6>OHdJG>rWCGjrP|yNjc;6HaZ`s zz$#hA@sQT~DtIX1!9i(p(~dQ~sqT!Nv@tg-MGQVW5!T{MX*3?0u#^1-JpkH96`qNZ zFlz3v5t1Iga8rooGiB2Xv{qnO^tWrHK%7Vze9J?Rqvb>YxQ$^FF&}bo55YG@z(TJK zlZe<3QwGM^MPYX)==j&?<%pY>-DNpAcndxN(I6DeS6D{3w1RIHQGxZsz?rlWJWV~J zzc^9Szfy$SEsv}ue~U9*B@3)t!VRH-O+Y7)SlBccN|E(L1SP%yzW^}L7b7vT;IlSdVHx<$gRO+Uqk zN28wrDFR2GU%FOn13KyBg$gTAGt;I_$|$UUzs%rG11Jhj=5ckeV%nf%4>vSt0kW@m!9Vo-OM;K5d=>7pPJ&GUy-8d~k zwN5koX(o!S7xn7}!P?S2>s|<2(1s7A(*d--z=3+e_IbS&;4)|9*|>ove1S;wB<9?% z34?rUL#y@6X{zy<=UvIYcuUv@l(+LWtw<5PBE;6L0Uj)rdJ;_>dS)Cy?+PKAdaM|gaWRE03XUw8>@_l@mloQWQXx7}xmANzO1^Cn*njEP zgK+#*&MDP}0E&CKf2-3JFaR8jh$`S91ka5!8v$6xQtWh*`~KxykTx}a(l^vOrwEV) z@AF78Ehh}JjCUo2*+oaVO$m_Yq)y8i#>ANamBS-f=1{QnosP;^Fidb&C8YDI5>1ij za6CNgB{n`hiC!DM*vb0D!_PRCh-{-|J2HtZUn%k7lpMLoDerVcqo5QvK7o>0z_(w!Q$4&xLnXx|HNQ^mnf4hq(`MRXq^%z&3S3ite?7v!- z^Hm{Ja?~8U84m{gnvw;^3umC*5Z~38U8(9}j{IraG5pC=^It6MsV3StRrcKa8a-(F zWf6pT%v;qCotLdqECz|=i01;y5-k2M$TTN=sIoAR^8sww;K?GA2JlFquxL}1u7Z4M zGq?s97>c(oEU?{;!BiiR<~ikRD_XUWk`Um`20ZalpEw;%+Vk0r`sdoyJ!j&@MU`Tg zea+CPfC2jAI+v#g6)c0w%xSJVHre?|<5(#Yo{?YaM>=>ozBj^(5I0+j=xy^P7UPVR zG88591?EZHJjB5?o|)QhQTCIUK`915tjgAV=gN!Md+liR(aP;PHvwWAhJ^bJB~C2L z>F@Y;9UDJwO(*CZzZHUL$Wg)+8&}t#@MBRZL(m>t9j>~Nnj=huZ5GFyl{x`ylZ1gf z(JdaA?UG0K5-s=ov>SUVI}}g;BT2;mnsde*YcCKAiXO&5-yO8$XCdc(iGha=+MM^5 zY&Umvorzh=K^X6wQhZBn02u_#iz0ugc|mT`CjjMXwnaoJwM+Y5G3&;`^@SCN9^R>~ zz^y&*1P=iuHez6ZZR5kn{q+_<^JwTxl`=o%1E$qbROdvEsrv8KJ$Pj`gR+V!WjH%f z5Y|=DjewmQ9^WDYkMl&TuBXHYVH7WzTmC*#oe5y_g!Y0Yw>vT)^Nh#skZOZ46m(Dy zVl}>%*<6)@Fv}`By~Oo$6{pYCSEo_I!kDG%G`U67-uTl8jg#`&T-KX;kWwfp&=B@d zb*EAw4&hmyl87?doV?vQMxSMK3ID2Q`f&+>^ap^Zq9vpaCzvBBL2lzD6hLv$x0Ps- zr)w3%MR1B1Y@XAHR&;5!VskC908GiMwyt?b-oe3j0}v)(^vE@~+b_r)+4bmZz~Md{ zwmMw8+#5$Wo4V%8J)s$R@_Z>sjKW?t9zGxRo~bS#bl= z^fN$0u@Lo2SeHx7970+Q(Xl+!I*$$4638Ne$8 zOl>fhXG^s0fFp@UQ7DSkacJ&Ejy?%#(9Df}atwrLjkJ*d19c&dn0ae<4%viBOzF?t zU>8(HcXXtNTbZzuLq}y<7JAcCh#I#Szc$?^6e*NyID}glD1F_TGB6vs`r35B-BDp7 z>knx>fiE==Gqq|2*bzJH4EC^b5U+@u)QtMCce|^P+nSkn2hFm)OMzu%8>tkLp6eiV zdK#te!V&TUeBdT@)Us6=<;AMNet|yaoSw_h6s96u4^}}tH5dgt$yhZecXY>g7e34tOYvpu3>k8OgR7hRM$}AAFqir^7)6*b}1uZshU>XMV{A+ zpW5D#*W(F+Q9RmE-oD&{4D!^F7hrJu9VnRaJQ$3Vu2NAJAx6gMnnsx?vMNN(TVh9V zu&srK(kS^arEn6C0EZiflwYEV>{xH2@}FibTx>bjXvX3~vkc|QFNnRD?Ah2$QYQY- zQT+oCC5}D%Ec!h{lyZ(soBjuIlQS||W0M0dhr8$MH=Lc^ciPeTUQDLY-^0$l=OX1I zqC=`M$2~iozXJfa#;3F{budA2Mq=$%vB(#z+y>53RH8`q5EsOg9V`uO0QuR_^+GE2 zfFlziEXQ0weVcISsuOEl6WkSy8~Sjal+QIxB3CR9B&_ zmk%Lm3dA^!3YKypu>aDLlWtptwrKp<>|!Vg!dy9Wujln9;O}}(Sl<$VzlG%3&MaYX9uDd~+7`B>PKqrMpGEC+xzAZ^N8%T`QNmQMp>XF-#=TXT(S!bq6 z$H#D6iMj8cVVNod?Ts1G5F0oQ&Zl$TOo%p46f9~w>Ep!5qN8TREdiF%CkhvgnJ!AJ zJ!o@ETmDHL#|#ITb?60Wc%xPv0tUCC916n{qPKAe&#g^u0b=)q*qwvOi>FE*DAJ3+ zYSqEWY4p1Ef9@UD%Aa~6Rh%?reznC&Ebw`*HBr%&!PVl>S6wsm-e=WwJp(P2E)ef$ zk#1lXbNUjGIvn~hL!RM0dVXoB53qpYu2RVr{qEb{sRZN_%+u8eoDeTNTwG8N*tCBfDmP+M_LmPU*&{P)-EVhv;fNFnW zpUaI+B?`PS^OR4WB3U9E{PtM;ft2vQ_kaLH%x2aYTu&7UfTGgKQx&zS;y1^A_^^*f zg|nygs*i{FJjcaMPJ$#7z(gIJ^FgkEgL}_!NT*c6biI71DVz;2Q&0z+*>GO~HJ!r0 z)Q+fZ)RltMX9)|4TUx&jmk_DG%$Jd!fas1w{zO3GYZmnwIY-q& zShPho2mcSE^#1yX6UTqv2|X0mM(hQQ8N}Q@3sB)VNz3u{d!TNfL4;sRUzSRRM=iRR znwDOv--|NjvdA|b5we;9g*`ML81NxF>7LcBE!}p_h+?Th@X72UM$NjwY}%7CwGpSS z{ovn&m@M@ki1v&&gzto|SW-U}W=!*=Xo)ZMx=MFK`>~!lQ{!m2r*?sb#!rz5xeTqg zC#9I`KAE*I2TnMQ8+E&BPV%A)Crqgp>huDOO}Hoqu9b*v7%|%%7B)st$K3}0ec*6q z(tAC~C)mKSb@R!y3G3&$2c<69A_rAR_(|x#sY{#N7^7XX2)y)H5bt8e9{b@~}u&A5&4(6t+6+ove~rZAAJV z4CUdqyEgHx65nm;VZa$XMYQ73y6GQPu8x1~pk+RA24|GtBAh(+HRjM4&6)FIUZB*r zKlK{IA1f>EYZB8>N;9hHhQ^a`MAQ30pwGL_-=`J`-!bA zI5yoKKNP0UX&>u`3ruh~7bnJJgClmI&N%57l1an*405L-9*Xi2%uE zsa{+)mob7fH{Ou#6Bx`Dh$F{qZ zhdWL4PmU=2Hax9H>f6%eYr^0PYAZP}7Ep2yp^6J4qSv=TXw`7l#ZLmOmuvbo9KGVf z_ld5W#gdls(xE5E%@t7U61gtdl0{8C_nT@b7SC_}bGynE5ypBh{a?aRG?oBAK)}DH zzY|Ele(0xrZ-R0V>B?i*;1G|;9T&UXf=7OXz!TU1lo~3oBk#u>t*$JE*VqbeCoe|n z`60NRMtHbYsA$X9BSAuzWg+Jm-nw>1v;mNa24?&5iEks4aZ9Z=n2DdVqI1kZI-2Ku zPRREo67>|jp1xo(nyWx8w1_|zu~;q@g?6q*o7&(1^vzqWp%>6~u;~dhG1E~N|Bk(aJ)p!qK? zxOHd&7fS0gRWsCl($AG?ulj(y_qj}vyZ4swcJxXNYat$w=2gbDq4|VN3<|B_vWZw| z-_>nD+^2a!R01fZIvUq_J|~C^CzQ?Ikl4=QxCNuB+UF=@tq_=$_Xf)`+uhlZLU^1y zxt6V3DWZU!fSox8JKwCNfw!Pg^`LWs9)|N(L!r{&2U#Spc5u-{+XkOY5WB>E%=(Z+ zSRQ-(g;{nHBZH_pJ5k#OZg78wLdT!{TDsRLZ;fXtbFT>sg#q^~4MG4x=TlL>c`aU*cgiLc!$7uDufk~+vka%G;b z%96#Tm}xZT(!~Tz3Vp-j(6#w!OJoxzA&Xx#ml4qr)be?E>`)s$?81u*|maR-0NyCI0;?@cv(wodd)tBdX6> zYy!fi2NR~s0VVUdJu)H5CGx@iHua6E2$Jza`L0g);+?g}9sB>=)=^2Bo03toBt$C3 zxw7OH-N!XWKCFpNI~NXG$F;&|=o9x=>w|Sl?hJZ8YszOAC6V32_h?bwa3B=)dhCJA z*tC-0t503W7XNc1UYkOths)xw=YeTlVC&d0E40jW$>~*jcd6>9vnpOjej5&={& zyZ^8KC5>3Cehr*GvsGDMV2~ug>Ktt|w*G(OlPqTn=c!Oc@K=d%Zse88b0yQa$ooqS z&!eOj%glT?sPn2pT1ufWA~00wY7+P_45jM8E9x+Eqk>Yp9(W8tJ*u<# zAvN<|C2~ynIhEP|n7Jqe12NgiLc3@?=yDD!W$PSA<$?%`G&ZTQKTcv9&Gc{tB$Ntz z3!;0@{y3;o?R(T#x%8;P zQx>9T1%3qc=;q%HT-9P-UP~Z!x^ajAzW=NN-pYWaK8g&SX~#SK2znDX^A)LV!_p@l zTo<-GmDho01ibvnT#{7FBU#binb2tp4=V1DQip6%g9SNX=YAqfLNnHKbNjjs0c$x9 zWq_pa9!mO|7r1)n+Q6Q|UIU2eA2pRaLr!o57fCV=1w6u$?rE%We%B~HC@aQ#wgljM zC$kRfC+bYj+hFW$L&!RfR-A+az+LH%C72%EC7hz@u%_eaJnq30)(JSFb3a!J^!puWfK zNl>ulVk0IkL0MH5V3*m0T@-DH2Tm?hC8REGOvC+o(>TM|K`)PD9e5RpI+oqVo!V%0 zvbb1vXEnspzu;`^~6JiuH?iZ!}Hg6d?V_^RAIKC?5=ed$UW03&qy);FK{o{!{&DuaFu zz&-40gS}hiM<9=5qQ0(<&5s(JE>qG|mLW2{)b9vy+SzD(d5Hv2W9g#7VHVLirvkfZ z1f?2TLh5u+#!N5;rh;=cUGi+JOyyk!at<_ZC>7Z^idI?RVEBmNf^L)^N;Eygdnxx2 z!9E%OZ(Z@IJV;05lo+9dar-DJLA~=6^U+n}jaXQ;H>PRmTE+gxL@L%tcZ_`oyUAc) zUUiB3+yOKLHq-&>BKB)@F}) z+rClPHCSp66L8roU+DX+xE?o+?O7>aT(z^x=Ev+bL|8z59Ng1!%xdJ0M5DB!Hn7Ka zW&BiXWy7O-dJe_SclZMkU=PqE4*9nVa=H+VZ#^jn6FkEXq9jry|7Ys*F=IuKi!bzK zt9_GD+=!&sLS~Q@Q2-%9%Yq%7SZRW~LD_p|x`b>zA}lZ%ovd>*MI}0sAGbFOT%9_Zz{NW(-s~rMwqkJSmpth&V+Wgi&m(|0Yoku zhy``3Q97rGyxHDQK^S+sF(qcld_pYvWJW|I0H>4lz{{mzX@sP!PndQHpjrYa;skZO zQ)wCN4?AP|%4^O)0p2dHoy+q4w=?k&e>0wQOe+4q#p4YP8HSOKr#Vr}l}`L<&k~U- zxb=jZFk;F!>MD=*f=oUB(LDyo4SSV;k~{ff@emZ|qUAC2nM|j6EB)eP*Mh^Hz#dtx zHy{m0d~5#9Cfqgx)oBq{C}g*YE2^cfV0qj&Z`K8_4cD}>3erhLXW^IzvxOp!UrL30 ze)Zv*r4cfu^`Z{er#ua)x5<6iHO;R?W<0M!bID$q`>W9In)yd0yKJAK z=1o}zRb+02Oce$|iA#RU)TjD!6b_ta>(+Y@kf@gO{L!lFoj+LNA(+BW+XXC>F+H2N2LJ zns-ydPI_RYNWsV&a?6bRb)s*3^mpOB-8m1ZdoKOtcCx-}NZ0c-N`xG}`72Y}19@A) z+^7ayUMA!+O~#_LoPlRnuN&n#(bG)zvu6l;N#NI((eRErP-ZxMV*M34LzwBQ%AAO_ zYtb#x`lum$aqS~4XF94Q420lTW(HAyJ`Yef3~YgNj)T`s^l zmKT0fo0!aTFLD~vgi8r^1F}$GvxI-yk=4Bp*c41JG`l}YyQ4OU15TDIXkg2|Gdrx` zDxna@yWd3-xlhQ2T$|5?C_BUR!3y5gZM=2y z(tjTH3R1qF`v;NNVxGa+OGR=VKQT#S8URjPYBetGVFs%3L%}+)o(Y9^qhRc5^V3)X z%;A;kx1ZH)o1r+HJiAQ1d<45@4nMYP!y6>@pL{%Y$Uy?75%$D!BWhSnXc4j(@dDBU z4AH+BoD;|q4yYl^BXb&VHJeL15h3A`GmW2r$G4Y_=t~uo_5gqXuagzCfyJ!7;x)Gr z@I!&+#|1pMO2lU>1=UXqR=Y@KBsk^HUeSDvXrv`w+=o3_tncngAiAHdvQ#tT32mEh z`ufj~tm@elDFW@Ch%J-A4RhDbt3}kCEq8i!CEcrLpu64E_cn1Kn`*RbPIty~w9RD4 z7H?{7|9qN#a}y6!ZB~eZ)kG}?BwXVx=At%zcpRliBjLspLzwCS8?~Yn>P6D`9g%v< zkL`cUm0sFLCC&MDx}(U7rix;mrx=aeyG1UGAr!e>Q$Q!rhIZf{)UM{7L*aZ*r0=)i z$*~+LmbbX2`1=dP9N<% z1#!Qdud5TPSrEb4XSKaZf1ixD$_*1cc=Hmc=On+*+_nxSGeS5Hfy3MHF|dim#${r; zLiacs6<=Wqvrma`PL^5{(B6w&ZvlqDZ_jtl_;jlgduhJ^UHQ~^J#$LlYS(`J)zcL! z7J)yf7s~J+e-mLTBlzuFTp{m&rV!EVhke(Xp9v{VZdDFJ7^=K5b~{bdi>7lg-B#%_ zK0C7F2PcY}O*S$mDgpEh{D91e7Tdf+PgF=~y6j(dD`8QRX9&&cfAqTS)d^f0CGdjtuW_bwq$>C_K?y}2Xe%I4Ee&QjbJzx zvPYQ44ea@^abV38#i&C8-P`deiPlCD^3YOiY=Dl%nV$vv2KiXMON`Cd*<-*Q10EhY zGRo9i!R?>iP3d1bvh*UsVuVLQ852|*{x zl`UO;S$E+8OhQX_b7$5~`t2LUVH>D94N>pm^=tJ!Frg}KX;c21NT(G zUk8D}YH0A`Bad9BoTgyOF{y{$I(1RO_L99_yaFI4xdB_OWoL4t`p-c13?FRj-t=yL zkkSwJg;vEfD(LeySN+$igPq|B$_V;P4*~|j2Hptkc9ao4gwY-Abv@XbFEIX9YvqueE-=zLhbtAo7Fe`2(naVh!;0Qk zD%HF%2%dDdIAo34e-hofSB>@we?YC-+eh*5ESI7w3rThu7U$_|JAwb9$zWA;U zJYb04WXe$l3lw7!aso;<*$D)5PBg!x97)HfJ(`_%ZLn?VF83U#!>NQB-29uQh)~HR zJ#wPdQVRsqQa4`iayR-}RMGhyHhh|>?)t%Q@cX^~~M2R`AzoGv4>rJ)u4euC09=SWs*1a%)V(x%YgvbV$t=1=e#2yB>{@g|N~1vdj_g-ShhA#pi`pz&;gD zL7P+|?LwL1i zYSQ?c77tidPI{svKG4<&rtV0KAs=HZ-Ti!S3M9^hT-=-w`u2zRFS@AgfC1PB05uCn z!J3G~H)+hxP(3lQuZAT(bI_AxY)c&`ZW+2C_AF^*o=HcI4l(RWE`5tIVv~^t0*>4F zPYA%{#UAj2(ybl$DfCcFhP5EsPdcn{oLY^r>$`o&Y8N!G@EbPMfBawC5CAadecrGV zd)?CdsHftQ#{H5lxdpaQXaf!9|2;eOhR%41qb?TM8`Eu74u~iq@#6n&U)25-zON7x z$BjF5SY?k%4#MODP08EwT%YfKhz0VdPR_wpUmIcp(LUU(n>2Bat%YlSekTL^u|sq~ zsBGBU(vTG*yg=~}B=8b5yRjAN3GD(4l-&Q?c^lsF8&vA`FJT87YT$(b!h@UyS)J!? z5lcv-PQ!bMhF7!_;^C|hCa`rq(?b+626ck};#gS3NE$D_KNyr&A2BJa>NA!1I4`cP zdSwaqO35cef-PG(@T!U3a0G}v0ncOh7YFQGjyyv4(*mlmyf@)rhqR58v7#Ow)Yy1Z zm@&nu`2vY#>ePoI&<7#%6BC_)HS;^>8GP)YTd24DiA1pHKs4MU0^*KbiDR1-&JIG7A)DZ9+nKy5Tu=3>aC?7?L6`wizz8It z%Oyk#!G@hN61t>e_(hIFF0r6EQ@iIwzJ)Utvj3#0<;R}Ods*ij#olu=&5wUVi^Im* z0kCn!*rNT<;~Uosg$*eiTI1LhT0dfoEQftd_M!-Zu^$X(1Ms)@eQ}ABu(E)gkJ;yW z;}j`H(HSR>IK(Q84AXV1o8<$Y|33g)yLnUvo#9^EVO2S`+Q$?Cr6&iRb@Ewo8S9ND zwOE6V2cxjazEpNUXMMhdwtS7%E(LQ*hW zre~V17s8bXYhI9?;4sGQ2Vu$L^KmJJcSzp}`tXsHKOQ)wLw(1i3aK7zfBfK-gQ^Gq z=)C_ZU_7$=8So_NEK(q0rjtF`5V(Yrsx3)2t^4*xBrC#k$5`;no{RlG;K61)$RGS* z!+gJQHvgSbSPP(f7}HQ`MF&+Iwxvv=9lbdSZO7g2-X>8r3|2?1Y*_`kOiFeog6ycI zD3uU5q#|TptRazSdppeRdcmgoxsf=XB8lPB6=?>G|B32?e6@e~mpR$kiXz^rqit^K zzKW78FUEC!pcmYqmv5Kv`y#d%d{<{ zUYt8U>5~ZdmpWU|2{MeRF{T%FI>zI9K8KkY@acm5@{PMT*^?q4cIZQ^P=2!qOHFqJ z2%cal2`&XO3k%tumel*vD13mTu*umBq>~o2lPN5yB5%9NaUZe>#mcLs0Q55snQRySJ{P5-H?;P!fNRM+Pb2ZM2Bx%ll z5m*=%?sLh!lh@W9r!j52MX%v0E6#crAuhB3|fyTRepu?!bhY3Ji7z~%0ef|96>d*IiK&-L#dCdpD=pV3-a{9@SVViJ-b zkKXmttCmxu7CYsL@Ti1)OO5F^5 zoZ?3SKG;$|?980E3ordA^u$NCNW5@nR6c4uBD0BHhBn|6F^A8JNVlTFCy`r4v3*cU zi9??l&EJB~y!gbVUquhU%7X>zE(EmhN!*SI-qv|?;;0LP z5^LKuc5t=Yl{_1j+3fyVf+rcQLL6+(0&tEE_Sn0x=k8)`S9J9?)erriwFY$drP$TE z?nqzM^VKLBSW3%dZbO;X8zi_Pdr7ztkfc*g8^P$bRoR$m?fe@UAUFnp)`_A?$}Ah{ zrA}Rtl2Gsa*ITMUxkbgC7;UT4@%jk6s;J_8>+!$|C%QlQ((Z#g4_%YwK^wag*4M$x zHSH3`p3Gn(SFuV(R{rN}Q(tI&ke%lB{C27GaAY0zuDS!lxmf^5|ATy*#N<@oRyEGZ zHf7gJKpBy#$rz+o?zE8hI%I{tNfYMc2aIU{)|MiIPrGjGBzJx~vyCo%HV5ELEF+e; zkXw2#&~?Y1tn`6t3k*#=ghr^A_iXTEZ-Iv*pZxU1b*6jX+CEjv>BS=O@%t;z;w$e) z{k+r!zjHI?7Y?15R|rV9A+2R$znc$hR|t-m+oTc}iG}$LsNI#TQW{b});u42xDNR#0Wd;NAgJZyF%HrN|Nf^$;$|DOGSQVPVzyHT{FdX3eCIe!iA zo*?OD1#)B^j}Wa6=w-;DLD~(VOloosXd$)=D|&Vqx*?gwJ%ZgBvy)^2C?LD*daTpR z;D1DQ~OcHWk! z`FddtlyerKtMS9vNJ7j@(8#9ledi|xv-Rq-_Z7sX%;hl4MQ%zayU2XWImPi`<>l1$ z{N9p`9hU?Y6(j2q|Ma`Q$%pM;_xmyI!b09Jh|y$S+bym?MM}vnG|LcJh)N($*NRa$ zB_9^mO*)5oc>~s6O!w9GrB@KQxDHOE=^uN4d%=9T}MF zo+lngm#DtqS;+8aol9z(N@LWPp6FQjvSo96Lul&R{|+%={&L_+8fg1%RFRa53Tr!W z&ttwT!h?EoHhqxH^EYo*&KrM<&}Eub{pC=h)Btgfpne4IwDbjAed9k#|D(j~SUTZo z%dUzgdT8k0`1_V-M;57bkCa&wiahNn zy1`|8CsEM)dMz*(Mo_hjpQmHnU@v-elPTVOxg5!pqh*edaUeUD+y+)YyU|w*c&jeg4{Zs*uwDCBtlHc$529H)XL8BL004)p{amqA){W`DD;Vv zxL{bN1PdZ&)k^28&+upb1NbJ7S027r6AxgTb>c-Paz{eer~fHC9QOHc`-H39_|O5y?43AYDLN*7I4 zNH|@a(8nXd(;|!Rqu}u^>j9kQ|1A+fmXO8Y%dws$&n!InzX)2CeFmI2XhTnK2#jq` zZN@SXOsfO-a)B8}rgTS4Te9s6;xCM0m7qg6h=DcW-ydqdy3-RaPgr#^Nd5?Ou=w6K zkkjm{oF5V$%i?uy+RmQBmaM7k4ik2wG6T%~&QqfVcrs$X#vznF4qu6;pIl zqXgHUcQZ1SMW(yOFwK4oE;TG27}N0M44-`*@6xgb;HYz6vbz$Lg2)5zfQqSmc-L8- zc^q$a*Z?XlGBfGt+t^DF{Xg#+=ZC|8HlFU&;;MpRQ)yZMjVDB z0BcC4NUjQWB!DwS2;#B%$Tt1ZEaH4Ca^jJ+3j^&&K?!_pY*O%MX$KaIf{lC|`wy@` z+CT@ijPLWIvcMVXY*6{7eyh4@S%wIIhHM(1yWxEn0P^Z@7_RI9z@AM>L(b@NAiFW_ zn~AVFDWY2ZJifFk8xVYD;7H4z7m(!=m8su<|GFke>I@+(Uv7|8BI!!Px9Kc= z6@HxbsUj4rj}<6Zf975_deEVQ5!2mElG(gt@J$%pq)Uuc3d#uYq}c#d7s5~GB0dd- zzXbLO%Xe3avYZsegm?r2*(4&4c=p7XO{Qp=&N-;*AYSgVfOzv(Mazx+J&wDBN!r>= zrK#cHZXSK#_}US$-d3#T?CjCB&)@AuA}i7c|_+O800=04q}4gq5_d$<>fG=qL}@ zYKF>OCV>s8>C|O+?k0_u`aQ$q!jyT`q0sxl)c;01esJ7GABEZKbxf~3^grN?^w&kh zVhr{G>Cav&@@#VcO7S`sd~>R`F=Gl*&)xt=(4)dqgZqC*mLwXzh$8k(k#t(F{@*pZ z%Y3}h#(~s-#Ao+4Bok{5h=TV~0#*?XVN%$$L{mGCp==V1sb`7l4$ZS(->?jr z_TG>R*H5{3cuvS{KPUKdP8QJF*3SLN)}oB{D#fQ%Em@EA!B6HE6|cXp1N+OAejzpFmX&%Hp@5u%eTBj&dmJMPO-7D8p>n021mkF)IiC#Q9;!to>H$itP6%0U zV`8=cl8PQN{LJ=$7=~*PxubylkKpc9%ro1cq>tMS%Ema371MC%o>}AhU3L^!68e8n z>xpex!rbAG67aVF{~62hyIe!E44mPwdcL`wNF*1drVE;`;xIlZk8+!5cc#BXj0Z+G zDEaLQz*8y08LZ%1dsj|SRd<5q@=QRWFvfIEfiM$;$`rr2Uk!hv$Z&L(o}=c zI42#_ga#?Wz8u|ynLogMYC=3%4CuFo-;@JAe~8-dXLg11E#|q3QC+N+vM45$sV9e% zG0NioLM);3SdUNA_YG6~OF>wH`C<5wzw5}>`KFTx!!mkkp#n{hJacoG%yY*Wusm2y@MV52OVk>G_TcsX`Rxz-mI9ae! z;qWRz!>zNh1k#Kz^H345z*@n#V)ASzw|Y0(hqs0BwC@R6N;7h?&4$-+w|w`tYH&zY zQjC6tK6t*|K@?=|rg4-y)#>uuvT&iV(rtX=qd)n4wvu}L;G#})!(3%@8Dq?U7p^FUSV+E>YmP1WD19fTCQ{1$_$ld%c%GpzAsZPNaEF6k7q2xs zcjIz)H;mE|YG>neyh-EcIOKAj`bgWLPl9kX;w1v17I2=7C%I*WbI})x1Q~I?a1)>x)(E~7d;q&ItOFW zgTk%mFNO!GYMaRjSqefV`3#V24ate&5)kxz(8XzNyAiMJXFajfBrNMlv(Ap}3&624Szqi&x zW`R8rmKMpi2AtB8F{$jKxZcIewvnajN8C+*Jr`?DoIcvciTsr<{gh-YMQvvaKNMUVemJPsv_x$| zWDQx(Gu?=!ijYh6IY;9qh$OtE{`kG@p~9J8-13>VY-*_FA8J%BD#~FSuBN=Xa_BB; zy!ha(rFxV9^7U+6XflG?5SNmKL8z%3*#L-~B0P7k3v&mXFvpZCl2JNK>GTKrqKtH$ z@TCaKUMeXuk;(ZMSdXVFVVv7psEQ4(iciryDFZi1T2vea9HXyRpk+Bg`j+HPp@9v^ zuR^R{D(wc&x>N5HkkpWieoQ_gv2ejstx7y~t+HK6RkbS)wFpbrmFeELV~nIeZn z9PrKuNO!u6G}W5Rf5V?&0QS4aLLs3`a;()D(0t1rVU-!Gs*vHpE2F2gY5fOKW1AE^ zoc@bz%=(YKf(E#_S@FLUZ3(iJ98&|Xid*pMm~5CE%*9_xHb41uGyEjg?c{vfsO#tY zCYpnD*g={EWoB*%lXna8xENDNuJH4RZk7&eq&l!=SXs5It2@bQTKj2;UhzICxK^5R z=}dOfu<8?Wv|hW{%YHk;&=f}$p8=cu@}$xCk2~~o!dv(3sh5rwKS@2HPKl#fj3Z5s z@MFa zLNE?v5)q$G$3VR4z~qJ8b>j`0k=|%jH+!zpV-yvlZIHEY#i@|3dw;$wx1C1`rKLDt zj)szJ7uL2NC#U&snzje?4B{IdBCC{@S{DvXZ+`is9o2H*^_0r4h&&A$?P0yq6LC|7K)7$Urz^@k^F;t6dEmZd$}mLTf9Y4F|Wh%L+Z7#UG&yapgN<3jv2A4Yc|YGPAA!`LFq9a zti~}{b*cK153ji9oR~74WYN4E)=C=;#Lstujgd{Lu9|w~apBS`^-;g;5DZ>|?!W~A z`rlTq4^$jdQ_||yrO}U`qZ(pzOaG7_G%^yAkd9Jl%#5a%m$kUHW$+&md|FcJCkYW()YCvA@_HZ!kv(+ z=Jh=N>31M@S1P+AbSNm=sfvK>m*hHU>G@JNA zm?#VV7%)4XZQ^zGyUpy`6n$wzR2b>s8Y@aK>|-)jLqU6F!%gjUKZ;93?-uIl+Y9|2 ze_|5HBw2CJUSZYxt+6wF(0f3pM$~7+9_n9i*EN5zLgax;xcPT_n*(Hz|% zZ^`9g;CkD9KFiCbP{oG9eyK1p^ecAqEYt-^cU;%y6Tb*VmG40<=~L4pwo0ROEh%NFwe+o{K%=4A(Cbe_u^%1p>lEFyK*iv7W|JMt>(PmjKwAqa?EgW4twdL1`juV_tq+-$S!GNhs z#$xr%&-&4LmFk%&ndnbm%`B>drg#lQrp{keHrfF>C3QzwWpo2yi+njGHb}k`i6GAH z|6>6HIIMpH-6L#1NKd|fO~H6iJPctB@>IOS3Z~iru|2wetZcH#_D?56qm(7-mMgh1=Y4H!O?& zlMjwa6VM^|^oi6mS(DkX_(q|JK=PbU7Z z?Wv&RwHab$Ma{8`r{Y|SwzO~<$ynPvC}SmYzm`8kg^^!RVU~lk!7$X3P~={CRVkg9+NuH z{GLXE2$*aYBzZU1gXYghJ{a~wf_5H)LbD!JqkOV&_Ej>tw*IaHB^e?7ej-2>>YqUa zR40NiyJ-%FJDc=c%lo)s%fPHgQsONx0nx%Rj-oO=8 zLdoDL)Fwf>SWL;L-F=D`c!hfBi0}vllwoH$+V~(DkvPdvdrQjHVVBkg6A8YT*J707p#>gEYFFdq zm;M%xpUXylcj-XXU*5RQ7FmA=)ulDjIx{OGSlA#>Vu~(NvxSwR9S|Pjg!0pj*{l}} zpL31tcmqLckBg*QdwOKxz2VFWDzQBF70gkM<%}<)sTq~yFw`)z@ik|59|qMQk9y%6 zqtn7B;21AuRCz&^VX3|NTjurwjZOfkKzQ$f>7YL^yPy|wXl9f1%2P`q2LsDE(JP<; z&f+%gl-_mQi%^zQue*kMZ=Wzlxyp3aJ4`nm;Miug)D}n%Sf*XLsT4=RR|8+TMXO>P zMPWyFL58R(jIQ78QJnQkEbs}n*xo;2J61k>rUg=dB@~(9uJ-=#G66%0;mk3^<9@J`%Tb`PP`ta|Y|DQnafx}#-Ro5I>x10VAMGrbnLeK^@Ti0x>R4K^; z)hS{kaL6+ZQyXu!9zpI7MdkJujK+qBI>U+t#slRlfm=o#w>{?EvwTz@*D3x}>%wM4 z+i!**v6+q2uulE+2HD8AID=9+Qn7Kh1aa+n+XH#IT{+{Q9k2L{@K+hi)Du^kEJJ@$ zf}!{!wAN;#1Ftoi`36wZY0g#tLIy!ObFHgflnjd5-nmq0r%K^5(hAGg$t0*4+(934 zHEG=7l1I{9zP24EE4~-=)G9zs_p#Wv;!#fKDJ$MCmXDp$Pco`7_iGZ#0TU6l3sPQE z5r5$s>d252j@vvscU@cksYsWM4jh~I%LzK%N7S@ia>3#i3ubhW){5lEZ)nhk%X?E> z?x=p6Kq@6Sx}Kg=r-~7X{Q7ZK{OU6C=UBwa`UuWG?7hy`&T3$$6?O;v2q z)*3;4;xYU}ae?pUD20C;1H_?Q2P45PR__1CA@+0q=cq#d=^ma08?mT)cKNFS3&ze% z^s9_Zv9|o29jcxL)W(27MAW{|c;O#%oGh|MM~%_|goBMGeq!kll74jSIBrFVj`-!+ zG#3u8ZU!CZllVDiKQYk6*XT=3&OtV*yExSlxw*4gdUjK?p_I`EY6Qwlfk0R_Zq%|) zZN!sRe+0Drno_LIj-LNIT5(vfxOL*HEForfgSl-*A|wMZOGDHV!D8 z*#Cb1*>UKF`>o?GAFP%3dNe4^k;>a8sDTKWc1FX$2L7QLx#khptyhf`3AF-Ilv!OG_{$kqe@}4C(I6gcP^GcZDS+<-l?DL8 zpmoV~(q)vjsJai8lR6NV%{VW??3_bsfI}i$R@>dX^{r|mrTn+tPENMWoI+i_Asb$v z+wdtKdq*tTG=u3Nd6A@qDnq{P(;PAUs9*JMI7QmZb5ly1Q!V=(f{PE~@Lxm(3QH!V zItgZxkm2skW@4|hNo`XYC)M#Yu{vC139Q~k3^_hdew<9A4y0lv08uMGVrR6|>|HV4 zYN=HZx!PPY?OCS843)}E5EM>zab9pQ1mbxIvzX<19ArfLLRCg;^YN$pT+eYua`ljd zCO3P)1;hhC>O$HlXBF7N@Z?5}Sq+IE3pdu^=L7C$r(bwJ1;j&vICv!nIP7ZMZG}}X zn8rCO!Df@X&<84?IW^%@D!DAQ192>&5|%bsioLROAA>z4c88FPpg*|xmA(y0Sn&s* zxMFehE?<>aTuWRGim(mL5{FaAjaazVV_O%Tm|H1$vo@nRKY$hy@RuV7a-lQ)5$%BH zST?SPTc0I?`&EjHATb8mKT(0wOhs>_I{D!>y01l#x348cIy8FBh z@EX`}Hb-hxnFjd3$R$Jo#t(yoFM22nSzZfr1&}%}T zc84~RJMFb(2wuzvv$|uq$P{HV{(i~)GO>+5u&w8p-Z*d-FXar_KhITF-LeKXlUgr- z1(bRVM}Pt9%ChN=W(^M(oo)h^d07hAD{8~OL&3_*g7`V*raVlpJ9oIo*ny3Lu+ZNR z(ZjYtnjaVJ7SM0nzYcxs(~986Nkn+iKYZ~ALGZcx8a+8cArv5T>DhO%UgITGoJQmg zn#9zr-VHV;9D8^H)VA5|@jQJQOEkV%^ej5>w7T54Rjd+#q#Q!!BIBs@7C~Q2io|~y zXhW55+aVbKia1W>x=tUZ+a|4%DJB;6|2MLROd!P)A4AtUISK06oZ4j!|`Aa;rlcMWfY|h~N z#eG^!(icJ&=5#RNsDtKBJ;LOH3?t%HbfkT@mnl0?KM#wQ=pu8O5n^r-BoMTsAwQ>X zOQcTHtmCK^Y4G5#FsWfifkZU6udpLQ_CQ5SN$G$qXH95^%pj5^K>bz2N-p$Cd5Q$< z=cmw!2aB6${WzvP?$Ng^NPF z)ZUz`fOe2i%20_%3?jn%o5W&BeNFjx+WP1Y7DG-b5?RWQa>>e) z>!m{FN{WJj$aByy1IbIAnL_OzqkIQQ20*HJKs_L^pmDu5sBslAF-x)8+SPL8bNx;= zShH$j3^r}??o7f6huO}!mn6my@#i6vSzSmUjU$P2>DydZr0xkhpX4_Bw4@Xl#X`a9 zbvc4G7h^0(4E`u4Yz5k-eJymrItZ$$+Lt)+7oc7>V35O1umh?8Ou@OL9}j5%rfpE7 z8lY!>+5aqVDpeI-;#|zCL+a8=#WcwilQAIb0t&-6Q?GNyg<%P9PJkpx90~R-edP zZ+^AgP{zV19h9XS&NJCI7$XEZuPz`PUfHk#Sqi~qdh8I>suE?T*gxa~p7v*JyR@R!;a{dv~3!z42+6cCg~|Y5SWq758-glIU3&qdYW(iPE2JhAsBLC zbfk#K5;2kGihZ$5IZ~uj8pikbyJ6qmOOKGEZ(JEa87(XF6rVOI$E|59DaC$I4-D=@ zgc|!LZz`Q00Mc|NRVPED3KUC--K=Z|XzpQ3?X7$OBp8i3OdwF97pW(ZvBp_PeRa?O zgDrspfgK$TeMfi^w!LV_nUQ1PLGgi14GVpFHA&XJ6B@k@ZHN`K7}F{h1JK!$Q~I5N zBqHgi;zW}*(}vh;aGp^pzx$YXn3G~DaT4f`3&;k88tt~j{4v7|HyUSfh!Op|ii~h+ zcIpOh$v;y~b%J$3P% zfU`^{El*o5aI2p6^(8tUcsPuWcuX0FPRyubuc=Vs-w)Eqf+&TPZq{et`D{+KIph-X zdFGWEJlBGypEOBSq1C!QXeGtCGHCX5_Fn{UfTs{3KLt|gsCW)1IX_?)Sa7N}`r7sHOWzLN zAU1u7hfM7)O;C2Dn{i$o|lInrNYrw4YcT^X?$1aUFi9tN51)%wq{R;?=ie`Vq*OTL3;@M7t0%P~*0D&x zOU4P6;(U&uc9Y}A``~@_W7F>M>D0SQjgw0YOI1kq|Lx-`$K{lj`1eF(%(U#*i_tw* zS?bp+H4-@y=&(0hcLi>|avcF;Pl(5ZDsIGEG}T((q7e|_l1IIbpz{|!M;-nnCD3$H zFPCNz$c?Ei1*FQ5u=Sj_ZOI>Q2T6k6JR5j$k1a^{$2-h~11E45;4)(=s?ic6g@7S; z(ahZgwuQnO7}wviD*=+W{Rps}qnNNQ5P)%_$Ut7Rhl^^l5tVv79iH26fPCitf6?jd zp;If>411~tY1EFI3`K(q03)<+dMIhVS_y}UQIP-$A!5_zpS1MhXb1cBHD!SjRw&4D z?d=7;?_c5nRKITE`>WTC8_TV{MbywBv~K@WnuHXR9<>ltSut4Cb^_{fBO|9V3Z@oN zzPBc+$2ynhE-~3H4%mrxyH8M8{-B~pG`^=IjaP(@6wI^D=Z(Z(KMZT$Gn+)CH$eT>E{&3Vae?gMTPtY71K-Q)$}PO!~t9oAv<* zpsLa-zJ?Ml&9ybd?nvytbzEFa(=Ix=26uOt!QI{6b@0I*faOapeyVGA&yvU`@>6~i#8Mi2`H}A) zi6OdF9ShDzW~%gq$hQd0gVK~3rTNXM(efDj&1eG}>bJ7fwH+tu@=UM9 zhi`V49T&=2KARBhB6doe9drx7=>{O7yIp>9t$f`{cux0O<$>rOXThd8S;*nNz#HP4 z!NpWA60Q_tHvlbKS5>(abet$$)QTs`b0e?9z6mx%ZOOIv!roFq(iM$vOE^o^CiH~z>#*{{dfHD+7n zpeFbR!<+XH@`+JoyL>$5L)R!%_$|DsxfxGBpj)J_)^W;@v+m2b8im_quJ@E9$fazY}g_%dxfTV-&4&x>=T8(15?5c)hYid18#NrV%&?pru*OzY zC|9*PBb$}*_C<)5kw)>#D-@=iB6rH>U0E6@mEiVhdB?ubd#BIBkcRPlsu;D@mdTAL zb=K^l7&sh;3a@;OsnaL~#@WborJ-Gq)5Z5x-7ga6*1U`l9ZxP?KYp<=^k0yiiLo@W zI=_)}f?B4eKBFTQQp&6+IJYYH#^35Y%96hOUh7EyE;x`FIh#*Jov0d0Z(|lqd@&S5 z#UZ#bsHm%+I4uSg=GQj9=T}q^fs7bS1Nbr87(k|yUrMG_1@SThsm^~$?aq%?mG#hH z)1)(FjZXEl*vr*a*;Up_!$ktvpq)8GUl+*7z#~05S`{}82-QWG&k_|yg6vZb5*YDK z9G5*}Q^aiD0AchH-Dq>JGqd?~Vhak=JrSd!xPZO|n6%Ksdsiv40#x3jSlzu)Yo+&k zhFPT7(|}s!@cbcB0R`EUYOSsYI}U<)Wu@$ajRl zn@o8KSe|HIu{Q`}b2mxMN5EhBtfeu-`SG>LCsJ(?HL|*!vX6S@i-xNFd;si7VusAc zPSA^4>XJM(4!~_6_adIt1Lg}U&USWhBmzRdKGp~Bh}9?G)H zVeWE{R*1pf;V)B-R)c8vmQq-MoO)#RS&=3@kuSOXbpjijZ{2?wcQi8%{Hago@C=V^|7+kdTjGkpgd0gPE z0n@Fd5{(v6gD3B1&i$2!SNLBDU7 za^(tU#-~jR+Ko|?4Q=N~y)#n+d0q`oyDQwUqLl`Dj{6xF7_y}=;nN!N(FDR~&--eS zzFbyd@Sjw+6tJS4?f75|Mr==sH_CfAjzuKcThVq2{hctX7%Clw=cXQ5)N&n!`bE__+N zUkmz(GNBjTT<{NTy9C}mmF`jQ4lwHRRHMi)a<+VRmas*L(`_0EYmtDY#{OlTHjzT! z+cF_jsHHr1B44X`?lSQWjHGNCmEqFu3%Cy(Uz6QN&u}w`J>Gt^+Umm^{g`a~LaIz} z*l|lh@KbMQ+F}e%jAL4lVg|QL+FEk_Xool*piXdjf}XJbGTis2^x=Te!hsM@xjBIC z+6MU0T_~#hz?QtFh)1&rwD&EJ_<(+vYb_ohw=LsJgh?+|+AUr5R0_Z&QbY9x$wc*I zVDU^UI^o3S`}Y=i!d&S@2_ms739y_NYrzY8k;&Jc*DRM8&DC<5lRg?vn}|yZDbDXG zsJmyacqki5Xucn;N!|s~b`C_2m$||!+}CV9ai+hA`e@Jog+->0;uVsvFddGs%^e)I zc*1K(b;)1atDDLveLvC}&f|f1o)#(>b0E_?bOqnQI^cR5**mhZnZzn< zB-gVPCY4n+@$I>1Vns_gG6k{d!_R#hOFi{ys99^lFX7&5NGHjv*I1qxY{<{8aU+fO z^`%it^o`SGyJE@@%j{U#l^~reGN%oMk#i!D+LN{FO%=N}g=y9u*05B9w`O9^(D(i* zNkjBNg6V2|aZf6$7Sva`c}1Cs9ey}a_exn%zv^lL$*o1cU(oaw2z&>t_bI+j4W)>L z({4`m%#ky@lXbMZ*@GrIKj^lCGeJn>9M+7{(A%~uPFISUapNh;*XNg^H^!n}N)J`O z6MbrCvl-~xsDp6!GLnGvfPSu!r!gIgFSx11OPnag8Rdr97Vfl3cv_P4!?NNUfpZm5^3N^I7tOe*+Fxo!`N6&1up8_(MNiWFy)^Iv+*q z8B59c^iW-QpceBn{|Texz7E3z4aS&e8DDw>F1mga*2jM9QGqEpg|i)Jj1`LMAILsI z+DlbocWWdOuhkOp#ojWZ+!)W8XX0^D1PXOP$u`ELErGC=fsQW*5>1YP>(0nzDk@Yp z-4#zYy2iZGpN)le(eRrYy`GDM;@J=nw4sq>t==b;cz+N;jb1A%`zaOfxX?-%k=mj{ z{&|Q#kt8XnP51M9X#COj+p1L~H7*sqt3F|U4)vUorkykt3R~{1xwH5vEIVG79&WqaCo4jjiPp3cRDYmj;%+;bZ4;C=epvKMnT)5-Ur; zKXo$c@+8=p5!8`hd-L)g5+Wkk7$DE|ibbdrlgL{R9h2;e?xcfqejaxx20HM~O?p3v z+;;@X22pf!y)F_uJfbcyFo7bUYG?cV50bQRhR_&Yp)sJn<4Ha~)%_7^aTD^SZxHvn zO71DjEGKn>$+76mX3vj%x#QFA-}REL(!O(somu|~&3G^4f>R&nV`9i~DUuj;K2)VeFXEM?lPeaN56Ceoj;Q`9_SVxdlLlK_G~^OA^cFE zk5htYA;Y2}5`@J`#XiBwmHkm*Pg2kM`6=gnk!HWwS9mIxo|6e)q@76ZZ~@g=<8O{@ zokneza>7yoSAl3kRnAC&Trpa!{sv;=MQUn>Voj zmP;CQ0+%WRPkQOeP0aauKC7W$Vxb&5VRNYRy*YSY+pTls#+5wr3R9wSO!+=6DjIMQ zHf&57rYep(^?-`?+&akA2?in?vOYJ`$%;9&62ijEhqygH8NPQl#O0^g}`Bqa40Ge+A~e z^zD^>m__$#7BRAk0n|6CUv*FPDPGq)kCr0V7&<6+I?bflg&Qb1Lbr*AqT)kmynaF+ zPV7~ggBl80M<7%G9w|T3C5C+?NC}sZk(y?F6;n>C#%b-&eXk{Hof`9X>$LNr z#uQ6ke-;)#4TmgUX&b$>H;cEk$K$-p%~ z&Y3oUxZc&>bth~wYq!lLQx%g83tS6r$Ty>PCS12?x|YNO>$3cN!FQ+LunEf>#<^^v zG0F+=!bj%q=6D(H9i!243q(``ml+XPvmF#_Uul`frY#NJ#YNaw!P1N6zRWf)6#vSY z-!f~{f?K7dj3DWT4eDTs){3^4qSDxvKU{8zQgo1(_I_shb_N@2BcQwPS>=M)89luG z)-C(7`V>@NoRKKyEgAZT#pmgS_#vw?&pe}ZN%$zhvE4pWHc;VntkKf)%D75re$5=c zsmnn5JX|J2&5BiC3NsRWrV`3df88snuiBW~`+H}mA>L^Y4d$sKydx{P#j<*bTzF<+DoQrZ`04ck>Sg=PVLmdKolKG(!!V@ZhrfsX2<+Mo>Ti z8`iouq}z3!iNs`;Paon8vA#!iF1p-Z8Rbz~#O_Y%u3kUUj*$9Pq2NemF(rehw~DDv zPh_6QCD>O41N4#WG1fpQCTx$EiXKE)L#ivvdZEHnKB3ZUdG7{meZL3PfUfiZ^hAS= zMy%I#%L|JWtjQ$!;NguuVgLnJR3MOe^>zz?8A^*o>0u>(;gkOuU2F z9WCE;+*|Mpu(rtsJfmvU38069Mbu>}WWRTgaS>90dEx>C49BW?I!u{d_)6WjY*-Zw=Z-o&n3o;x!r$@T^SR>QNQZ0+Ae zg&aZG2_8M5I4Zu)E{PA5VK03R z;o-ZLPTj0sxt@fm1fkVe?~0?8nM>H%u7q`RJKUUMcC8rW5gOxX)I+FYIkqcB3wxhh z?~B)(2>lx(FJlhKMzR1nU!sW+KEFuw6hQ~}8U$>iW$U|FM!s>|{)(6^44zRqzgF*@ z@;L;7nF7UpIpoB{Jx-Vlw3Kw;Ku?~es532F+Xy2oMZI>dMFZu$koy)f==5z%!S$6i zGc4}2o1-5W`@0Lk_mVk;I!^*?K%W1_8vf<&hdaMho?H4=7KiGjh{;g6rkcWr0yC>Mi*#lAlu=uM0-OmCR zF7E%KfaRamwU!Wy0$b1@IblUMe|&O$l!ndH-5TU+ZRO93vBlEZ#rHd2_~ z9qtUVkVvArJ{MNCbOl+-KUVw41@-P%YiWMAmebPl-+RK+5d?OEkVCzAY-zcjm8GC3 z*wfM49_$3NwPy3Nwr~YQ@@!n(oj{&ek4by5GeiPsOKZsf0Ty2;S3xjjFxdbh<59qZ zo7IBXf{&Hk%94+jA1GkO%E`mS4+8Ok1o(NaTz=MN2I)d;caW!xyDy=dtF^O)0;`0& z0>z`IS=~L@foxnHY+RoILTUzfwsAQJdw^}7L7raj*1m)u){ZvRKlLT@vy=Y3W&e-C z7*=TMlGOD0>0EtL4cRm$|?^#(qHM`DVUkGyyS1%(O+04j120SVw?bYNjnG4-r1q#nnH z7DVS@XIm)=jwe8r$9pKKu*%lX9vm1zw8tbeDuTAVm$fU{k^=>Z^q2#W4XvXEB*TBg z$qnS-0&?(har1B)LXtc{4o-oeNlrtc?mv-pphMd7lNu)Kx}vfh)|zJprPOqe|~_0LV%G3z-qw@>b0I|1Nq^F zWRgJ~{N8A2!1q$zt%=#M%{F{57s5Y0gmZl?CH69~^s7Q`q)0H=f&GQc(vRS$~v0K|)Sc2GHy(}ETmJo|$ zhnSnEmj}CxrH3m-I7of8u&6-f-+W+TfuxU_Xs~#&IIvGTF8KR!$_jdtP7)O1dkyCF z=YRGSDf(lJVPT-*^8cmRVZxxG08~s&8sNT&cvX`W!WOtpP6UQ!fo_ z4+V9N4`iRv)s%){7BJ$nS)_b0Nb8Q!3gvf{I0WqO|InFk!Gv1j$X#*htjldC9vn%& zK|XXtnghD);o2P8=*i3+^V|h@)+v}$NylrgdTWm@O6g?V%w=}U2u1=i-?8%Wj@?Jn zk$g^JVd_qCH@%1{-LE>R9s>!+7}0S!^{p&@)yY}Z^{gamz~4J#>%q994l#XM{CVk_ zSG#%C*;wh`HD`eC_$90onaI7Y%XL^cww1uG6I|XPUeh+GCAWnkmR(JB(*s4lZx6JK z3TDYi=M}p?-!1aBWNP2^^IikkAYd4*AH-fPe%TB7HnC?oMmJ#1l6;Wb@cJpYf3goA?t|*2gmSuFT=j4wo;AfArd4yQfFb!ExQh?u8v&hipo`%IaA$$L)vR zVsJR?|FVo6OLg5_R&WYYNRb}Lu575EBg9V&x2=k1{dc~%M^u6-Q^lQIdK_P?w*`i*xA|FZ3cH!b9&03Y7PtB$RixdH|I!?-ZQOn+eCkO ziCq_1{}tpk^1*rL|!49xha zAG}vye9d9x61Ui)E&4)-t23}3pbjh$Q!IG9l;o!rfKpELX^Q!K+GINPa3%CKdQ)F_ z!$r3^fa1;fA<^-ffq@#y=OB~n4^o>KRq6-@FWSQPQ(Po=9LrzDA&{Jsr}xmVn6&pl zDXE6uZ%u)0ExO!e=H+(~>kf*JaU#J`&-EFiE0AkkBcA71{Gjx#S%j)#306!qqR^!g zBj&Y`X~~v==Pf*&3w1B;O)^&%{+m*B3`~!&??se;+f%Qgo}|>^4UMnT zO(^w6B3{xt_SE9K!rcUxDPSwNmQy#>SKgOQeI1OPLfA>V= z#n-sm$pF9@IFqv^l_Nc~i*Az=iIixw#n6@a+cYCqUU9ceY_HR58xrF~Sgfy3EhvVm z&3`-ZPW>U*SlXg_DU5!5ywC=M=h_~g1z=APPi+?m2(pD1SbBpT4e$klVfeg22#P~& z6`%D_#72P555p&eWD!9_!Tg+vkjx|Hp{!wGp&_;n3=M-v`GxIB7*MeJ9sbyV{0b<%0dIEucV}{m)(m2R;xNVmq+N*l5xaXI~29hO2YD zTWFBsumJ7gg%jH@ZSR*m$=AGgWsESB9d|*A*xyLNY6}DTSs`@~1n+QMf;0m`E31B= z$-TE(l>JL`zq~it)-Xu;pse*{Bx`}9QGYNXeCXZ8jK&y)q27o1NZ*S+4g!^64d0o7 z@MoF+(d&oTm$PH9F(c%wgR1#dIQtWf3Y2Oy%`E1{aNZV_T`a!~B-7iG$4~GR(D#+o zK8%NFgb`9G(Iod=fbGEC3cx>u0~2|Trj|*sK;BT7D1qk{#0>L8din`-g?@@-lL1xv zyAPB!OIVzWL_B7cLliJEfo`qBieL|~IbY11-r6*koLzno)0mjKD^u!e?}4j46Y4%t zMc*@$9qP4${FUd0mZZxTP8Z;fy-%jO=#y}3Aq%32huz>#do5|-ltaoLcv|mVIK)rP zCKKrapPx+ZxAKd#Vb%-`(z_WYP(gxpUqe?hK1Jr)=r zGzQ#6n-WXY1GQvw?|dVm;bRTtuv$P3pjy64zEY&Z?-jPYBNv+$#3n5vHu=+Q{cQ-p z%utT|ml6J9gMT$aDGA8J-Vqp@_}hhrxW=K+fT1Bkn%_AP=a}GEO^7gpnWm;xn?0$0yZ(~xc6Y3&ySRumwN;@OWni;i7Q?ek%JF~y; z_5*8aiHZ}GuFw=bKEg0ITO(Q2$hN6hUH7o@)KCzv{hQ79h^h)h3TDN)V`xy+LnW#> zqUYgYQdBiN&9q>c?^D6HFfnZ$=rhf>7$)saB;B}qxl|38+fkpx1Ok;Ee65RKhdEtZ zB(`Cv4+tIEK%tEgGu+f{uL=a({ZMU=XhJQWxSfEy1^>xy*!->_wJLC9?~}AGO}Vz= zOIDwbukWqakw;Fop~4$JDP%SeY^RSs5L(~*em+ef(bXA&RTEg}s2gDWO#qbNDih$>C|ecmL= zwV*M~7sKJj?$!8ht;5AeEm_Fxp>=qdB;S*8)fa0rNxO=p%R9d#zU4<>8`XubpOc66 z+Ckv8YazM!?6~8x@)NUtDa&@^D%I4FKt&{qdqsSS*$+FdPJ48gBj2!5si!Y>3~z7`tofH}G9I|dsjQ=@+wGC+%eh7R1H z3pNOJp#C0u)7yjq8^NC?egoU%5(9#6Kj90qLg3{3 z2Ym5ClAOQc>;DRd{#W?c7s&&!@TN<|T|q;qpBAR)IEk+!L!)43;@Nnva-max-SD$3 z!*k36X%fy&F(|<~wIFAEWm$3 zS|VmKz+TVqi0^5hN%hcHxK*R(vA5g>=o#UXS)(=zrJXY$Wt(`lS;Mko;O8>|EZal0 z5=+Om(q7UL-Y3S>8^q;ZR-WQzo$kGmGcEN^`YKQFXA?Y9s#yfSm&nf?)p1}~=rN|# zvzd?7n(go)e$x%qHN?C^ZdpXn7CT0rqW4<{*&)pC*+6!H#!kG_a>QC;- zUtSe0^tj$6H*`!r)=a}>*7p==sPd5d{8h1l#DTw;@no6;z&6Ot9$jJY_CF zkuh(lj@Q3f1TClB3v^yJ085l-w=)&>MJHjR@82X$nkso?X`~Fqp$ZwrQk8enAztBN zV8H~MQMXyGT7D~C5k2qrY!D+u^4_a5k@fm+UZjr%#Q;DH*FN~XZSu(!i1l7Jw`tP5 zbpp-dCF%rM<4FB)d^3UIn*k8(5l4OoO%Q?b5FmSWX8#ej_>ai+SLoWCT@9OXGqFP(RrGU_pJpTiPd9+YDZV0-0 z0;T>yPGO)RkUyU{pF5KCH=q9sIsath_}>6{hy(vSf(LP*e&8a2PEpt4 z6tvtNwj<40=`KC$$`+)kh+*Y;?<;giaXsRpsAxZ@fyLx}ZrnX_W*@55Vp;sEB1WXl z_RX6s)sk#-S#rO{Rj{lRiwk_9Z=W6%83(SIaWf?s;zg)0d$|x{+1LG6P?G;`28%Z*EF+++X+f zcXipj&G%u4AoVoojYLJ!w5b&-wEEa2{cwM^tpp9(auSrHRV_q{sDPe2BH*|5Kx_Gu zo<&5=q3nmvM~M-&X7OxFO7XE4Y?_fpQeVNf+?sW6x8zwmhc$kWs!}Mm_hLf6HOe(N zJ!$^?$YxvuQ4+p>{M)ZqmZlb37WfQ!(8iQ<8 zSWy3tC{AGbSHyY@`da_LMXcX(S#07z5lh0u0}`dTqEL{g_z7CS)?ARt7G%x&=bDRy zg98%z`nBfzzX6>86=rp8Bzo8_x2>5d3InNtV6MIHGFr9)N|EE(IfL#M#xfE8D(|e3 zT7Tm3Cp|#&6XIfiyj|VC3@6g6hNs7am=kMeGjPJ1=$hnxxws`kAMz^n@C;xfv!&At~?K>+f@d3s300pTfBkSiqQ`Ju?mz1Dv?p?S( z(>)`i_T7Qek!o&nvwA<91D{Xw>RlW?qm(I*UuZ198?+p}2daBHwnYn7+M0O`r)=AOC{JST@1xq6-}D zo#`!NU?S-q6_Ww>lRx@ah(&CZtVR-7_*~ITW`AWWAjUh4n$mbT7}=!0ef% ztY`BT4W#SDzcC9Eae?Cf_b}i;0P8>E(O&^eX1DP5Y4T7Q2dl>OvNFn?6Q@t;{{~o3 zf1?Zpo1B0S|4nR?`dtcwO?V*q z>Vhbiy$I6^qBg3_C?c>n)qZibkJEc1oKtK?KX%p@{=?IUkk-qBi(~g?ns!Z?PpV@~ z%h#;BqgSUF)j+?j=MAP)qvKN2*;16d>Fx#cjW36C)XLytuzl#Ir#6GP49br>*ilNi zkGX~SE@1kj$|sb?33+7g!P<3i3v^%X6;F$7)i2(W%$@oe8f(Y792TgH+Ohtn3F)m3{lio>*u41TlQvBbu}2A={StiS2=orcA;iH4RW)mcA$E|daKP!A2cU8 zf7Be6erGgpTuty%CC{S;sv=LX(v;zQYbr+=j5p+IoI51?2K4#A65)dWk8!rYMEdeW z$ALtDx(-M%98dl!S_mZh$2~X>^v6eee~It`ng2ozjmkj+ggiC`LY_V?Ohz17EKG>{ zubsQet4GmHM;gfh+5f=`mV*kGg8QvegS^;fhR2Syi&>K^5Y9tsSa_F;aZ2uKwH|;S z4#@Xc?(7^4Ksq1=1hrkeMuF9>U=Jl57jYzfmdguReL zioeLI3Knfi2e$7D;rJcS^&}>ot)~@Wm#_kp>wAfABYPv+f|cxPH!M4u`Q#-!H!4A4 zucK(nKt957(>AQlI~T7H=V`qT7Y(x^@@YKuxTQvR2!cb}wn;ngQ#_jhazqz588nxw zy)kZ{sr5N#S@slbfx7e9`B-pfnREesThqKsXQp6<9dsHTQX zzfga|%kW?xF9?6usdfxk8Iw+8-y(7?IAxaJN_dhYABPg$Ez9Bi%cY_aeq8H z%VG6=lcH1$W-wn#t*{k*Az?S8$$d;L73eSo8DMMkJISrNq|2%@q=4l5x#^?+JJF-ExTSImMfFWl#+3a1dZ6B42V_^e1R{HaB zA^RVBP^!@uU@MDJ$bF69A0~1%nCoumF$( zz{A49!@*K-qhjFW5D?(w;NcOG(vlMqQ%H2f$*&VNr5Oz+-EG5UAX6I75>P5UC~WyKptf zk7&3o+{2KN@$d-EQ85o(kd3gEw1q7v}Wn|^#6%@6!b#(Rg4Gb-wPyL>SM?^+N$HXS5q^6~3WWLNQEGjN3Ei136Y-s~oe~}B)9Z=Aa<7BW1 zk8(jl`#^qSFk#^+IpDD*G!Q^;*i@XMh&Yl-1@&D>)LfcJxEAi?$apl|E3`izMf)k) ze^0Qm|0Bu%6zm^zeF30Cj`%=h!e9c#0Pl-WgkBU42H(MEotM=gO`WCCf6t#zF?;~@ z_m~|fY%1(wUM3b1AmG9m@^UDrs{zS8&adJn96{SzRt>s4{P(!`l1UDrVq`#K!cw2u zyx8#68G9i%i550Z%7N7V_5)xn9>vw4GOA3`3TyR(fg>UF0ic-CS{#Jgh#Gub{{RTp zWD-Kx&zC*5$DP%NQT~& zitR()ZrAjjWBN^U(PbR;-yfcF$12PvWLfwi$zjF?qn~lVOURHYKm#wTaQcjF#VW+uaVu;PKGI&{v$2m8K-ewvm7shtdVptfy-VbsSP#!?-34oQkDy^=siQ zNn`|s=)8n+?fMRA*O<1*_)x1ktIH@7G)xevRh;#iZ=#R=TY^iJm#xX|lAdtN%HnMw=e2*GsFEbIi}%x_NH*1E6N|de$}KsShSkEjnq7Z> zH@agd1^JE#I8LNZn2X>8WcjiNiZAG@P12kW5rDFrXIGlUy3z=^(!f^$h|b#XRA^gW zEQEEa2=;>36ev5vs>Hk6qi5V#yE74+SG9jA>`HMtbZjrz`ii zzkL(8e|o_qqIkm)mn_0{iZfs}+&>gmwj>gD5wE5>)8ja@d#$b{ee)?yI6{E_Qm%=u zU3c0j#D_T$SyQ%CYAhla1M0Zkyy&6Uszm1x34fesAC2kG5h5=4#>OL-W>M zPaD&!YAm*K?Tag{NDR;~PPKq&JuSAsAMBieX4b5#T<&8dvLTKyX(Lb44)%SaaCXbF zIpY7c?Uvz!v5&woXy+n6$5y%_7JRzTtUK)-pyl|SC6Ttvma;x=<&3N_!4jeH zMd(><{q(1v`WN3JFHdp}eBqVmbjTV4g}dY<#}wMRG#1+DcDWl>zQU&9=&{P!S1eL# zSa|=Ydd_Zqp=PgUW2iNqVHHdg8rM7xySm)wBRuHlVF>Hj8(#NCG38LHZUZ{HKd396 z0H*vjpJgyTh-t!ku^(shz&QP@0~1tet`Y-GENg{xcI?z*=<3@rfdZ#-`OxwA5`^$! zWY37MDa%G!Jf1zN`b5Y4c>+M=w#8UT>mj7_11_>a*v)XsIa#6=Hz~!p&vy~t_p}V; zKH%&mua<%5b`gkBQNie_n{>_#{Y>5vJy~V2AjusEY9`B?C%;oJzp@pLHs+;w4=?K8 z?q!z3j+P+?%vP1QE$D*Ns#H6$OOkOsNBQq9BG)9PkHz`8lgntG? z0VeB~z&TD&{~Pi*=xrBw2ql2raM)wNARTFr;mZhqQ-h9hKXiLu5NY%f8DxLtnhs#U zXW^6Y%?>}n+dds*M_S20NP7>}Q+a`IyBqE6KC1u~_F{DU{h1JU`2z3NLf)0l*K4Gz z+#anCAS&{w#Vlyp@!)o`98|n5X==M-4*kt-$P7$L=8)&1Zr3C7gwud})phey?+RJA zVv7$4sX>9GnPaPCYGOxdOLl2HdG0t!zGLYDFdwg=*MM;%6SmfI+$UC1GSzqxom~Tq z#dtSBa8IdlVC+rcmSur!gz@$P01i%p%*PyD%4~vM$6_lA2a(O@D$sx++10oAjhWK+ zDRG(%pQoF=CN-VTXVFRQ8wQvXlHlbbbLqgo;yMFwtzmtliNQzrm-C!4_#&=Pag0^$ zdB1-)7)0k8N1zAy*_*D*7}%RciiDsw8hg`Z9I)5jmnP~D9i2Z%OR{Cr9k0B4qrmm; zjHT!7+B^tXp%{B3cm3+yiPJJ2KU<0K9-6%o95Gc!)Y&KQK<5RWww9B$n)J_hVe z7=X}R0RV7ReZE$Ho)6Ej!QP%rW~fc~XfermtdCd=i+gq}{{9^yI>)n}XU<=}E(&?r z_LO|Q<;!Z{PB-mlKR3R@Z*g5bV3M5!5oiW3MZrvpM#h@8S9vwy(N=PkknN359Oh59 z+rH*45at;1f~6Z?6zyfm56jQZE4c+b3Du;zZhuU|n{L046M8Dtx~%z=BiNZ8`^ z=~Cka=Bsd)iXqFyjOwyXV}#6M?AYmI%~fZ|iXK@QebXWh z^>lQ0=dh4Qqzq%|85_?zt4*Cayxt2Q^!YQmIOcv|vi4o@=qH;OkQGj9e09=Q8k?p1 znQdMEuv7^h$ykjwZ4D^Qj9XHx8;}_bt-6}iIzWKKmtR=BsHXG^RlH-*_5na{pPhJa zHmfVcVvi=Lh-l@$%6y8;Tv0X8@v^^31MTAmsG0pjtfG+GAJe9KIG~?aYzZfEy@5)! z@j(9KrvB^(GSrmI>Lx_&qy3QO4rX`2UaSGs@;C#IPdh`!mR;5uy_m$12&qf$lj7#S z%gq;&pN>BOk|+GrRBnpIascw*0yBd@1gEK|_j<$RHV&9y-Uvd{i3JA?dvZz2mZ4435MNn#V$TQ z0Fdn>nRzY4Z#!H!*U6XdWjq1>4vZ85*{CPByq#`R$eG@^Xl-QG{@)8cRm0< z$QYzJ4o(NiSQjhTnH)`oe8f!YbJL)=0jqLmIZ>UxU*iL^H1EBL{{(q4HY3}HY1M0y zn$da3YG;=+09SOzdcvfeu+_CL4+losk2X?Ipbj&0ubuHk6J>el=jSw<)Q-uF_zr!` z;W~Zx4q5C{f~h6~?w>lsEX?~Uk`kYeaFT_+*_YIhQ0G+m2BmBoMO}LGUJrPeKeWXHnw{3fqv(h18=6;mEEyQfINo(j?s8aU*3x`FFKd8aY2?0>rs|PSr56IH zPbPBXHYy}tPx#mxKe3sVXsr+K;LZVts9uCl9nI}8dQ+8p7V+4qut;yU_0*8X)2$f{ zlPQ|#>v;Fy6wF23##^7+&M0rETeH7`&GS&Od_fZOR5)2x;nty)QNn;MV3R4ieQ93x zUIk~s1+V6u9-L=|;##?oO#yNjO;eR17}`tP`I7r=Wy^_ZPs)kFV~xgj6X65&3q}vl zB1J7fq72tBk*Q_#&Uot*Nh_4;5^<=x@%^*0ZaD*0WTvG!-nm2*wJCSxiVbgJ1=r+= zU42E`Vx7Tuy||i%A8LsC&}}_vz>JOQ>PRvy)pOJcFpHX6COz8&o)iT9u6t!`Y_VxIYM0ME_ajqoqD zB~t}Ys}>AU7Xw(Z~6ns(u$gwps(`m}?rEJdeUV?9sF-i0M2#s<@S ze-#@LRTtRqAgDNh-Wsf%(^?Hy-b4I686p<0Q^ZJ_J?h$VF`~(tL?@S8hB9?tHih>Q z>D+9v0-GLWCT6ROeaLro!}`P?BjcTTar6h1)R`9e17k51XOz^M9$MAc?RdY|1l#w<7Pq(_FCeWV43!uwlLW9h!7p_ca>K~e{umhsa8c9)mo3ZW5%HuU*0 ztvujN21V6Rjn`f|cd0eOV4NOY<_%dSv$7K97VaX?;z<&g{7{7X75QiG3pl8GTCp+q z++VfOc8K3vy^o>JloEc@8}hF|Jl!@>)Dscp_fj!AumES7Y*cwS zfAMN;!ETX`cX!*}6WJlD_S|pnAP=vuc>t_2zy2b{u=}K1#eFak1(klSFGGNjzV&D` z>iZ&xQ+`Q}h_I>z0z6iRkPayBt2V#_pM)yaO%UWx+@ z{Z{tqReNq+ zXiiI+BIk>*C$R9{&E1@elAsK(R50X@ao(*fn8hw+nFZg6HH3yo9wxYH6-giHkpO=T zSIsQ$Qj%P!)&BruQnur~DS0jb0ETqyyOs?!kb@&*8DX57`em{5wNg`Q31Q+5J5Zd% z1o)G41QIUaGy{yGJTUrFO%7D21$iS*a2O6c0ZRIkmV{{@5DmQjDU#6*<)@niCQ6n< zGmb@NEp|7h?yW^P($6zpKC+Ewbl$szl6lW5`eU+xaaIaEHVFc@Vc5?M)Ky zN=W-0U-i-d0FqJEYvCd%9chxsu?E$6{xkfA9YQ-19=`O%#dH3SzPr!zr_@Vg9Qsob zyMTr8KZ$>>E~tGO878RAbPe)caCt%h0HG}pphvIu)ew`;Kdn(CY)gq7JAc)`@Q?1F zYENOOsJTAhkf}5!wk$4TJbm>)bb8Y0CAu65^*8x zZIHP>-;khJCvtK+ebwg!`=YZp=rsNxkg7y7W&kXGKJTZwsp?wzL_InW$Wj>Jy6?~lMzyBkJ#+4WI)8LutPktb|o+-?KijZaeV6$>S(-FnXA`$f96 zw3Q*anKmqico{tO9+{w+H5n$+c}2(BHVHNXC*BG&eYqH^mZMI|mhHhjQ@I8{p?#uh zTAkGQdbBBZa!%uS3-|DvL!fy)rl@R0Z*@-_zVrm@X`kE{((y}3o#F|swt+eyWWpvyK zFQI#9C(sE1&*7T2uE#v+PCKJt1Id5)m-!l`VnH;C6mKMq7BmH!k3a`gKqsw_5b@WD z-(1wT#!A`4m5mSngnjFtay^VK4`&;_*k~RZ6Y0ZQ)*4teaK$4Lu4cQCf6F*mx%F;? z@TfO(v$EXj{J#+`vs>8sUOR0vP%&*B*xWmLj2IU_yR(DP3N#}3>L9VzSMT^TCIiu% z`WgdLONGXZX0!N4d0lUAKhcs$U73y#n>amj`c=S*J9R5hnjQxj+bou<-OzV^OaO>DoMuf2q27e^`@SFA^6lYZw&PLBaOoruHr|d)QwLX*L%g7q?c^V}&P+62!^N9)yy4IX%sMjyn-j7>3lZ zW9;x$qUfu}*DK4UXnIA2cKW^ZJ+g3%dvZ#F*+S$32Tql5Gsj{*+E@86)X)1n1wr#o zFPWg8F!1EZVb(10csrkH{LOMK1}Zx8Gpp-VUW!O%zlSs{y*(k*t|o0ff6FK@8KYsG z{KUtX#?2BgiGYhwhIHUP;a0u)4G}6?mE293BtLsx=2=z@NZJ>X?pr@O3cub-ydj;{qg+h=p?=5IZpzluqT1(Oht)~ zW`FmMBH0rRkF5lgzg7PLb$^ho&H4%3rBN)E*lkbuX>}UdVIvgKn53UAIqjd+)UxBT z5jk!TG=@w$iF5B$)UUFPuM$B#x;@9($rllUEPHyAr#K%_2%sZt>M|?Eu+i+=WS(hU zDn|@zURk6dVN`W2as5s?sczb`p-xpL%8r*r_;adjinl&7yq3z*xm_Z{UAP{TOgUrf z7X#eaB}`IUMD-}(;RUX&^iL1?XGHMyLOl~ov$7b$Xkr1I>fKM`YnoN3E!ozhk+E5f zC>d8&(`>a%H?`EQAiK74hKhK@sr(H!IY(5)(r27&9}7jAJl+V@WYXWS*e(g0(fxow zG=Eh;g>_TKwWN+}xJXp!c{jveb5+wU8%fo)*);u1M)|?{;&{jLM*SEQ#yXBcuCHpP z8^q(IgOxhBmdBs?V@(>is3W=bOPSWHl+-P=8XSiKPOItf|1;UGOHcMAM zoB|0$*}x;Q$9Zu}MqfSBXsm2JKBp_DR+<#hpsTIvG#)iuU@b;avTe!A^?bk{t z`#WX-0C#WL9ExcpIXS(M8MH3+dzNo+B$@55^;vPFYVobK=@|JfivIwsWM113ZsTx0YacUc zJ6#+uv8TQ`1bONrof8)Kozaq16HeJaflY4~ZZwyoiaeCFM8Y%tWj5Y@pkL@(Q+elmx?1+wucejIrBIWFQ9!kBT#8;K--CZv0myjiMGr`g1o@yZ3mY?A2|H`$YnzTTwcH~@e3)4jAD zQg2pt8dj5}!>3w*XTuZQgc3z2Hx}G8zbBGTbDH@4ULvowX|wGxn0jiGb~%3)c*(7G zNc0^G%6(E|BQnT;cL6@Wr`!4TuDri1`p@qw{6CqFVVFk@xna2#w}ovjq&GK#wF~Fw zOPimZlk5))E9z;}jX3hbSsx)=6;`XfnA=-;Us}C^;)$aP78IZ(qyGT)s*#+WloPny zj|{~X!rbVxHkp14VrU1-dT`6oj%zy9Dc6rJMRMq4t5AH=W~YYyP5q~HK8dYI6nd;c zsSC_=fz+?QI;pRd%yQ{rU$m#UgG%^?+{{X&5Fn)%(ygwZOLBG8J8nn@S(tBpV+s#b$)ff*cC*myVztyF)^zrk^Ubz9mPGYC zNeA<%bD!O_?sX%IcOX)Kb~x^7he3q;RU#M`{lb5|f2AEo&|y4vrY1;F`XqTj^3neQ zlF`&lVj_P!WRGqw*ZuSSg)mzYG3J<|Yi?m5J{Zc%Sy%efGcwZbcpXuCIkZvyGX!&1U((ZgEZK~)OYqt7pl@d4@-)@5_ zNBbe0|@Gg%S;ax7JUvk6zAM>uB(Kuq$jY*G|cWp<+TF#v! zE7_Ef3p)bqtul9FwaN=_V+?o`t(ELB@jxU^}sd4cj{EJU$MpY>V(AUXNH zJ@~BYu5HchO{HkPPOeW1YRO{~K80lVG05|z5Zp193xx+H@Z2KsDy)%$t0uQO`CCmn zEo-vPPdf(JmAfG)8#9B?Cj-|MsxDILb3I!vt1|ea(%V)k_I*y%$`PQ#L^496j=*vG z=CU`v)z?C$iag#N*R(s4x@#9{BF|rx{Mh`D=TgQm=@t-s%VvD`9d(nJQ-Tz!*IH)eSvOjXEO@oH4qpuv`$QAbSHztwAyk zb_JVFk`^4y%Md=Bm@oCLtIyd}l=~xGJwItyFz&|q(^?)I@Vv6xsQW-bm5;VAuTx=+=gabBXs(QM^yd@TX++Sy$<`ecXBlIN@W$CCN>Jbf$d;T}a~d>vZS zokuok==z?uskqWDG>LT$W&{Fh71lVE9**o!8SC%Zb`?1&E6Ic?dq;J#s-F)0acex+ z{$_zZvf#D2npHRhfUH*;`qoPk1EyG2DDw6&bz95(#cc>dHj}Kg?44xf!x;dVQaE5h zz$4f3t<;m)S;?6 zDs@#vbPpt6C-8Zh6aGG}$=m(c9lr)2rF=7s!eY65Gw;(_tUddpw}mDX_-jpZ{{TD{ z4n07@{Y8DmeiYBn>n9qG8x1<3JJ>3y9GYX*=y}(KZP)D)DJbn7<;N??$iU$KG#gZY z>CettcKmJQJ-FS?GZ-ajbE0CY(NvN+1k)vm}S}5&rNLZ_h49my1&R&8m)$dng&cy z$e&N@X%C>8phTJZ6Y2d;M7JqyMKQvI&NGkCr9)|I#;){5T|Qf_Sl%X+VRL#cCN}pz zP+Z3#0F^38Z`0Iu2BFO@S=IGwjU5kxw2Ac}6vZw5jnoini|6gRw`liF*;K~hH(+^( zjNt7xoqCDG-DgG?jG-pxk5?cJ0($hW4O!@DgKbL~yJ)Z(I=6{*9UkFe)wQiM#&#JI z+}$K>N3IxyTE>*5u8|zd_PM2RJU3SH&uuJoLn4MrBVfwF^dOE1q~PN0$9s}g*k&h; zd{3r$Lq&aCTX|B+9Z;C#E^>NsD@t_TNphlPB^7aOJV~SKTFlx-rR--_h6K5i*@0JB z*a91o$oZG1agmy@XC$s1$nu}sE5kQembzxGXKN(a2{+jpB2w(?`76N5IAQ+)kJhwb zE3-42x#Zf0lj6HwPAzKl!#b>D-XARqwU8=da0%m%r>%4;+O^;2L^baLjF0ufMOk}SoC3|o7C+%`==@%&MjA_ZOdqH}(zH48KMl`do-Rl(s(9*g-l-fGC|=uI zNbvo#Xp}4EPn)*~2e>t9lKGa#cDZvJ_&)yt$-ZB;M`G>@OgHWbPIx)b6cnJ(p!ZY#;!%twDp`21dDUT##I(^P%V8!n**8j3oY{{RxflJCPP z;F$j28G!GB#dKt9^(14Q=|IwHPBFGjpHw2gqUiXBnb!Ob5swRM8iSAYtGoH0VtRn5 zYOl~JADwaHFU)$h5{yk>?TfpKW4bcBhLZvu{GoGzG3)P3+38|EvwECQ#iFoyo5I?d zAH9*}w2=2@AQk+w-<@wl>qccK&YCF6cDcY3A5pQt!n$sbHkf=mC;KM)1NS1nnG|{g zbNy@MbERxm(f64&xv;9{PmKICr|6y;yT8%}#jVA;S5mh!W0x7=wmX{JF3fAxtr~N8 zM>b)X(1j^ZG1Tf-KiU#?e?x=td+xrJ{{YlV@C<*vv?gy?;rou(_AfqrWx)PE zPj8nW)`J9HDikPId<6jRz{$l3nQk1`kT?fjOGq0nLLOp(w303OT! z`TjzfEsDjtrXoVww_o><@)W^rLe#)ee{lZ*?=SK+x{J9PJXIO7V?VpN1N(#j07F`$ zJC&I8+N_I`Sbg@&E&?~Nr`Dod8b#}|Cm-D(ulUpyO>O@G9}#@3_t)3fhp^V@MaTC* z{OB7L0q8$k0G?nx={`~SkMpe2t0HhS&{HPJSptXu09L+|j7-pqNkYAN%Y70If?>Zi$r@kw^q%Bd&kXT8DCzRy1H+Z3{ru z{8_31*4lJTZK6FMPMf}@kb~3a_2Zs?^W{A(Ej1YPJ8;Y<~$IQ+KdhQ;EoUYfQ(3$RD8`mziKNI+;Pn}{G`eg9M2|z;-(FZ%Z z=acPORE?saa-k;V)985F*ZgPVuNM41)@-2D1X9g>Euh@$pSxomjW1vMXPvmt4r_SR zjXT7Px!O$a^&c5{uUqjSh-^FqVWdlIpr0n@T|mfM=6n)jh#?rp2+Mb=R3W<@r!TTq zPmi7vweZ%3scP?YZ*{4{(tVZ~jI9|*F}6<}ZdS)~2TD$YPQ^xdS2;fs>2UZ{#$F=v zCyIO-ABns-tGss$sCjEFwkfxjXxwFSp1gD%_8Y}$l6sLQ)4ANgiX^+gvGAY5=rqMJ z*hhIK&BCy^wSkpPYN)s@%o$ENz&SbXgQlOoGP#=37d%6&_@hMdowd%ds(4RI)VH$D zCBc-!zwcd;my>pd06uQ03UeH=f+t7Nj zlp0@%E>=P$fZ-jH*>K~&cJ=Br)Z>|VpzNK{FMVB}Kcwro`aR{OGl^o+ zbIW7&uMZQHQc;yiw|&oR51G{Fj3kwgtK!|O{{X@_Z+eT9XL6FavYe0RMQ~)(<*#Sd z?9Cz1M8LNZ+`^J38C(J|I@i`86!dmAJOSefye%c{Q}3HVy0|-TBT_^Y;BQ~{hBlmv zQ>V<6HH@9Cw>#wV4~7*D;MMI!W9ACd0gw0(U&4h=nK?bId5?^|Mm$@u*voE?nl-CB zTgdB=I$Q$B>5OCOXiD1}MRF@W%9jd}hIhaKWc@0bZg#&8ZB4(0Aeuq7u37F6(T^XX z74dnT{jMR~(ED7vKFbc1IX!P()_g~zT-(8IWVTlXsZz<#@<&&xow}x)KK5Og>4h!F{qu~_%vZw8cNek?sBJ9mXTQ{^4AELcjm!sDC5Q^6 z)Q~?Kce1%Kgr@9`OCJL2a_IVIp`@?bH5ozv(YMuEfp*Eu0=uw5&H&)49QUkNBa%;> zQ?{lRRDw%Jj_1Oj6ipk#vgsFdi>J9WJjS(&Q^HA!C*!(K_e=89^(YvI-Kg}HK=IabL z0~?o)eH-4NQ6e_S_ebkYhJ}k8Cp%Y!9>3mSdVNUlRc=q+U)TI;>-^~G3v?p3dQvHhdU{h4Vpjd^pYI>%S))QCd7uZ#z_7+X z>i+;CQdr*936GLk?d`nT)SIwbyCgxPuC?M^N#U(1J%(}pp?u~D zU;V*??mfWAy=6MFhUaBG4CJpZ4zkz5TJFrxsOf6o-Aj2Ger3n<*^>k-szdM{4=|=@V|!ad^@fiO;bU+lH%U-ST_kog^Q!8Jr3-C zACva?lWk}|Pj<#uv*EPxw~B9M(taUYcxLCs(+OmOBfy}y3w?+ z7VF+A(LN&B=}SCut?iwmhVAE(7ij(2JU0Lg0&|{5O)72L(m^-oc7FjaZtXr4=&@g0 zNo^_fq!G$6!5UX(BzuAn<5*%HJiNu432?Sl#YZP@M`ZKgeNmoM!AOX#Wr{GLKjnlRhd>yBHl8b_TgOPw+-kI zSeB*ROEP^n;M?e4E!6JZE&bURs}DyeN8INfamGJ7&ix8{vmeF*tp5PBG z@J9q;jV5$MSR;DV?N?EcDpa^&g~~|cXHlM{6eM-;>M3YNiW-YZR0k38F_cvq{{X&K zXwcFq$v)j^dX|rT@OGtd;~xmgZ+-(z5Zhg*dCoX*#B=q}rF@nmgs_$Qq3VyKqgptM zR+pnZ&rs3sygjW)p=&qt+vxI=(cH$MG^zc~$ARd5Gu&6zV6hOy$D671SnMmICgQGj z6I$Cif;i(vwS_`M2p=jhr@nfV)SBy)x#X+-&PqKl(&NQimAsJ;CKX3W#{gt=#b)(7 z`I>@gw{;{IGCidE`xM1@MyCcg?IQXS`qCvQCCs-b(6sxn7HVy$K*VYAH~LlDj;ap| zzU^FC+)|+pH$4m%71WZHwmUzGJ|Kg`S_0|TU@g_eepE=m^3FV9xAY$W07~$17-xv9 z&n=PfVzCO1K2^EqI!}qM?Cc}Am0$Nj@}OfGuc=PQ%T=P~CeK~)?c6$sjb(KK%E=JH zD~#iV`ik269(3Z{Ee~F1;06cJWo|VZ$m$bWk9rddFIG$lFw`S{o z&xrNeuRiwSe94b+i-iN*71zlXrH&tOQR;W!4}4RoO{D4+HOy=Si&wo~T7UebuBk#m}((s1?cPz?MO@edFBv)@H1a1320# z>LN)%55h0qUtdA*P-)PPSt5oRC7WVJ8kS}xfB+dJ(Wsx1Yrx(Rlg1BkuXvs!G({p! zH%f8x$~uYXAH|-khk!6oTI8)>+9TJ);gvOax$Iy70000603EA|v$Dd9k)$|Kfrcri zsE-=>HLl$M0K%y8maX=Od9AOt38Hxp?V)21y?YNz^c^(pj%&Yq#wFjyKM?-_V|WZt zt7+OW*6k*i<5jw}GDamvAy)u^px|Wo-JXJvXB61uCv8nn7~RJ|jy@NzyQFw~SG?3$ zY?_oodwuh!^DWm8T$qn?91n!n%p{f!&@ym1+|11xASU5l~$PptV@H~9=*stG0Id| zQtxx3_$}jocg8KR$KoqX8)>x3(%TlQz;F4(=W`yITAGy9HWHQ2Yxa%0kHi{}hkOGC z{1$WS4)?bb%HdWx<44#So-oW0;-apEZSM+4nd_IgJ|*}OpzCS#Yn}?fTUpL>L|IU+ z@6sWk_FB3l)psIDlj3iRPl$df>7FsrM`1g$Cef* zQtWn~F4J{Sh!=kp?z}SoZR8N!XtsM~+r1IQml1~P^AJa&&JS9LqD7{gGky%G!~X#7 zy%SBl(e*zJ_|nNjJQov6g7L>(l5p8!kfaYmkSb++1B#Q>`c#VHlew}O!8G*a+QqbYcTEq9 zC7#A>J|%=lv6Y8m{PW+8S6-IT$5o+eH5*H7C_F){+ql(XZ0TX#7QIjiS*J{J5P(B} z85qY?SvT)l8YN|8h5~!*%x-BeCw!J;lFUyepVqWPZpigLA>p&NhS>-S8ifS+_pMV$ z1k-NkoOp#t_Vx3R;+@ayQ6sjWz>iw^Kc(8;X;bPlv7UQ28!eQKsq4Is{@t=GmZYCT z`BBS!RkX9P@kFrL5F?T`m7^c>&5m+CyBvSDT2s|sj>i$oxMu6nrjYg0z_@QOQ~r-&}JpNGB}PqNuCw+*o(i>op2%zm9sdFq7mGWVI) z?Uf~Dc)yB#ZEdc_FqceUK1JHD#1fxFUZo5_uvSdvt5wu@XA0?&T~{%&^dEo|Z-R7z zjDF~!{CL)@W5}=Z$mb7=V_%35r$uU}CVwI3$=qZiK{)jJK>RaF?rN$2>gQ_bZg|c* zQ!r$n1@TNC5lu$ZR@3j6*5^?(t?X#65(zdaE%R>c=nZq#r763kwuN{kqTZyR7Qb=h zeIDXWtXEc2TcdW~jvF6R8~*?us>A4-3|_HJKJj6hPp0N5zlp4?OO_3tSSWMU=6w%(MF^o(0H9z2 z+PtRjdkGGppriwZQcOdI8xnl^Kf=!)={FuA@b`svZ9WeN>ytE+Joz%fkHe^3btC2A z@&M>_UCR**U70$4jahylc-}7q=sFIcap9@#?X7&9yB$*B&4|mr+s#MVPeubAV;=Pp zuNg>OjUsIu;N|9#{{RRGgHyMc`v&rR+p@uwIh4kBM7ZHb&g6Rc#b35daq^nlKf}FG zPWWx(eGAUCd+l!O^5Wd1w#5>vgbYfIAD5mF<4@WrVKt*XmgX4z8{=fN(EMi)g?0N~ zGS<&j`*pEvYnDh*Z;6VqJYamv4nZLDYNK`wM0UTjg!5eJ-YL3~-r_m+GV657x6eCr zTlEJWvsu@A1XYdM{twuAkH`Ka*Zg%AzNK$?Vog36=amc*qTue^x08kABz5(r8t_rD zS+cf^@QL(K5ctB&OlwHI8Lw%-XIoP%ys*u;0lr{z0WNd9JdaAzVj*PuiB)8gWQsQ%GVQccvrDJ~;SF9c#n$YW9#u+P<8g za{Rdy#JsXF?lF=*`0i^;bF;H1O8TEH%RTn9sXVXe-d;>{LMdi%KmhDI06GfnQL?d} z`<GD2d;d*QHXQ6^0@R`KdI6G14F+?Zqo0i$+P{OWmEQpUBz$mN{n zk0koj5i|)T@+?rs@=E6lh`A*C04iOUtYlYF+*#b1=Z;%@h{(#ilx017brh3Q*cxkd zxv==7sM>gbOGzX1br3c~bb0~m!?_*3d;6zOTW8E#kHtS0Y7z0MEu!_s zv_XGUlls@M3>`T1Ib~K)siWiXi6g}x9kJ8kwP@xcWV?3*1;=E^Z1edFs8Qs1Mptih z&jqyXiztRe6E@KMPSSld>sq!jBVofX4I+=B{0|@eQF=Z<=YalZwvNY%U-*(DkHQ*F zzPl!|vHsTNz8U^N$EyMBOchBu^7J%a6`LUNINCm)DRm8^F!ib=SN{M|pYJ#F6fMWlil~qv z`3`^DU*yzv7e+%=MaM_PbNitG0MM42-4gbH*Hl&@XSHf#I>_KT^3xj+89v_iGuXOq zb8Jd6kM(Q%{{W3-Z2bu>@F$PuU%WkNdjfi)RDwR@{=ef>W@!BY~UIB zNzHQ;Esvjm3)y%o?^n_z*0o;{=^i_@xeq)yR}B)~NFN~%;BpTDsVc`Lb6xWCFx4HV zm+>3oKZ!0iweYuw^jnB@i@9xPl4)2OO0mMC?;PL{QO!rOg@?@1q<%15>bfU~VDQF_ zw>~P?jHtF#$+A0Uiy}hjf=2{_#{l#^8Tz^oc2m`STjOc%wZ8`VgGtit_4~~#Y3(m9 z?j3E~Eu~){d4qww9Q6Y@9FjC5nrIxJ#oyVA?_az4d1!S-vpP!e7BZ{5Tu6M`H~E?4 z0Pr!`Q?CVbVDu{dL{n}1L3m2~Fbl<`-OIG}V;pl8&*IdntJASE_|8`M;)jZLe+^l9 zqgnWOqFwlH8AP{N8)YmD>^@jymHD{kO=zOFxp#ItpAq~^)O61pMd6PL={hB?ymryt zM*5uJWsc=sD+Pz5=jO+LLwZz7momJVl(i)B$HcD@>Ao`X=Z3WHH%PP8Z}ic9cm1hw ze5+O=$x|W_Fuq$7dCwTdN7bVDEw|96W$~*}ZvgAsw7O-4nx2b!9jxmF(@*V1$rcqb@-0oMxlht6VN~ zkMTFfnue37u8W}9%dFnt&18tuA1Qpe!;(oOuzusESJmTon+|hBQ}~nOO(N?=@b%7( zrp@BZ_!{Cw`(Kr90s`12o2elW8STbt#*a47LAhAX{{V&MJ->y#MR9+k$NjgX+(&Hi zLvgwnF3h3#jPMGa_0CtGc%?yHIgdo}2DhzidRyG(#l6bt94p>4V2p zSyY0K#YR^;c5b~Y3OKJ5c)oSizp#8M1X`bn*+VpJ*oM_O{p9RE==5K_dt*7R=}~jl z=#1x4N7Uy(u`CutKa2G%j}>Yst+`_zgh$b095<$0c73bRrG#&77vgxhtlpg`%Dw01 zx87*St4#z#9X1Vd#T8%bfEeh z!xvsUslU(jIlmD2Kf`+TY-iGXSQ=c=q&#{tz{G-Q9mLZMh-gLN-YrYU=$ z%lHcNGmegkK>pOzkI|q10Ew#ROf^D{Bo0>o!M>6a{KXdoY4u%IJTC@}nuVCNwzxMD zgp(2mJ%RMeC+cf9ii%gcOT!)&);w9OeTzne?GlU`BqtHXJz2YdGut&Xl8V%`=>Gr> zd?WHS>sx;k>$msOz=0Oh&O>Vgk3v%p{{T|M=qg?&RHHRw^>;IOJthK)tl;UpqD}$zlRZ`)7~wq zFh_5GxoaVun6@zaPEUSuj&KEaT|H63QdJ}F%RBu~BA;LqDJd!*sN;ev(`_6Rr|jP=HH+l%tNPXIV(rj-AKakWzj?iV2fY_} z1#4_XPx}k{)hn?|!iN3J{HvYI>*&>1A!@hBjv<@Qirs`hObD;9XBhwt`(=Z#F$bHYHe+ zM2J)#aoF*|t+|p~knhm$ZvGBk*k~Rfo5LE&hs9bnvQD>`Andtm4(AL2Bn|#O*?kPN6zOZyPTfquWSKMRZ?09D<=3W z;eQkOF4Fr#)~2wy(QVwdgs_w5Hs&R_4#U1ZN#u%}wPQ7)qZXNa#Xb?d)xIU@-XM-^ zWw5ibxV4ftX7d@OR~Z8s$ILs_Myz8S1A}t8ivIu)wJ-Qk>)@Y0%<5?xfhP9yiwl7- zUAgJUPPF~1bH75lA1#c}i9QMOXNWX)(c-c2-leCDXri^2;hrm-2?8p+D}jbmS3ajW zBZ|^BV{0;bS}JS53AJ>a!Wxx?x^A(5q-o#Tx3FqOgjZ4F#~dC2k;gdTFVd82M)qTA z+Qzld!;AaRhnn7vCAWvP%l$$~-6PW=XrPQEDFKcPJ8<7OzeS@SV;7 z0E#?4tQ(kAY1YpSvEY*Bml;#|_pJH!Xr~Q~QSj?lw$WA{D_DZhQ-e{K`rhD1SODnA zRD;mrk37};RNlJ_mD@s(jlLUez97BQHU0Vx#=mQ9ZEmeEB4@I=P0T?YZd3Cg$DtVJ zp;9r^pt&8jFKAv2)%1T1YF}s2yc-s&c_J*9QAH`Xng9+_*@-)eB#tmJI`yO4E^FL5 zStr5o25LSN{@b|Je48k>8-XkuU97DnHlSytH&Svvu%qzKtwk+|6=Ls;JV$@1>nFmV z5s_0;C?vns2;7={N58y;ebxMZ#n7eA?>&s`RZxw}HpyY4Y0sr-2+3^BUgqh}>8~U` zUEit6{{SFyUey{2d(u8n9hpk2bm_l$^C06D(s?@>y1u=pX$zf3_9zZA5S+34DCVY9 zjCE|%jS98hsYSDwxcHx=#^qsx@?bxRBB(!^&*@k^T{L=B@;p?P-IvULx5V8RLIlv> z!|qX756I{ArS-aOw~^vf(WZ3TM~JkIT!qv12xXA}0Lwh>fBWR%*3qdM{4De9VQWzS zBw97MRhZl)3nXlkD0E;|m4-ncgi^6dwC^1m;$ITH8oC_c+FDdhG|5gRjB+4z)99!4 zJa-ktUXL%m6Y4U|!`m-qRr|~FIEBV5jh@lcYT90%YvC;iP_?^`OY5^Zxqvt(J+Fnx z?UC!&vQAD-J%+Y2G`n$qsoGkX5sBtVi=KZ+CieU=n^<(`9VS!w-)!mHf@lr7{ z?LfmG)BuE1V0oX#KMcwwUlZv7i@Q|C>8%ypCa zj`}LliRqpMw6WAa9cgH6MdC#F3oJnyfVk&lI9`758SS`NJfFX7oj1_s{xn&gGSk8k zw1n#rz{@kM?qEJr2JWN+SOR_N(|5S-+)syWPNlDH{{U2GjkxLN6#4P^*F`q$aAS0m zJH!>K#K~NK;SZ}f@)Vxnwj#!A(J~H@zdyvk$*S3$dXX9gIdQUI_kXCWAb`^(TQH$z z`3XBez3wU0y@HjmuOOf^P|dt5dJTEQoV;Ci3m2}5TC6{rhr*T@P4#v zEeA@&&Tp6ak9y5s#?O6*N3av}oc{p5Pf|++FBn{KDjw#mV;{vDbh?I`KE*GQsM^T4 zHj{tPIgsT~)2KeilUG(Wr#?ws?mPqW0?)*1Y;OnHyg>qib7Ln5J=s6Jhpt9&4>jl1 zq~On96%>)&EXr~8&0uIQ@t!`EfO0-M6U9M`e)8}>}%G#Xfk*6<(^bhQq z<~JIR!<&s#@SL^8?aOoM4CNK|;Qc`#g+wYT>W0;fAWAT0C0HDS4hS`(c6UN?QdWB% z&xC#=Uum)>zOK)EWz%`)SpNVY;vZgp>#~+9ttNPQ%$gJTsA&6iJ&RAhx72LnyRwyK zf>D)W+*fjGtE1+n2|`XXR%n*u7_M#Qx44aDhD9jo!y$p{4Gku&#~C=aCAvHd#u^KF zV_0i*7@F2xy}P+QulG(nkMKR~%&$w?%T155z+)CFo%*fJN4t!S3oBrKL45xJ=vB_q z+%2b>b$JE7yl|wl$f$&|Pzg90B;b8%Jr&r_+sD_wE00Cft$as)Z!MW8%Sc@bmDFwC z_~ni;HyXl};V15wDsqOb;8A3nRE`99m6!xn1hGAU1HEp@vDsYsE8+&N9CO}i+N|pu zot|pofO$VD=OprY$*j|Y*ltapwfi@CVCfoGui`CU6K^}aTPK{mOi@S7gy*JMVE+Ku zF-1qw5p#P^_=n=374N_B7lh%x&=N+8FE8&F-L4Ao#S^F*<0tev6#lQ>i=Ff@c!S4! z?vvtg2iX!Z)*jn2kbhQ28Hmsjzb@a4yV z(d=N5DgMnHKkE6c1Im)tVyNZ%KjYI?rhRd9V9IqA3)o38y>uU1!Vlu+Y;V` zd2@HGO>yO&YgTxQMrX@M2|vcgy5MIOoasuUSC*{w4-4q)qG>VPXMD^{ z%_sNV^*___uEnN^@~X?)OPQOf!#soe)flu{kSVu|DEr>cPU1-rkiYZHerBGew1GX z5A>~Pv6VOP5~!?=u1gpC*#7|D6rRB!i3q(rR2mM)GyvB>JAF-NjfiHDM21Gr2c-?! zTvofXC$*GM2z4ZaILO6jrFLz}Wo9-xBe_OsBv}Hs*1^sXr8|c!>SAk}6|SQsGff*u zcMmaK#M>o^{{XVXnhS)b9gDhtz2dzs1X15<`hpIhY_=;Wwpkbh`BqY)33NImiE%~m z_)dISg95`sAPzryFs%No7GKuVO}ty2RXeB=sDMXup!buGi$n}_t4sCdT@Is-8TD|8SB&K zK9#jRG-^djGaODO;ibzlG@p$c?x|sWd2ONTs|}znCi{oa0G=?u)p5Z^^)suD_EJ%O zMIRY8_VDbQ*wD1Uu_?hHXpleN$4q0dy&qGJuX4V(dGd8D>R%dl+Z_VeSCd22O|{Df zjTYhe41#(2eT^rDT)fu^VjSk_t=(<^sauq~(GBM<5oT#Psc-rMc zw>-H}y0^vMe@*)#mrBx)+GO(AYshi#bBaE$t#LxLo$b&yUyGXlm#SD;%cbd$CD{Q= zvw*oc=7&NkBUQ>SU509Z7d%0y>vo!Kx>l5By4nM)$UxlK=hvkk#v+YZIJqMG{6_IW z)CHNf(gU0f7GZy;7qCKW_PXizmlkB0vm~X#@8w_!TEg0riGuY9AJL zJ3`4Vo}TZHitI7@(d?pHsU|zY8rAQNyi@(TslyCbx(p1^M;XX#iGEZk=mQ^W-i;-F z4t!mIXHCfJBw|S_0qBNlWn`R%f2r7k8CTVTQ|(udx8%^)mQrdRmF^}Wyp_j zBf)de>l4{*d16Sn$t#=yKbfL2a&|^_s=|yM=IoK#Uuh8dM%v!( zZsq>k3>p&?Z3styTo2Z|D&gY@T3eo04=}5Ys@xTzj2{qcdWVQz^-Wgg?rwuDOz-GH zDgYb-!QfXaf`YV9W18;F#oNU)hU1z9c3%!WO!_vcwwEq{v+i4K0(gzjMjy!k0QIY@ z4O@}&y_w`@xFuR1);q7n_Tb!CqQ}D#ns^)t`=b`o=cz$2VRu$pT26tt+ zheEpS^gJ@W!|aD7Z!4^EqdTz*s-q;6*b0ZB(dZf+rm-pJohA#X?6!y^SaBClJy0nJOiEU)_9U~7jHc0>s zAQ-^U2d$>B%KHVno4zNx@Vb0P@wC1opInOK^jpiPKAP+VyRFpil_Wkd;dgV$*JWc)UtKx?X= zAxlXdRd7k`1C)I)LD%E^U)e3LPD-FIL)o}X`H2#U_$I3pj! z8h|O1?)7D$qhKEAui;Ex04_MIA(OZKdq@8Bep`kl0}J}pT-L}^Mmib;K-vtA$5T;rTN&EqcW-R5>AMo)m=PoLtB!ddhZy{7 zWwCHen(UuLg6<1OT}I+L<}O)GX|yW#Uw^GSf$CdfaxG+Tz-Z>s6iA4R!Bf1NZOt#l_gqCgoiLF63gH10{nTuI{Av=66g@k&hc zDHi}MDhzGoaP>6GT$k0Ab%`$C>p-{h6bWxIm98Z=(r|!$s(H_Ilh10UsMWP`*iR8b zx8l}`8RU5x{{YdH=Jp7yQ)8^-{{V~Ribq&;{{WDUd0E%{oW5U~>g6BBkIbv^{vEo< zjxqcZvHt+ap1mv{v}5SMBhAg%@BVD5-~`Wrd{+I$Qgijn8W^&?nsS?6P{{R+7 zSNA9X03%|j>@uPJbWrhxBzz?B^ZLk{{{X&Tvws1#597$6;&dh-4rs&G$LN(0V@xvl znKzGN*mNgi0*kJH9CP|n(3&4;kH$$Q*N^mNx3`_r2_g*MQ*Fs`csS%^y-9SHsj|n4 z^!uan7e#SzDTo;G`%c~&iRPa{Q?jPf)7-*h*9FYLM-aAPS%|>H0!gNk%-^|hGS`Rv zJ#*sgcGT~#=F&VY7_4s){{Tt8J@#krC%;qtsX@uv6)Dr99%-|((=9wbp~NH5d^uwg zAM){bzI@H@S-BscE2Uw-nVw}_*}vh(?7fHI+ZxY(hBJv)-iakl}D2yE4kgMV$hlf2zdJW9hJ_fP-C5evbT9rqe zFHsou{{ZWLb=7##jh5$vsa4HtbJWtB>D=P=eM;j~ktMr*@+$4muP6L#np1Msp5+P< zp&m#pBTG&2_NRN`?MGI(w`nY)xsE1f89Boea61jllkO`v*2cX|212|MNU}Fx^uUi4 z1K9WP1?p#0@a2ueoXfLhpYJaR^TvM~^{LOA37->-q3ml)2-&(tp4wQPWn@C6eMT!o zn^?5Zn;rukk|FAhRQ?AQ9jicJ@S7{Z-v2_@gSmA}bFyU9x zuz5Xs$Jd%|y$w%YR;;WCBk==U*EE}r4&TGEX>UHEJdn5cgE}IGiKNcu>B2W-)O4$- zO-lDBQfpxFH;k8Efo=6zp|I9``QZCHOXe)zR^87Ve-dHWnscJo!ct7Tsrci=8fCKF zU+Z>I!tE+6w45_WKy?`EK_C!0to@rGGApN!{40B<*lH1KFDKS6!RKl*`L>Foo6cP0<-p|Uw_d)KC`#83WKW{_mqgZe zORHTf?mL^CekEwomF!=4zw9t3%N)_3bv+INk2yQ1=mA@C$zOLIN)x3EVpR);4!Fx<*Zx z#9s+tUp1AsxoY=IzbS?vbP~gn)Bt}fmrh77t~*2cf#FSTU7asnw3u2#ZYLy^*%0dI za33h{dr;`lu+)?u8h zKN1+dD>Gpj^U{DGWC4%Xvp48w>RPOJvqNnhQr%zA#5S-E(n;@*!_=GsQ8_59$kv@1 zRd$JH%folKt><`({z&6JH0ucpC?1b6`I!FzyMbOVBQ}gL8fxtKaM@MYG{3Krr)%Kv z4MN1lplQ30@yP&{{{Zi&$*(!)HRkuR=&ThuEsp_)_IqeesEW==V|D>$wT=G(i1#Bu zt$loDqCRSLlV*mW;vcensSDVX&iy_@tDkt7l|Q9b;`cI@DJ`A-PS?Y}I)t!;R%qmi z@Gl}lxc>mIiTpqB43DKlU5uPOj?Uvr(={2OyPnz$nIm7_$ir~QUON%qqVJ&Anj9v- zZeQaji-E%07W#jzAJ(b6q-kaB*ZZ znH%f>0HqXthAMx@MPD1BUxQvB$9Ib#_rul__)eTZ@uYP=JfB0f&@2YGZT6=iJP?9I zF#u%b9tS+sF;eEcc@DkeR32D6tn6`wMeM|WUta!{rKW@CP+YJ*4)SO$ ztz@{nnm8bgZh0hNs-xVRf;|o(_`~~7U7i`x^=)lI$DMC(!Y}*ikLg+{wYjIVlixw7 zO4qghZ%eV)G#i~CNtQxxq>^P~(I_s;Lh`$UWAUqszQjr}hOM3T{mOEyvZRPDE|N&%AGe+ z*&TSiTyU`Fd-OWZV_mcG?Xziq8&z5Hwri2e2kYGagZXsIaiqCnx#eN-m2s2y_?@~V zhiM*38c7+M*yWjyNj|2ykczX9vmR$|*BDY)M>Lj+93?r2U?ot2*bpU3CH1{nlY#JxSi^^7{H{QX)~_MosXdVdE^q&|@d1pSGrf7n8m|N-MVAmjng_Rx5Lj>n?u1+}G2^C(^IQfYt znM%{)6|Ihy`n{&6gUe#!VA=b#&N=eOKcy~pxN@{m9}A>r@R*ZPg`&}3<)$7%2H;I5@;dyu z%bFKBF)>P4BpiyfooqNJ2tKsYGuWC#h+`k^ zH}VyvOrFF>v@>Ei`j+STm-!k*t(gvZHE4lXl$Y6msH$YHq&sPV>oBB9b~ZMj;{O2a zQ;C|)S(@Dw+vCrS?fxO(-jltD9nitFc0Zj%HPB;~=dBuzuEuV$ zfA&R*AdJs>aNb?KE7jZj0o0ztu&E^lc63pvDzZ^WKk)NepGxs9{+Z%yVQHyfxM=QN zb13K{4bx|!9A`PnuM;qot5WBqE3T)~V5!E8xhg9@hpYjL`P17hKsl>KO!;o~ZNI!x z&`%&A_t~$ur!R3IEj8~lw1efVxfUy=&l~18D&wIYsi`q?b8dPU!><-eU}LhsZ0YXX zL;nEJ20ah^r2a?RsjI7)PANMb_5O^l<6S>g0Pb5^AwHZiApZd4#**00y)1GLpnQ9^ z_ke%swy!fe{{VEC%kw>qqxg&FRQPZI03!#F{otGb07H88uz$gaqWq6DHU2lvls*}N zJ{Wk&{{XQs{)#o(1CGD(5p3aqXZato&;FEwM7{zk{{S3kj4hlc_q%E*H_Nd zNi>_~VtHauHRR4tO|$K(Q;j&NM`T{Sn!w#mZEa>)OR+%HXA7PO-Sntewx+VPdK2i2 zrrhf`aO!?+7B3)KWJeiQJ^2*%B3DZ2>%3{M>H1pBp!iXgS*8S+QMP=}IRS@XLThhp zX!6A1h7r`M?Q1*sM*s=P3JGJt1HEL~=;(r2C{0&Gab#!z(=ZP=1 zO$yS}>?v)fz*046(BY(2Jy`d_Cm!@Dkd#xmQyH!tN!EC7{{TX_@Ybmgrv~jrN>+;8 zo4`7p7727NWe6C9$>Oerlvbo>6s1wFRCaIUJv!eZ46~8h5dQ!(is)5hk1OgmWx5{+ z#5)20vdZ``_kctH0JBvp@giAeUX2|liQ;W5SbdgPK+lZ%P6|Kddj1u>CmC$dGZBWX zLs_j2t@URzInNX5^W4t{ucAtpz9F*a-r$ZTUhzrlgk*Em^Q>ypRU>A48GbfcN!k1_ zfAdG6d_VD&ekjtKPb}K%5IKtS)z8laox$}P&JS_)HR4pJ>AqHdjXH{P=8L)6#dDMD zD>iL&&%8zB4PwFdc`c^332mrBWejg@TOvakB%R|VZ$6y|tz@O|aU&@v5p>B+#hNq7FM07!~+ka(T>3bSk zZNx<$AG{Va{%Ol@2npB#+)r#(r5o~?YtW>=Kk;-6swCF-GhM|DK2&RJ=&3Z41;IOr zD~#ZS)ODp78ER9y)2Y$u-YeD~#>RbSZ9P_SS>&{aMi|aMQO_KXdR89R4t9yBvXWP4 zpl6I`uEdeK$M|EK26*?6onPWgY{4+;C?q4*rg8`VI)6Ir!zaja;{GH-1M!Z=x3S90 zM1n^drnDkjlCeV+V5V@#ztXJ{A-P}f56-N}N7`V2k$&>`K8Br%zJwTF>OV79MeaQc zc~g9V5Tl_#-W1wmy4OMjkM820q}_HSwLj}W+%<1wD8G1m)>k$shy7xGMN4HRsKe`0 zY=~U;q&Kl!!q(qy)h_jkfMw>=)6rv7^RV}41ZUAlt$fyRio5nP`JYvj;L?JtBVWW? z1&@eq4X&XdL%KOvhl#r~p4H^x@e`pgXr9Gt5vZ)p4F|;2X*#L!*NLYOdmi7i-gdY{ z_d|L=*RS*It%Oyh?da{*b~&Mlbmi=-J$f5Dhm5psCPJ6`mAr1fV_drr!E$P8;Hph~ zs8umhTc172cNDi%xNaUtah^CNpX*;w1!$yra^B-^q*XzK$>~g1D=#LR9gPhlXyUw7 zoC5@-%a9*K#cfU5oVDWQuD3_Hcpq3go`#WJAN1Q3_G!WDO7w5|5W%F?nS*C4NBm!# zBmV$Q0sjEVZC+M&{^>85=6e}G$M=-J9C5Dr^#1_UDL?cluTKg52zoEb^K(DreBb6! z_;JU=-x&MHFaC+HsI!*3y-1tB6w?>QUj&YoV{sU``PAq5TubHveKW`BNy#;2WUorE zG?B0H?pW@;F1jYA60QE8rrPS(QJyk(oOe<&&q~FcO?%wY*_{RD&6a_sNpU$#Ya3L_ zz`z604^#Zj1lh^?jqqldJ)245y?#BuA-|7pjkApZ0B2gBoOX5`lkbi>6{RX}Ztr6} zRwZGf$qmgJ7`qb0l1@n{B-eUY*!lT8yR!qtzADo^Js0|H+pY@bE#u|e@a#R!MC!&H zn&I%3u`l;)PUo2Fe-t&FiJR;Z+u6m~ZSx!PCy&y(C5n@J9_=ialvnQ?Y{S;PU#x3d zV_51Fq>!_#oJJJnbm@*i!n2)uO>(oMj$1;7Piqayd?yZ_rit|(e@$EXyp!Zwyrwe5 zkUY*g+CNfw=yA&?-jO{Rt#vu}A2A?c6VuYDp$0b%#{)c{NVRk@;05Dsyv- zjE$d+*0JgBa(;!X=Crwn22D9aT@Cv`4_Y4ppAAm&e`x)i?JXWg z<&lXh-%Z1AeFbQvrJ2aroY{)sCIg zg6c`&J3T+eJ}L1Yuc+C{deB1mu~{I=mj{L0{6q{4dYXy0-iJg}gj5jeJYC^?tCMS} zcp}=`4DDxyf=a&R1ylOgZk)GF(Oq>oZxO|(MXg;Xi!05i-y{ZNx~Nq^d|{ zJ+MbPt4~{_IYBjNvFV-z@l=|L{>{}J{XG|SavnVN_5T2PclGOC)T?vpBj)k=A7J_E z{6CS|maUoR*yXj!=kUh42a5FNVu%DflqvrJEuifG0PiPIeLaP8;%Rfn{ZC^m#W+jY z&u6jhT4t>c-MkaP=_E3sbY(mhBOr?M9Bj{~QY=fRX?LPuKFSyK-DH#H<(YAYKH~$Q zYR*te-5N$(Sc&{EqDwWz&1O8yi*ai!30!6`?)Bs9Irpfwm5n)`;NBV0i`s6rV%DGP zn9m{4&AaFMzJHtM`LoRy4rVm?YexHQmqx^S&m+ozdf48}w6Gb#Y($YvElM*3xTetqF#0zF>LebA&li31UDUfH)MnR^f8WXc|m$+eVf#EOy!Q z&=Lakk};l{?0Bj6X=+oPnXF~tt}B>cEAc*?;k_~MwKHRsXg%?%XY((m)E1o~6oksHF_gB_q^7S1?K_kQtLo7`V*EsaGAJhHwPJfv7cB#r*Xnq_NCb4eql@a2ctG%Y&U9K~*k-sC9B{PSN4iJ!EqDS4lu(1L|1 z$H=>LB3j$aJg01NA$C5144TRja%wB7Q<98s&zSr{tIexw_d0~kM3TPc8T*6yK>7eH z>}b+dprDVPsZq{OOwjO0g0-I;U&@+ts>2z~_frxPGuza+u>5gaN=gXJbFJ|g!5w!@ zvU{C6^50b0yP7D}q=bL64nux<_NY~p3m#eX1}eaU4hTJ~WvGec=~Rhhk6NXE%7wFWLD_P%e0Ts*4MGm zU33h7B}DixnfI4A7aJrl;v_#-$s^HfS+zSTxwE6xuG;qcYlr?e;Cc?j z@~=t}<&BS;tyR^Vo9bYAqgsbS(JpW0!6-2re|^tE{(A#ONvKDekwS~aRaD}9>3?}X zp?NjkJkO6Qz< zP$_Mp?p`21AhTOYz8~1y$D_##x=C#k62$f0yYT1rtq(pji}>EBHSwTb3ttX3_B`_p zf=pxY;WvJ?_C~Zi*yuhVhx#_n501)+`5JenX z?}n{)7>@;#)wg5YITe(Wz3vJY)Z#pAr&{Uy>#c-x$7?DqsT`_RlRZ}lo(MGJ?u#(H zaZChRF;qiDsV+Jb!0l9sXq{vyfC*;(dXBYenY3Gi>RVfgZte27Hq0bb>PKVx)(oX8=`5D2&PA{Nb#T~`GuuQw7l?pnZ z;EWMKMYy!rLy!1>;+SGe-X0PqOfayc0GuPUS$Gd{W%6r!ZM zor-w(#3_Y~L9zoJ?!PDh0BP1<%`-(>#khQP;#2_)fe`2Ob=-{51pr z0LJL{G43B&7yc8!68``Xhio|i0H}fg0ONFf821;}5Wn!9_?K_XplSL40I-99@v45p zJ;(Kdzwn;;pEw>I(}(@UTmJx!QS29SePD{?#$GPCLXc^?HJLxg_Yz7+xB*xBifV*< zvL%X0<@JLmnQ49EJ!;}zUsJV#jBg+ckq_=fgMyShdF4! zwy*UIi;uFU&C1)eq%fHd5;Tr1JCZ|kTljO|6`ZO*hh%9&8zLKx4#vmqqUS-pzFUEn z@~rMAWFPJ3e1q*!r8#WPETXnLulPZ9%Simlp=(DR?zFs?GJoJK%n#x#KWigjXBib9 zL#OJtx^&Yu%3VVw_VcoZY$GbVxEyW;*O9@`Y*w(JG0BLb?Dbn0@I5PIjByL(5w!IO zud$&Ep$S#T-9MlI09v#mj2m{z@*lhX?{V!gd)Ks#T=ws9y9Ji0zYgI zLnADDwU`;?$I9pG1}j^fp2}~bt!H~@sl(>jT|sjf_o8sa^rdoKTz3^B@(ByV=QyfN zmRI;=A(Kk+rntxVx4M_uBR}-YB?>YB0DUq1Yg$p}kr>%~JUy3PKJ!71dMNqX9!v4I z`I_TRuvYm3w9Gy8f&82Et~^|{NcM6nS97ZU4?ocK={0R$%TI;XB)OGhR&I((#!Y$C zmn>YF^runc?+d4a^;n|3nk_-4g=e<`N&}Kd?-cSdSEqkowTvozRdhD2a#wdVUEIk% z%#9?@a?F3v>02dbWFqF0Khd>Aj+VdsQ7B?4P*KwOJN^;d=Y1>1A?+iW{{W-vQ2pz7 z^l{OU@z(;Ln=|8S=W&c4GuI;{-kPYMjLE_+ z?0Mbw_X@6k7^v7+=_HzPvJ(Nfg5^1TeU);kDzL#lg zk~;=)31>fa0iJ-2-~(8wLA|sk=y~_YeMT$KAI%gj8|-i;yO4i~Wsl{%AB9~tWKulw zPc>j&xzprLDjh{im?d)=135+>wWEcj%wZa;Q*gG$6>oAWBE;4}XKg%&52SoKR;VM$_!`O(HhEu5!6`0XL`FppNX zl2?ggzA~bQWcw7k0OSLWz4o8T(P21(m}X@P6kJyCaFb}%EX&e zh8Uh%Ej0leDP|_o#sC>TsIYS1LGj%gQ64J`(yjpq%Em|HXt11YSC3Wm(nf<$y^&Hz ziHHa=%5X<5>M5miB$Rh%XNm8_cwba$>}HE(TbXAHK?9z|f;gwD9W{9;7jvXOeAdw$ z0}b7zV-dkLuMCin?yT|UwvNG+pL*wM^*cF-KB;=Jc~aWytIr}E=$}5@oGBygN3(2x z-Z!%&U1s@CV$(L``0p;`P``voN&0RSohd!pe_4w7QGq_C{iE>`%g&i5xQ4=D<{`E< z-0_gAxi}mXoRV|brZlG&9TA0Uj-)Qmq*kXG_CLEkcOTY)ScIC<8L^;zuk`++s^kz6 z%`!x@3zz%Y2z%AL*vdAz@CTv%>o#neEChLp$2k=&xV3So2eGX6H!}Q3U~jZNLe+ls z&^j;s*f{-hNtFl7G|{)=%dx2VcTK!s`E72DeU8zQ`Pai@A?)f(J(>Cq3G>s8yp1@U zY0fJcyE~UPbz|n=h`t{l9PupHun^YMFWMmh@~BTjJ7Dpi_4XJ#{@#+4=zPv5pR${Y z(tI@dt3QP_)Y7#JM!eHfLh-b07D#e(K+g;_la4x`Yg&|f-OM8{Q?S*(JK3ZJXnIx5 zlb(%uRTWRJ)h(aQRx1wgt2F1i;yRuE&a0{1-``2*U0ldyk+F=E?bv%)OjVVTn?1+j zE`=tS;cvB0g=W8DHVEk$7*EfW_}8IAHjU4i%y9~(TP=~)&z#oj9(0+?*uUC-A>Fw9 zmKG}sKkdl-kN!G~`PV&nt&dj)4q6HKZIrdR4Y$M#U1n3Z&I?9)AFej5=kzt>W|ZA( z9?mA2_}l~Efs=urY5PYA zDp7hRYn3FbP1S32)siUVh({b~k%54!pa4FVi*4>zk;Qns;r^Mc$@bki4JTPnW4*Lc zq@PI_qpz|2>fu&UWXchmvpoLS#I`;`gIB+>wAF3h;JJaPRofylR=_7XBxHLLUV_zK z9z|MiGI2$(h0YJct40SVO-AA0>!0whtUttZW`6`%pXh4Hdg8lJ3P*~3V^$s~wAmD>sZ1)ZC8_hJnC7j&NB+>|=8{}fg{YrN= zF-dF&9Z-&k!uR?zF_R=E4iS*G#+A%*_grgyWM zj@9==)lbuodQy~Bw-=%6kof-q;kS$xf8it3Ahf)08LsW}bL}c~k8_HjXC?5lU9HOr z@Y~_8vzOBDlqvrJpG*DY{{S8>MdY^^G7$U~@N8#$-)cpD4Z>sp06?RihcY3!(LNLS zRByZ%Q_P>kEyG+n{{XgNk^caW(eE;O-$S19AI8}2tlq=HdP-SB6sk`uDs1;ymygW- zMGBSmWomg&nc_S9KN2pxGyStuLe8?>lK7CR0sYG7Bep>8#cRrvvIb^NG?x>}H15(% zBNumLlFUv?C)SG$v0-laHwzMAbu7n#syQV40ZIF+q5AyC`5NP|tj}u%xp3>$(hfi2AwX~s*$@7PKfCM#Q(po~Xp3iQG#{VCXND6Xy? zmN?E_k@@6Qs_>4N*ReHlb*o8PX>IxxV7^}y{5{kzk>F>gXk^8}^5r-@FR!?&>PDKC zP6_Gy)oMl1mTW$8j;cjzEsS8*#jMSVV*BaGaZxRcj^L~9S)Bru4 zEB^pI&>o0AJN`g@eJT;96$q^lhQwnfiKynzW5hoSG>f?m+GWJH496xsd5QP}K_8tC zmEMNX%qInDE1oTTYxYa~t8Lr7iC!((#&(i3`d2iSosVXmxuT`qgoBPMb}gRG;6I8M zPh)SUc$vP;(kW$z9*L3M?E?cNocbE`tJG0$ zO!{0kE^AX;MOgG`{u@MQx03T$y1Eg~9IYG07)J`hw%`D8agMdb>)mRPN)!|w(>vpe z<~m04O2ZYJlQe(2WL;3Oe*Dfj- zk2ho1$tx&L(tS@RUbW{RPHp&_=F3yoq`I?~Rfem1tbi^eMGiH5p)*Ft%3Ba{137b|0_hO+~$oYT^|+MlEh*Qa2X@ znqnqISqJYfQ3v~bkMah9w~;({*4Y05kNai+0Dw#X0K`#nZT9?)Zwly^8pnw2H2bYd zQX7c^6B0J#=mzD$B=M73)~oE``j?+X*6gwaRrU#^W9A*XEItU4TBq0 zVng2~`rvfOF=;tn>^+g562$eM=l=XJ{{RuEO;d2*JvK-nX@ z?qEPXgPifer8gwK&Bqw2KYv0$5?g6j`jf|_-b;6Eqlp?VfS&jsojodHDK&X36yp`K zpJ#6^)znt@s$OYZYOlZeR*{R7vN5YT(uPCUIUCCiV4QQL4`Y1tDr5JWb z%r;iHI_X*Mp_65(KQaT=&-hd`NH(LPV_DMl=(N{mu(i*b5dQ#Fz2s5p?@4Y_lfABV zmYPM>8Vu?t`VxqFPBln`ZJAIV}Q{_m8zJmnk&#W9+nDGChA$ zMANMTc@P{3R~=;bq}v51)}u9jCr|#;(ok61g=rrn$zFeU-qc;#sJ$5%i8Ng{9}Viy z9fhj0+DbuKln{0wYDv3}FH+v9x(;|&|dHWBImBGOr*jYF#fB?^64IA#3JV;U2t?+5Zd z8nt6JX`&|Zr-U7|ZK_Rf4@7HOPTr^2o~E?tsr;rM%AfNw_WD1CH1{$YZ*SyYKoE#i z_2!~}(poJIAsTSoQzkufJx|8gkXu5KL2qX+$kU>RWa@j9id5mqMkY>Cx!6K-1#WQ` zKjoSFkLyAgLNiumz+|k0AH)4cUs4}ZvvsWuNbNRmIo-mZ-qllWB4KNijlb=FRgKxY zNS+;=*RN69rQXHH*CbWo_pHsw5$#B4E8~p?^^0VCaw5OBCGF(*?x+2dI@GgTbsaWW z_%r00HEWBQ)*V(i7S92|Xp{`_JqbVf=&s5MHFSBowN|GX^gSv`16Iu*d@2wy&%I5J zeWr)Xy7qtJAk{(YF6JNj`L8~vl4$z64)o&ukrkDk_ty&rtW6}1*bbiO(ul%Sc8Kd& zg&6bA*&dOjcs|z3&`WfQHNg3cxcOJ`4P@f%q}}dxts6-c^^Y3(&K(J4!b5v4mABa= zTN2NCG^zuY&kAE^`?&rbo((k!^H^@?wV#W1T}Q{Z`b^CnQ9*Y3tBKEdP2?yoK7v$fWt`vmJCz)5qM+wxTpju!U4hIaijAI<>~nR_&q!+yDFJU!yt}nYBel4eC6n(2 zY-Y5LSw&ehnywm^St(98XtAtmSGq={HI4iY6~tKFy?TI0=tW9*HcG`WE>G?^AX zG*5d3b4LN5HJLoQ0_1O8gM;mmDc`X!5tB`=isII8(MkURe40fapTpk>N3UFJ4XVg( zEiG-DW@ZenkM9AFan~P8#;s*e-eG3;II^O)7=#KyAUHG(a^o}rvgB!y%95*-LT z?Dcz}EtqB>S$vTASK@UtU<_A5=UnEzYpA`RoAzrnokID(&u*irC#!uFWd2o+Y4XJC z!O`~BRrXr4&XHpshMjF`3Hc$7P#&4U>s&{-Cw)euU4OzsH`!;3J+_f&%!s~n@_)2F zD0>bT<-09vkzY2PeCwNL9&|h;48QW#Kif2sO4mATXfIa-?NHXWGDm zc{l^#^ruR%w0`Q?e^-okY~p+~q-ubXV6WM4nKr?lg3Y6cdm|9wj!-a z!F#)&r{T+eNi^vrwYsu~2<~S^Mzm)j30t_357M=khn&(O@l=Ee(KZ6gOeaZ8+!X2K0qS=UFGqG(sLxd3^lHV7~}lhA%9rO2d|cV?G{ zv~laJ##=(}7JbE@M)vpSrpo^gU{f8E9k6ZVyr4>qk<0 z4Lf}g=l=lKt21^G1=%6N;eCC@S5iBbo39J{{{Z!?OPSb)*Y7v`rmlYpg*WdLEn;+& z!aj5!;-&0VcP#A4%=$PN=&x5@qeqkw{4QAwtJ6bL}cLa5jd$XSu2_FYaS-i zK21i%YgUWL+HCN%WBsVlQ}nLLVw3oe3YlbHw4J<}p>Od6L2s7k=36Oy$9V(9N8(05 zTGdVx*_>4{n3yYa-13bwe+|!ltzK%k5HWp9W`u4evKGi74oEqy)F9h(JzAA9FpMEZ zbXPikHt#X*@aBbaDmO`FkL>Y%a@?Q5RXVVytd3WLtyS|^m+EWBqv~3OWp&HMNDo^} zH7cZfGoP4`VT$IRcP);}cuI8jiak5wj=2%h;?QT2W53f7LL)pGAsj~{`i-~;(Si1_ zI<+>P*HhY~H7K30nb7;ze6O*k(4qOyb5E39$Z38tw?9Kw$Sz?6oE|WGRPqm*5X!*u zj-2(Qm&jK#?}`;*!#N(bxjciJ9(&_`4&PSO?k(q!aAEg>`==w3@5l13Xr>>71se@bkm>vl#>u+cD}b-)Q`ZAbu6j@bHo8Q_XP{)vt3o zz82`2;^$ER0KbX<0L0Szqz!+ztM)=K!yOTS<-VYQ?W+F(@ihLa0wtW{J0i^f2DOq? zB$~FhBc7%1w;#-kud37ROU`i}5*z;j2ql^S0GO_?{{Vpl{{V@j>a_b0`)3j4F}w!{ z@ar0|{{Xhb{3$#oKEeI7U$P;8hw-=k#`R2l$(o<3(=BIJzm*Yx!nn6Pv#aVPkW~D` zka~(w2TY}%)?Z~6kHV{Cjhdw3d&Up-rS(PJ(Pwo(l?Xl)TQTQYoP8l5<4fv`xIFHE z%1AyC+JDeu>;C;O_|f%P_cQ+7sr;yT_(y4uHG6^eg-`XT^+@+0_U&KgLLY=S=l)}J z5Bqip{{UvA>T&Kr?b@ASH2ffhf6Q)TJ*`Lo02NE>aqcId&-n!(2`xbRwR@6(>xduL zpVZ^rho9AZK^9^7SEa|y*SFGRuqwaM)hsMJlJmSown-f>nW5?WL|ZibWQ&X!##z3s zd99-e!{JAtR=rB@^&QKsF`Bb0*vQsz7Q)=h=_IblY;eS-o>s>Spm+XVO-pt~(~GMv zRAA{E4eXavYTCeG?J369j2+8uA86G2`kv?YJ6+3!)6I&tKN-l4nlma;_9M;Je7DtKons{a7BMU51pYh0gGn^6AG`RpTK-@pbg0R)+l zdQ!EET&?KNiW|L4BRXV%h0y;1;%g?*)DaeiscH$PM2AG3wzIBMc)Kz6VN%+;ZWkSG zs9wIiez9qcl6{^QD?G@!8I!v1tZP$sBDq;~B&kXB9(Cf+80vR=)I-DYZkI}Cceu68 zsggF4oD7b=uzr=69|!F3cYB@HF)^A+=vwgq0EMpCN7TGit~t2YB(;`%b(`k$&capC z`(B=(%f6fxY?0 z-S&xC;#l#^mi70ll{U$pSS@Z<@m0)^;k|ZCn^Ou4Mp6rgKXtu6qO+8FS*{*xZ{0Cq z@g$lB>ezUu(tBH|M&CJCA%|w;s6L}Ps#8qPt`16WP%poY^tmGjJ$Ql_qg*<3&tp&B zGv%zin40H}?9dXIvR*bY0CK}P^)#DExX`Opf5flhL#bYPmeWwwAZV@Ux0qW7ILK0& zAa%#gIqzBX-Pr5)QKt(d(WB}rj7uZ#x72@H2zwElvnC`VaeGO35O`ln1B-b_(&k_ODvOitW!w*ibi8B9 zpZ@>?rvCuqsoZ|rV}Icj(ZM9zF+Qf^Q~cVM$L%CVFNJht%UH(W?=v6kNPO*?y3d8| zF0GO~7}HO>fbX`sw_S!e_2U`li?F@)mZ^-={A;UdZY_LgVDdtz15>nJ$ivmtbo%l1 zuBc)O+RA0S8{EqWX*YurAAtjEbX*!YoGK`O?>MH;-LC(IVN-t)4Gow}x(u*`c z(Glq)ehezKGqk$~^*tF+<3-TD?!y_B{{RyHbnb%E>_)_Ycz$$U2Q0e?G3q}$E{Dr5 z!-E0TkIt3Qxn5v1iPi%@Vhs!R*Z?0g|wA-8ew%c!S8%ra50h5nO{K(%VII zaST%3F-Q^I29j5{u*~eyL%&> z_=&6B_*cUxQ?-H_ru#&2g_}D7W7hzK`qav&D!Mu_7;mlAaFeo1=Tm5-o%-Eb{HxfzQgU%jGnbFe9Y*0Pi0NJkz86~js<4+E|l6T%Wlimaal99NR41( z_yVP~MRFZaOu4wWRE0pqu}DLDEQ;KJ-!%?s-kfBf#ff!GOFicD;yZ6I8@%QVjP&dD zKas9xbw(^r4^g$BOY>l!Cxs-JGAn|hZ)1{voYXzbdLC!1c#~D}cD<7s=0H+rFd^dfpMVbriW_td7^iJ{MmM$EI09K78kYJ(LW{{#zZypGM)^YW!zE!fPsy@>Vy(CXnqH#P&M& ziEC-D=@z$k$chuM9Ccjsr|3mw@RH@{gpT@{xWZhrL=yOK;pC&ueIouae}!&aAFChd zS$itc(UtXzEnG`x@J5PEnpcI@=TVUNk{8Fm31RwDbZXCeSZ=&JBO6Qc1l}b0fY(zh zszYZaaiWYWjp@*S6{IPuapgvHZL71mjC0boIm;{ePURg(ue}23HHiM`sxoL}Fet-n z^!N3uAgV@BZ_cb*5!&$Q{nq|fto277H}4XLtWK+z&|r$0Y*aoZJ9lAO9Sr9+PZJcq zp36zL`(@lX606sm;~jTZ zhUWh6&qvc`TXlxcLll=1?F`KBK47CD05P7XtKrb7=!(7=u<$pFHCz2VL)7*8?kvC7 zlc-#UKQ`Qg&4bMjQbv|*bTh*mZ-RVP9sHUetFFrxhTlD!`r!Z!7=kw*N66omCnmQ* z(_IPtE2H>J#TIhi=$ekTJP}FdF>C9DAmAQ^gV2LdnXZIRP8*|MPXlQy{{SuZaqcxK z{{Z-^r6UJf_9H$D(m?+JkDYLTyQ<7TnKfx#%2=*mg?I2)lu!JAc!U1{2C`P3%@{m< zxhV{yY%2AH--$IV3qd}sLGgZ;`8|iIsZXu3H zr6&+g^(PV8!*24P zh{e&5&=NEFQFb<`9nq`*00>@&f94w>{q*1CMcJP_6aElw4UT`am++DQ0Qjm`Va~~7 z@CS$6@9C<4?W~{tRVLW^&7B+J_lG{vJgpks$_Of3bAK~acibg5<7QF#(WvQ~VrZTi z)NWeV%JM~@PwnEwEQde)6DrQ&}L=#o6T zd{Q={rZkE}d29f5Ip^g&aqG#f;Zn@(#o<*~8B26aJomSsviQ`+%*}n|t2Fz`f0pcX zyRL96PJ5Wg3mDdNwu0+k7roW~C44`*lU}^HeP+^2>4p*%>83XSkCr1F-~Qeo}FtPQ7b4 zKE>0*(}U3&{uJ?Mui>lT75I0=R`=c|y3k4(X0nLMG|0@V0}pM=a&kw^Il!$URitxb zGTL=yn$WZ2zYchZ;ys3i;!h4}m)a(=q1~5?(^o+em}ML~=h2jX-~s2-lc=8NxNIlY z(~NqT$Hn{mEi=Vdz9-OOvhh}lXtI=u?v7o~h`q@GG_jlpIRxO+w$GteDRV0_Uj_U( z*LAx+55+hBInFXI~A*?7<4wDz|%!3?^gvz=p*pphgo z@Nv|9rl*(Of*t>*Q8E zqv95m4y)tMF4o`=v%zj;asBMK-TepaTGEx>f@#8^GC5sO!55S1>;C`<%;w(S{{RVj z2bqPyZ@fLdN9EjCQ%K^CdwfT4;q4&kmy+pvEOyCl1*8mGPw*S}g_QKcl4&}6z0X+`IRC$N!c`P)splgTK=9QkDMMo&)PpdnVJHOnP* zYdBM_E5=%xm)1TtwgD#ae9~>`ZQM#akM8Hvo35VXSVz&6?sRVx-a^ZHr`l-9{a*6n zcVnK2GT)_V?H54ye4DY_4w+@8c(%gRPL=K+I~khh;n#1N%+Un_djYiIdzvo#l_$*` zo$B_gG9UabeaH2olh8x&RE*gUe*XZ|`iiqIq$jURvO>*)lS|G}eddwP!4w)sjN%~A90<}g ze-y3Mu_vh}v2U5&>)eQomDFwEmcuvKQ16b|T&R7cC%F3lRC6S*TBh5w9bpa3l0B}c zW99jhG%pJz>txC`btJfD-?{$`Vf`j3)f zr2JXYbZ8`lNbs<;hDi*rkj$zJ9Axmm!}6--w^AoUbKRF8<5jDF%o2b62p{}SHKCu? zA96PM%W8k-4L|+=03YF0r(-vOZ>bA>VYfXrX@Bq_fAKY^wlR5JznEe8yKaBx4=?@y z03ZBKKdgBX{gE%`6aE&}ss8|&OyBqTe}z}p7h)byKjvR>pYXM}49RylN*Wz0lo5#y%RqwY0UD z!_$Z@M#zF(n8-|EsVm7+I3S*T({*7b%Hl-~uNOk3s;auneG7Vrj4k{@1Qs@$d%z-| z8F^uaRFO^q0*`8>tz$}98R6&cCo7%w_zDLZV}%~3xsIEh?z?M$t?N2}hIF|+iElxB ziShof5!C)TKg+!-J0oflQhd*$&{*DSULU=e!=3@~&-UH*sKw>W5yN-gAsus`-~+&{ z93?$lvO1ZKac83V+fupF?{tIVpBBTGj{^bN4B+4m+;h`7Xr`G61z6sRj)pY5h zy#aL_8BMjw^9rG~vjd)+hv+Y>UvRfT+!ne8D=)M|FIpmS2Y2;bnC#xl-R-8fNCw%^hQ}a`V|StGDMt}*fhulGbIoqP0%|agM&Cp6mCc@;dhNc& zp<^DxautAlHaO=Us+KRvUd27m>&IUYF8o2^9}d`QTB+0Zoe)MX@6==igUaM{$6k9;3_^~QLX{i4re=6Q;Z=r}Z>>q<8@YAuQ(29o zy}F!j+=r5?GDzeDoaf~j#wurtZ*7WHwA9A%{{X^mb4AqcE$zH-sgDfW%q3d{XGUcQ zK3O5Lf_mU{&jU426Bf4Ag+ZseTKnNIiDJIjEj&Nsq|>}PtX#dt#j3&c{Qm%XLt{9` zM^VRGam2>n^3*3vPU&n|zW6L}z8Pu06MbjMdrQ{5YlVY&a(uuB-NPI>=RK)BQoe{# zp(XJ>NfY5dwJiEny5Ej3by23?!+QeHYr?c-@tx%IIHd8P-7R6)bFtC9D!O;XEnmdq z=Pjt)M+3vN7%6}oIrJWt3lVi@SF*IPbC+KQ+jwV5_=P>(5Nfb%Q_XyqZ=UTM9I9cRH>UaNEVtlV2)0f9VH5^qk8l|3Y1)bHngQ%WOEyZ-=LAjas1e|CS{ z_Nq?CGKVs-y4A(W(RBiJyTws&CP{dIx_u<0oe>Hal4+N}w4}%UgD=K^>yiFXQSA~s z9R_FUx}!&7D1AQspS4^ZtZLu#+5H--_YOw1Wj_<%r2ZAxouRmiOb(cqBBwhs`E%B# z?p-Lf&JqoGQ1Eu3x<89_`BXx2y?Nfb;EeFY+#lAeHCDye!t=gpn(N~K01ZiMn{JUD zK*-8ranEkmeDFD=hpP^siHWKB!phKU^wey9DvSx$@Tc&jsV`xsS08t8;(*qwD8^0n z7$$5Rd|6I)MXjt|}}+w;#A%yy!{ z#xciAmQviq@nx&r=$8u;lIe*69rloar6g?)B$MbF0LT6Ie=5V;w@s#wr39v(%-3Q04nLXPg;6;~ww z6s|E5UPlQUGr8!hGHALDi1%}fG!W;?<8qKsBO;5Th^_&_AQ}LW8URz$fE!NY6j5-o zui@>>HPbH}ZP~vf4O}-laSKK2d+c8~IH2SxXcB#!;>@a8B)3{iMQX(V03Ph){{S5| zm0gai9}SF;hgx*^-U9KDhG2{hc1h!Ajz$XP23+Km&I-1EzVu6V*xK(?r15^AZ*}oI zz_z+%t2U`(7~e7+vOylgyZtl!z%$o((xN&=+z&013m$ z#Qt3VHMDj(@iMiSQ`Y_*=#hwR{6(%RCH?38c|j=y?6Ei903Z2i!e_xb%YWDlWBTm+DU@8_rwjxKJ z!#kfi7#TPubgPX$eeDV6PgGv9)uS36mG-jsnsm~^9Fs=ZD9IZz&I9yeoZybQr6}v5 z?p`;RX2jn8st>V++-LI#mycc9#wvMO?qb_`pGee9tD)c8YSKhENga*Ea+MyZ2Z~aQ zj_4GV*yKEQ@h*KBcy`A_w6|SXUYX*ylT|GY5Ckd*3EOM zT|sMXzj|5Z5uo~#aZ$?KvK`VhwZ9SRx<`g?b?2B}Y4Ap5-D=Axm^~2W0)HB~(vy{h zLgEULhP=6QZt8#x&}xT}>bEy+`Fq396m7FRb9Zw1}Fv!+8vlB+V~u0T1i zepZbWLmK|XY71g-Ersh1!r#qbFpOtAbDoscB-PRjqNF;;nLed|WgYFiU0Ch=Ok2P) zWkUjU+Xt;2tp(H2n$GV|dxGrd%H&@oM%-s@6 zm011+(35(O=(b%I42;Sn194(_1J^X{1WT#u?QQmfeQID0x6JcbZtqOz8Kaiuws2n+ zd>Wdasqr_50>$Dx6!THz>3J9#F+MuvbU#7Tx1~|V^0PXSjMbURSnD>Lt*?i*M3Ezg zcW1+}8&8zrgY-OCS>`y|v}$;Q`q21$R08S(O{|~+AnUN!eL^YRdg{7)QMUTjjCPrS z#-hyYDtLcUv-=F$ySE4JcP9ox{{TIo9sMg!PI+!p@uZPnctc!Ud1I9Qo&Mtn!Q1Ue zR61V~Esu#J@h*jHZ?5U~8qJ$v0W#s#@yKz|`{OmL!$wN!ob$(2qfx?2=)}74r-I)S z#o_D7K^Z9?sMs9i_>`JHtuEz%Xz^`5b3(P*8U(F1g#?5Sv+{-Y8~sq1CHONDMm3?MwI!vZ!@(}eJVhaN6LPq`qNv9 z>@N-U`3-|14z zL=zDj7h{D3B>D=IWO?STai%TYkELpMfn5If302DO^;S@E^uejI*9R+FNXWUmxp^aa zCewd+_gSQ$<t)kJd_@2WM{}u8fAPrbrLs4}vz_pst!bfa_PUOtrbVh- zz)KrhTMFj~1n@@(2LuttX7xu?@;&e2$BX=b;!hA;Tlj~=Dm*KA8aK_0BypiqR|UTE zBj|^E&s@_5-xw|J}*^;|g1A=z*&~)oq(~F#U8!UWF*0eu`+U}jMXnF;_+Qzu- zW|7^MQX#i_Rp_k6hah%2&0H@hZotkH_Kxu6F#J%_q_c%%U1l^A1sDw|lWA|M44=-l zgR*2ZOZI}-KF8w?1}!i~ml{-Sa;y*?vK9qF9>Emj-m|6Enp;Dp_(kJi68PIxyr0A{ z=|9*O5f`|ft#C_Yg$zyx23YaH9`$na6VT>9A4jG9PWZ{IYM0hf{{UoJKxDMF2o)Js zf}v&aoR%Zho4*=&lKHPp4FI^d}IXE*1R@UwASWn}>2i}|L#@5s{ z#~?@t8DI|Ho%8v0sk2)%erQNdFIc{Nd2Bu%cy07cYn~?6FOFp+{ha6Y&*9dWv*ve` z6skrD+q%)LygA@$Y~#td-59x%y2u(u5;6Y(fRp%E&u3$SH&V1*j^E>)zhKdPGpe*w zy_T1+T3%fkkg<*x-Il=Z$RCKVdONhwPO5I!JnO}NCGfY7yno^gm^FzZ(3ir$Vw+Ho z+D0)syF;CucM+FRc<4_!=C+D(M7MbvT3(|Ycq8H#nFXGMCAGGj5IVk%356~`a1mr) z2L4;HJ$ltOcCHJpj)zF_hLPgGh@Ks=@g<+yts?McoXa1W@!`V-&Qyc^O~z|EJ0{Uf zR+o{5tLX`)cxvX_drPT&6{UE>;V*2_pC;REVGcpbRXe-#NasAznz$>wokzmCydnPp z3zq3T102bvK8+xDVBsW`O&)e{`LGACew7uqj-^N9I$w=4XctLqrTD|b`nIR0IvF6p z)1opo67mZ0L0pV_gWPdk)O0jtzX0qtjVHqzpwun(C^TlcXVPqS1G?%c0O-TEdoON% zb6fK(lF{T^?D__aHk*6!i&57sJV$e=EH9uw{j1k^uRe zd{Fq2ACKgQF%`&rN0N7lDZ%r0Z6FrOLyk|N>rGv)0#;8_jjLQ;Tlfa!;v!y4;Dkxz zh|CytW)QsFdF6Hi>H+Er$p;kOw+DM%+OY7ohlt_$jcehE8h;B#2$M^UZfO46A_TNq z?~u!%MjeelZdR@&n>|P1WZoT*NLqFYS|#V@F8-fCUh z-Ax{)0ckV3RSlF0+zFOziMW^_`xWJ{K+7VBl!=^2ON6xD=V*c6uPq3 zjo~|g32XlV31`ymzR!DkZ*VmU0-VV00=6IMS+F}}+Nw=B%H*rv7~UAuyg2$r{)^*n zGT?Z%?qvH+t!&aBkxGRaSC}Q>OGHF-_WAn|im0o<9g{`c{pl>d;vj zRf#bt?@I#ehu*cm7_xrsScPezSDmNo}q6f z@Lszr0vv(>I79$vJn$>fqbDPlrk1(szXh+(rSRKEx&Fz6Nk>=NX#imwub9{>8zckQ zsq0FtY{^ZYJK^gaHjS1zB5Pr&U5LR@M$a*V{(x6ZP8!m8HuY^UPmf2wbg;Azut-4= zE_1Z|P}!6vE@i3F$EMk=D5la56c6=d{3|Be(Wy(r_IAd`Y}-O{b#Mk_9De!elhidT zc+hzs5Z1+_tY_@cje{Mz=~Az7j^=EhAMnlceY3*4`*>$jVj1C|{yiCwCjjt0uz2*Q zr#PC_g(|v96lUwv~?omTkxw!a$`X|U#4;C?khxRa=FfXepNp(| zS?qGd;w?qnn!xIdCj&Sg4r*DV++G6mYE6hPF6?1#ligcJdxnuz(k&*);J?%4lgqci zwvtoP7Ii1`%}G{b>RO(kroxiyS1{Yg$7pFt0GxFFC@CiNL~vi)U#iJxI?Wjw47~{U zJ?U6)%<+$j1ecT3`$psc06>bIwnq3}+0_2b+CHDDc*geT{ut+$%G^r=NC67vfZhkF z_3fI%q1Dvud_8gF+wYF~))!Ju7SiH%*cFo0LRG|Me+y?T&nG`hjS|;!oX^B4B!~8s zvysu2WxlawBz@tChDYKE{3@Qb*)MX3?MWWr8`v@Lr?38udYnJP$0KLc@tUv}60-6)jO2bEU>{IxDgOWt&$ySwnc&)!{iDWyC7agzazF3mR?zrSZ+1URUB>Sm z;s*YLwSmitc1ZWu!p3qAJ-g7(UG@^i^9EiAh-84L85sH;R9w>5A@K_s^bJB)A1cD! z{uC|6V^2qmb%nFzmW@y8S{Tc6eOFG0Qqu11ZU^peB50r35scGBUe4&h@Xy8q<{{yK z5<11=3ncQfC*8e_3<(gPxgAGdzlSYIwC;~uoYd9P=n+8$x&(pBzkQ$=jnme$r0d2uQd z2_#`ag4QX@10|t%4))96iv&R~Tf4sqYZ>jH9%)-IvvG4f`R{&|o zC;~G%9)}cwVVtq#QDKakA)P|pMv@>o3hEdG>w`@@Y$lW38@nHP_JMa=W=| zT{hS3w*aG+DA@*9Ao2C7xY4aV7S_>5pS;5`Ma-^w{7oXRCBBBk3`?}yPpIi&;U6W(_On3%3&fmoJ6kHvnteNPa z4}3Xur3;IlLuk91LhMtWiaHGX`qE|-sHE(+J5aP4wmTR@^&i%bq!71yvoQ}U@BTZ|7wT1jYgMYPG+*a29bNUxIL#Ex^yQES^ zF`RT|I0yO~EF9djR(C93snq-jr9JucBr!(p&&elY=BCyz4%IX|9|02H5YmraMYpG+ zBl=XbYP(6EWAO_GrT+l1mCycz9XV`{j4Rcg=ZAION5guwT9%D+*7tGbZU7L_ym{iSPsWRP{$vZO*3ND!(j zf=&iWJxS_lgcKE!`V)LW@vg1oiEL!n1-c73yr_}{DmlhU$vm77%A8=L?jicOk2TF> z!J21=Z)AjN)*C`>LLNY)3LNL1px|H+e9_2|EeL!M;+yXV>aVHkDQ?$o6GA-nET9qz z;1SOhTH`wjK05f~MhhLqoMfC0maF}9Q_7H7r{gUj#-1$H?e!lKYFb2hw#XxlwxMYY zMgtZfb%!96?^Ep}wA^;Nt>O=f9}#p7J`D#{yt7#)wpnAphitP+3uG$^6^iq;{MgTY z_oo#ZY$lpGj|cdZQShFtE~BNiM($XeCTW*{3_&OBkU6TH6qT5|n9)fpTwKaqFK|mp zNa{(>X{BXh^)|dNhFhDI10|vgKN%2zTGE-zi`gFItm)C}mJMws#P;C2u#yMb>Q#Xl zKT%U7o)YDE9pM=6CD1MY-y=zKjU;kAbz-i|pVFk#TZ`i;#6A$#2YI5>?c7 zxpc1y=}D(W_MKk-D4?;o9b}P|bMzU>{{ZXNy)`B<=bejQ6TH5+mh;2@Aec+7X|1*7 zBOhpp@{*6h;Qkrp(u|a~L5QWu^w_hn=vErcNVo8BXpZQmRw*QB)mU;WjA1!be(NfQ z@kNrXT6c_fB!zejbp$sj{`zIye-l&ep!Y{d*5i=WpUl$sFhly>`wutXGd!=0NIu$hAK_2g!?1m7V}G=~YNP%} z;7|R7ApZagJ)Aoa>rxs0t>aOh-YN(F;gJ6Tg)e6g!S%Uz8~wH8FZvpJ{{XObKjBBR zh`zTW$*cI#8kj|_3>Q4hM#Uf_F8_bX*$7%aGb{fSA zsgF|j5wF&9zsfIRq)5BwG<{{R(7v5#Q- z)9gNLzZO5sFNuoAagz?85;r5UZP@&&s!;4p#W%55(@2Wl=8*)FTu$4jxRr~`D|)jY zPx2kBM3OL+WCz<-@Pf)o29=0zjzfCG{Kr~d%2Qslc7tXyp_YlUC5 zT;!u}U9sc0)}_sKD&&JpnH5BLdqDMzKh~$5cQ0F8G-?#c&-DOQw{W>M(6LTB(1~UP zKD6E^j@$b_AM90!d)+(bi~j(0E_m(QwnlS`<-PSHp8c-nk!|H$TNvEJZgz%wWF1l^ z?~d$21ExCTSV73bZ9!e`VEvxc`&vSYGTdxe5=nC-N_vtq8v~3S06^!hTZB<6q?)Dk`EXtZUIn z09I_5vEhC_IcY!aBXipwqaV_)e+oLVyT3z@Wa6^9+F$VgyQuhf{{Zb;ROU7#Y>}|t z9LxRJL&FcR;e%5+MrzDy*z}D$*T8-{Ku`QrI^%ulVqhSVy-+tF@DNn}de@&;t|twx zxuf`<5REzcGM=&UYh1Q0+HK9H{C$9LDbL_|{c36E^kwm;hb$cW9CiNyg1kv&NVw4T z4m+eJ$L6^e+YDt1Z8T!&O>T_Sq3X)pf3oj^^!q#i0EKJqss8{n`^e;4e=~tu&lf@)&3)x6H6KH((*6%?lcAqlxv|mh6mF5`kUJ>#!twdAt~qA3 zFUt*CuhnO3vaYgx7ob_or+8Z0O@CXQc|Y1$fO!!U#$u15K7{8Ta(Y)x9a;O*Rz`1@ zIxS3(8%-RV`r2E{CbrXUq0^#}9EK4fQb)0E=kcwnd^heymZmC!n!A!CibW)J9tKd? z_XPg{3PgYMVj=x&T3t>&X3_OM%TR{tttI;bj9p{o^9ENr?^4cK+WMKgY(6NL!?4UQ zEN!(W+~yMw=*cWkC#WYr^(L*yo5@jcsFNF->~s&3eN1KO%T5+Tak zk^CIc~q@0v+6p$`dzKjw6U^`O{m@}+Q==Qpei*C2LK4r;wh{&cl$n*Z_7L6Dz0!Hhju-e{{XIPB~z6h^r zGVSNVy)IUg*Ff=gtq%HFJ}zkkBDz0OMXlzrrO`u3@5q#Jk|JBzF9qD8%(tbSzEU}WKa0P0BqcQhEr z4qFsW=CS2(9uM&W{{Uxf1OEVO($RG<*s>?P@c>+p1#6^#y>I^j$7%B}<9K)6e0RF> zKwH6j>i#zW0Q`1~uHo!oaUSLcrMZ~p+tDg9nV{ceh17vTQ@$QQ(# zkmbBDsR920U7-H}Z!mjP%+fgChW6WTl;Ym<`AxRTc;%1YY(HJqH zosxY&sH;rPm7hDSQ!%z%mR#_DR^X3gS3Qg(-OF$e=dE+Lc0{)rAa&-mW~qurL84>^ z)eXZ>lf0s)J75xj_G)ADFg!ILb^ibpoPW#QU-}|C3VR}+w|HRj4XhHw_K)=Ib&4{- zb0a15k9OzV>s-_4l~j|^?4=kg4o`Eoaf8rS?BwWpCy&T8%sBr5ke%QD)2BD0IJl0f`Jh-3{VFCh2swp=xVX)x~0T$uSlN~ARd8O5I+o8JzOOUzZ^{=R&m*# zg{Q`Sel-hqXQxk(;t1$Z{ymSKA9k&&T-}W1 zBfbzHWEfHS3iK&q=}}gv6pndRoc< z!2bYgg*5G}3UKF<*7&yK4-R-fTP-1( z^{8+3qoiIHc10?P{D~tyR~_-s9-m6r6HUTg$cznjDEl|MIc+-Cyer~+&k4@&_C^Nl zN|BCp2|qM>^+TV+suSlyiKoi>k-cwjE#X+hGX?%8JfC`_l=B;z8T=jNApZdCn_Sy- z>*V=}2jPxBnXOjE+R|R1=2g6V<9BNwoLVTAcM=g^AvwH7OxHFj%g0zd8j zZVQZkifuP_z&$u5Wd8ubNvV^t;O>#t{5@$~!Fos9bLI#D=g<-t^u;Dj{w7ar5%CX> zEp*7hZ9hsYdw#@ZG@W9L^ZAJM6%%$w)M56F4Iau*0eJTB!>&*E9;i*un&+f%-aP)F zg8u+|sxrE{RiSmvj{9gMr4K`t#qoqvzlF7hw$GexB@2$Z*m_gBQTUO>`~`wtN8#^` zw7n`-i&>OrCf&5*qak_i$ZmUPv!9vhBHxDmI|bd(hVAX*)U^G7OrAE>BAOReD5SGC zd$4v+P2PZ09tPw;!|yr9FuT1-YELUYsFTKljyCU%G{e(wjX$!_ax( z7q4cia$#a?KoOV9Ls4K!~y9D#61sZsU$>)ve}bQL8hfxME2;trd<5 zZf9H*&nC2rIp$H2^ID`~BD?jiB6G-ZKJ{dcZnVT8Cq3xUo1A!$#OdR6ChNx2%;b~3 ze#$Mpz6RdDZvOuO(xT1C?Dq5{Lw#4@fATC}SnH4D1)do%meD0ye9prSS>YqOjKcUyaCoYiHsqHA{JPV|vheAGsFNsv=zZigP$5ZjWGLj((if zR9fh1g{?=HUe|V5@zR#?^}rpbfP?SnCJj`+9ZWECkGEXDf05gqoY$W8XSB6P5qoI* zZmAe)O2(Vk(Ti;b9Oa_P<8rXdgVY>$u87oD)Xq@4(9gBATU|!xNZvy%R`Ch8+c7Tt zj(%RbAp6pry4)jmt&L)zXPz5N08Pvx{Gi=Xwm@d?6+tJTKnFF8dRWmbYW7C;jkI=l z5!=K5t^`zE^*nVoYq8GV+hdrY@+!>O)p8b3_k9Pbt4N)S#fd!s03lXDTM!@LYkw-% zTO4q^k!IRKyboIEXKF7mPQ2D^(E%JDIH@#34gur06clsb9bfpL_@4*DG5QZ%mz>TbBe^`r-t_{YJQSGV^vKw3y;o$gcu10yo1 zVS~XRE_(Xa==U|JRZ}^3@ICsHJe!=4_9OoQk@<>2hE+=lbP_jXp2+}yE9VqMI3gMsu7PmmTSFF|cQ6@Pna4b7=n zhExuWa56rbp+@TAEL39b$~uRKF0}1N#>(Z8Pk8$x1CgA8&!?(LL3iWC3=$8vqeJ2Crs+UAn@3se}&KjgpsRW4Rf z)?!uBJTG&s+BMFeB$5@7#7@N@FvfT{z5~6sm;E)CINDgG zenDa*aN$4%j#*bHJqJ#5E;fQ^JzBoeuOroSwR2F+%J-MCS=r5Xa^WC_435XLfs$%R zyRtHEwPE1z2kJgJ)}hX^s79-6Xy*nfv-_w=e*53t`wE7+-HymdkyypFxzxN>q}pBU zw?z5zq|FpVXe5An{)4r8)F{FcO)XEH#bV~_$#!HHcJ~@Qsdr-RzEcNA9Z-+yPOT+1 zV8Y^>lJ0f-zR!sM5rb3GSz+-mjVGT95BcZ^F`xDv59R1<$Frq7GwGuxRvM7_cUX&E z(QWUb1q+Nx8S4~-hC}WRbX@c5MZwve-oK>X*=jeso|$ZyuwKRGU+L~}CQvcCPxrA- z;F@|fnzYumOIUoL?Rl)qe5T&^*tD_xWMe;%G@Z~&oSwEd^vyKt(NAlqi>0u-Al>Q{ z#$#!ee*!50oO_C10cFzYM z^^*xll2$il0D=g}G=zA@x#4|#!20WJ{tKE$H0F)c3_&(BQ3C+B+?dN8s5e{dv-O$3#*Stl#-H-9 zxIWvc4!4(I_w~QVpED0*Px)8eAKDk+^Z9fC0DoKjX>+mr8h^^Z;1{>V@pW=f{d8mf zXy<*!>|y+??j+x-%Dg?}gU41ms^@*h>|y+??j9|5c`&O5`)7axIn5l;xc!_LL?qB_ zHnFsl#i+uI_nDQ{52>S__ZP9I{>moMZYGxTgwsnRfQJm^kM)UHPEEIS97H1+rxk5a zrUAeoO3ChXMUYDpTt@ND8a!>mS5QG7;L%{YKxj6}^2XOu!T$g}65+&O_sBT>I#Ot^ zc@|)^iq=0a-V}*F5pXG^DK_^UauxXQKdoqtnO&;Un;F_qr}Y&`lDRzAgpRa%0}`&l z?QVzKw5YT>sW;3%H;^zp*x>gygF2C^17r?QHJdeyB_lNkh-Taq%_5P5;qm_f6$gor z{XE0}0HP9+r`bax+-LN-2>6%_efI+-wXGm4QEZc~HL z=}iflGb?1^@#;lV0z47UMt+pZXmoMzR>{p(!6asQs@^8>-lVe@5eOZasLKEbcprr; zjz1c)W5NiF;Y~4P+*DhpHx6-*^h4ntRqr#K@strs<8KQYS;T^3P$~i&44KKu$K+^{ zRJ4qa^rt;JsWWF8pcr4`cE99_Ji%^(_W_K6gNma~Tv9sw#``=H%v7?*rN|w?8O0YO z$-5kn!RhSh(XXMi7X(LiS(;#@C4lLJ?NDo3S?X;3OqS9Q30*Ft(S0j341$ zz<#9X^7O8lN0to6HDx;6=s!Z2e}^>XiOgf{@$$WX=;PYE>A!Uz4Rva=zji(;=7JVU zBzaS5&zMVor<%Vq9h{NPe$QH*wkG=gJFpjJWlZOEWCBJ%QSK|soH_1&M5=o!N3rO7 z?Sgn`;skegWk!SIOt&9)JHRuM>AgYvn#vEOHN?eAJnNygBtZ76I1e}Yvuqu_JfQ~Z zH5k`+Ir@II(XAJ2`W}rM&}Cyg!&YnJ9~nukT@W-s53@%GdP{YWo&CZCf&S6srDrze z`4QJiDsktLKEJ5Gr+Xytke_xm{_59#8cLjOc{S=fv*t4`t{&3mW{C1QBm2X+tCG62 zGp8p`T*&5<`~DF>9{sHRx-PccWj&&w)cR*6{w?WTv!}|BR-ILdkD5JCsIGQN8>nSv zVUTztq-QJP4GT`ubsbXfE&gU&GVaBQOyrNyepQwla-620Ms;J7j4An}qVVRkVd5P$ zU+Gdjwx!AkXqr5c*8zYed)GPPNP7N7$h`JVS4#ChW9xUOXGlI^b|?88y`PO;7B-MT>vMVWu%Z^%HJ?FIn>GxE zVs`*>o^pRW<(3MR>ME^!#_+_>oH-W6I?jcv=xJnidndK~WFq1X%%^%Dhk|kRI6l?X zbKI$UZF6IE_ScY0Z*Z919H>#3BO@U5(1SwM8#H`DqTFb)>2Gs1atr%3yiJ+;Rk{O_ z*NQGEJDDs-I5dkk{54~(O=GNTdL7@{Q%`_B$r2Q1LV64idS{$;rDXRd%Fe@L*E~~y z8rs@wH}dT}!Xp!d>-y7Bh@9FNZ;d99ESI;SgP)nt6x)P%WUI;Oidv*vvJJYG{L>!j z?&tJ0UOfv@8=kVeo>h-N-6A6JM=7{5ty!7o#)-J}eT!B&qGmIv1MKnZ;h0eyoJxBGcwx%~AEZ()E zA&HZ+PM>#=%vBR|sq5aV#A-I!{Kuc+HMI6QDZhM`de#S8W&20Y1!p8{7F3V``a?kc*0AoR%zP#MUI;rD!5TPSH zkaP8-$f(bjEgPTOR}I%py7c?;{{Zn+th6tm;-&)#cRQh8|UA?(`YpdBb>XO;ta35QKZL2C~EAlAwlu|I zTq3G>Dfph(%<-p$rN%0NTz<)@-&btS7a0=f6B?oEyt!j_CaD0r!e;bMpX z03J|(rCZq2{^~oqtMNv8_*erfv9-LAQSfU)bHtadwFd$RwTjIA99{pK4s!cP2jTx*g#g7%coXrbTRl z5J0WRaz;t~>KoM2>dp0`63W`G{l1r{TwdNkmu~_WcI(IZ(-K#9MSULL;?G8Fi=bBf zSH;xBuay}9Btz)9&$(|}xLL5S+*d44cgD8AYVh^^ILR><1Fs{I{DpK~SmlM(mWLIi z>k>oZ_+YgmXN`82c#8q}as29|StzA)Yhd~6xwWAS>-X<$=t9jQOmQ9#aqU&@{K@)^ zd6TiX@V87&AHvBKhWk|R(@Y5ar0@si2jWd&np)iMugxiFk43!F+r^#-z0h}N3zyKk1^JAJ4DjSH7n; zNODP|KgV{$RnXXuS5$)^nV>1|{R`eIy}7othR;d?wS7iGYi6gjp00hAWr_Br-Id1( zbI91cg5SgX4UdO()+QTBz?F|#AE_tYl34qi+YL+Yvn*|Uzh;hhc|iKtS;1lr`7~U1 zDrwW6VRQE-UK<>vc4721G^DJ;6&)F=#M1a-tz7C_G`1S3#;v8ewz%i-fIe6J zM}KPb=NA`a)pmaLc4uX@R%D$1kVD<-sp&gs^EJ6Dewp4M)Q^bNFIif5Uo|_jcBjtT&KP2#Q#Deb?cb6+Jrt z09+*5N?O^R>{2+5L3v5fBsWp%Q!AT6E}?rQCI0}?(qvyrBo2QtqKMzAQcIyGlEcf? z$>4pB{{ZwQt?6!a*H=r__5F5OFS;0^RdLA}By+*Q>DsbyOB!<7CUy(c-lSx5ra>Owl+hA17!a}p*_3}e)aM{- zvKY{QSw5fCRT12P^{Xc2mTW!?{jj)XUfl*S7D=t~1dNrj+SBYQ3unBbA zJI5cqp4}yG!*c*m2Vql34+;(Pmx#npwvyQMKZ$o5q#iY46t_qeMr{1HVf-UIao=`F z71N_cQc>kS-p)x)owHmi6PVPe%^TK)bf>H+U3l0=qqbd2PwLBf7V z81$=b)=6?`dQXm?_Ad!)QfYo*yI8?f7(0O6H)9{{=Az`&OPO5r{{VthU(2iAzUfVg zg^al;X_T-a{{X#>)Y+wLBh@@X2io*KM&Edk&9;#@9*dPDG?s=Z72xmqN;PdB3w;*a z+6!BC^3CPRIARa*jC1*ZRT7kAxzX(IXwX`Y{ zAwHWFjH)^U*~!PLquIfleQEY2bn%8Zzlp6}dYv&d{{W#=bfPb;`4HTn7u#CRB6yx6 zV~w0B)1w){&N|SkKwnsWirRGEG``a}qJ5UAB*= z!((KdV2EI6lE=9xxisFa!M7)NXZXS*@aaSkyQsiV=4eBX@6emW4Fg_kek_!U2rZ}S zfw=)x;zT_Dn%;5#||KCVFQCkLOt6e&4Clnm^&MBxZapT}E3tZ;Y?~lRdZ#6s@_u(8qHD z{3AIS9eAsYpEKs}Rb@w(Dw5Z!&-`S^_J*L{{{XWfANerV*Gu4M3;r}cU*vg~fgYnI zGk<5;+gzw&xXS`aRO7L3m4xJz)t;hPG)!9R5`VJv@&5IsNBUHAN4WV>ovNK&TQ{Gl z*dh^v(b`0(^8+HC)7g&5+tKvI@mGd)n|obTO}D?(eAuP5ytojSR*<-n3E+%252n%U zT`6-VV}@-~l$+{L@eMxD;lBhxSQcG4vD$JDc5*$qsgZbTUz^OY@hahDm%>Vp7{{X9 zlY^WRTd(C&7jo&Ql)h%C#vMJbb$9VKz1^JpZR)`sMmfVdVF(8+)L^OoD2CdCZL1FP zhg*ld-^fCMYlA@m7!a%UgS`M%;^kK2VDzq``uMdz@2d zyF0Pzr`>oP!MgN1%$Dh>YY82!7D+P5!2@n-TWKH+a6QcderI%Y8g`*ArIpBqtoO)3 z+dZO6+<$eMPTsw-S;{wOOj<~bRGMpvRjwM@n`p<88X){afz3Fp4)+sHU-Ug(j-tuW z{yepQ(HrC&R znId&#)c*jo59?fs-0f}htDPA8yi*`X`@Yo4Whp?(1Kax6h{cp8VyCy|S|xKfF(f4= z&eMe$`A7GKRkb4fkQXPg{{X&4U5u{A+8>#Rs+zi+7}9?6E^8yGk}tl$T8V5p?pn2% z4gox7u{smzTDgTTZKZ-yn59D-4w(dC&_1^~9}Ijj)ATgw z8(2|e)HNt#Tp}V(kUIbY28)%E&W6^I+KA7(lO-jjAT>CIw& zb4D;|nuLc^)f}z7jfc1-94YECLXjNWrDA^q+X(zIrn5~fjU~EqBLWlw&<{#4aZT#V zo+gcd;V*aaoAtye|?j#7`G(P!8KukUe8Lt9uvrPSht# zLT5SPSi))7`~m*}6D!aC9`rG?-;9Lc@QP~U*pV@`lqnv&O(gXNxtFSHkzM$I!gyIN zd@w|RZb$+9%F3oUBaovF^}+U`M$YKybLKJ~oqQeQ&pffw*6QLtH_gv5=j02XO715H zl;iKF3T*8>Bqbgi(w5|tFt(3&dgo~CR&q;o%=~Zh16H@!B%4A6=(=>t4a|WRmRBU? zWR3>}0zSRPYNMsc7{&Xqc6C1xwQWY*!BOkJ8=VMElQvK|cvyAk(;)tIZbdk!E?E%k z{v%)bR&r}mxDroh0ulcJJsfSv;7R=@DW=jIO&`f&T7U!GrmxR<*UYfnkaU(`6+<80Q18 zt!o!jI;f7W9y5OQiqmWQIQz2dU-&Ga{{V@tJ*{8m{7ha7oBKy5bvs*Y+pCSYjR}4G zjiiaRcPApI?{^D!lNpslZVtNrvnO@@dEk(xXPyYZ3 z1)1PkYpwq7oZr;ep4jL5pLSE!H9IS-d17rN#FHzjQlDS|9FJVm_N|UMUPsKC)ch;A z)T}%zmfEy$7ReaF9G`t-I5=-kbH}}NO4rcomZh%^;Ei3qZkzuAK^4z|+FqwdX1(Yy zBs<}XkN82ftaRG{0QwtN(%qRo&KKh_#-n@({R3e?`7qVJFM>ud__W`j=y_h1q{*p8 zE5&hX7nVT8^6V$+S;|&DGHARvcO=gm+*ks@YbW7Wt$e5Lq*5hKzy+Ck!W08^D=~~8iR+Fl^G z9wE86j>^U_F3!@)Ir5j)FO~KolnNh7xPuRm#oV1G4+SP7u)XOBVvM5!y z@ILdC`VW7m4tLhb>vz_~cb2dAi4qLS8mF2YAHnK=pD|v6ce8GnshpI6IgZO$=cIanDbChjSS&&#-X>!Gzy-eFU{t;@=`h!Z>G;?cY zNcNgq{{XAiOOI1*!hxwvu~Oz36*CO}Z~OqS^QmRUx4IMHt~v4<6A zb_*aHLy^%K@$@xQ*vg%`i3UASwF90X0oxopTrh+QfgP1Ei++6n-glPrs4*8T>dr7Ql~S#PU=+oBW7`t==z6)f45pq zD&l(yH3{zRO^S&CjmRp6N6ff69WXs>p-wZlwmGWOo%xA=-K}WxTFc_?J{vo0$J-Rp zIdz4JJ7b&st&Za(=~kr{?G|TJlZxgO#ri$wuVrTqh1%ORH&NU%U`SwxIKcP8{3tSm zv$;OAZFhAQs#zF7TbSNcBN6f|Wa9wj@I5}XY%@Cjo=+TUHiK@+?WVdp895k1{{W%> zl_shCwlsWE2%E!tninWO%^@s1e5R6Q^+S9O7#~|C{v5iuHc~qiYjh6PAmf5F z>FwLlR*9~qIbTLp(S`p2jRbm*y*e8x^yi7W&Y~=n58}Y?Vbz~)Wayo9AtJ-)T<3>*m3)uAtndO*YA~1Fw9fWmU zE;;J8(5fV?VPS{K!a*cbtcx?Z-N6U$o|*MOTG3L{?26dQoy|5nB@qbt5gvF13qY7R=gscP8b=CBI>Y`X{H?{{YuXw3VV= zJUr{q+E?46{{S)9UMIJb%4^k0Pl&Fe`z+HCHY#HcAn(1G<@Oo&sJ*s2J&X8W$=u~F zf+VV4OD2A02Lz7eH9H+IRBg#8hqi*cexj$p`f8v3r&Y|1xhK)DxR1`zJ6UQ2{@#@T z0PQ+gLCaIR`-Wd!XzgsvWoLIBy_!pfU_StZ`P7<4Pj?=Un=9J1wQn zui^IXK@4FO(F}q9>8nIDGV>Yab6TSoS3)+-c7Q_v08v%cghQXDSpjFmxBI90R+8A~ zhr~)N7Ij>j5%aIqR6UE1qYjd6PHJ#V?mWO|$+?#E2FA~<(%7#`FaN(5o0Ye|4 zG@4wl=Jfz1R>8WbUjH+kzJl~ zf35hQ;H}N?iQdN~4<^nSqlroE3x;F(n(I`cx*SgvE9i878S$Q{XQ*1)c!lGhV@wwqdiat^az}&dOBaZczT2Zy)Z$}f(`D91&!|k%BgJkz69}nsmV%GlvVvXC6 z%^2O;Nm4$zsl#&fKIP(^qYI-#7}hwFIM-?-g;D4QZuLC$Ie-*~ao=mVe~IXxXUv*& z!HuWymsiJ3ebJheSf|Qrc+%=CPYUXfGnEemM7RW!?l!0ZbIBrN` zN#-n8>k}QMe9OZO5D!pl*)m${V0g;@$}fn%6cbDptvvRdDt=Aqp)>U${#5!7De}7> zfsvZbmT-O%)imqRh(07X(}ruP?bUwPQu3P(}_tJqf~t^s0}&p`BLwY+bj|^yxM6;uVDbzFTn? z@^}mDk6d79&{55$hMRG)z9I15o#F+Bbx58?izNcNTxTAiN|h#tn~ugcm&0ES+Fc~# z$);pE3pA|DpK;LsG;^}~!Yy%-XZD)uVApMGbrdqu(D$9RC30 zrlzXBIWHz%AL2#%qCX9R{fA&jcvSwCl^rR^(Hdeui!WAciqym?5nAeF5;Fmk25B}@ zjopl22A$Tr%bq{d-2F0(S>T=E-iJY% z03mHa{{Yk?Fa0SswJ(L4Eowyay%$*0bogeAQ}Fe!s_xlYC7xKB209IdR5Q@CzJ$%L zXm2YteipWkPeohV&ObIZwH=sCsp%3%LTH92BZ8N1aruKtH8Za+n&D%ON0$UZ?2H_N zo~P<~$@U(Cm?dpX7Pbq0rQJ<#jxH?JNg(PTGH^)rBag%BR+ZDSl&UyQDQwEtxAzvX z$rMkBoDj-+9MGiFDs&-H^EIk8uY5tMYBo_@UEDK8AMoQFz1a6ZTB%fWRxzi7rA~b5 zYE-eB*)EXYKxTuZBDT|yP-{D#Eoq?p`+Ep30k^vQL`*jfAyT8!v)}2FK%;lL40kak ztNxE0Mh_}_k@cl<@|tq$`bN+r(NRe2nah7lcNL}Fg*B};NY0G|4CI3C&+A3QmG>L2 zYg&E$2_2S)Yp1fDmx@PX-rq4bC99P*yc->B{=8fN07FXBp5|4yUZ=2YP}o~W7E8T! zZd1*@w6bHTB;z0C*3V}p6fshCJ9$nvb?#$BB+LBT^zZsk%MJ%_hj_P^%Y%7Oo;WWBz>CXx6D52tCP^@iuZ|? zIqE+;#Obj-OY<)ysP9tEBe_Rfc_GrTWQnmNKjUo~L{EJwC;XD((xCI47R{Yhq()MKrZ%L!(O9o)Fh9 zO~YJUS_vYzygV0Bxd%R~d;3#MQz<)1KBu|Qqg(jj;q{HCx8|t5wSsvXA{Ei_uazVm z9Q#+D8FEiU*OKOqndejKN5Y>Lbp0aA{{T^+PrA|`W=p%4mokYD&CdYf4xaVerOd9* z3b0XH9fYEh%+E2EN=$0ELTfF>$grw-SNd4#fV1esrG3kWZ1G zvTh?0`GwcZ#_ieT9=WM7XOetBn%*BDv-zpzG;>6Rp1FLEeuAy74d?Klr{Xy+*Fn@i z*)(v#&^k1Za0XP5>rl5dTb_CFK&YEn4D05}4YT7spOv%vnyD8(PfeJ7)sY7^-~qcx zz93+Ix^?O3)oiMpm^OCpckq)}5fZG%k^SkD#Hv>q)2ko zJ754a+_QePuVr>;XF1t*B+xax>9w1UCtcH%Prpb-%qo+4t1LwACpa8~!5-qGl~~nR ziM4z@s5Q;hudH~!XnaEYCWE2g>w1h439eGyg%F|Xl6#&w_Z6jxg(*#3r{c|K*If8x zsb5*|RVoqVkYVF+>I$&zL93grO+MVQz!5LH{{YZU5?4(Z zPvc&qcvXY=b4_91-4Ez0BCL|^U1!ZXKQ?QF#Zi&SY9A2v3oTKsnn|T7+a#JO_eUA` zJ%1o*DN~bIvC!N+bJ~ewWq92;D+7>0=xH%68I|C~{{V?@vVCrw{{Tg;WtZzu=5^;9 zR*#}Ad@UdGKxN;YqYq6JKa#x6v4xf3fWlk@ts~ z2>$@bsxE8uGB;2#Z1*DtZH(s?E!nWzwc(6sLDNeA z0DJ5?s=FtWE2&xaNtod1nqvJdu}bVYS8|4{d-kZ+4xeRgLC)wP^2wz2E-KwfwEPV} zS@*Le{{SM#S4(m$boD*QTYW>wPqAFRwm$QDVSyO`0ClTI?0J-udz$)0lfQ{0SDMm9 zSdMmu5)A!0#bBM;yI$%#(kh5!Sb9@NOsjK%r193R8JP027CfAND`aBO&8e}@1#btwAlJxkx6qYo~bL2FPF#oNzv2?zNptX~nLmW*-wdPSV))KZ#x% zz13dJ#aWx{=m+YfHHCV_WIER+}}qg}}wt?2(w3So_Ba zJhtwIx&fZ}tZLSb6_)0-FtTz}sCC~LbV%iCY-ZoAqB+R$3AcMwNHakPFu zmCZt%l)0JLUZR9FIo%7wR+?6`7O2-+h3)!AVp(qPT2iWdftu<#C00^aX9?p^6yMou z?iH3-XO#Z;EU2PaMO9qwA1Z=QGD$qrXy~N{G;>5;`D`!qH$7V1#k3E)deW!tovpj62B$693jG(CJ@f6i@m z#$s|=tyGeIN$*q2i&3_M{{X^cs`(26`ze8XM9_ktOdc^uD7h8xM&2vFDo5EgwK&FB z$}mUciaAHhnK|*cvm!>5%LK_8%B;{VfFEC&9%_m9F*p1>tXyiZCET%FLwjW{yTd)S z3k%_t1Pntz7>0Jb$qOikshG$?X+oMA!wnuseaBWjnN>~o9bomR~)Z}qb5po*M{M^xsErE z^)4g__+#4ZxSqo`QCpQ1^*fbSa2ZG=s37&JBv(!;0fFN}WAL`D-j=d|=q8zP_>lhq z1)TU-N&f(^Wd1^@s88Z%(|pQCagmIQ2|SO(y2XcwG%Jq_>snphy2MgQk)#l-3*E=% zB%Xaa^!%%yt{PP*%@%r;aS@C)*zYt83G|N(S=rr?XO1j52O&v0z&_c_aXP+(E#(ZJ6 zxVYCmm?PZxnsUVtAa8V>J1Eb6r#P*t>jse!X-P@+I6XH`@qU#H#ipG~(ld;+E`mcxlS*%R6>PvHXDlWx& z=T;zRIOV0zJyYM?NdzB zwMj3m-UN6OB`inG#%S;dzgnn4#kR#d^y)OC?s`VK_St|D%kmz9Ev9fjr5==>&RR(& zf7I%9nXituZ|0C~nmiAZQQOp#qOnTGjov3*4{m~)0!V&Zq-P^8_F_W;i~7=C(QS80jy09-nlUacUo7xLb#_k?^yq>c3yFztF^;)seOyN)q>tThEN? z{kv^xqG&g__Nx&4TzeWTh$PP{6pS69bJ*vDT%N}Jv(@&gEA%}N!d@lQyj!P9d1nR0 ze`lSo<%aGZl0_f@kVie3bTx$H>AN#j)YZ|%>wgy{Sg)JI-Wk*{A4`EtE4&|#em1xECn7$#`;rM~7=ofw*vXU!%354?McM)vfg-9?U>e(lg&=un~ ztthx|WnQFOvpSX?@md^5&tDL7HR%to^Zx*mUX-^x@c#e_%!89sZgt)d@HV01DBA7a zEOfZ}d%1=N2VJAT*QEw?sH36W=-NkxBX2K8(I?j(K32Q9Vj?HLJ0HLrQ>zGWWyxB4 z?1^t=wua_-7r{DQk;RSQXYz{-jPxo$ooPmDqPj;tXige)m={(a8P=rxGTTd|YA5cb zk(ly+pLG5l*8H)Nim8a9?>Eg8jPWOjZ?qYL-KOYV{_bS^t?lf6s~N$^bI_rQg>`uD zVve<$&~}~x$xFaH&1k3p00I;IDa7W)bUuiL@I^~G%Dx}d+QQCgjF7HXzE~HDSuICRL+ep9zD-pjul5O1;%H*^?AHy2Gmx#1z zbv-HCTRBTAvm+tOk`DxDs5KU8>SWVeGnM!}pYcVf{e4`2=+>>Lxa4!5BSvi-;#PCS zu@A{3sY~A)Zpi37Sms}Y;zDv>%Gtj&8LNlHV<4OtGmyFQ^_8}-BokUpG^Cg8$5I3s#Jvb3uF!bh(>WDfj@K)e`x+iQ!SIH% z-}LDzANR?rK@W)w;M6POO({=5=*jtFrS3Uy&6vF&*ab4sz+ZNAN^TSsvYLj`frj^OnN+*3`hjPGqx%}Mb80Kz8$wg^>B*1!=>GuYO7FOH$q=6eXnpkX545s>@l?Z_Lr;M;7Ub!} zKlRp6{wj;U;m;ye_v(tq((uGn)+xWDj)Xk&q3IP{hO z0LG0)%)z`DqT?T8nJc(c>H`u1r+o=> zZ*!rygbWaQ&pcLaRy1HtFyoq$msfxe2TBG(aO6gPPZgvVrZpahOsd6Or%~K{*0QzC zV4aA(rvCtE{`wSLZ33YSFDdtbygfxyBE@zGJTm_P-$Jw^;#eFr^VJ*qR+8AtI>?k7 z#MqV-^RL#XTNe+B5-F&$pzw23U;_}{n#|Vj)-VQvt6E-O%c6Lq`%H@RQ#`PwNb9_S z2^s0oQgDp7Iw6Ua6^&mE=&|V9Xthb@xpN~+B+}z^vmBH8(^gADWj8m>&18rog-{ho z01nickg~d{!93L66ny#d2~YOm&r*zk@+_1~bH0bdP4MHw(d+u8lj<+z=@u*I-AKo1 z$7RoK;Qj#A*mY`hEe}ibWbva!WmdEBtON^NIL6Pkfzg{C6VO*BdioViY#nk^3^u$Nfy&arQ+>#Wajs*K(6 zN0Q`{xgNle)ON3OG31JU4<>VlA5tyhP1+qTc+rw=y4^C+v*gzK8sWqS1~@-@T`!Tb^g)JtAEbSf0vKSRu+jF})8% z{(I)Rg4YX!PR9vyg zU@2I^YoXy59}>0OZxhX`&E{N1a!P+^i3mVCC|(W-IOiEW`qlELjh!^rkeB`l@yCg- z?)5|Ao5{Cm;Q6{X9Jc~z0Ir1;;(KMp(=x(=h^JBwLkc^2CG=W<7u zV%;|3$RzY?G?TfUsW&T@KWASMYVuy(=yO}W)|>O(eWM@o?&p#T?ilv#pKQ>{sTFi| z-XK79Ux~bQbpHU+NNL4;$jKJ`M%#W0Tc7%HwEqCwby4_?r@3$9A0G^LFHf?k^IC$b z{7BvK@c#gcb4ndQ(IWo<&;?}oG-h_23~>0Oby4>qYkzQHwAEn|&Iidf zeBguXIuZ1yt!HCLYuz51;>+owv$nIeki~JRP4;D&b%<2mbi1dYVV$MgIT@f&TyqzLSoh zWQ+d*K^2<`d`!J-sMuWI&u4FYZ7tj%>tIP$ocnu=gnd)ZG-h=W- zIil*98b^RUHKu7|GpXHM4Tev-3JzLF_8I>GIIiUzw{y>Wvmx|Zbz5_&#bc}MQHICc zp8c)bVmg>29YE(DGfmlOR8+S{&xN%~tgkJiYx!@qd#5lX-Twf`#jNB0Sx9^ObIx)2 zuaHVTj)TN@7t`HoQ)!YI#8)bAR5k-de)dOyPsW^Nl4+S((7P~N+O+pl#|$Vk(n!Ho zex&rQZ?1*f;zb%u8p#tY#=|PgatQSlt}7C(Sfb$02jR!V?E}Xb9xBzf=q8s6E;qIwi!xd!&Q0iRS$S&PbFA&0+C{{Wp;8nT`F zg;{&2bZwNe$g8=)$ga}X(jPYJSfPxtUD7a7lfW50%~EZO!{m5M&j4%{-lho7E@3jjn=xsHbAi1`f?S}F+^2mP5>(idZb?J_k z(L)&;-tDZ;Y+eW0u6XQraiHz|>ucQMv1kvc6-YxSkRtUJNeQzi=0hWOtl9}2mxg>o*M1ybX2Q;GHvKP_%-V?^;kw)b;5R}BbLo$M8duSp zPAN*so~?32AU&%NqUd-p#6$l86(ov3g-HJZ$K3w_TG8%y(fAIk#?h_58)({OWTd_z zzs#g{@`1`}J&kowRYm3~|JvV~s+pj(`q=yq2%4Jv8jBaJOgv67Lun zkZ#a42{D7x=I{Hy;d}c7{o!7g4-a*I=bw(nDo`~w{W3jkOS!YTJWRYcIacZ$@I2S7 z;dNu?sp2I{r?f{M@b^`@x0>U_x}Ym{Xit<+IZ@Sr4CH$sYUGQvac8wwF{qVjY|fL! zmSX!wx`!EHh11!GBCcB*Q_@Ej;8QS?VlPKwv!F16xGqyqozP2q1GSXxOf%uVIIV3@8{4tPAP4!@}FS<3Kt zMW&hK`fjGb2fTByX*V{rUf9f0qGTXAPD&nlJTV8SBD!PmtD`EAyEY@%t@ZDR-w^d{ zRF*g{WhGWn0}MqN=LCvtNh6)bsdBSx#LJ(8mcai2&xNP{)2(6g7)x^B#Bcc}+LnLk z)jyinT}p?MZMX_r^#MqcCqWfg_>S6eY*FB$76a&{y0bVr!R$> zFXBYd(tsP|T8T z=woS~61n(+D_(fI)_YG5C_iJ<8QM7}BylvmPDVe4f62;qR6Y~BjT>stUe`eJr^EjM z5gijkvX@=aCnfC+gKDDp4E+#}`QY^fo+(v?mF1wPLz(DyBfvf&@d4lBj}X0uyMN1u zjq(6J8dl}K_{B%oV;vyA@y867=XK!^4Qaj;(brAU<8-zTWINomGaO`-_zLEoM>lqK zD5aty@y~=b&l%}4SZejjifv2b&ZicG;)asrLy@*=+QA-XgSeIu@gh)LkC^^-CWF+RedGDku*(@cnPE^nVJ65yHCE=`9b5TtLEw)$4@Z*(X4ux9ghe2fT#Wvi=6vo@#p&0v%Z4&4@SO?V*dbz zViz7sx6k|t#-G-)s{O`@rwdNz=Z$rH^H7P)-_KMOJ#nR z<`s>ZwnkK;1P;0Ptl?5pm6_3s!P2Emo-CK*1*_QHT6mJgf23MEtiW`1CmjBG`Zu*I z@w(<)q2<+8-3e^=I^8zz9ZJsL?%B6NGKODoP<_Wr%bxK&J)4B=aK0l?vwSv4m=pe( z(V_MdH}nRg{^6}S{`}AQbCS2X`@-VK{{Tv(sE+2ifj%hdTBrOadbRX=W#pDcV`Ffz zu|O6j1n1CT)~uRSN-13R{{V#F@i_gT=dshVCF~1x5kb_+y`)k2hF{LP>N`nX*(;`x z9q~-}%p$`wt=)RyDaDK*bot60*SeNF@8y~)&mC*H+l43{!n+rJ3x)j%jOAGq&+3{EFuZg^TEY&~j*+29ptc$1dBXh&&{we)1dRoQ*0H6xVnlnF5Iq^;S{{W^y{{Z9I zt}HgS>K>LeOeHUwf#dn!KNfgFK|4*+&*D*kt$HbF&oVoX_>9|b{4Rg$Ef?}MqFi?~ zJ})~)-XE1>l47@H2;&)MBn$vOYNMel?KE;4uBoU9SI*G&2o^_JqcWy9IbnhN8oqmq zofQ|k(fFfOzW)G(S5r4S1?9YL10iV{aHQ}F`VuKzP=ZocXK~=T8T=om+umDkPqL!4 za$$o4qqjVJ8t2tA=GEDQ;O#R`xADh{Ji8l6#k9~Y0bx<)W-?CRILY*_o6#{}L|xYo zek^!T{=IJh0Q3<_1Nd2Y#>!5+;bJ>qe@Z3rvnc#%)6zfH>qpY%$LK4I3A27@rI~LJ zLn}bit#$8*8cnV6ZI5h@u7?d9^$e%672A6=g4Q`-5MTI{!&jE}lHO_BjBEzWi;Gj{ zASVE}7=C^6R~l+*vL#(YNi=o(PKi2dQEHmS@x8u{3Ph@cIH3*qwy!*%nI!R9T_t2` zDK<05o*zCU(S%USV7|6-aceL?azWER-~PH@L#IARbFcVO<1hG2v=$etA=a#jhV~yu z>AZU!AH)${btB5pN-Z^WufDm0&emI-xub^OB`+kBFjY`Gl21y>E@xy_(psKnsC-Gi z)SZ{Y8gy-G&}tfn zRg36%k`S@V?j*L9`Uyu0eVY}_Qwrm(bw_17(W#<65FFs=#mg+)`b6rgH zcQQOxrQJ($ZK~-&e`s10@}2~!daYwtpFC`DLa!s&W1Kpcl#eq@d#GslGLmM1JI9mX z7~t0nRpX*pJxF^Q>e+uoi~Chzu(Y(AEfzUN#IsBanb}T4fO$D=5>H%r&1r?g$yD~W zH!;Ry;TnF6-=l?-j>0k<6I(z9mC zoNvSZW5YHc7+RqaRH1UY)1vx{c&^R|RgFSOK&VfzN*AlUb_pO|*1Wz}B5O z!M%-nz9ML|+sOsph5N-KlyumH5O^c+ReN|n%obN#ElMq1=R70g*mNU(YJz!e4(GP> z0N$stAQCwk&(gVD-6PlJb4kl|YFApt+{z zQuuJiR`9P=b7DX8Wol!&ScBVqM|75c8`Q2PjL3;@`9L$fBN+W_TbxQQyCcIs6Zq!) zz&fn23{82bTo3e{P;f|Icdw%XpJF=tjOQL>(7l=NcApTw8|tei_x79Vmoog*M)ACC zPy0XutbN9Qwa)3wp`)?R_^;z;lcrirW#C&zx|zQ9^yteP6Z}|jnUB{4>(ZQPH>`Bi6b$67~oDSSoGiM;$Q)V=NFyuD>mT|u)bx{+W(g1ftK z9D?lN?hXNhySwfH0fGkzB)Ge~LvVKq!4llvHFqVS-0z%oU)`!#uO5G3)*9CI%yjp( zO*dfmNvdJW?2R<~!AJBZ%q_oej;-!m-)nqVEd3|NK2ljUyJ=?4X@BKYq3hOd&bdIG zSh56?e30_nCuUTkjUy(Sm_z-5?gU>Ciq6xcb>JROu-bRFs~22?v7ForcCZRgf}iz{ zl=d>9oV(GPAN36So)h4&Uv3-fyv^TK1@s)TbnP?v>6k!5-8Aj+N89}US}zX?+4=SP zJM)a$eueNDu&{T55w&C{?&^NZRr&jxslvKY;-X_b`8_Pw!{+Xzaqls)NmV(PT}Ptd zpxzeY`OKn)fx<2qUaq`nTmQ8*v@DO3_6DwLw%tQ}r?@;Ta0G18by-)MZz^f7?e$91rR6!GOnj}3p)JR4 z)IaF>t`_^~rcH-$x$l!4S`(H-aqHpw%K#N1ksK(~o}+q58nn06(h++vsei*}e_Glh zOR_0j3Q=G}IitQ%YpOoV09U|OBrH!~|Lf7Zl*wobPWADd1DzDYRfgHz$vVMuDGg47 z&+y?sgPUtAzDX1+p@}PT(~knDK!V1pw`2A7`tuKxkE=Cga$$O$M zrDX2w_U~>_G(9f}+xV;*^=+NI4O-F&jUGl?DYXj;g>73gBt>0?Cm^^VJvYN&jF(%Q zlwR+}ugEUGOhdoj*@b*M(U(`$9Z?hzlq9@qypPGZq}pv^ed@L2&yYA*d5FHHKQHqy zGdUj54@+3ah@`BuTKisB>DIjTemphD^stA=CeOTcfKo_%h$WK+Gn!f`*T#qjd-e}o9b63sOZu-ktDrfLfhuOl%^Pxt;XptgO z7ge;9+27;KoylPEpEcwci?2tMt+>U}$U8rTzl5cwm08N@g;idYj5a3tvG0S{l3rxs z!3)Ej_Um4gI{749kYholAjq&6izH1eR6+VTNTc(Fd`|(dS5YI}OvxFWZ0XA#QGGM< z+sJZVRJS*YUxII-MTG?)O82>oFCmG7Id;R>3cotXr3ZZFWGwVDz9) zavV>QN%}!)urM?MTVwSD2R*#tFitU~SXbnhgN^v0Pcqtj^Ty?+SBMSjNWUm6R=Y}Us^WZkU}|D zYd$;F@!0C8E_Q}dpB@*(2Tq@ex7{5%R1wMuh0uQ|&)h~}yXGn6VSSx)rmJs95Xyr2 zL-O5{rG!eDeBHsO^lcV*{jqwSyjo*zLw&qd1db-DN1#T6LqBImun=+X;!}_^_H7ae zS*-mi2j|9-@)BIrCFw*J&kB)W;S0SATzMO)!R@EU(-ocX51S0yVC< zKX~KDB&>ZW2lUX&Qt~G<#vWHxsll5;Boo#5?|R@d(;|vV!U%tnBFf5-xvDhOX(T#s z*S}HH;#T8VT@oltu$y+VXU;G=hITsichf8?%D(ZS+| zJAfABwt?vDWrOk)vMncIi{E_|;>xlvd`>rk-2N&{&Sh8tk_|)El#lZ)FIeAxg>GmF zo6YK@qMB)Elg4t-c@6QTnpDH-NpCxzfA`*B*tsZbC5>n) zNcvKhV`(jmr(A(SeMXoq*Qb+ikHZv&&Xn*HZ{tmFpdz+u{HX#q2F`7R_lA4)aUAtB z5nkwW*T(-sZW*&W>+mT(syC#IK4ecV@jiyb zWubS5%^icpYv4^?W>3m^a>3q3y4c>;ON(%W4f0kMWuxT4M<$C|lfz?MrO(avf{xKd zZHq=hv_WIThWiQ9g;>nC`8Rg0~tQ)JhVf^X@=?6Xz6LZ*ZWuuvs5q(Foroo zx~;8Ir%CTKXwMFBrm?Su$zSy&N;NTmaV#lideXVq=A{G5d*15k;UD{CXy(Mrb>m&bUrIV$s$Z z$cwcKG*1_v*Z8z44)d*Z-D&44UP$d^hR3oz0h(`&@?lbY9e>A6x+9>sYzS80zSll0 zN%Kzpm@6V%;EUFa7r=B{E6sFLJ$ocTekhYUk#d2(6>nMIBH}jHrdWRKD*HS<=SNvN zes9y}=uTP-&yme<7dK-?vO_>vDLKX!q+`wJ=DA2#kyPz0W}P-s;|dh@>xrq% zRaSSQ?<{bo%*%*6wlVVwZ#G%uI&LHvUlvw_uJT9O_~Zh$7Y>YAgyxe>ulab5jsJ-5t1W< zon@7Ktd^*Wb~G%l8prhNr~YTnx@(TYGwfj9lz0iKPo}CmYqHAir%k&+HJc#>e+J4V zWWgnof*L;4+CEO%E+N+iDAQQ3#=v%BWK-<$c0o_`ugq6KyaqCS*~&=ZB^Lj4B9~r9 z67DJ@oucUUcwu)2_=`+~Kc8x#I~tz)!-y^2!yEE#D4ZUHlpxlDR(YOg+>lpmpY|$ki&Qfevrbdc zq6ik{(Y`;B{CYjr;ggLB$W zkMMx_9BId8O4@NiGcMNhv?SyscG2|Z9`c&I3^M)G7lCJu85Ez|7DGhCIr!S}Ti#X2 z5IV@^lQVGZ!4k0Y=tZiN9qx2Pah~#>?=?F@oo^$bnFMYzt)P3|e>s)(B992|Z_p6u zzw879%IngKYn^xaZk6|!fWz$#ZYsA0YyB*52i13=RFO=!x+8&wuukxMVObhf-|wt1 z9)8p|5;7a_7XCTXMfiI9F^WiyetC`&rXPgp&gC!T<#bzha5|mZS>GK8@QHv_ohHh zM^A@jT>7$m)r0x|gxy}}#9!vPCFiabWn$}a*>QZycs6KxcUZseqVWqKMpN5zsdwW| z^_^LE$vQ$SSC(oGfrvhAwFgmskFGc0pkA9B(}~J~%O!Z*meZQsT=irIXA9))-43D* zEj63yJH!5+0H+X;4)ISt;%)R>N2ENd%e2o$=aFQ$rKYTrB5lN43X)!U{{Ac z1(!+Ar}1eL_P&#ccwd>H)GidnM^tY^@f<37puk0?FUJxd`C2O&D@Yoo-+a?HA57~V zWtj~K$68Z6VYia_uTtM^mFIZ7~t9@npEI*uVb1{^7fxMLO-G5VNg7 z)GsI;9^^QbJQ=rw^r1i}Mwgk_!APc1sl^f%M`9_Mk>uU|{NZ}c?qz1k(m+ooDza5TG|(lu9ND4=B7>mla4$s>~lPLt{(@9i9% zi>t=mPG)g4axuz!HVI_QMdMW*zV|k!_B#}jJ~_1YE=PU0XqKJ&&|l5lEZo|d5-hBv zF=RCy={M-o>f}kJ#o8c413k$ckEGH!4=1^c9@@91iC%g~(ZPRGs6{ zy?Hjyh`tdSaOgU;UC>9Lz23n;%~r`HtJ(;3WfaN4*!gc(|Vqd#mcCGi)8-5^i2og8aWjhL2#uSj11 zfmnYB8QSC94kMS+3!PRG@yG53mbKuE(!d?j+g0<&Xhk(FKejW`7t7{VLdC?KmE&9q znF=#8n-gpNzAJp%?YH5Ueyp3%LvvgKr+Ya(c0|w=KZADwhQ!AsPT1y>E^E~->>}< z-PH6sIz!T9vK`nCx8J8BzpC)(d_AKumKNK6aGfgKR^7rs%i>_j93t$RL44Nr7|k${ zsIyy1FTi*s_>N1acdgAoaQ)|t*n8hk&*9HJPCpV!lZ)5O$u88|r%x`^RqNX2mEu;3 zLqg2IxiR!0#)y{G*o>yQ3#T$$2T+_@hCN!Y-3p1W)6Tj1D|yr=@Tzv3d@klVZ%eD{ zGgkYcp)YOD^msg=eOA-0KLNA6qEG3Y&S_PTH0v&+j^Oy{>F#R0HNxdny&WmdXDQ@~ zH>C`R7y)Dr|C3|McG@yxu1;WnzVI~jBfDQQE^DRON^hA{!5gu`%-G?8yzi`5hHnRQ zL%#@%7#1BKQv)$C(Ph*b_p6L_AKx9@$4l??!J-|L8D4YgIq)c>Jpgt+8*Eiv3L3vyV>NI#f~A>wV`p6xG^9dON` zWa&@MUC?n{w*Ms)i4MaAn%Icgt&bs8PEky8*(0*Q5EtHxZXcaGjb#yyc;Zqwb{%3R z^m}kt>NSXA>*m>Me*XMoC6uf2CR&%ZI)1R5<}P;Cagzbdp_fi-XS=kTwaOYfIr++X>jA3d77Udp(hwSYS$zIG)!bKWF?Z>>aYlR@$_Q15Q$js>u~%7_;RR?VJr!X1E^)z8Bb9 z5h@ijD6bTQK<&aMbmQ&b+S&BMN351-h~e0W^V2Mz46r!rfwhE>0~h&DrFrTS9puyH zG-sX>mPxA!!J|P0lN9^a(aRnO&)y|ed7YlP`y6hz$RhMB%%T|aEoyOuszH8JM-c?{ zKGsIXKp{Nq$w!H4s)%{q2zlKbDNMOE`vHRctFk4V!RhogvxxhKRJ#n`(`6NXahw3V zR;N=JQKeC48p7w48C@;~DLtvhmESuXJxdP~szpAWVq|5wq zqa>_Xp@TiS`HaSLEhGOo$Z!u32w5!{{A6}7vD4v{AeRR~YylHtu0X-vcU6aja=?U` z-OFUnYQ|z%lfM~yP-(@pG?y`r@oO|#L+2%FZiyM@lXWZ6uDUFXen`XT#vHFe=N!mOxk{KMD-*iKW}LK@^uM~R|&3Iz{&fU`xwKq zzj+o$ddWx|+8RQS9OL3r_N5iaS{al8s1G#cT`qS`56I6o{MdnsemA;DDkS|12}$n~ zQi&SJ?+YNGE$7KzH$wjWJO$(q9UI5mgWOgiZbI;^3F(^6@H%na;6=%**e*-Edb(WU z%$5CK36LkEqaE@g&ZwuH0k`ZSMP9Kz_*#1JlU@qsc`r!yyb}a_lUxcj<>FO37s-tR zYrAH4s~GzjGHLlRIS7>JE9D|L7wIF%+4q4&`U$#Q=h)0o+h7Jd+gG0+Fp&HQEFvw`~)h|V#z%#6$(zMJoDd`;U>T(q=elXSK$?tmP@ zC>3DBk>>I3(KY64Ig3kiQ>jE@g_p8SrgF2_i?O;=Y}_AgzmQ0rerzm;;Eah>t1;AD zudz}sP962N4AQ8T(+v1x-d;X(=rOZKt;h})EF4fiB|0P($I{Vu9}9=I%g8P--&(&D z5-*2#OEYm)RsBQ@G!98o%*_7#GD-G=l}f&cPj+NIv(0P zpl=_Kdw|;n5%jf$_lMIkE_NM1F|#);DMT2J)YoWih*-pZi6cWTx|{V5<;pN@Vt?g_ z2Mq`{pq?e?(OXBuH*-&Y)*qDOLAB0B&iY3LteTk&PbpHc3%g-_zJO9HVl`0E6)C7e zU4EXhpWB$_kjzlTW3)mFx&UlP@t1WNiX+)B>x~&9UcR%vA#T1bP4QTEir-#Ha2gk? zjx8>;8%sT>YY<3*e6n;p_P4TLpr&<0>VZRoaH*RdhusCY+1_QI6&myEB`N=ueaIW% zSiXO<6(kypMu+UA1Xw;=FN87Uke3+*bOU#CUQZ1#7fmDw^IEOUBfily7{;Ou6@;NI>_Ob zdA}E-QfG{fnINsCezUDAU-Vg+3@ZMi(YsH@Cru73nPavB?A}~5JcmDvrTbJAfLz9! z^J1)(efYrkd-)PzM&0J<#jO~+oIxGgGoNg<7r-pN!^2q1wT&M~-MwhDtd zG&V1BXp0jpxlY|Mf9cC&Jk}>!0_L$ONIv#$w3(}C+Aaip@OT>I(Y;jSnzqs#`?l&B zV0KS`nO&i>3whtg>l56Cp%O2u`{5TW+(}K6SMN9xx0zlipv@4Uo@hpDpL(RSC|>lT zEDaZyw1D!+M6Gzwpr)cPnbBq zb^gF8iIA(#fA@X&Z%~44fjhI#N1N+oT(jm=SLk;GDP8G(G-YQ0fx>{8eL}X@y^H3)uCA_N@$4XsqO9ZK2mu0ke}X@e>Z5qwdG%oioN+B&ERA6v|KWyZ zje2T7x~f3mt+%oGobZK5P?kp|iAg&uQHG%K1y-22PJ5Ja)B@{8eTi+fg`~VMFLkq$ zkR53<$Z2n0NWpChfsh77mvBlP)VSOe0K1-Y5Bb%>AK@?}v0A?`y);E36^%tOMT|(3 z#wcAgEEjc>E9mU1y!E!H%lVGukdO1&AQ#^Fbwu7s=(HgHG@*LGJ;hH8E{MoSCqFMMdCR2*3^MAda?0GuSWZ`y*VAb)pG=nJ@jR)6>(`y9 z;#z{3DEScG)MZP#Mwhw+xF+BBSlJ6n7P8+r#2eMFFkG4hNZoH*w3^cGh7f%(0^ZAJxq9{RA3B&nIt zN`4#f{M6EtRGc{02bn3clA>QAL6l9y>$3g&Iw8jm&rr*mt4L)s>oxNSSfom0)95L) z_dP$isn3O@J&a3q&(juC45xWt;s>426PMb3%@?IQCg7)Ts(8rYimo&N)-*t(PLuYE zO0-evPr5v8gH}@IBweN+DRC-$)*hVZ_W?gO#KG6G5Ep<&`=b!t#5x#Wf}b0 z9smVY&evnqmX!JJdkbTOHDmGU&=BaSvPE8H(c#%Le7b%7y(dLwwUZn|sxw7w`s^V; zfwzmcB+yYQqdi@24~@NDMzJJD?y zqqh?CzWDgxVaehMwYi|@q2Co;v5J@%vn87QS02pY_ZM2tdn9>3u(pM<=>%U?9bMl0 zXk<6U*9c0ztzax4O`dpUi|YDC$|Pj`Sq9_#n9sQ5s-;$HZG0HUuaofjgo#WS2!*X? zGnf1#)%Wm$M;&G!Kxc*QkiO;PgyN50>Y!7I?&jWU*?o4!HzbT-QqNR7-MX;;;+i%= zC`yA(4pezrRvm3g%6GRgyA_!~|EVyyq?RGzkoi{26>H6fMq%TOM^$fyVBLsIVYfaq z_2ZQ{mavizsGnMqVk`#nv2Fpdohc=LF89}7cb9B+U@};-BMT^HDQtM6DF&Rwl9uLE z0d#R52e;Ug+{1ZywGAgS=73lgQ$4UHH(0uy+8cQti}AWya;4TYWlM?=HArScSOC{) zIsDKG+gfG$QLbh_<3X%3h+!j$nbHF_%?uU+#5(@MAtQ;ieZ#b&BP<{rXVnB$q-{JP z6r4Go9K0Oa6DLjLd4w}HT4TVqE#NoStobWrV5vcpk+K|>{na4FZRVPE0G^D4Dg6Br zP79H=PHm%WO5m%GH(4y0o9xYbf>zEMyok?OfJDrfcOLr$o3gLL9ThVz=5h8NXDWg7 z#E!MTobt$V9jl8o(Oe|?SL`;Lx*jIp`f*ey{B6HcSlKH1S$^Q5%ux>yY$-wq9RHm!B%2vug-m1B z%fMBtb&Z_G5nJhT->o48tmO-$8)$3?a-W?XjMar(eK-Aj_JXg+rm<5XQ%i@ zFWz~@`T6o5L;hzzAe-}R)U=f8=oh5AG0fN_$I`A<_1*nN?_a=?Xziplus%$FSh!xN zxf*&F%T#rv=CK-TjsCQb`x^w9ZslIbK#-Az=8%Ikmn$Cz?oeI_-OA`(=)LNJG(m5^ z-|?V+Z70NqaXurV`3*Y8ctTRIMmu?Or=U-n?Bif*2uJ2fIOmfZI8CAT#fxu0lp%!f zBm-A$O9iuE>o3EFK(lM!ya1kK{1$V3FGx zgRZcz3QttdXVcjoExZ|7<#`uJU**C|$$Wj!RUoaVL!0hlxNGpv>KztCn3WjQyR=p)9Zj^7S(tDO?0^EhIM z-X;18nXJdaKE-f#D>pqF z%-AH~`HkBOz?r9~EPC;_mb9h-G;~AHL*1Nh*J=t;m9dJ8c>zDTU*>)1lUiRbKLM1|UXsEA^)(d9X`~Umocezb!<> zj-4`k9Z2bK(;iYf-n!a@N(sBpK{W4T8e#*1=rGuXU3!mH&jHVcsTCWS}8{tW; zUeU3{QuPjWp@TSeK+pVYwa|@60?D|}i_r6E9D^Ci6szfAK6Oj+cSl*4_vINngJut;eAR5*%@g?YwiHlL{E}>C)(T0VPBFN ztq|XW=9wB(Kt0bEBWBM^rf?`Acj{AR#K7t9>zikojr*ygZE|i`pW{m2;`IsC%)@l4 zIg<=Akr@JI@DU5*vO4T^T#I-r#j{S=cD%UarlJ2$?`MG1zU@a0`Z6b-rXB0DC#tCSYrNR$)^ln~`sDVu@2 zy1~v}s(;`SbWX4x@_sqS2_3>dsuC#Mw?gDBfs6A3>AgKl69{IBEK$Nc>C+`4WIQow zD{IFJ-Xa8ZIdRz=a|=c=wX-T`G2-4%Y#QSjV|0#2@-eh6cvzudELWxg{Pk^R|!df)bLdgPYf^caU!yc=@3^>F{_QBqrP46vV_*g~$4Z@`~ zs%;P^v0=6|V))7vvWl}Z7d};3c;!L`9ICL-fPS}f!?k7b(JuA`9n$pk9=ZI*>?aL8 zMVQ_{k`pAIzKT5@sU5LgR)I0WWws2p^7-u9J!N5~y178!tiqf8xbhq~xyA=J1(_ak z6R0Z2Bdmx$g}(BpLXq$Ve#RV{jh+wa%}dTGJOTa_8RF&%FW%m+(%Y1c!gL?}gAm|4 zS?%Q5<_SS1RZ^y7Y{dQ8KtU$Tv;A+-!i>1%jWyOPbVz9?RoLxE*t3M5c-Y-B+uwdBt5Jtn~Apjkh}+XAA$!uYR#ekCJL&vUT;lBFKEX zgRQ^nZU%nWX~n0)#0MN|^6ODe;c;(=aTD^FHuog)g`qrx=2dYck@CAglBj%+%moJO zC9?9HYsxyNStV^Zgx;-MVWM8i-nb80z4D$~7x* z&QV8xH<6d;H%Uw#nId*tT-O&2jO&*kU3g-cof_PCmkfKxJk6orytBe(b!s9c_>pGX zzz`GFuTO%ld+X)3+U+|q`{gZD0l^liViJjP0SRv3*0z?%li2Nb>4B}*tZ|CG|4pg~ zb;uB$FhtL~m5|59@nx4I;&t#+A1C*BR+<8%bw7~v(Kj6VQJ*V5G#W&D?=_^OlkM3y zK_|D?J>?TzZ8zW&aa%g#tcqIE(Fm)|V<%Dew=ZI!eG(Iu_ml<&vLGT{8t_ZI4*nUc za)W-80*=_q>#FMzbpHlnwnb5=Z3MM*MiY(;bc}%hNNPR53eM^UnDzbx@0{PDUo@SM zE-B6&b-Fapx3SN9275-D^?<$@qrUwyO|v6m9ixuMi6!0Gu8 z>1ok_?)m0Mx*Z7Rs(bMXI>7+A<4(>Bhvn1b_`bppJQUp#?ynZE=6+F^q#q`G_ZyU< z3dH39+|V2rj|2PBSem#9TFySq@)YM8OrG$~X%+Mtc+fN`^ukFY zG57!vI1e=yBb%9;F*6WVmkyM=o3h6R;Lh`nKaM@rUj1>+6Q8{_uM84;qbZhbIyB(o z#PwJbKikylO`U%_#k|%|WBL4c4KRn;D)j5uiiYR(Mq5&>*Kl*y2d8><9&BSg5q;ih z#{Mq;vX>coe&?d1?t>lsh_ffR5(1)1A>w(lrJ0+g&SntLor52E*|}3CB|m5VZB3_+ zlvMtj%=bBW=GbQjHC4&CKj$*XCxgAi*)GL9R+8tM5t&@$g)hXWzD}41)lIW*owYaQ z1kCJ+fncRv5D_0n{R0vU>mF#B&(j^UDzBHkbJ1EvHuw*IlG;?}N3BLR=QcamC!u8u zrB(I2z-%l%G75L4&=zNNlnuPwiJGC>aIs#{CYv@^2h?C@jO>1|6M01!Aa`0VABH1i zyp*iz!(bOD#`X(@y8IE3@QsEuKL+aOsj2O6{WpO<YL)4tQNY=WDMxhXKGE&`G7K~mB<+&)2{ugyd& zM=FcsH3L0S3c{ad>D-X(*?Di2iNny>Mx@aLcY+UO{#N}=ww2D|uJv}mO0S3BkxK2? zlM4cbOAPtEGUQ8Jr3#mp$q%L?O*pHA6ESQb^wl^0Q(AFo*}4Ot!$u4Xi$zv1%ZnI& z^P~V}Q7GZU@vLNvc2LW2sqlCbZEVIf=V9tyrpZxoL|(sx0m5CL>3GgUnQo4LpboJI6Pk zlYuPFfJws~zo?!z_WkoS_4DMjTgIsB?I*@PnF7WFS(Y$P$U}dPv^ZArtD&ujk>~Y4 zalyI8g%aEuOyOp!9e~8S@?4NxY`q%>r=S4v^w_7F$1(ZkrcO6LjfK+q(tthht72QV z^-KH?E7GCQ+mQbJayhk0v0Q-xsz^9;f;-|27f3nQE+%F%9Hum4J1W(;2o}MQ7{kjq z3@Q!n`vrS_#0%AKeDO) z+CbLSTf^+OU zWBt)I8Q@rLMGx|7YBRmS*tG$}#|yCY=o%rs|1q~|#dTQF+Gao;;l@8)#l z+(urDY{`E3V(T(S{rU)nmvOhveL&$RZ!%RXhH+u6ih9$i_&HG)+aJErm`+5DSNi$n zio49(HG1xf^L{JpI!XO)pCxlxJg11`=TH=kRqCbbr2e?b*G>5`>o_U+7$gydI-Ur{ z1S4XaCDmw=0(W5m^H<@d98;6vlqPwGyHkB$j#Cnt4NL!GA;+=V@*J&H#9DP*7-+VLLU%OjJTOuxI#Xv_T^3JsDqUW zR<{&-j~&!`0ubrUX5x|f{Da5sE|nj)6GMOS)a6YOFa@&+EDBrmWUZgjfMuMgpiEfA zroA7?TQYA3b9yhZhd1RmkZ)ogeLh6L4fPHqef5lUC*x{%KIj>n7_fg0d{u+mc9(Ay zvZ6d((%YDI6F_x`oj9TFD%aBy6yEd@ey3i89R~=hy&iNP8Oh|mHbk>U?FiguubD8$ z6NA4dn~TU{g{Nieg*De1tP7Y!mi1UjdJou-MHf4L3M8epMYn+(HVjp7y%KH|oJ3B^ zvw^w#paCLG4PzcXrRsU~c}(v2)zEqp9lUFtAv-v8;*fnvIh}^Ysz3gD{=04Rrq4U1 zz9?GE$!RR}H!bWRPbv`-MG9@*nUatG zSDC}4YM-^d$eGcloA?stMK&L6;!MCBp}$1 z#CEax6y@_vUCem9u^c{zGd(v2Wck%~{_9&;fyFx5V=nJ3#9DKW2dKJCz6+fPDX#TG zhK2Cv)39Io?EQm^<|PH2>y1_<%0^mz>9=1R3#|<y|#21D@KJpT3p+vg{kP^(i`c@teb4gVTH&E$g=YHN9*$(fv-!+P%jZpat7fi zMjETt4>R>PXoHaW=cAm1Itl)cBum5<}Qd!95m;;eBGHS|UPcT!@Sm^y_(= zdMwUv5N|089T*2}?!Q4Tor7wkE)-W1kxl{5&-*|kKQU1%z^BQUdK3A#DNFQILbNr- z1i>*8N`$vgO;?Q7$NKQ$ue_XnjJzwR$SFyGg92Fu0zvj)8h-$Ul_4N3lKdodOIqzq zx0s?X->fT%7$8q?G71l%eT?)dfC~;-d-?%QQ1kuZ6Ngv89543skJT<1h|olNo#O*J zD50WHX(e2a=~ll^vlruq5JP|PaOq*oIcD^Rs<-)--f0juqX&s!f0V*c#&2v_$OuuN zmoXW35z%}sY9+X!ggdU>Qb-yY5%eDCEi{soY3RL3wc-j<^E=yiCGUzjnXVK529`R-2Mj`j+qq zMT655`Y5g5erjGtR5n$w)^5QbqlPfe%(5xe{08O9d9aCBR_3aCHp}ix(bGeyVkaJL zrK0Y_@zk$nmF5QH5TR1V`#h{jl3ma10d;*Y&Wz_1!|gVn=B~Qv+O)1Ad^7SIA?L{Q{fP(FOZ(r?c`d z!?FVqZWwi>$0GMBUPi2FQRJkvxVTw%1?0NR&`MjQ_8ZhBUT4Yr#5CRHY6a#H4na+= zidSGFEm5Yv$_pDk4I zZ!OV!_-5vn$o`z2<*2MPJ(EY}ijk41A|eOE#dp}q@ah}ZphBx5(Z?di=ypu3&LP0P z=#$*6le8Dq@)$YHc=9xLqQqBiS2DeEt9?}2>ue=;T&4xyyahWT{QzNMw`wVca8F$SCJ4dSq1@H39S zTF!94#e@W4Mz(_0F=Mr7G*=U6cQlo|51InJxXx4RK&ITyhM8*lvJ&T4!jL6BV%)~( zmK=8B&X&$ZD~t8m-#e5-dlL`{#0dO0Gc|E^0bzn(y$69xK>VrBaHgD2#%$IO zmM-RI*5(dwKtCAgKb;D!%uHXqS-aVp+gRHhTbi?ZnwvOT1J5iRUF?nB%(NxNRBWsr zT-}TvOwDB_yjZ;KonBj8o4w{Y<})_t=I3QK?R=S1V&q9$qsmV^=F33MX0poS}wz zk}JKukZ1ayi1Fx_b`oQ~I0@ut$mHJVaXS}tJ7YI%M+a9cYbPrwV;5lh$`;mcRsaYN z#@2Q@f58&?53v5Om9eXw`wJ!8zx)5kDze!)nOpu73<>oJi^O_=>_Tqs2gzTm z8v8Bf0bldyKgI)QV>C=Wd0@q>{<%X8|Ia)0XG&~l#%{(ychLJk8=smEvE^{XbXAd6-0Q59GCqiexTk zs%utZ8aR4Zjv%H>CW3-{=CO8c>SSyt^Jn0H{J>KD6JDCX@N$~k{ZEWCwKKN12fD*t z{(-gV|AI_^aO?kwOuS|mrd%dm{HzuhCcLcN9Nb`5V=e&;RttVEQw~lZV;*x39>>39 z8CjY;n7bIeIl6d=TN%4at7uUC*)CQWS9UNP7Y7@c+kfk3WbI(#cx~-!ZRud_=I&zd zMfl%$`rof?{`Z~-|Nn#){}_hpzk~F@0r>~={cpnkC$`NV;5_G?NR(&S3i|T8NbZG@ zrx`xJ(4nnyKj%8WQH%V?cNzb0-~Bhb{qwWK57=L+w2Z7gZcslmg67- z6Cl88!UKiWwarb$|6rgxu)Dw-iHmZefnWZ4_YxIe!Q8=>;}sbFPZI?dQNzXE+{xOM z<0TmR&l3b}SWR^>89o{(H<*J9%)!IO!^NuyGTA&FDlcCPRzyHzMu2(o0ucrVUK9)g3;(MPh6&}ca&vQf&Cc#& z%;xrwHDoh&v}ZTB0eCUHle>wXwJAW?>;PkOb9V)fEmtRCa=>`B@Tg#ve|-lI9!&b@ zAv!!BJWfhQ9;(^SN~Pf?r-Dlb4vM3dYu4X=BE|RvF+3dXi>&{)>u^CZFd&&Qzx2iO zolNJ{vddw3(LB>at@taUhscUq@efsJiNqsAt}c_?-0v_t>W4LKIx<@f77SW7`s z5n*oRilaGknkZ~L2a<2ethWL7=86s_c%Kk9cN_Jn3yWS(F^S&ovtj#Q1>%xE;B^RZ zOgmH)e5XgcUO!`xHCr~kHcV93AF77i6b&C${A{}qvNaBzYJIKf;3 z+<%FL7ijYTZ30B{|0O{N!BG7t{4g&-z|LcUz(Fu5uzygX3lFQOP^IhZ=(z1|Ox{_a z&Am&~$AuSM4-tn!=jQKsMAK4w*WTd{!0$5HUJWd+qC`;L_Z8+|qcA)pnh2tdAbXp8 zqwA!67D{4gH<`oJ8I36VGA#26R`&pjcc+p)U-$uT>i9m&{L{?$K??Z-MC<}ZHka^b5p{f2W-%1v3? z!DEUeB^Z!zfWy?7zew(Pj!fuqQftQA>JRa^BItsxafF3~0m0;&f{no#|EBO4U^swm z{tJ=ow44>6qwPo{pj0sy>U{z4(+j`+4+8u@@_;{#^4|mq6Z)Uq0I*%SAXpfXJAbGT zKBm~IwjV-OtLKVfbjYxE-=?S@R}`gBs54aZDAG@TvSf#&$ra+MZk%PDS)dfMe{nh} zycD(bNgKDy$S;o9G@;l^Yfo}OS&$wlA}!t)#vq6pQF>$wUJFt>D^ASDjkiCoKAD4t zvK9RWUjm)5_4g0sP=sz@MSVRey0fkk|J-knwV0pjj9hO__Gxn$>2NiNw)|AU*$}zx zrjxnZ-u%jiga~Spg-8DDeZ$K2a>mwpl^Fu@BVv)uMWgX%>H>E0w z*)gp_M+uIi`*uZXQHUg$YX)rtX!&KZN6iW4)=amD%)I=zknVtw;r1l2R5CnA z=(45jHi>`m%XP>HHwaVJtiVImLUJ7IUWHc*8s_f`xIH4UIa2r0KE!jC;@1|udxh!x zvt1;zp5jP=t;iEzNvyXGuc3)mlu9ElgG}Mu+Y~IQ=E{ehv^QU^BDSo5Z`QP?CN%$+ zlXo|8WjU4@w&*gZcRo}prkhUj9;7~!l~yNv-+HJo5hZVoV* zj}y$n%MI3pMIy>^R_qe;lgtXjh64v7v;mwo?;l+C)yoQtHoX>TVp#v(9{dIu{C|?d zO91NsDnxMLgK+`Ufk(kcm$0_976-Hj77AGhQ#KAku)v=eaM+yxdO-mQ1+H%9W)#xg z6l&_C++YD-3UwJ#K$)S?u(mf>cQdwk0^}G9anXN@CIXzi;J-hBXu`zS9>78JKe;XJA8rc^a9dz2 zJ8WDtuM;UtlfW(d_S?F-^}A1T^F5k&_uOCw&s7wYlU4XT{SbPehQ!tRLJw1cw<;veA!Y?AZ=z`xc+XDtWC68R2F+ zp8`H%|01zgUK+|ud0+LLpasV=76zIO5+$c5XQ(EN zk~4w?$x%s4lF)R6WRM(1f(=TPD2QZGK!PAS2$CcXNRT853KG<>QE_l)?w$G8U3Y%< zpI+V7Ri|q0b>4dRyHBms@X5<~-Dtd`V;uiXUFP0)KttMp7)@2v$%{=vE zG+b4siuXQe#gINQi(B-otP9y$b4Z8IFk|JsXTP-GX^}xZjI|IrDUG|?YTxW|So$ZYlXBL%T(N;%WF@3$!Xo$;H{g`f&$7D@!x$VwlOc{`@>mXC8y|udu0bddUT<7{mKkEMR4=y=Jd^{3#qNmm5Nhi%h`0>M`3G zSHr(PB$UPV*z8USFA-!Lkqq{_DOn#ZqjwhLqi%g9kKQt#LCl0ol8(#KfahjBwdN|- z<-K;taRYWdfnMilI8FC}^Gku26iN4BML6smL3)q~n#=kztG;EEKEo@RSuOrr#r9S0 zXOiFQs?_&>;`eNtZM0d$@B5&K9#YY%hN$UUNg4b~V|(P){~@JMe+9kC<~MJbe1Us9 zn=HI}h3SgPYpjdHIK|B=T1Sqi^{!FL!R%Kaw;cCf{RYw5puG<_De@s_!ksw}YMs!-j5!{DCd z)Fad7Hg^VUleNvz?WK=#vEH8YQfrB1E|OoRNPeaK;1`aVcrBZr@3>0IXD&M)3)4SL z8NljSbWnWj*O2HSJeiBf_<|rS_81=oGlm@ z$Ob`3_;-AXBYz5=;Ok#u=-=_}MVKhIT4jrryPA%E2Ro>3J3+kYu1@yo(5;sl8ad8c zWxcodO?FAM6)EH`b0c=HANcLuk0c04XUZTJX3{1n;DDw$e=;n@@cXP#74n+&J+!>? z9dBSr6btq2+H0vcs(@k|UTOKMYjLYOQd^@2ua>#^*M`fhVarU|wy{av!xQWFvlF+& zmC<}A73CtQd>stEx5RH|nwRvv4Y8@x-G-~|UA;v!ZCRy9zxiSn$S7V_H}`ozwdjV#d>w%B;c7BsuJKfT0MUTvg#^UGK~G_i#5+zOsmmFbcX zMnJzcZRK3(mY%N_uuJ#9=9Wgb{Xz&o9 zq3BKC!X2e64Xm+7dsA0u62F{l-md`iG{#mC>PFJTpolM(@xvAu;bc0AT``2`O`})} zTR3p{&ytXV{481PZQfY_mOm%G+v-sv!-xl8FE&?(&0l$7gonl5fTU*)d1HWhS@)HWFmVUiSCU;xEZ`8eQvSZ-R&1 z%xyGg1+K{~KSh+%6Knm-Ok`8_7s&`N;CaFdBx4)_`+rCnxs#&+j4;vvf-uJwsv?4< zn+Krqotzf{NkAe?EK4Lz=wzI~Bj?XSoc;mePX%~L`}~bqe#UXli8U^WB1IN-f4k9f zX8E}?Y5@{W@&Vv-e-rg#hqn3rMeak_s51|_?T_qR1{iQwq_1?26D*pUjyZ=Pgn-#a zAlLe8$WmO|v^A_42yuf8DI1Fn1}LDh=q&90$@VfTo`>!pXUD!4Yen&S7l-yKSZ-XY zd8n0Om%*3uTU5#7=a>c?j&P?tjp@FFuK2#2g$*#RrzU$vBo*c>U%B)mw??Ae;I^-O zx7;7F9yD_XE0(5m(iO8Ya?mD6E-jGTNNlP(lKj5N_=b&0Q* zwy9E$yIr@$n-FNTV5IdsymE~)+wSSKezPQZSQ#RVcOzM@R&s;_0jaXWBgStGnjrtu zl>2hWaH0Y8Ab3V-7Bg}Sti*jbH(e!7;IA3zyI54#!jBT?(Mh4JAH8r0nYJ z@iGM-G;b5}E{+}+2`W;kFOw57Oo~g;X7lcDjQSn{!C0|KV!?h8i|8J8-2Nc{^5&%W zJgzLY$!TI8XTG-oS7M#SvgC9>i6sYxB9{Yfz-o%%Z_xVDb0LE*WXt)p=Yl{W$l&Wo z&-HJB^Y56|v~(MKeWrfFT>S#T0@w?$w-&GqbTLb9M~xe|&T$t=85P6R`bvH1Le8`S zOgnVB5|nEOFYY=s6j$8b?j{{tupGs@pi8XF^df^I&;MCwks+nAzWd|riLR{`PhqJJ zP@$xkB#I;gv%xz!Axj?To!#d73F+Q}N4mfa*4+I4>lT+f6F)Iuy(hAU^}=G^l(u25 zYoM=0MAow0+wPrbr)r^{p<$+;Q5+#}tyTdrVeqco$NbHsE-i~i(Pc=_>qLvGxcI== zH3^x)_B!W zN(X|Od)ZA$LKM@4Wt(uvOw@8)mx{8kP3LkpWNV$PCN-kkV>45Zx7lm4kANfKQpV=; z@bJjH_TH*nzvYn8*`;}O&s=0&CbBMFdbN4yj%0?^6xxCkdt^xKpm+y%_N1Q~+%uu1n%^vBrNTjQy;_-~5q(%-l<6UAIE7R35EJsRax=5nE25Ikh zr)0sK_0@pMSg$i%wt;mVUCN8YqP1E_l`PZ2N7;<+&G9+q{`efJ9~bNe5e)nvCyFW< z7U@oyC5dE~*q>9t-+}d)JUR_5rL~;LAMW%7L-=)~3ksOicbq$k{{*a?CzL_5$r*6` zhipCeMB0i?&n zfHCm_B-ODokzdjNaCiB(w&0r8q+q7MEMb}Tz=KYzhH`AL$q=V8tt2~~Z#~qah_;^P zQ#h@?9?M2O?b#3H>fVCout zhr)!gV1nu2vgnG|f|?(p>C!<6T(&J(ggJX{-iJH9f7 zu+P;TF>lTCM)E5s?Mp7Jf!TzsiLLLG_QH)L7^7^pz0D7Gb1NIm_N}}e36iQ9h$fXx z?=ZL1P(cnawKTmCa%NWdQD4xeZe6uCtGMwgW8n;87k25x>jZ?-M4>L$u@oM{f5%q7>|hsU^H z<+UL5GnV=s~&(~YJu^O7PT^nijYRT$H0aW?1tjOo|vj=V_lr-efT-bxQi zp%?Yf_tykBI&bF~T>*<&ZtEqAwdyjass+A&*kenO7@au{QN7<_nc6?O4y`Pq#}Mpc zd-tT3;QdoYH^RzQR%KE(b<;1r|vAe<`Av)`G6q`Y5=DHpjfS`nxu7Z4zaP3@`ogs@J&7n{1 zyY&FAu(-|y4U2_Uj+panbIL(Itj`goBq0aQ_0^l{8ib&i38h}4l;@`Tk_d~4-TI?< z$95#*9y-kl@)>d;3044>Vdl7)2jQ``rj_SsXJpM-u{Y}73y+_=Fs2Oq{>%mH6Qux3 z0h9tL1yBm06hJ9}QUIj@N&%DtCcwA7G8H%NDPBZG7!ASg(KNJ+PJBZ^3OH-f+r5;C;tv$^iy z|3y5a&M=?Z=j{0ATI;;l)>OjBp~68zLc&*3me)Z-LWLtCp&+m@z$f{=w4acWkfWXD z`G8J8lM%c7D+5|} zp8H5ve3gWRqD>R8Q``E3&8_OHs451m&~Q<4QH~Df*Z>97{{iV5g8Mg`7VoK!e ztA6B3G~s4|KYsU(iEEhiOzbRLh2lx*&yKC%)O(SAC=;Y(uornoPcm?5t6_Xx@2Sq7 zc`MJshA9Misy?)ey6#Z9uBCqF%bGFNF)I^`r_3NyLU}|wt1bIjKiYXtrT$&iAm=_qg{yMQ;@yJ8^>$s`v zdB=rsb)1R_G{vPwMqTOc_chhM3?HGjx*mF`v6TGh*{fhWf9WF6VU(y=nUnV`rmpm5 zgfj0tOnQ(B2qMlLYau-dK2=#>(1%l*!}(Y`7<#ZlmSc@ z35oAHKhuA2!XpX(du0c<3I1qLBLDY_PW#^r4&=X=f&$csR~abgf4`E#{(Je~kpH{F z|AzeU3jZ5|^uH_oZ^-|y@P9-8|9#U{M!#pp|8HmboaH8p{`dAQ@>uu(?PAvB|1JAc zn~A*a|Gqi2_)FiD87nfp?Y~`Iqz)xwccA>=Hyx1GR{uMJ|DWz(8#Ojlmeg8L6?JZD zEsUyT(se(@-G%5HP0)e=v5CeWtU+%sq$Hjoz0zbblT;AzD>8%Mv)PQLxzf2Ajm9D_ z2gA&x+87+HJv!;2y;Kg`kxW>z_PlCgb&0I;j(Wy~B~t7CQkTa|Sogw|eN_#0ZH;xW z<2mMQ>goMpg)**l$n&a0nZl2D5}JIv-?32E$5MY{M#SWmxmQ&!_z5G*auzNy4PvcW zeWE2;+WAdH&PC@jWjeC0=3Si(ejcncc)c)a9%RQSL)aYAsET zrd}U(?KT9E={)Yg!NZW{#zI-@i|7b3T z6zSwP_YgtI`lxLj9662c?ZTf0No3DwR^kTQc&H3;o|`-+O^>U8LV}f(VY_cqRw0tJ za=U)q5&F|apn~JhMT~q#gcJAn0#ym2eIJff@vKmLvae=)`F8(S=zi%te#n4Jl>$O= z$4H{kb2bBUh?#Wo^Up^eZ$(k0#;D1&en`kp z7M&%e7#5jR()$#L6s}lSA>9*b5jMX%tJWt~sJ9$BykFo{^SDgoj@Jw2ekq=K$tEv%q^0_Ssvrj_@}4Z;Y~u4mbIXw0V!# zXTRTas%$CID5FFZR~O&MJN_=5*B#suEDX#Xc~WrRp3vQv6QsLFL!pGRv{hyQc1!5t z6K0q1W2gT4Zpq#LNhVQ$j8UpS;EOon&6|?8{QUKm<=}m&x8}d2dMziOoV{=ORv2%B zEqOi77Q(_VS-ShJ#w#*25L$yZC-wsz{@u3e8u@J2`w2WEeK@PNay|wdjX%@G+ea=cTdI3GH$F z{cqi38*3!aZBzsCz`)#lwq<}xYqe+<84HwRD$Ow z)ROna!{x1r*AoOw_u~3gt@|SkKE@merH!?&@ye1o@k9Iz&)PiwBzL7=y`|>Bz@GYw zkt6Fo=P)bZrgc{mH1rT*+}kAaPBdc1Fl*l?kC!{>63xh$D}z_Q_3cH{Zs+Z|`x~aT zlAi;j7{WP-*(WbNba}H$JNZNxRvFQ7Zr_App+~g*$_t%!Bs5EFij>(~?tBp|_Z8{V zc%b>Bd+c!e2gc*jUCXt$UR{`#s%T%~c=OAP)$q?leEvlm(6WLFN@*-u0(NXN!pzn5 zI4+Xls7xk;r`7KwmewJ3vo2(z8IhV}h>enk!aXp5&aG;0)Me9x1y&zKkwAT+vB|(2fSiei-&ExBer)F|J1RSz#E}T9 zHzFGpd+rwz(s@d~Z&}wkL3#4)!`xir8Z(xa?EA!$lf_X76!6uIH_s=F4qjPXYFrkg z-ffw57G{cG(VW=2!w+0?kRyAGKe&2Sd3u@SX-~2ZzAKcKfD#eE>|S|-W<}rC(5Q*! zZ%Gl0YQEnhWMfalM?4{j$po8rqyAVhVFnc`ej%U%zFpFHM$&8Hqa-J}vC4|oRcAm1 zk6F`ioj5hH`iw8B6*iu|ROkcAemK-oeOS6KDS};mW{CyOD#<R=*gC zrrz7JG;hDb9QRHvdyE(bJ>>gAJEQ*0s&RaSH2kPin(7hSr&y0{TK99mm=&+@&9k8L zxH}d&r5j~`3#fI3?^lMrOysl)b9|!#usbuDoK9#1PJZj$&z=SdodD#=Y8uFFk2e*B1cqm1>_{PiMvn zT$IRG=gL$k2A)Tb8)!B{10`}^DJlf}6^ZnHln$@S1xG!u6Y0{%xa>&sIb<^ zJX-=vn9GJk){l3D@QZ6 z&K6+zbSn*k%QVj?`%cemUS6zDqm zmUp$ir}%sfEZ|bRK<3auEA!?9K}kOidTyjtA5O&^f-*5e$>CaryATZygpWsVV`k1G z=YFrvbwWrO3wcp)+^bw;U>4(?PIhld&twOti19G86J4^#KG7th$q=LVrFlXKytfUf zV{c#GF_Xb-;jg=o?)_98L%8JhFL4vc3oCZX_goyQ%8$Udw&32%vQ|BNE}!ujZTXXi z7@jKf_pel!7a^KTy^>BU9rGSUXrGiIwN)bojdWk(%C+Us9_}}ia@IyJ5I~#AhQ<7~ z*4TUr?EX^^n2Cukj>z#;&RY#xq|0!6G?$JRi^Fp?$u|T)afYA3g@DbpW^uW&WHZHK ziQD?x^RuH54tbd9(b-q8T2F?I~(runpdLM}|qF0#R?)!`Xkr=SZU){;8+pXdj2{4elnAIa2kV z1feV#B_lGHsQ7dQ{aB^11+m9-N9BFe(NjjfMh-;^(w>|^T(961$?bno{h+A;C5+OO zD7?e9ClgI%m>&1dp{ifSZDXT&zY-l@?{w3{<4Z}3`9`-u#(iNbzq48syQoB6a|aLX z3*DoEVIeXOc=DxL>rlN^uVi3*y(SiALT>MZedE}k0H0zHJsiTND_i05ttoAYV+73) zC(ww7M?YOKPHgz^i%pjW2WK9yR*ZFJX)76;?@o;;m0zA~_g>=!62Z;NREQX1mI4*1 zHz{~@qB@g(HjI|DXHTNGJ|62hW)g*WHo0HGl4pqc6kSmbP5<+*GZaf{mqs#Vev zJ9}0(F;Wvsm_4wYiN(0pv$UE6q$}$V#L}M&W`F4rcvX@_-38dwd7uY@)irF z4byS)D}{;HQ<^7eID!5pTi%lTI~;*&?a+e5Twr#s72C@l`zB}+lIOV=YP;dwq`hk( zl6TL#Ka$6qdz+QGaPY9OIy;G4U3KW2CXdZS*|I_z34AmUrI}{m`N9I*6F0O7518qP zSc26OaCX8~+Oqqeh6lr}q84u>>1@u7)T>@KXnrG!CVn2GvOoh+n){|+%*Qu*FKN{3 z2+Y3r<^^_kGv`y6^_sUY9hPqgp%Kf{+x7~fOCI4p<}7Qx_3u%qB%Y_dYpr zdp7-k=w@#*XyRGR&pT4sria>7>?uc(=%mJq-8)CD5Drs4p;TtDt=Bs`95pX8tcr8#FMUT2 z9-(1qkT0z?HrCs(_ufeB@#L8|ez8leJ;Q%?TynGL* zLpp~o4$IV!+xIrO5c#D4JjDFS%Z>1I;@`q@ia5y7UO9glCNta*o@!0X?M>;KsNJMI z;&)*}mt=BT>LO?;W1i*_%YXpG^q+{NyACXDmLpmeSwOLLrESqeY8|1)Z{E*ZJ`MSb zm7PS&$yp)*jx{L}##GpM(~$UW3mgvbmAjCcsg#_4xZm6_@!N>8YK)`0^How5j~pTR zA#2%m^z>PXUuuxQEqOqk4T zo3xF)8)~^Hdd~neu=S=L%}gsP)1EBawW_avC7;5;bX2*NEWWj>y}8!4zRHAl)Oi?U z%DupfwTX%bEogB)9H>>GWIQ)wEh{mDVmt>UOH+pSd(v{}mndz68f!TIszk`_S3`5Jn(>>}2Ja<+r< z=ObvD97S6&Nj`~D9hhW7y@6c@GMjICvcPa+3g_IQ>FOCE@J}In&2ovDs(JSV>$o=n zit`z6+sc4)fX%mMGcYhQM*|0facau!WM%Jp!XEo=`#f7q*l zJGC({B7fzX_`&s4-tTDAndVBSEkoX}y?6eN-5OYoh~(j}>DOpbu%W;FYigN~&f}VJ z-7i;Ar2UFkbXLx>P)Hg{u9rUry-);w!FvDT)dgOotn7aMjlFC^K~dH)JVD_yP=1Mv zh7Dag1(@ZIhnlz)I&G?N|G_W~0vpAjLfk-0nzjRtSOB!u^}`b2R@Ri$N_(On+{1sn zx{Hvt@Q{t8dgf_#xiiRZzgjdy1Taa7HrGCQdJxNE-D?&OpSh62lv5bx2=&Ckm~|@r zX3+ehBrkY9b$=euhi-8szTBdrxrDS1FyYMfFl)q3KiT6LC=nPn9b8*bHZ2wz_i5xv z4ztPr0+wzY#D{*HQu}g+s-Xk>nuU2|;j^`EW9wG~7vQ@j&I8ACV3^0Jk^3OtxIeM(@U>iOi^!t`0RW?Cg0v31?c zoEO=YZ?FVoLu$lX_YQ1aBy3yk@gI|7{`pvy%gPu%nYouimQ)X7Q{v~8xB*y!_B8Wr zQfqm=nX4vyZ#u|FfKB<%?Rp~3Y&VEZzx7C92)Fdg;0`E6c)3IQpHFhYueoCI^aaHv z^SFo<<$WczL^|=}C&6B^3B{EspSX?)`@a14*GE0`ySrrx!=sx}$S6p4~kI}O6_UbLP9<_a>f2q zife0eb)E6WVzSfRQLVIl0X%0QNQLcD$7dsb?(b@k6sW=34{S-mCvEM{H@GX5_JQxtUye#QrBj^so+TIv~q~6-`t_#2-VaS>}g1B znLw6JAH!r$4Q&fOW5eo?hv>@v7ejW(A5s*hkmz4pu#K7y3?Yq+SYEz+bJ^vBmIsaa zF~xiJ!SAp`H4`6q-^H+mxQ1A{wNHV-1Jx?3$zLc$78Gp13=aN_#3--Fg#{ZqavAJ> zD*r7348mJ{e9_z=0FQHno*ZfglUMjraZ84 zVY0WVfccN`SgpnOEXrv3;kvR&4Nv}991ZVjv%|~9FggJR=2GSzDiA8XS9s&B;jQEb zSWF-DUeY$3v6e9XsrBlja*(&2?y#*Vq)it59g=$@jLl@1SR!CD*!b^5WTN=*>pudF z@{4l>mN8g>^HKkf;m}Vuu(iv<#S?Wd9XcgMCO-CAavOO}0K(c?picsk>~2*vdI?GObV~xe8$Vy%_ zrHltMyT4a&SG;~kqqZkjiz9e2H(x7}&j!c(Y)F+8O(dYDcNjlV9tvjJ4Eh?>n~$1o z74YZ2Z_g)*1(-KxKw+~Q7t)obAq^J-jWJq;_J6p?Hq{gL>6ByM2@c`}5)5?OD;u7i zg+AlULZ!KtU*@th<%@k09?PIpvvn{(RqU8@CPtHWUpa8L9sGOFcLQUsdu0X%oirW} z_|>Y8Ru3FPfA|7tD*D36x=(@@HIKH z(wW=4T$Ua(GC$9dzggLc4=0~5e~PfVlKc1@MJ&W^NXUl62*%eR?H+PGB3i_<)CO9P z0g-f6`hAmM)S>;b7*G*WmZi>hG<+;jnriMT$pA%{!+N@=9CwQ=!xO5aWZ`V$$anRz zsP63d)_%}c9YYKn-$)wVSk#1o;j&QVGY{n1x8cw7P27PVT&eaRx z1yV9=aTQbv9FE`868h6#_mieF%IQGJb+;s?P`eo39p2sE{32Yr`iT*tzM%_Z$B*+h zeh==AnPyF0B~G8{h)MV*?HcT9Bv$f{ya`u7=fgrh`eBOsT=NKn1l-l~rPC1i<}a*jp4I;@Du8oHZ^IC1w(}ZW6U70yV0fH97({HUcHG0^EIkfU4Rd zQDNDBeKoj{Dnv7gi-HbiaDArj$*1kNXc#nUXbT^{kPz!Bn|CUw?(5-s8)HL!xVCb; zG}&i~8{993LsrNEujPTzoKUf1&KS>~ar}VI zNxS?MAquEoHXSD>B{voZuXfN+{#*@S{ltrHh(T?>OB6oJqY5$15ScAKgqv(knWARe z*ZB3~q11?FjD}k7;6<77-~D0G2EASBL`w%>K)IPNr{>zjL?uK)twelLx|A$L$rnK) zcqogOR16{0I!Jc2P-;M@9o%jH0PqpR*24;$mZ=&(KD#wZ9W7>g)!?^J>+u>)cX zgjG`;*(colUk*^fVb&Xx<0ypi=g#=usUs5KpNNgRXFOR_Tum#qC1b=H`>Uh`k zGC0ZkkF<}&v&oLNnaT6CH|I-lmUa^r14|a3#1Y+Sap4ZL%{&w|7J>dA&w+bR_j~B( zpNHT;9L7P+HyFGWrhnb<%bqoQkFw6|XPuxLgo+lOIDhIu$yq!N{=T2+)}5!X;XQOIt591jwKx>Mh+LZy+DJIMyZbn&-+BUE zdIeqbU9GR_ha(~;XO*(OY)b{`K;8(e?yXyU=>o_&#QYNkZbGzb5Aljmjv^#t-0$Vw zhcn61vMm+KY~Fpn`j4@$xDd0-inMX|T;=r{uh#4iqqu%ORYyJLlIASl1H_*cx|0?} z@@~fX<#OQA!GSuOC*9|tSuX8~1ejmG?cyR`4HKetuQ?OR#|5)k&IZ?lJ`;8X(pzop zui(OpdLuhb z;qQ2IOLnT+b~?>&no3;fx8-wlvD7(%h3z$EV=ePc2>JW>C;J(5bIu#v?98K20UroM z(^|o5oS4sG_Cd{#Onj~SeG8&f$FDb!j;l^wjO!7H`mF@K|iT^&7>~1&wP+xdO{u%5_a$Z&TNNyBGbK&Lw>dSEd~m znSl+>C$7!-$YnCf{(M%Z2F_ZGLO!O@g|%#BFcYCsrN%&fPBG?wpW2&$gRB>q=-XJ; z{Nrf02^1N9`yZstYYydn)F39=hTJ)ZOy4(F?_NKSiQj*tIm+z}ieWv+E(wZtNwWeJ&2H9h@@u3xpCUI7=C7v!u=y>gW%s z>gO&SFK{`S*5R3DGjbnn__+UCE4%L*gd#@_~qC?t*YR^;(==Bx9c9_Gadn&H0s2;3?$eGg$| ztESZIc`#tW)}+*Ee((&|&_lYkgqCHFIx%DU>7RriUG*qeZo$^`$$C$KXlMAKOMXy4 zEMX**bg@`jn=UgR~XD$<0eqz zB3vfd-=OKSF-?E6I<1BIW99Hx$4Bim@dt8G`qB5?jU#8>j15!?huZbkc5le6fVoz^ z3Vu##_kt9GbqjS=`nPx`8?Y-NYcP42Xwh2MSin1AbE{rk&HJ<(UNlk#BE#s*MP~j! zZI0Zz{aa)JL7I${!`y;2CbDw1HG=+ynTp>!78N!Ok@7+uhSSG+7^4T-d|2iL#0(<9 z`Zj<*p~mjFPMrcJ@eJi#z~?sWR6fEeogV~UaE@*O&*f`>*^MmI`8`6Ksj1;Eoc(7b zDOVNc3+Dn^e`%S=$&D%HA^qP5~HQXcsNus{d2iFx74PTITT$abctJ@A_0F2DeMa90ptFbOno}sEgpqb|xe3Oe+k1r;;jYjn2(_ z`jW}-7Gvo+Kv1b%OBJVj5sgu5;5oUVb+j*v^IKk`kT-qrq6GD7zwovJ?0?i|>Br)M zX0xYN@5h{ZQg!#|$wE52i&_=?vhV*P%sb0UZ7cG&+k{4AOuJi_^OT4fy`QGe{D(-x z)Kw)7Ckyp7Up)O9mz4{(#9x{IqVtN6&lf;FH5R4|rB+CbfEf8~ZaQmdka+0-*2lU` zW0VgRCJ5~i;~RO^C4bqzHDB|rDYn266+=^e5IerlS(v)6zhjWyl7Rq7PKL<(I`m28 z5~yAf@->>EY!y3lT{$L3D{U#rcNJm%8}t-d-u)}Gko!|q2!E@LSiVh#zwCg^`R!OA zA^e1wZu<50k(9W9QTrHfe6|%KhKb+P=E!h9Su3C~nUZ~d0>Oc>{`#`a#3(K6O5K3)_Xd9m5&y?*f z-yWkG`b(rJ@6+?pO)n%$6rlFUZ}Io+Iw-8hIag>;*U>|2|Jm%v(W#BDKiT^3RL)&B zlJI&ztPYalfyWNFey;VTX&76Y6sB&$lRdw{`P3)rBmRPp=jJ1{(((S?mRl04ub2J; zHJ>}%D(h&Z3Znqnn0CIzwO^n0&~V?J-rs6vB*BOD0!u^x)%?&F339hkRu_`SYH~M) zjQGkseHbR!ANu*Q1n1M&ugGNM3e*xS5=SVwN+(9%j=lcVnZ%-Vn?zxN=%3q`5X-+v z0Ys4=`(eeL?X7r1tYc-d$dER0`o;g7F2 zY3?SA-~j_VRi0eh406E?wh$mQ*>H0^y89f&ly_b7Vm$SHi9!&Mn_Ttw&jJ4p&QA}s zh3qM-gRqkaqBFqpm9ALs&Z}>F4Ip!Vq7VpQLVkmp;eT!3-vsk9K}oQ+&6I%6XsrV13@D^CZ1<%#ch@pmKEvv*)TYQo{EibPwHca34Us&=~ zABZG=urdyc`@Btleu9^H7n=~X-e_XAbv^~Y?tl2p3`5+z zYa3_H8@mU5F-v$~UWaW2FE;Mud2Y;~a9bU|0Fe%Ep(|^d)e1cmEpEE${TmkhM=Fs? zRw~kBpUZil(oQq$O0e8xIw-A4DOuaxJ~LOFATSD{Ag8b8<|McMCk8geXp#i9-sK*r zFBHd;OvHZ&=M~sC0O%y$!}KtyYwz+QPR?e)0zIBD*~M=b-R4qg!6TMG_VBe)@}pOGEf17$Ryjae zI$VG4PZk0Y^8^U@aJiH~_2>}@ATc8)eEG5-qsTDqyiPO0;qo++Z=##4Eqc}G15?k4$N|v3Yd(M>1|f% z{OoE;QZhw8aaa-Xbtc5UO|59^X>0pflpZiF77CDB-*U5=Jqm-1*RzORyWegbKO|u4 z=>Xi?!x&Y>O0eyZZy2=!rB5IbGSH5u!&1bZlh4cE;s@Zx_eJA{XaV^|V^O!5&H<>1 z>L%!Cl`;t`-r4AIhZ;+zPsdUiBE)gIApG_u_hSr`ugJBy_$7Hj*Uk7OPrU?mjW1>Z z&gkq{U^Jafx>u+)rq&nHGDZirVmkUF(|WX?hfjNJR9l|JTkBM)o%RKX@eht%%)!k^ zsUDbeKBxMI&K1Q=k$_}Ap}dVHhUjV|PTA z+Wh(|!>t~qs*K#gx6RBJi(q}hrJ}No-0(a77Cby?3^Vb;#0u;K;uj1*lEp;N&q1Q+N z93vd#-7hL>uYkOOF=N4M#_;vj*GreCFjjuZT7x!7wYFSh;4L_MYR$Ag{iJ$&78g)q z2%RdTMTG3!^a$7FjR$Ur$Yy0A>{!d#_A-U?xx9+0q~0;gHM?2RXIoCQ`JijeG)0OS z?Iv<~)y&L#5hi{8_P1R+eO|SGw1pR>-Tvek8h{4<>4uNF+?-AR=3eMj!u+`T`(Gb5 zMfLnzo&&^cN`nMrue<61Nnk-sxz<8PlYj#1cHYw3J1zdN(YdZC$4k{jM|G$}Z!&Q0 zAF6MI(H$H}x`USLY*Tmg{1^e$Dj)Y&x$fnA+kfpzrHz4Z9JcZ0Tx{Pw^gae@xLPAEiT3U_3U3DaF6!ssMx zo&;v@Mv@kkN1d7*Qyqsy^2!p%(#il_9<3yq>zJlFe8l0}Q^q}D^O)tbzkPS!DCsDG z$#hb#89gu0*6nviljZ_BqTbAV>+{HxwI!%L(6@pf-vNrVW2>Qq=c(p+y=(?JZ+%rq z#9N)W3{1PF?!#1kSQtSk?YVF3@2i>X+oa6$W`4ZT@V?BmeM6A$68KSP;Wf6jf+u=} zu$h`e{)FREJ_FV)%)NWoQA&gI&zh`0P=!Ba6rihC*D|2MVzL=uG0z6Q4L=apisaFftH z`zXQ2<5#p}KcLfj!oE2|8RS&7Fx6}&ff`#GQ;6xKK+Ca^_CB)vE7ppiwR$Y*CwbEJ z^H1HPTEVq6mwTlEdulESbebs6DunY!4~2l+=T9+(py8s|+QVX&oe$OmWhrxMvkjrS zi^%uOMuTk?Ns}5^o*YBBQO8PD;6d-l_6t?bueAJspXC(ww7BasZ*0H%@|P6xL#8g< zJrXOza^xq-HV-XNV=d@X`}%R9@}ub%rl_9sM2nptr|33r_SmM4X*mqiHCVC9gP6#) z;9m6~NwWXQ8>VLi5r{)9G;fgtR4qG@z>NF7!qW=C4P*BJDmg0vZ~_CkK%NlLzB2ia z?b#wT5cP9i`m>^xvE=d?G-dH}0#u&oQl5!?2IRTa{&*J>I@xQ7Iu=J|=)n4Uq>MIfZiqlp=T zx&)#DF4#7A=oF%keowL#nV81;)N>$DjqQ;Mw*g`>`ZM7|uojrrjl|qHI_lfJyd4t; z($E@hptENL`Qq!xjIl#@bjcU%9w(1bzI*%(GHHZ#NR>XuBqSd;86+bUx#s-39#73b zW`7PbX4m@TC$_SlOwTt+5>#cKb0kgpI`vgN4#xwJS@>Ml(U&jBFk*i&`M0`HLeV_m z%kEH1>O{#5$+r&}ly>Er9@1TD79eLUDrZROnh<@v!fftV6Xq@_@nfWobuN@9+-Ye! zryQgp=)QSWpG82y1)+1qc24_azhPjj9@Q0|@7mrn{~eZiUIdm37}^(85g{omPkh_- z=C<31==2fWJvFo|KheMB1vb|M;{Q}0Qr2&lY^w~;A}8HOfeX(%KCP!X;TLUWuaOXq znrG*B`*cHmRt>OfWY|P?y z;a7nsy*H%J0lKuufq5;ox{lxZ7-|7}oyAq2GDQ6JQlBGt>WkU)Ic({-fgYK^qq`f| z58+G$HRD}P#p!NP{T?nboqPaa=mNA}e$e{CcU>AgC0w%*`X>WYc zo?IFV&@JfH77&&+lH5!FE$-%a6x{^NYx{=Np)E=SY`doz#smeA(nL?~FGu>v&~ew{ z-!0qcGk@2g#6<00JAAX_NIUVf`5pHoPS%fk-0%qOe2*7PS55QoXEs(^UDK;f(L_cn zrLbbNhXUg3fs?(a3WmD;Fdxvn_MtO;FB0*(APJJtngpQ9`;q#8G^x)=c6|8+Z>~0$ zSV7$wdhAr5*>*#MbN)JwK>f?qJ8*b)Jjx9LA+OmClBY9ag=-90tBuJghoBHx&T3I7 zeWbFJy`G3Y#0G%&FLx(p_?LyYe*o(IQb`UtxbCZlcp3#&s8U;q{XQR~{OFQ}u3S{RGF}MQx_+y4E-q5M2w-j3*in~B)Vqc$`9Jb8j27~JixFi6D zt6&`w|^~vry^KN?dJ*?crlb36a2E5)Od* z^xH^42kaUz3ZDQ=IMMBxLa036;-Zh}>Z^BY?bY^vpCzjEn$*t0F6FGkPY zbXTIJpwYdC2V8(>bS8z#f3B8q{SzUrZIgSB2|W<4pwL&+Goax>K5iM=Tjy)P<(&q~Y_0fs* zyAM=w`5=q#?XN8wsQe)Qg275N5dZFgr-gjh{WCV_0S2q%obk(gYpXtMxT5KRBsBi- zeD750nr?5N^(wO?ct*+=2!7Cd_*)0xx`Ej}$yJ8euDR{b3-YQR-ZAZtbz zsswuiDtm5yeXD(;#W4d!FLlr^;tMWq*{~wWa!=R)T=0yKVUZvN9%~xHGs{r0*yH8^c)>_5o_>F{W^|9;ZLc;v@ zxbt}b)(I&Lv}RlAlBK}{C1!5F(eH(-is*b~y$JtfpDuLrf4Mat3*Iyg?>~L8HlY%j zV$$vicZ-Tnd=RbBcYm}r#^>Yw3M^~d!8it~-4b5Szp{P@=To5j*#oW0 z$Uy_xGAX@mT}UzzR6{;K1YG4pc_QN9A)Qrpspj6XwZ1tnnAy5G^Z zq%t3qLGtVw6r4ak?wy1{zCI}gd;6kuee|Q&niW4-m8a{6qN+zRg@9L}Oq%4kjWr%Jgtsg4Z@IFvM8NEN zjDY9z^z-1zce^#0!i<}VH#i7~piBmbRRCaMo-1H{i-@G#r&#BMb?MExydjJ%cS4V- z^`in$A3XaVQnMnN`@>1fY9rXsFN(-(07ZJT)zlP?e_OM}2l9@0h=_1p2Ab5=S5kSf zT9J?@Vj>lUF%I1q*6EMkVRaFGB}E+3EDd* z{Zk228h^Jl+$M?YSuE{(jutXM_kgg}5~H!h{Rip~BtTTB)?h{7?!HiBHW$3wZDZrb z`gwy4yv;k}3GeBSYsY#51$aX5LevAl)p6$NcxX)TsIJ6L2RzIMeV9$!wDEm6li#n8 zKeKmBgK-|MM~PD%Txj|bCJ>)Us%xp_D^e`!zFC2__s6&KT{j=3q@0O94E%gKyM@Cj zKX~a>Ok1TyURO~oZN|%5*v9UdlC=L`x0A-wBR`eE#_kafz157? zr3P}Gq%mF+@GQ)=ec4_{8f(lTUx=27vFvLJK?iuZAWNl@B6>`8uhBx> zpG{I#eKjZf~r7hAn+om)SG zTnO)upab6mBI+&+>PB?^i+%$UAHK)OEmX6^X5U;kxZuf`c7^{gV=+mXSqXWx{fme! zN6t_jJiT$vvX`@RaB}w9I)GWMk5Q?{`s3;k?MqUMO<#~BeEkbgkMoOwvfC_ZV)vC* zSTMKgq*<*DaDeT#om7sH`fu;z%iADu*Aa&l@zAC7PU@h)o{Q_smri`Xds@tpNQQTU zRKqkHp3u*`CN2=(lN3zxGyV2E@Eew4;Qdn0ksJ~`d#^sXYao3e|5-a}3 zv84IO0yA2<^Rm-&e5O}+@*9mdl?$E%2~isRVdWAwuwZT~p4jj0>TjqqFn=M%_0(al zLe<>cpbliZs}#*Aur_Uj3*n^lFGnk0ZrMR?8Z|duu_&&<6#%?^#zS{htsi`nDYG8eStY=8&ds~4!T;h8}ePp{+quLWZ6?G zIq8%YgK5pK{6ry{%0*Gf$(FQ}n1tlcM!K(ORpgFL*hWOyMgz@nG_g3HU62X+tFT!S z@-r%@rkaCF9-pd%WrUQEWw_pCxJ-^f!1(>Y9+{21pTDaA?1|CS;e7i)Ex=yDOM;&p z-pE-IgqgIJ`|sK!2nA2Ap9J4s2iT&eBIkxi?YA2VF3Bmv26mskVRN zlL&Vbu0%@SID;m}NcNu!%t)4b()42F>$aGFh27fKR+XTKKHvKmuIXn}uQMF@>yu z{Q-onTf+JN=tdq74l8ru5*oE@9}3awvfrPS%zTw~+S}kzZY16tzW$a8#!UhlK$}7q zl)yC2i139Pl!SS_Sz=EN{S_LbL0wOTQpJu#9#1xlt3Vz_Ik`VR`&)r#M-1_L>MfHS z+;+S;&+5Ia?MS`XWSz5zF$6<+qHyL5mAJ408su{)4Th0!Y?l`r(rF*jNqGpltQDcd z3sId4=2X>2%7;}dJ|~q}g%_l6Y1!(S>0-w9hD%jow7jm7GaBk~+Bce0JlfU7{EmxS zeaLJ^RQ+XsuaDM30tE@djk+hHX-T}KBb?Ol67`A!1J)tVMos&Sv}Y-YbZS}f&#HsaHYOev(;I}>qUw~qY2l09`SY(Br9XuPgs#TBNTk*wGBiM zb8;*-vn^`F{w=5$dvN;p&y(L@kybJyMlct@+#8w;J;QTL{SkF(PC7ujOzuHh>6L$< zPeX#LDanA8sVE8beq;(?qIMvvOEd{ku=joq`_Do@i}lH>V=*UsyN(LFrw$?{s!eYT z@9*fBAs&aeqOpj`aMoA~jKyc)uV)|mgd&=M@bGR$<9;NPra$vzXkY?c^xlCn4mtOD zELGR%=~uLyG)Ee?lppPFp>ccOB43fJVsKJZ(VX&)dtz5&VrmO)S$GkhU2!C}RaI?7 zpNc*Y4#!7szZJ%Pn2;x(n+EfQdl|_#{cR4o*0cn59lec=A98SE(qt=M^oI-eyC-j$ z@!Uxk5d4buS)Uv8lY+cZh4ntzEwp!=`}3x|IFXI~=Q-UR4nEf>?PDP}AAD#L0pGnp zUR%fEo6DWNLmdIaQDMJjLkl7+IYaL;VJocLEn|E+w|!fSQS3|Wup9-VRr+zBdMvQv zS9|>gt?gQm#+P$Hh10!itDyZWWJwqtTzkp?(oK`3K$3zv=2)!l<7NF(LQvY*IOgh8 zI?4J~hTaMN+Jc1R`thd;GGJk0bS@f2AK9ONi*6hKpqj}R9`jL2MgIDFV}d=Id&lw>{b8mk|NgyZ=yA__o0g46 zZBttg^|&purM9iO?v8Mzk^oMCJ7FBa2r6?-CBijbK;DaQXg>m0ueLe z`v#Ek&35WTwsSX=QxkvTMQMF~(rR<&rNYPYm2jJzSL1y?gS5oUweA3a$yuY}4jH1U zG~;1Yx)k(Z>k_R(gi*889^S6ht$-`9X;_gt_&~!6b|?HkHDt_=U6o4qMzqiwKl-#%-%&)=M@)QO5`mtsiK#X$fhlH9*i6!(7= z;pK9@H;BbO4TI-9Wk5oEAuB!4?FW~x02q3guk;}hy;X1BXFwRH=C2>Up_=KDYCdUT z44V}cWR%M!DFs-%wqlti@xnGQbEpOdNPcBHkPX?YVVG(lI1Gk`K#sK#-T+$}I%%~V zyI~(>r*#O!XCnRYd&j%EAwwYy{+5Gc@rU7(;p8{1v&kgm40O*BSngU|==5JFwO&hc zV7brR$H8!8!k&2hWe{Ygc4_I88NI+({9X9>akR5zV~BkZ0~TpB+?0BC`*VU@2;VfT zkJ*zqpNY+D${H>Ug!?A{q;#_C3%TU&_}gwxb3q9SBc%AupSd1|u@-T2|1V0-N`GOfr`Z7jGWgzqI>)IlniU>R7=-bg zGybd?6;oN`^bu5v?eC678!Kyq^r!yy#uon8*f4249D7(!Eg)YXHZ#kqW1#TVF#Gef zeBjpyLG{MBK%$35HBx0BO;W*F=}{!iAzkzZtcr1;A6xQGbxS<{!a-%z6reWbH;s)g zQ5|!pJw>>7+0f{q6qgUr(UiipV)X)sVK@%l$$i>&gD?_I(W9?ABSbNenN1r6(;X-{ z9!$AZ9;K6>l&AP{f~FKHhL&_52%#yw2iDv;iLyYi%t_Tqoolat`)daFLoU-wvBTLS1zWaK&% zN1YkvW`*4SE8F6q)vd`+uTCvf()B5QBbI3`6v&G?+}jp>QX+^-QnejVN+|w$+o9Nx zep7%pw+yoPQII}OS`XUY$}!d`qTIrUj=l}rwJzL0TXkycMfiNq1X+ihgZnQU@#+=L zt+e82Ynq$!tzT|Q5{v6qn&84?o?l*ygGDbhy8ihHSu}Is?%?y@=tvO9etv{lpRdGD zQzPI=WePK3ygvJOjI~U8IoqFm+ksU%OjV-YEU+IGG2APZrtMt2Y=KI}MExb(g4?r6 zt}M4XMKV^{hFrq!5kfL)EQ$V`>D~cfzf{9f@hwW-q)PjCF4Kyc{a(wxp|0})|B9jSI(c+*Hw!MrUmYOu z5VZ|%rA$BwOTg_pQo6brtkTbptz~eMo_`S@C5`<=Hj*_=`y`1X}@tkg&}ocV|l{& z(Y0`c3OCBoLay5QWB3omXTVI~8t<++zVZ)e-%@Fg{N{%P}HR*m}1X*UV%SK(m% zkuHk&4I)oK?WaMFX1AA$5Tyl%vAaX8LrfTnNFI{3;1c3V8;N^x$s6vT(HkjH&D+{i7tQ2UvC12t^P5+m z9zFyGP!q%4v$EKURJhIJsf~E4^GXr`sOU{#?%=v^d6>8gvjm-hV|)Z8w9`|KVMn}) zX_^0q@6BRcSOfIM<4R#xlT>HPdN8s_%-SuTLo+27bSHo4)xvn*`E65Wdk)X5V&f1D zc2x0znW|LuSifSu3c}ia7%SW_6c8Cmm=WrMfQ>$FKM}b}STgdqPv+Sxh*{_F`PDR{ zcN;PCpmps$ePpI-ZGP8~VE4)FIFdTz){mz&jZTNt@_+Ts+UK1Z?`C6bX7ZHZg+S39;Cn@XTpl4{52@-et8SH#4#JbQYHv ztFj<&d;{ObJg1^gbM@8@%(h2+h|0NOSl%T$kI8#h$b%9lrM|lHY2R|B`uWcMO-Fs| z+3(+Yo^wkkjn0yl1lbF%VR!HiEK|1|i&OPRv}rZWV&w4^ON}^HR(%wy83b=HNtN;< zg%1sb%RZMdNWdXpowP_849rBr7D$8CoY$5&Ew;;N&7V#>W1> zIsMwg1@f^O;$iaopItWmLL7kE*Uk3-?brAP!g3Vv(c60e5n zO}V@mvq25U|FGO1bcfhrr|Vt{nNcOAJz%fRwR)AqUmWUDsw*QgW^DmBNd~G@v5w;; zPi~H^%5#ZLaL(Vk%@g}B)JXRGrfix0pW{#N3S!T-2xbfCKQpm*-Q@uB!Zsb<9UU>%&jT)DZ!UOKwawLdB_||m@h=X6V?V`M>B|n-A>t1-TnDy)ms0C90mUK^rpfI zEQhqB%ku_ZcTKJ|cPFp1*8GWEzY?uh#lxP6QytK!aC~8HV%g?)$X4}>;MsLIQ=c^2 z{rJ>i%YdY0iNODG9_#k;=6)<^zAl}4?_JuRg#=!eqwOxj$GIDgf0(h$qAth+id?6E z$Z`68O3%NtCcb0A5V;UP$Y9{NDKP^Zdom7j{giCuKGyXh=7+YdP#S-MNoggDVhvUr zAPSO3(IlCwEVnTl5jK^WAJ9$EKwyPScJpEWcKS8o_R4*x(r(||$CorggFkO@bOsyd zhA^IA>Ru3|^Om7otv-2@eZmZiJh*sg$Po>t%tL_vyR+UaTL#k1JwDuFKkC@eZRNQ^ zScGzJxq8=6cbCNvS|}c8mUQEaPtM+Zbk<)VhM0MM^9zr!T|DiLZDZV+D&bpmI`21E zIG}{5cw$It^W2URYgRzezY(nNxMJ_9m<4D00h?N0G)0!#F{RBG1(^Lk`W$g5aQdrS>2n~X#T<1FNwD3grv{}$ zhA7H!=~F2bC*C;M=-dr3GkAsS(urJ1!z%$@y#{*lP$dNUoX?pyzxIf;_Ph+q% zbv|#$3{?L3BZ0et6MkUfvxr2t@%raoofQ|sdmnTMG>|G7Fc6<=TBr7rgwowK=EWJ{ zNK5G0x>_>}h`Tvbw%AWn=q_5H5DqJ(fbB7U17K*FnRLFEEa7?&~8JD zHdv9pUa;>3|7GH+~`4aOH z>kPTisirSL!Ht3XTOU&>+Gh+qSC-npfio*~A-;pgC*H^O)uzb}AskwbPxTv*W4Z06 z-f&y!-RMEI&Sv(J>s+LlO==ErH@aj__e^xu^Xcz;OCV!$z*595>R#CDRF^@J@|N6|v59`MkH?sOK7PtaW3H=2YyTZDlrdod*X>JJUBX(Qi z=C87Loez_7a2Y-%{P_}+*ZzUuJp8|U_ualhTUf*djWHo6rOf$hburQR_3Wik$%8)X z7U*&v&G<=L4F4a#TmBvf@%*^cb7$;?$p!CPi4d@xeX5it@1G=%Y<6@0cFk?dUps$= ztzV12JdGuMB9O`v6h~dQABb)edZ*$NtB;hRglRs{G_{n`0wJ^ct@y`H^0r{bRf7S+ zV1U44UviBKL>A+hc_gon%(G~NSP6H5Q)idPIP59Oc8SHVrz5u6UmC!D%}eEfn5E8I zJ`kWT5e@e?N9%E`%1;0__1R!!#Irimipmw~>SIio*s?;&+zhdA7Hr1)`e!bkR7M4& z3v0hnIqaSa5107&Y%J>WqU-Hfbay877yfXJCh5Q|QRL6VG*E3F*fCM55)xlU3me(5 zt;y5HOkuqYdlEedaovj*w4QoP^DU0b;h6MInfww`ftnr$wSH!Y6W~<-o}57x}!RIdAa=STiS$obE|l{ z!}1-%5u<^*;xY@pM)89%`oy1{z0L6TfSK-AXs0`SjDm_@HusE6!E~o~U6g!&q;f^# zO~$_Y1?d#mCaA(=dqB zCMEN(T%gH4ciB9vift*gV_7e>z27#5Fdc}qFk`aDfA>(O&W*8a?+j)e=k@4h!P(dN zrZ)NOF8Ye}`t^A3;Kzw{UcFPx_-DLx<&9AJ`aT$2Bo&so$(HyMn`kaio}TJeQsV2X z&@dRTk13z+mj!ltqLUv=6qq*_;I@ArozCR6@@Zhhawn55)9jzqP6sus@_^l7&;XW!+FjZ$^D7N z>VpDzvdKSL@9$zW;xp%QxZWR5Ep;GKAlPM#*XD~T(te=gkj-2NuKj5YY+S7JJc|WM< z5%~DElty$u!hCA$>@lgD^?CZ&X;d!d5XiA}*Ia94ENMb(u1Ei|WAn8~&=c~dib~2t zVm6y>|Khbamw$uA5I#pzLwE~LV{k5x7JZ?0)=enSE)2Y-F4S7ZR|VrpGnCY6!k@j` zTdF45?JL)VqB?I59n%F)5tFLh8FX!5LSy6G!YHZPIW?=^&uAH><-P_NhLo3n0$ zTjFc0JnZ+1E32nty-}#dfBd8x2lv%)K1sL~)x@3yqgx6sN*Q3QkC=aItVbidp${m;dlU>0|dDEAAwO-Vc znMjLT&R|B58tl!-okpJb)Z+g>Qhf=jgKeOVgtn! z7LwGQc%prHrWOqrCH?}l=A=YgBw>x;(%4{j7~Yj5?aJUjyz@?93M^^>+Q<`K%Jo2y zeJZS8Wgzj%_NuliD1s7hbX|SKyBIXJeQFx|Sm;@vN1ukzTZ0>w7RLoDB+6hg6C(U6 z(cB@|!jFd)3D}WMh!d%d_Lzm#Q5c)RA~#%sYP=pF8Reg@vJ&qBw*q;t>5|!3vM=IF}wuMdt9a zoZtd@U>ufY{4BZ>`uZX(giMxnn(%?i=m(9DI#XEZdtXjh3QbB^ae$U26T|u?otr-1 zNG#OPGx9^6&p%_?6+2ZIl$4fiOu=chsp4H?3>RR#7SYEhie(CGEItcUBva$!8cwqx zube_vsMAX#ig!q~=DitsYSSSWDYRoGMdtCTUV*=7l)%wBpi9A=8sx{X)F0V;ql zku+#tVJs8gA_btwQfG9_xmH_7PRoEG>TmCKzKD3l8xr5ts0Q!t=Shs5FSLfJihfBd z*bxp}(To@2Mz@CmX4G3khrPcc>6IR}3kX;pXcPj?=bZ-lqYfp}HS`VaZ3~N@0S_Hd zA#j#0bO^=J=j~0OysYxc?}N_&zCxO{LU~mpw%l5NO1oe%?Fd45$iJ7knw0(#5y&I| zn^~fL|5MB%7UZq-rp$o!4G2{L^WWXE+={r*X?$q$^xS;1UlWs-e3`>?PZM({`c|Kx zPZb6UeU++h3rcp**S~__fw0s*vY^4IpJ8oNvHM`eah*Yjb>o-&92{E{#`ZV8lJ;bf zuNJbm`%(tvbVBy$A-(8^Kf)_5)E7xc3V*S@9uoTKEuIX#KoqOmR|Fv#SPYh4ewFGucYEUAambmxl8HvpFOJOA5BUQJ5 zU}4FJ@iRuk-)dsD>98d0LEmInAr1QwD_hnF?MD*^E!ifsl zQ%}rCpsUps-AiLT_>TmA1X0uR#Gk?{egd|q=BN`{oL^1yhb?Vix((s5*f1qB_A zr#XY`>Aly0%5was+SA3;sGoCCe9afgfDG;@>iT0LL)Y`FCSR8OleWPZXwly{#Dbs3 zEx)qaq9pHKc<(EnvA<+}0-#W{imxSC=g8@U)s3PwKDF>4BV3fyu2~g*@-BZY4H+Ki zaZGL$aohR$>}+NJjx<>=F&;I{%v!T%r6`jaxA z%a#&mF+-0Zuwco2Wk7*!{Bk9*PVT|dHl7>Cawg0uho0*+at>43tosf{S{$=3c~tJ5 zI^{>^9-f*4&LJob)Zl*%0FI6HC(8)87E1-OdZpKt6+o9edZ##4t1zXb7U+wE_kiEiSVRgWICb9^-!Ut%6H z5>B>fDH>ArQs?)H2oP)LjDx6*S@f~QAl;IY>(rPF-3ojOeo@uE`ziqKJFc()AWy(Sqb=cGBnTGLJOHdYk}e zvrW4RC^L7cl|bEuo2vGyO0#6G%RRg=akh?>N&7hM=+BM8j^`koZ;63)jb`u@a~{5$ zEn4;M7}Quir&ayDbAGRplV@BC7I-^JFY&`vQ&BLTX>10i<6bj_(!HDa>p@ViNdooR z)8Y30&KUXLcV&j6!_9IwjTD}AxKvud9fifn zbs;CN`MQQV<1bafz2uAW6GMu=#v&J84Q&Sg8LeUUWlR$xvtEn*SiCVi<-&uP)bswN zjZ~8Kr+u|H?M7enbRZYED8Nr~^MRz=*_?-S@=|q{g6__-7PUg-a7}EKI|P+bg8g5g z$M12XsXv=f*C_fWlh~UH&mYr@za9Y<-h05U+=_K&YB~pDfJB6)H zhqpkwC7vIKd4~}DMO}L5$EsHm+)w@$d}dP~B@gZ*<%briCvWn`^zo0>igo4P8WM~_ zOyI`R+^Aweq1uE)QTRqHm-3NuM@%|#z_sOqrc3Tf_&XX+{GupRmyD9h#~;>)^bEf! zFxNvE)H|Jho~Jf#C<%XhVhBmKCvT#QrXG)KpU^Fdo})*;95~cLhHi^OdL2v0rrZ1+&fEXBiR(vCqI1rs*Z9NdsJm6RFtiFi{+T#Cl@!v)M>>)tzET;HJVPz#x z3KeEQ%xzsjQB?OQIb9Y_;ftXOQT_!ILrfvEl2AQrg5B$3Tz`MNSVNqy;hpI866XSv z{}L-UEkgAIQdm1F+|6x$xGXeueX1}1Hdj$4gPyW!Jvh0XD9c}YGuytHwN1@v4ML@gw&yTX(@Fp}mcxT}P z-1px>5xGS*BwlBgpu~;{Qv?M>1RbxX_U#2OT0*V0!M&)~0wQxCMtqkv)s>!9_^aLx zbeIpPey@ARe-+qn8PmC_|3LB_a7WY+2EO?uA0K9g1SxetNIpXDCalj6)B33)C5)`h zB~<8&py`iaJ;EZZr8o2VCYBFw*P|u}3+#@5P=Fd7#f^R-yGOnGRQs;`gT_b|$vanc z2Qf;51Req}3q3k>lBzjPvVaa_IC8}-AW~D?JN9C{=)B*V&U`qn#EUx`e)tlr!dW&) zlX1|}<^S4*y9eS^=2B^M2&83Uqu6Q~y5krSKMyK&@d4iwaKMJ z$j*3&^w^Wiy>mHvG6o%O;cnifj-vM?>yWW6m93Cv${1%V^uN_bzyLOg>0Q~kOoOW3 z<3M9J*X|rr_{A8Fu9)cAsbTEbD*L^gV8ChrskzZMxrc|{5ca7OA-@rV9ez%8+zBuI z=0wpaARiw$BkUo=`Bn>&XZnht6m`4hZ6Adt|AcZ!cUvg#Hw%RK1pX5f2}%I!%`=GGy}f<{=x??MY1G2G5ZHEpGT>Py2r}8=;Vs{sex=@E2JO zbICs{jjUTnnkJ<%{==6z0EbxY3GK)7gM5g^HTkGpB8*4y^oooDVdXNf*&9YXP>Z zgO*unW8u!hmso82D6aJ66s&I|S3@8X6|_unrJUVsP*y&0szYy0R2zSjOm(kuwESI^ z?k~BU;q&43nar;MVz)xRLS9HvWdRD}AE#2t81pa@ULr{OO^Biq2RIC6y*?RoZg65Y z*ZR7|E^vqJI=Nzeu%;p3Cyss~h%d*v*#5V!_?zDFws5l|r9O;_u6WeNzC(s2%vfT-)! z(Wh`FRls3x43z6UppDIkm95P3t}yJ0s2ubUAXx=1sV}sF(Qv~%)hN-@qhiI1^i7Xi z3TxauJD~YILe@-qt-BY7uuzX%RXNa6i%{a9J*Xi1Rba@bCf8+A5^R^tZv<+y-CT(7 zfq`WWv+dtWO>=LD@(UFK3xDxhb7rz#z#8W)?t}h)Un3@x4nffv%oms7K#sW}J3L6& zvDF4{(8Gx~uH+sdJ?>Ey5(C&>O3qLw)B$q-ejn+y57A$jusAEu19p-_hOn|Obb7-CV}WQE@w+DZCo zR#!?Wa%cQlV{&1j$VWE{z{>H^cX@mdPuH|*kDki=;<5p+!fHk=7c6g4pKiT#c`Yt|^X-*3Cg55WIZuf8Vre8C&74@Sxhl`$h zjMHP^O<6L$vtiHa638`dwGUIJd~V}zfTPrimO8}@!*WLp-12F(-GhWmaeqrAg#0%b zP)~yXTu@lz>Q7?WZ|i;+VfBd@>gni_QmasLXpMY0$=N2nhT~ zpHmIKH9E+9XRVLA`t#%oo;QgcWpR%?t0gmU+;(T0^qy2vy56si7JvJOpV3CWYVecn zKVR`z9M@*MwA`MU&=K-Muh*%`Z$N2SVWWdX$1L zQDSN{CS_ju&2NH};;aWH@yZgDI-Xgv0C6Nd-#iZ8gc^893v>RHbfT`J3pj7qCdKiY zOBlww#-SWne?Wsii~Kg2WslKLy$0u+GfR2nS3dtamk-V}zeW5)~rN7sD zdkfvY*~cDV+tG#{;+c7P1(-9zq)PXI+m~C65Ebg7f*PEyOHFM zr7|UoQUPWCc+*B#9de#}Sk=9zd)zvRzYFBpG#-JdpO5uTj*WmkgJ~s6KeU;QD;aTo zPZCf=ETjeA#-E>D6xEA;GN=*wveY+h>Vmk!p6k;D*x8PoSg*HV4^&n5gT1;`WRD4> z-TAQc70BLwu7qm2cklas>WHmje_qeFN9gHnBuG6>fX)wm`cFuR}lQ{wn?g8?@OK@XH|$8YV*^S`4Zru4k`yuPE%^t z;p;koV*5qz3uzS|N3Ay&qv;K3-_c@b1g+;xN~e8!|9RKqLj;( zYNj{$eCwy>llU>NC#znPt7Jg%nWHM~3!j-^jJG}R6-hxip%~jbH{hH~T#J!L-CMI9 z?fWVKTAM|fiJt>wyc$#2)+3FNZ+t%vy?05XKgrDRjlo+qVZf=Mv|*4dF~#}bIa^2g zqsyby@w(WkeL7e8F2HB`0trt)cL`hWY zutWiQDB615d4y|VcudPJc_CqXde1k{K)S87qT|n6{Ry>U#{U1NFXawoHfQ{#h{oW)O^q_o@}eHbQ>lps2>zy8Ou*FS%cj1TPb}Ch#sAA^6O9jhPNAy#2krk_>Fr$jfjlL z>*-W4(ZJDVqY^4=xLT%)g4OEH^;hsy2#B{lroMCFJXPpIqYj5 znAMCI1CaUB!jF=RF++qTOcg8PR5(ai^2n>`*0iNKh;VG}K4ouBx$Q^cHB_xblu*D4 zD(_QScBF-3e44k_4Mcx^0B~x!QP|lK5*F8>RK_g zxmwloOkICB#-5=b4@<;$uAY|?7@{giak!i3%VrKK{>=j}_SFLG)JCc3M!{zi;6;u~ z_0fkXA|tPpu`3b>E!uUv22sLQuN8uAq_K`OUcgz^e|^5S62qG<3u#;uPJhAyhI>yC z2O}j$rRkw!yoKw~J4QIN-vf82d1$rCnDOa@<>y41pl$PY(m9$n>0{MV%Wj72;trsv z#rYuVJY?k88|SP3bRXoLNAkQtd8+D$DelUcggx?>0;eN`?DiwlA5Y%BF6G^9+IQtF zR?&lOMngi`K-AGSbN6|aEFOgB=PNPp=PW8i{MY~}zJwwGfxWpjo?or$kFouKVEc277_*bwjUZHS*Djas* z%g8~)+TRI1qx|HEv!=?Iz&eE$ffBj3CR9^O)3;tIahbfp#<_R=#_^9NpYtPj&_YZb z=YF54ePiQg04!{u&+bPFLLKl~yXY+X1)Npr&MojP1oS)oi~iHvwI)D+FPn2lb1kIa zeRNNzQTpL_2&2U%0`sJb+?hqfw4^Y53+q58ps@lvu3m*52&oWd{~e5d30&pB8pmR| z0>uNv{Be2J&rvygQ;XvMCpv^X#lLJTH-XbCgk87fZuw`{##W%^eKA%LK`uXtD0x4$ z(eyGI&<5-F7{pK@Q*_G#S1jz-p*N7P_pglBsdHhRLnhJ^P;<@<;)S13J5>HTD9la# zAQlfdroZ+^C$~{nGOy5;W=^&eAWFhmZ9=s{aj7~HIw21t#R3ZLZ5kXQb%8fYy;>{#y(Il{-57dF+!p75bwT+H9{&3*5N45ze2-eucSa|-vX8OQb)17t?;MZ^JiX7z?CLdT z+d4JV?*SBlzS?F)Tc!nGGRaX#QH=wG&+{@sg`%m7n>bO4B+9Gydv}K582ByN-rUXUl`}3%Db+6Nzu3V&m$gm>g_n@p@L9TvN#D=MtM@6S7&~QvClK^%quzo0xUP}; z11w~jDf)q@915Dve-ol)kp@rC{>3h(7oBC==R47L2OMiC zIFN8eubd3aXx(ThfGLEYO1a^wE!P&o6-es|^wP)v+-JHZv*$CDDg!x?jII3RsUuxWS+ifOF<>eEJ@?i`cgZm+$&M( zOv9`I!xTvZVRHP&^v5$D1R4ONU#;Lhh3W1^H|{F&{*M9cC#Y6F zc@#^I@)^1@j4x1aA(&a}bZJ750NiQ=g#%tn=Ads);<*)qhB!)~<+V5+4s0dD@l~Y> zRegWzNf;e#lhi)_C-9Q7=kah2zc2Ha)PE(w_Tz$q0P^(g!$m4o7FASS?iA75-Cw4A z_xb(Enrzt8Ppfmow+fMCw!ayh;|Ke5F@#f)?ZK@xV^pqu-Q!gS z-;eqiYfAlUY7+PZO)KyH{x^w15r3il5wK*SldJfjruNgdsr6!nE+-)h4(3~5Vcdi$ zV+t{bf&0vI_P$0$yfct5hu@#rL`8v`+H8bayctEeMNyEPqj^eyeb(U~UHH!0QxM{0o&NcuP%2XZ5{kwE z2ar63*`ue@8c@1oEPlo1)|X3eXCmO9>@_(s{xJ3-?N%)c--DQyE4y`EF*sz|u{<{d zk3zv?UT0p@&!J)&qpRL^@y4P(|83bosvRN3`H{TnvrB=+(E9+r1!~n=!T!ts_G}9i zZ&1U}&*WdVtojBR)|^+BtxY7Eu;U1^%p$bERT0Br0C1iEnT!KSgc4PXK|K zxixKv|G17h&f^O&vYd+gL>0Hv5F84$g!*D{aV4@P&hBDF_j&Tnqoo9xv4(e=&+iF9 zXno?x9-*Qu#Z@G`}6dWIsuD6_x`{{Rv;-14Jkr?+ZWkz?nKpesv59 zGtNRiPUgN&j@UV62I88xk%E$Sg0W=O<9?|@8shPN6kX&aG|N38*DF$T(4iRixb?=H zf>A-oELuKx@AuzQw#ky$SkK(+N{tZ{0yLrUFZ~6$347XCI%?(}z?>sAFS1 z0=$^N+ONjJRL}3UXHzh`1-icPqs^OA`g!{%Q(bUOb*u)5%uuL5Dl6jOpmQ3PJlfkt z9`j;tILf%9NC_N03QrY)WzLug(}8A?X2~sx*msa%r$q3ziWS9dmx(Y(76YVcc2*

    U;WV@lAGeic5jVT!xO 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 Y|BEsw{{L;?SXJUC$pIeS|DS65KM->yG5`Po diff --git a/reference/writeonce-app/src/index.html b/reference/writeonce-app/src/index.html deleted file mode 100644 index e8b6906..0000000 --- a/reference/writeonce-app/src/index.html +++ /dev/null @@ -1,22 +0,0 @@ - - - - - WriteOnce - - - - - - - - - diff --git a/reference/writeonce-app/src/main.ts b/reference/writeonce-app/src/main.ts deleted file mode 100644 index c58dc05..0000000 --- a/reference/writeonce-app/src/main.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; - -import { AppModule } from './app/app.module'; - - -platformBrowserDynamic().bootstrapModule(AppModule) - .catch(err => console.error(err)); diff --git a/reference/writeonce-app/src/proxy.conf.js b/reference/writeonce-app/src/proxy.conf.js deleted file mode 100644 index 393875f..0000000 --- a/reference/writeonce-app/src/proxy.conf.js +++ /dev/null @@ -1,18 +0,0 @@ -const { env } = require('process'); - -//const target = env.ASPNETCORE_HTTPS_PORT ? `https://localhost:${env.ASPNETCORE_HTTPS_PORT}` : -// env.ASPNETCORE_URLS ? env.ASPNETCORE_URLS.split(';')[0] : 'https://localhost:7053'; - -const target = `http://localhost:${8085}` - -const PROXY_CONFIG = [ - { - context: [ - "/api", - ], - target, - secure: false - } -] - -module.exports = PROXY_CONFIG; diff --git a/reference/writeonce-app/src/styles.css b/reference/writeonce-app/src/styles.css deleted file mode 100644 index 8027431..0000000 --- a/reference/writeonce-app/src/styles.css +++ /dev/null @@ -1,5 +0,0 @@ -/* You can add global styles to this file, and also import other style files */ - -@tailwind base; -@tailwind components; -@tailwind utilities; diff --git a/reference/writeonce-app/src/web.config b/reference/writeonce-app/src/web.config deleted file mode 100644 index a8e8036..0000000 --- a/reference/writeonce-app/src/web.config +++ /dev/null @@ -1,25 +0,0 @@ - - -   -     -        -         -         -           -           - -         - -         -       - - - - - -     - -   - - - diff --git a/reference/writeonce-app/tailwind.config.js b/reference/writeonce-app/tailwind.config.js deleted file mode 100644 index 310d316..0000000 --- a/reference/writeonce-app/tailwind.config.js +++ /dev/null @@ -1,12 +0,0 @@ -/** @type {import('tailwindcss').Config} */ -module.exports = { - content: [ - "./src/**/*.{html,ts}", - ], - darkMode: 'class', - theme: { - extend: {}, - }, - plugins: [], -} - diff --git a/reference/writeonce-app/tsconfig.app.json b/reference/writeonce-app/tsconfig.app.json deleted file mode 100644 index 374cc9d..0000000 --- a/reference/writeonce-app/tsconfig.app.json +++ /dev/null @@ -1,14 +0,0 @@ -/* To learn more about this file see: https://angular.io/config/tsconfig. */ -{ - "extends": "./tsconfig.json", - "compilerOptions": { - "outDir": "./out-tsc/app", - "types": [] - }, - "files": [ - "src/main.ts" - ], - "include": [ - "src/**/*.d.ts" - ] -} diff --git a/reference/writeonce-app/tsconfig.json b/reference/writeonce-app/tsconfig.json deleted file mode 100644 index 3e0d780..0000000 --- a/reference/writeonce-app/tsconfig.json +++ /dev/null @@ -1,35 +0,0 @@ -{ - "compileOnSave": false, - "compilerOptions": { - "outDir": "./dist/out-tsc", - "forceConsistentCasingInFileNames": true, - "strict": true, - "noImplicitOverride": true, - "noPropertyAccessFromIndexSignature": true, - "noImplicitReturns": true, - "noFallthroughCasesInSwitch": true, - "skipLibCheck": true, - "esModuleInterop": true, - "sourceMap": true, - "declaration": false, - "experimentalDecorators": true, - "moduleResolution": "node", - "importHelpers": true, - "target": "ES2022", - "module": "ES2022", - "useDefineForClassFields": false, - "lib": [ - "ES2022", - "dom" - ], - "typeRoots": [ - "node_modules/@types" - ] - }, - "angularCompilerOptions": { - "enableI18nLegacyMessageIdFormat": false, - "strictInjectionParameters": true, - "strictInputAccessModifiers": true, - "strictTemplates": true - } -} diff --git a/reference/writeonce-app/tsconfig.spec.json b/reference/writeonce-app/tsconfig.spec.json deleted file mode 100644 index be7e9da..0000000 --- a/reference/writeonce-app/tsconfig.spec.json +++ /dev/null @@ -1,14 +0,0 @@ -/* To learn more about this file see: https://angular.io/config/tsconfig. */ -{ - "extends": "./tsconfig.json", - "compilerOptions": { - "outDir": "./out-tsc/spec", - "types": [ - "jasmine" - ] - }, - "include": [ - "src/**/*.spec.ts", - "src/**/*.d.ts" - ] -} diff --git a/reference/writeonce-app/writeonce-frontend.esproj b/reference/writeonce-app/writeonce-frontend.esproj deleted file mode 100644 index 2d087d2..0000000 --- a/reference/writeonce-app/writeonce-frontend.esproj +++ /dev/null @@ -1,10 +0,0 @@ - - - npm start - Jasmine - - false - - $(MSBuildProjectDirectory)\dist\writeonce-fe.client\ - - \ No newline at end of file

    68QKz8~vvwjm`sn!#Q^iKcd^Y7Gv4T{)E2NG)e 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 diff --git a/reference/writeonce-app/src/assets/images/profile.jpg b/reference/writeonce-app/src/assets/images/profile.jpg deleted file mode 100644 index f9bb3ff3e3731b35e9b910509b464c3a70f513d4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 71858 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 GfB)H}e@7Jn diff --git a/reference/writeonce-app/src/assets/writeonce-articles/gitlab-runner-with-kubernetes-executor.json b/reference/writeonce-app/src/assets/writeonce-articles/gitlab-runner-with-kubernetes-executor.json deleted file mode 100644 index 8736c61..0000000 --- a/reference/writeonce-app/src/assets/writeonce-articles/gitlab-runner-with-kubernetes-executor.json +++ /dev/null @@ -1,72 +0,0 @@ -{ - "id": 53, - "title": "Gitlab Runner with Kubernetes Executor", - "sys_title": "gitlab-runner-with-kubernetes-executor", - "published": true, - "content": { - "author": "Shoney Arickathil", - "content": { - "codes": [ - { - "language": "yaml", - "paragraphIndex": 0, - "sectionIndex": 1, - "snippet": "gitlab-runnergitlab-runner-deploy/gitlab-runner-with-kubernetes-executor", - "title": "gitlab runner deploy" - } - - ], - "images": [], - "img": { - "caption": "Gitlab runner with kubernetes executor.", - "path": "https://writeonce-filecloud-bucket.s3.eu-central-1.amazonaws.com/images/kubernetes.png" - }, - "sections": [ - { - "heading": "Introduction", - "paragraphs": [ - "Deploying a Gitlab runner using kubernetes is a great option to overcome the limitations of other gitlab runner executor such as docker and docker machine. Follow the article once you have docker and minikube installed on a local linux or WSL environment." - ] - }, - { - "heading": "Deploying gitlab runner pod using kubernetes", - "paragraphs": [ - "Copy the content to a yaml file and replace with the gitlab runner registration token base 64 value. Execute 'Apply deploy configuration' command to deploy the gitlab runner pod in the namespace gitlab." - ] - }, - { - "heading": "Using RBAC Authorization", - "paragraphs": [ - "If you now try to run a pipeline it will fail due to lack of authorization for create pods in your local kubernetes environment. You can check whether the service account has permission to create pods by executing below command.", - "If the reponse is 'No' then copy the below role defination apply it bind the roles to service account." - ] - } - ] - }, - "publishedOn": 1737414886, - "references": [ - { - "dateAccessed": 1737414886, - "title": "Gitlab Kubernetes executor", - "url": "https://docs.gitlab.com/runner/executors/kubernetes/" - }, - { - "dateAccessed": 1737414886, - "title": "kubernetes Gitlab runner deployment and role defination yaml files ", - "url": "https://git.writeonce.de/shoney/kubernetes_clusters/-/tree/main/gitlab-runner?ref_type=heads" - }, - { - "dateAccessed": 1737414886, - "title": "Minikube installation", - "url": "https://minikube.sigs.k8s.io/docs/start/" - } - ], - "systitle": "gitlab-runner-with-kubernetes-executor", - "tags": [ - "Kubernetes", - "Gitlab runner" - ], - "title": "Gitlab Runner with Kubernetes Executor" - }, - "do_aws_sync": false -} \ No newline at end of file diff --git a/reference/writeonce-app/src/assets/writeonce-articles/page.json b/reference/writeonce-app/src/assets/writeonce-articles/page.json deleted file mode 100644 index 207e9e0..0000000 --- a/reference/writeonce-app/src/assets/writeonce-articles/page.json +++ /dev/null @@ -1,73 +0,0 @@ -[ - - { - "id": 2, - "title": "Gitlab Runner with Kubernetes Executor", - "sys_title": "gitlab-runner-with-kubernetes-executor", - "content": { - "author": "Shoney Arickathil", - "content": { - "codes": [ - { - "language": "yaml", - "paragraphIndex": 0, - "sectionIndex": 1, - "snippet": "gitlab-runnergitlab-runner-deploy/gitlab-runner-with-kubernetes-executor", - "title": "gitlab runner deploy" - } - - ], - "images": [], - "img": { - "caption": "Gitlab runner with kubernetes executor.", - "path": "https://writeonce-filecloud-bucket.s3.eu-central-1.amazonaws.com/images/kubernetes.png" - }, - "sections": [ - { - "heading": "Introduction", - "paragraphs": [ - "Deploying a Gitlab runner using kubernetes is a great option to overcome the limitations of other gitlab runner executor such as docker and docker machine. Follow the article once you have docker and minikube installed on a local linux or WSL environment." - ] - }, - { - "heading": "Deploying gitlab runner pod using kubernetes", - "paragraphs": [ - "Copy the content to a yaml file and replace with the gitlab runner registration token base 64 value. Execute 'Apply deploy configuration' command to deploy the gitlab runner pod in the namespace gitlab." - ] - }, - { - "heading": "Using RBAC Authorization", - "paragraphs": [ - "If you now try to run a pipeline it will fail due to lack of authorization for create pods in your local kubernetes environment. You can check whether the service account has permission to create pods by executing below command.", - "If the reponse is 'No' then copy the below role defination apply it bind the roles to service account." - ] - } - ] - }, - "publishedOn": 1737414886, - "references": [ - { - "dateAccessed": 1737414886, - "title": "Gitlab Kubernetes executor", - "url": "https://docs.gitlab.com/runner/executors/kubernetes/" - }, - { - "dateAccessed": 1737414886, - "title": "kubernetes Gitlab runner deployment and role defination yaml files ", - "url": "https://git.writeonce.de/shoney/kubernetes_clusters/-/tree/main/gitlab-runner?ref_type=heads" - }, - { - "dateAccessed": 1737414886, - "title": "Minikube installation", - "url": "https://minikube.sigs.k8s.io/docs/start/" - } - ], - "systitle": "gitlab-runner-with-kubernetes-executor", - "tags": [ - "Kubernetes", - "Gitlab runner" - ], - "title": "Gitlab Runner with Kubernetes Executor" - } - } -] \ No newline at end of file diff --git a/reference/writeonce-app/src/assets/writeonce-articles/testreadme.md b/reference/writeonce-app/src/assets/writeonce-articles/testreadme.md deleted file mode 100644 index 875c28e..0000000 --- a/reference/writeonce-app/src/assets/writeonce-articles/testreadme.md +++ /dev/null @@ -1,5 +0,0 @@ - -```javascript -function helloWorld() { - console.log('Hello, world!'); -} diff --git a/reference/writeonce-app/src/environments/environment.prod.ts b/reference/writeonce-app/src/environments/environment.prod.ts deleted file mode 100644 index 50ff873..0000000 --- a/reference/writeonce-app/src/environments/environment.prod.ts +++ /dev/null @@ -1,6 +0,0 @@ -export const environment = { - production: true, - apiUrl: 'https://api.yourdomain.com', // Production API URL - featureXEnabled: false // Disable feature in production - }; - \ No newline at end of file diff --git a/reference/writeonce-app/src/environments/environment.ts b/reference/writeonce-app/src/environments/environment.ts deleted file mode 100644 index e1d282e..0000000 --- a/reference/writeonce-app/src/environments/environment.ts +++ /dev/null @@ -1,6 +0,0 @@ -export const environment = { - production: false, - apiUrl: 'http://localhost:3004', // Development API URL - featureXEnabled: true // Example feature flag - }; - \ No newline at end of file diff --git a/reference/writeonce-app/src/favicon.ico b/reference/writeonce-app/src/favicon.ico deleted file mode 100644 index 615dc124da4ff6d0bd363bd5f60c9497f0fdb2ce..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 63584 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