From 9fd5322a3347e81f66b8ff18648f2658b02cdcfb Mon Sep 17 00:00:00 2001 From: Demis Bellot Date: Sat, 2 Mar 2019 02:33:18 -0500 Subject: [PATCH 001/206] new sharpscript.net website --- src/AppHost.cs | 22 +-- src/AutoDataQueryServices.cs | 2 +- src/AutoQueryDbServices.cs | 2 +- src/CodePagePartials.cs | 8 +- src/CustomTemplateFilters.cs | 71 +++---- src/CustomerServices.cs | 6 +- src/EmailTemplatesService.cs | 7 +- src/GitHubMarkdownFilters.cs | 15 +- src/IntrospectStateServices.cs | 11 +- src/LinqServices.cs | 5 +- src/NuGet.Config | 3 +- src/ProductServices.cs | 4 +- src/ProductsPage.cs | 6 +- src/Program.cs | 2 +- ...emplatePages.csproj => SharpScript.csproj} | 2 +- src/Startup.cs | 2 +- src/TemplateServices.cs | 31 ++-- src/wwwroot/_layout.html | 14 +- src/wwwroot/assets/img/favicon.png | Bin 4492 -> 4492 bytes src/wwwroot/assets/img/logo-32.png | Bin 4492 -> 0 bytes src/wwwroot/assets/img/logo.svg | 18 ++ src/wwwroot/docs/api-reference.html | 42 ++--- src/wwwroot/docs/arguments.html | 10 +- src/wwwroot/docs/blocks.html | 51 +++--- src/wwwroot/docs/code-pages.html | 24 +-- .../docs/{db-filters.html => db-scripts.html} | 30 +-- ...ault-filters.html => default-scripts.html} | 90 ++++----- ...eb-apps.html => deploying-sharp-apps.html} | 18 +- src/wwwroot/docs/error-handling.html | 84 ++++----- src/wwwroot/docs/filters-reference.html | 48 ++--- src/wwwroot/docs/filters.html | 173 ------------------ src/wwwroot/docs/hot-reloading.html | 10 +- .../{html-filters.html => html-scripts.html} | 20 +- src/wwwroot/docs/installation.html | 56 +++--- src/wwwroot/docs/introduction.html | 158 +++++----------- src/wwwroot/docs/methods.html | 173 ++++++++++++++++++ src/wwwroot/docs/model-view-controller.html | 14 +- src/wwwroot/docs/mvc-netcore.html | 26 +-- src/wwwroot/docs/page-formats.html | 6 +- src/wwwroot/docs/partials.html | 10 +- ...ed-filters.html => protected-scripts.html} | 142 ++++++++++++-- ...{redis-filters.html => redis-scripts.html} | 40 ++-- src/wwwroot/docs/sandbox.html | 34 ++-- ...filters.html => servicestack-scripts.html} | 114 ++++++------ .../docs/{api-pages.html => sharp-apis.html} | 42 ++--- .../docs/{web-apps.html => sharp-apps.html} | 166 ++++++++--------- .../{view-engine.html => sharp-pages.html} | 110 +++++------ src/wwwroot/docs/syntax.html | 61 +++--- src/wwwroot/docs/transformers.html | 14 +- src/wwwroot/examples/nav-links.txt | 4 +- src/wwwroot/examples/qotd.html | 4 +- src/wwwroot/gfm/adhoc-querying/01.html | 12 +- src/wwwroot/gfm/adhoc-querying/01.md | 8 +- src/wwwroot/gfm/adhoc-querying/02.html | 4 +- src/wwwroot/gfm/adhoc-querying/02.md | 2 +- src/wwwroot/gfm/adhoc-querying/03.html | 16 +- src/wwwroot/gfm/adhoc-querying/03.md | 2 +- src/wwwroot/gfm/adhoc-querying/04.html | 10 +- src/wwwroot/gfm/adhoc-querying/04.md | 10 +- src/wwwroot/gfm/api-pages/01.html | 5 - src/wwwroot/gfm/api-pages/01.md | 6 - src/wwwroot/gfm/api-reference/01.html | 30 +-- src/wwwroot/gfm/api-reference/01.md | 30 +-- src/wwwroot/gfm/api-reference/02.html | 10 +- src/wwwroot/gfm/api-reference/02.md | 2 +- src/wwwroot/gfm/api-reference/03.html | 16 +- src/wwwroot/gfm/api-reference/03.md | 18 +- src/wwwroot/gfm/arguments/01.html | 6 +- src/wwwroot/gfm/arguments/01.md | 2 +- src/wwwroot/gfm/arguments/02.html | 6 +- src/wwwroot/gfm/blocks/02.html | 4 +- src/wwwroot/gfm/blocks/02.md | 6 +- src/wwwroot/gfm/blocks/03.html | 4 +- src/wwwroot/gfm/blocks/03.md | 6 +- src/wwwroot/gfm/blocks/04.html | 4 +- src/wwwroot/gfm/blocks/04.md | 6 +- src/wwwroot/gfm/blocks/05.html | 2 +- src/wwwroot/gfm/blocks/05.md | 4 +- src/wwwroot/gfm/blocks/06.html | 4 +- src/wwwroot/gfm/blocks/06.md | 6 +- src/wwwroot/gfm/blocks/07.html | 4 +- src/wwwroot/gfm/blocks/07.md | 6 +- src/wwwroot/gfm/blocks/08.md | 2 +- src/wwwroot/gfm/blocks/09.html | 18 +- src/wwwroot/gfm/blocks/09.md | 9 +- src/wwwroot/gfm/blocks/10.html | 4 +- src/wwwroot/gfm/blocks/10.md | 6 +- src/wwwroot/gfm/blocks/11.html | 4 +- src/wwwroot/gfm/blocks/11.md | 6 +- src/wwwroot/gfm/blocks/12.html | 2 +- src/wwwroot/gfm/blocks/12.md | 2 +- src/wwwroot/gfm/blocks/13.html | 6 +- src/wwwroot/gfm/blocks/13.md | 8 +- src/wwwroot/gfm/blocks/14.html | 6 +- src/wwwroot/gfm/blocks/14.md | 8 +- src/wwwroot/gfm/blocks/15.html | 14 +- src/wwwroot/gfm/blocks/15.md | 4 +- src/wwwroot/gfm/blocks/16.html | 6 +- src/wwwroot/gfm/blocks/16.md | 7 +- src/wwwroot/gfm/blocks/17.md | 6 +- src/wwwroot/gfm/blocks/18.html | 12 +- src/wwwroot/gfm/blocks/18.md | 4 +- src/wwwroot/gfm/blocks/19.md | 2 +- src/wwwroot/gfm/blocks/20.html | 8 +- src/wwwroot/gfm/blocks/20.md | 8 +- src/wwwroot/gfm/code-pages/01.html | 30 +-- src/wwwroot/gfm/code-pages/01.md | 8 +- src/wwwroot/gfm/code-pages/02.html | 4 +- src/wwwroot/gfm/code-pages/02.md | 6 +- src/wwwroot/gfm/code-pages/03.html | 10 +- src/wwwroot/gfm/code-pages/03.md | 4 +- src/wwwroot/gfm/code-pages/04.html | 46 ++--- src/wwwroot/gfm/code-pages/04.md | 10 +- src/wwwroot/gfm/code-pages/05.html | 40 ++-- src/wwwroot/gfm/code-pages/05.md | 6 +- src/wwwroot/gfm/db-filters/01.html | 3 - src/wwwroot/gfm/db-filters/02.md | 7 - src/wwwroot/gfm/db-scripts/01.html | 3 + .../gfm/{db-filters => db-scripts}/01.md | 2 +- .../gfm/{db-filters => db-scripts}/02.html | 6 +- src/wwwroot/gfm/db-scripts/02.md | 7 + src/wwwroot/gfm/default-filters/01.html | 6 - src/wwwroot/gfm/default-filters/01.md | 7 - src/wwwroot/gfm/default-scripts/01.html | 6 + src/wwwroot/gfm/default-scripts/01.md | 7 + .../01.html | 0 .../01.md | 2 +- .../02.html | 0 .../02.md | 2 +- .../03.html | 0 .../03.md | 2 +- src/wwwroot/gfm/email-templates/01.html | 60 +++--- src/wwwroot/gfm/email-templates/01.md | 6 +- src/wwwroot/gfm/email-templates/02.html | 2 +- src/wwwroot/gfm/error-handling/01.html | 4 +- src/wwwroot/gfm/error-handling/01.md | 4 +- src/wwwroot/gfm/error-handling/02.html | 4 +- src/wwwroot/gfm/error-handling/02.md | 4 +- src/wwwroot/gfm/error-handling/03.html | 26 +-- src/wwwroot/gfm/error-handling/03.md | 12 +- src/wwwroot/gfm/filters/01.html | 2 - src/wwwroot/gfm/filters/01.md | 3 - src/wwwroot/gfm/filters/02.html | 2 - src/wwwroot/gfm/filters/02.md | 3 - src/wwwroot/gfm/filters/04.html | 9 - src/wwwroot/gfm/filters/04.md | 10 - src/wwwroot/gfm/filters/05.html | 2 - src/wwwroot/gfm/filters/05.md | 3 - src/wwwroot/gfm/filters/06.html | 5 - src/wwwroot/gfm/filters/07.html | 10 - src/wwwroot/gfm/filters/08.html | 5 - src/wwwroot/gfm/filters/08.md | 6 - src/wwwroot/gfm/filters/09.html | 2 - src/wwwroot/gfm/filters/09.md | 3 - src/wwwroot/gfm/filters/10.html | 2 - src/wwwroot/gfm/filters/11.html | 6 - src/wwwroot/gfm/filters/12.html | 12 -- src/wwwroot/gfm/hot-reloading/01.html | 4 +- src/wwwroot/gfm/hot-reloading/01.md | 4 +- src/wwwroot/gfm/info-filters/01.html | 5 - src/wwwroot/gfm/info-filters/01.md | 6 - src/wwwroot/gfm/info-filters/02.html | 4 - src/wwwroot/gfm/info-filters/03.html | 2 - src/wwwroot/gfm/info-filters/04.html | 5 - src/wwwroot/gfm/info-scripts/01.html | 0 src/wwwroot/gfm/info-scripts/01.md | 6 + src/wwwroot/gfm/info-scripts/02.html | 0 .../gfm/{info-filters => info-scripts}/02.md | 2 +- src/wwwroot/gfm/info-scripts/03.html | 0 .../gfm/{info-filters => info-scripts}/03.md | 0 src/wwwroot/gfm/info-scripts/04.html | 0 .../gfm/{info-filters => info-scripts}/04.md | 2 +- src/wwwroot/gfm/installation/01.html | 5 +- src/wwwroot/gfm/installation/01.md | 7 +- src/wwwroot/gfm/installation/02.html | 5 +- src/wwwroot/gfm/installation/02.md | 7 +- src/wwwroot/gfm/installation/03.html | 5 - src/wwwroot/gfm/installation/03.md | 6 - src/wwwroot/gfm/introduction/01.html | 2 +- src/wwwroot/gfm/introduction/01.md | 4 +- src/wwwroot/gfm/introduction/02.html | 4 +- src/wwwroot/gfm/introduction/02.md | 10 +- src/wwwroot/gfm/introduction/03.html | 3 + src/wwwroot/gfm/introduction/03.md | 2 +- src/wwwroot/gfm/introduction/04.html | 2 +- src/wwwroot/gfm/introduction/04.md | 2 +- src/wwwroot/gfm/introduction/05.html | 2 +- src/wwwroot/gfm/introduction/05.md | 2 +- src/wwwroot/gfm/introduction/06.html | 2 +- src/wwwroot/gfm/introduction/06.md | 2 +- src/wwwroot/gfm/introduction/07.html | 2 +- src/wwwroot/gfm/introduction/07.md | 2 +- src/wwwroot/gfm/introduction/08.html | 4 +- src/wwwroot/gfm/introduction/08.md | 4 +- src/wwwroot/gfm/introduction/09.html | 4 + src/wwwroot/gfm/introduction/09.md | 5 + src/wwwroot/gfm/introduction/10.html | 11 ++ src/wwwroot/gfm/introduction/10.md | 20 ++ src/wwwroot/gfm/introspect-state/01.html | 10 +- src/wwwroot/gfm/introspect-state/01.md | 10 +- src/wwwroot/gfm/introspect-state/02.html | 2 +- src/wwwroot/gfm/introspect-state/03.html | 2 +- src/wwwroot/gfm/introspect-state/03.md | 2 +- src/wwwroot/gfm/methods/01.html | 2 + src/wwwroot/gfm/methods/01.md | 3 + src/wwwroot/gfm/methods/02.html | 2 + src/wwwroot/gfm/methods/02.md | 3 + src/wwwroot/gfm/{filters => methods}/03.html | 2 +- src/wwwroot/gfm/{filters => methods}/03.md | 4 +- src/wwwroot/gfm/methods/04.html | 9 + src/wwwroot/gfm/methods/04.md | 10 + src/wwwroot/gfm/methods/05.html | 2 + src/wwwroot/gfm/methods/05.md | 3 + src/wwwroot/gfm/methods/06.html | 5 + src/wwwroot/gfm/{filters => methods}/06.md | 4 +- src/wwwroot/gfm/methods/07.html | 10 + src/wwwroot/gfm/{filters => methods}/07.md | 8 +- src/wwwroot/gfm/methods/08.html | 5 + src/wwwroot/gfm/methods/08.md | 6 + src/wwwroot/gfm/methods/09.html | 2 + src/wwwroot/gfm/methods/09.md | 3 + src/wwwroot/gfm/methods/10.html | 2 + src/wwwroot/gfm/{filters => methods}/10.md | 2 +- src/wwwroot/gfm/methods/11.html | 6 + src/wwwroot/gfm/{filters => methods}/11.md | 2 +- src/wwwroot/gfm/methods/12.html | 12 ++ src/wwwroot/gfm/{filters => methods}/12.md | 4 +- src/wwwroot/gfm/model-view-controller/01.html | 16 +- src/wwwroot/gfm/model-view-controller/01.md | 8 +- src/wwwroot/gfm/model-view-controller/02.html | 6 +- src/wwwroot/gfm/model-view-controller/02.md | 2 +- src/wwwroot/gfm/model-view-controller/03.html | 24 +-- src/wwwroot/gfm/model-view-controller/03.md | 4 +- src/wwwroot/gfm/mvc-aspnet/01.html | 8 +- src/wwwroot/gfm/mvc-aspnet/01.md | 4 +- src/wwwroot/gfm/mvc-netcore/01.html | 6 +- src/wwwroot/gfm/mvc-netcore/01.md | 6 +- src/wwwroot/gfm/mvc-netcore/02.html | 4 +- src/wwwroot/gfm/mvc-netcore/02.md | 4 +- src/wwwroot/gfm/mvc-netcore/03.html | 12 +- src/wwwroot/gfm/mvc-netcore/03.md | 8 +- src/wwwroot/gfm/mvc-netcore/04.html | 6 +- src/wwwroot/gfm/mvc-netcore/04.md | 6 +- src/wwwroot/gfm/mvc-netcore/05.html | 18 +- src/wwwroot/gfm/mvc-netcore/05.md | 10 +- src/wwwroot/gfm/page-formats/01.md | 2 +- src/wwwroot/gfm/page-formats/02.html | 4 +- src/wwwroot/gfm/page-formats/02.md | 2 +- src/wwwroot/gfm/page-formats/03.html | 4 +- src/wwwroot/gfm/page-formats/03.md | 4 +- src/wwwroot/gfm/protected-filters/01.html | 5 - src/wwwroot/gfm/protected-filters/01.md | 6 - src/wwwroot/gfm/protected-scripts/01.html | 5 + src/wwwroot/gfm/protected-scripts/01.md | 6 + src/wwwroot/gfm/redis-filters/01.html | 2 - src/wwwroot/gfm/redis-filters/02.html | 6 - src/wwwroot/gfm/redis-filters/02.md | 7 - src/wwwroot/gfm/redis-filters/03.html | 8 - src/wwwroot/gfm/redis-scripts/01.html | 2 + .../{redis-filters => redis-scripts}/01.md | 2 +- src/wwwroot/gfm/redis-scripts/02.html | 6 + src/wwwroot/gfm/redis-scripts/02.md | 7 + src/wwwroot/gfm/redis-scripts/03.html | 8 + .../{redis-filters => redis-scripts}/03.md | 2 +- src/wwwroot/gfm/sandbox/01.html | 2 - src/wwwroot/gfm/sandbox/01.md | 2 +- src/wwwroot/gfm/sandbox/02.html | 4 - src/wwwroot/gfm/sandbox/02.md | 2 +- src/wwwroot/gfm/sandbox/03.html | 6 - src/wwwroot/gfm/sandbox/03.md | 4 +- src/wwwroot/gfm/servicestack-filters/01.html | 7 - src/wwwroot/gfm/servicestack-filters/03.html | 5 - src/wwwroot/gfm/servicestack-filters/04.html | 41 ----- src/wwwroot/gfm/servicestack-filters/05.html | 3 - src/wwwroot/gfm/servicestack-filters/06.html | 2 - src/wwwroot/gfm/servicestack-filters/07.html | 4 - src/wwwroot/gfm/servicestack-filters/07.md | 5 - src/wwwroot/gfm/servicestack-filters/08.html | 7 - src/wwwroot/gfm/servicestack-scripts/01.html | 7 + .../01.md | 2 +- .../02.html | 0 .../02.md | 2 +- src/wwwroot/gfm/servicestack-scripts/03.html | 5 + .../03.md | 2 +- src/wwwroot/gfm/servicestack-scripts/04.html | 41 +++++ .../04.md | 2 +- src/wwwroot/gfm/servicestack-scripts/05.html | 3 + .../05.md | 2 +- src/wwwroot/gfm/servicestack-scripts/06.html | 2 + .../06.md | 2 +- src/wwwroot/gfm/servicestack-scripts/07.html | 4 + src/wwwroot/gfm/servicestack-scripts/07.md | 5 + src/wwwroot/gfm/servicestack-scripts/08.html | 7 + .../08.md | 2 +- src/wwwroot/gfm/sharp-apis/01.html | 5 + src/wwwroot/gfm/sharp-apis/01.md | 6 + .../gfm/{api-pages => sharp-apis}/02.html | 0 .../gfm/{api-pages => sharp-apis}/02.md | 2 +- .../gfm/{api-pages => sharp-apis}/03.html | 0 .../gfm/{api-pages => sharp-apis}/03.md | 2 +- .../gfm/{api-pages => sharp-apis}/04.html | 10 +- .../gfm/{api-pages => sharp-apis}/04.md | 12 +- src/wwwroot/gfm/sharp-apps/01.html | 16 ++ .../gfm/{web-apps => sharp-apps}/01.md | 2 +- .../gfm/{web-apps => sharp-apps}/02.html | 0 .../gfm/{web-apps => sharp-apps}/02.md | 2 +- .../gfm/{web-apps => sharp-apps}/03.html | 0 .../gfm/{web-apps => sharp-apps}/03.md | 2 +- src/wwwroot/gfm/sharp-apps/04.html | 6 + .../gfm/{web-apps => sharp-apps}/04.md | 2 +- src/wwwroot/gfm/sharp-apps/05.html | 25 +++ .../gfm/{web-apps => sharp-apps}/05.md | 2 +- .../gfm/{web-apps => sharp-apps}/06.html | 2 +- .../gfm/{web-apps => sharp-apps}/06.md | 4 +- src/wwwroot/gfm/sharp-apps/07.html | 22 +++ .../gfm/{web-apps => sharp-apps}/07.md | 6 +- .../gfm/{web-apps => sharp-apps}/08.html | 2 +- .../gfm/{web-apps => sharp-apps}/08.md | 2 +- .../gfm/{web-apps => sharp-apps}/09.html | 0 .../gfm/{web-apps => sharp-apps}/09.md | 2 +- .../gfm/{web-apps => sharp-apps}/10.html | 0 .../gfm/{web-apps => sharp-apps}/10.md | 2 +- .../gfm/{web-apps => sharp-apps}/11.html | 0 .../gfm/{web-apps => sharp-apps}/11.md | 2 +- .../gfm/{web-apps => sharp-apps}/12.html | 0 .../gfm/{web-apps => sharp-apps}/12.md | 1 - .../gfm/{web-apps => sharp-apps}/13.html | 12 +- .../gfm/{web-apps => sharp-apps}/13.md | 12 +- .../gfm/{web-apps => sharp-apps}/14.html | 12 +- .../gfm/{web-apps => sharp-apps}/14.md | 13 +- .../gfm/{view-engine => sharp-pages}/01.html | 2 +- .../gfm/{view-engine => sharp-pages}/01.md | 4 +- src/wwwroot/gfm/sharp-pages/02.html | 2 + src/wwwroot/gfm/sharp-pages/02.md | 3 + .../gfm/{view-engine => sharp-pages}/03.html | 2 +- .../gfm/{view-engine => sharp-pages}/03.md | 2 +- .../gfm/{view-engine => sharp-pages}/04.html | 2 +- .../gfm/{view-engine => sharp-pages}/04.md | 2 +- .../gfm/{view-engine => sharp-pages}/05.html | 6 +- .../gfm/{view-engine => sharp-pages}/05.md | 2 +- .../gfm/{view-engine => sharp-pages}/06.html | 2 +- .../gfm/{view-engine => sharp-pages}/06.md | 2 +- .../gfm/{view-engine => sharp-pages}/07.html | 2 +- .../gfm/{view-engine => sharp-pages}/07.md | 2 +- .../gfm/{view-engine => sharp-pages}/08.html | 2 +- .../gfm/{view-engine => sharp-pages}/08.md | 2 +- src/wwwroot/gfm/sharp-pages/09.html | 10 + .../gfm/{view-engine => sharp-pages}/09.md | 2 +- .../gfm/{view-engine => sharp-pages}/10.html | 0 .../gfm/{view-engine => sharp-pages}/10.md | 2 +- .../gfm/{view-engine => sharp-pages}/11.html | 6 +- .../gfm/{view-engine => sharp-pages}/11.md | 8 +- src/wwwroot/gfm/syntax/01.html | 2 +- src/wwwroot/gfm/syntax/01.md | 2 +- src/wwwroot/gfm/syntax/02.html | 4 +- src/wwwroot/gfm/syntax/02.md | 2 +- src/wwwroot/gfm/transformers/01.html | 21 +++ src/wwwroot/gfm/transformers/01.md | 2 +- src/wwwroot/gfm/transformers/02.html | 18 +- src/wwwroot/gfm/transformers/02.md | 4 +- src/wwwroot/gfm/transformers/03.html | 10 +- src/wwwroot/gfm/transformers/03.md | 2 +- src/wwwroot/gfm/transformers/04.html | 26 +-- src/wwwroot/gfm/transformers/04.md | 4 +- src/wwwroot/gfm/transformers/05.html | 10 +- src/wwwroot/gfm/transformers/05.md | 2 +- src/wwwroot/gfm/transformers/06.html | 18 +- src/wwwroot/gfm/transformers/06.md | 16 +- src/wwwroot/gfm/transformers/07.html | 2 +- src/wwwroot/gfm/transformers/07.md | 2 +- src/wwwroot/gfm/view-engine/02.html | 2 - src/wwwroot/gfm/view-engine/02.md | 3 - src/wwwroot/gfm/view-engine/09.html | 10 - src/wwwroot/gfm/web-apps/01.html | 16 -- src/wwwroot/gfm/web-apps/04.html | 6 - src/wwwroot/gfm/web-apps/05.html | 25 --- src/wwwroot/gfm/web-apps/07.html | 22 --- src/wwwroot/index.html | 119 +++++++----- src/wwwroot/linq/index.html | 6 +- src/wwwroot/usecases/adhoc-querying.html | 22 +-- src/wwwroot/usecases/content-websites.html | 29 ++- src/wwwroot/usecases/email-templates.html | 8 +- src/wwwroot/usecases/index.html | 8 +- src/wwwroot/usecases/introspect-state.html | 16 +- src/wwwroot/usecases/live-documents.html | 1 + 385 files changed, 2197 insertions(+), 2060 deletions(-) rename src/{TemplatePages.csproj => SharpScript.csproj} (98%) delete mode 100644 src/wwwroot/assets/img/logo-32.png create mode 100644 src/wwwroot/assets/img/logo.svg rename src/wwwroot/docs/{db-filters.html => db-scripts.html} (94%) rename src/wwwroot/docs/{default-filters.html => default-scripts.html} (85%) rename src/wwwroot/docs/{deploying-web-apps.html => deploying-sharp-apps.html} (89%) delete mode 100644 src/wwwroot/docs/filters.html rename src/wwwroot/docs/{html-filters.html => html-scripts.html} (83%) create mode 100644 src/wwwroot/docs/methods.html rename src/wwwroot/docs/{protected-filters.html => protected-scripts.html} (60%) rename src/wwwroot/docs/{redis-filters.html => redis-scripts.html} (78%) rename src/wwwroot/docs/{servicestack-filters.html => servicestack-scripts.html} (62%) rename src/wwwroot/docs/{api-pages.html => sharp-apis.html} (84%) rename src/wwwroot/docs/{web-apps.html => sharp-apps.html} (87%) rename src/wwwroot/docs/{view-engine.html => sharp-pages.html} (72%) delete mode 100644 src/wwwroot/gfm/api-pages/01.html delete mode 100644 src/wwwroot/gfm/api-pages/01.md delete mode 100644 src/wwwroot/gfm/db-filters/01.html delete mode 100644 src/wwwroot/gfm/db-filters/02.md create mode 100644 src/wwwroot/gfm/db-scripts/01.html rename src/wwwroot/gfm/{db-filters => db-scripts}/01.md (97%) rename src/wwwroot/gfm/{db-filters => db-scripts}/02.html (54%) create mode 100644 src/wwwroot/gfm/db-scripts/02.md delete mode 100644 src/wwwroot/gfm/default-filters/01.html delete mode 100644 src/wwwroot/gfm/default-filters/01.md create mode 100644 src/wwwroot/gfm/default-scripts/01.html create mode 100644 src/wwwroot/gfm/default-scripts/01.md rename src/wwwroot/gfm/{deploying-web-apps => deploying-sharp-apps}/01.html (100%) rename src/wwwroot/gfm/{deploying-web-apps => deploying-sharp-apps}/01.md (99%) rename src/wwwroot/gfm/{deploying-web-apps => deploying-sharp-apps}/02.html (100%) rename src/wwwroot/gfm/{deploying-web-apps => deploying-sharp-apps}/02.md (98%) rename src/wwwroot/gfm/{deploying-web-apps => deploying-sharp-apps}/03.html (100%) rename src/wwwroot/gfm/{deploying-web-apps => deploying-sharp-apps}/03.md (99%) delete mode 100644 src/wwwroot/gfm/filters/01.html delete mode 100644 src/wwwroot/gfm/filters/01.md delete mode 100644 src/wwwroot/gfm/filters/02.html delete mode 100644 src/wwwroot/gfm/filters/02.md delete mode 100644 src/wwwroot/gfm/filters/04.html delete mode 100644 src/wwwroot/gfm/filters/04.md delete mode 100644 src/wwwroot/gfm/filters/05.html delete mode 100644 src/wwwroot/gfm/filters/05.md delete mode 100644 src/wwwroot/gfm/filters/06.html delete mode 100644 src/wwwroot/gfm/filters/07.html delete mode 100644 src/wwwroot/gfm/filters/08.html delete mode 100644 src/wwwroot/gfm/filters/08.md delete mode 100644 src/wwwroot/gfm/filters/09.html delete mode 100644 src/wwwroot/gfm/filters/09.md delete mode 100644 src/wwwroot/gfm/filters/10.html delete mode 100644 src/wwwroot/gfm/filters/11.html delete mode 100644 src/wwwroot/gfm/filters/12.html delete mode 100644 src/wwwroot/gfm/info-filters/01.html delete mode 100644 src/wwwroot/gfm/info-filters/01.md delete mode 100644 src/wwwroot/gfm/info-filters/02.html delete mode 100644 src/wwwroot/gfm/info-filters/03.html delete mode 100644 src/wwwroot/gfm/info-filters/04.html create mode 100644 src/wwwroot/gfm/info-scripts/01.html create mode 100644 src/wwwroot/gfm/info-scripts/01.md create mode 100644 src/wwwroot/gfm/info-scripts/02.html rename src/wwwroot/gfm/{info-filters => info-scripts}/02.md (61%) create mode 100644 src/wwwroot/gfm/info-scripts/03.html rename src/wwwroot/gfm/{info-filters => info-scripts}/03.md (100%) create mode 100644 src/wwwroot/gfm/info-scripts/04.html rename src/wwwroot/gfm/{info-filters => info-scripts}/04.md (80%) delete mode 100644 src/wwwroot/gfm/installation/03.html delete mode 100644 src/wwwroot/gfm/installation/03.md create mode 100644 src/wwwroot/gfm/introduction/09.html create mode 100644 src/wwwroot/gfm/introduction/09.md create mode 100644 src/wwwroot/gfm/introduction/10.html create mode 100644 src/wwwroot/gfm/introduction/10.md create mode 100644 src/wwwroot/gfm/methods/01.html create mode 100644 src/wwwroot/gfm/methods/01.md create mode 100644 src/wwwroot/gfm/methods/02.html create mode 100644 src/wwwroot/gfm/methods/02.md rename src/wwwroot/gfm/{filters => methods}/03.html (93%) rename src/wwwroot/gfm/{filters => methods}/03.md (88%) create mode 100644 src/wwwroot/gfm/methods/04.html create mode 100644 src/wwwroot/gfm/methods/04.md create mode 100644 src/wwwroot/gfm/methods/05.html create mode 100644 src/wwwroot/gfm/methods/05.md create mode 100644 src/wwwroot/gfm/methods/06.html rename src/wwwroot/gfm/{filters => methods}/06.md (67%) create mode 100644 src/wwwroot/gfm/methods/07.html rename src/wwwroot/gfm/{filters => methods}/07.md (52%) create mode 100644 src/wwwroot/gfm/methods/08.html create mode 100644 src/wwwroot/gfm/methods/08.md create mode 100644 src/wwwroot/gfm/methods/09.html create mode 100644 src/wwwroot/gfm/methods/09.md create mode 100644 src/wwwroot/gfm/methods/10.html rename src/wwwroot/gfm/{filters => methods}/10.md (93%) create mode 100644 src/wwwroot/gfm/methods/11.html rename src/wwwroot/gfm/{filters => methods}/11.md (97%) create mode 100644 src/wwwroot/gfm/methods/12.html rename src/wwwroot/gfm/{filters => methods}/12.md (78%) delete mode 100644 src/wwwroot/gfm/protected-filters/01.html delete mode 100644 src/wwwroot/gfm/protected-filters/01.md create mode 100644 src/wwwroot/gfm/protected-scripts/01.html create mode 100644 src/wwwroot/gfm/protected-scripts/01.md delete mode 100644 src/wwwroot/gfm/redis-filters/01.html delete mode 100644 src/wwwroot/gfm/redis-filters/02.html delete mode 100644 src/wwwroot/gfm/redis-filters/02.md delete mode 100644 src/wwwroot/gfm/redis-filters/03.html create mode 100644 src/wwwroot/gfm/redis-scripts/01.html rename src/wwwroot/gfm/{redis-filters => redis-scripts}/01.md (96%) create mode 100644 src/wwwroot/gfm/redis-scripts/02.html create mode 100644 src/wwwroot/gfm/redis-scripts/02.md create mode 100644 src/wwwroot/gfm/redis-scripts/03.html rename src/wwwroot/gfm/{redis-filters => redis-scripts}/03.md (97%) delete mode 100644 src/wwwroot/gfm/servicestack-filters/01.html delete mode 100644 src/wwwroot/gfm/servicestack-filters/03.html delete mode 100644 src/wwwroot/gfm/servicestack-filters/04.html delete mode 100644 src/wwwroot/gfm/servicestack-filters/05.html delete mode 100644 src/wwwroot/gfm/servicestack-filters/06.html delete mode 100644 src/wwwroot/gfm/servicestack-filters/07.html delete mode 100644 src/wwwroot/gfm/servicestack-filters/07.md delete mode 100644 src/wwwroot/gfm/servicestack-filters/08.html create mode 100644 src/wwwroot/gfm/servicestack-scripts/01.html rename src/wwwroot/gfm/{servicestack-filters => servicestack-scripts}/01.md (98%) rename src/wwwroot/gfm/{servicestack-filters => servicestack-scripts}/02.html (100%) rename src/wwwroot/gfm/{servicestack-filters => servicestack-scripts}/02.md (98%) create mode 100644 src/wwwroot/gfm/servicestack-scripts/03.html rename src/wwwroot/gfm/{servicestack-filters => servicestack-scripts}/03.md (98%) create mode 100644 src/wwwroot/gfm/servicestack-scripts/04.html rename src/wwwroot/gfm/{servicestack-filters => servicestack-scripts}/04.md (99%) create mode 100644 src/wwwroot/gfm/servicestack-scripts/05.html rename src/wwwroot/gfm/{servicestack-filters => servicestack-scripts}/05.md (97%) create mode 100644 src/wwwroot/gfm/servicestack-scripts/06.html rename src/wwwroot/gfm/{servicestack-filters => servicestack-scripts}/06.md (94%) create mode 100644 src/wwwroot/gfm/servicestack-scripts/07.html create mode 100644 src/wwwroot/gfm/servicestack-scripts/07.md create mode 100644 src/wwwroot/gfm/servicestack-scripts/08.html rename src/wwwroot/gfm/{servicestack-filters => servicestack-scripts}/08.md (98%) create mode 100644 src/wwwroot/gfm/sharp-apis/01.html create mode 100644 src/wwwroot/gfm/sharp-apis/01.md rename src/wwwroot/gfm/{api-pages => sharp-apis}/02.html (100%) rename src/wwwroot/gfm/{api-pages => sharp-apis}/02.md (99%) rename src/wwwroot/gfm/{api-pages => sharp-apis}/03.html (100%) rename src/wwwroot/gfm/{api-pages => sharp-apis}/03.md (99%) rename src/wwwroot/gfm/{api-pages => sharp-apis}/04.html (96%) rename src/wwwroot/gfm/{api-pages => sharp-apis}/04.md (92%) create mode 100644 src/wwwroot/gfm/sharp-apps/01.html rename src/wwwroot/gfm/{web-apps => sharp-apps}/01.md (98%) rename src/wwwroot/gfm/{web-apps => sharp-apps}/02.html (100%) rename src/wwwroot/gfm/{web-apps => sharp-apps}/02.md (97%) rename src/wwwroot/gfm/{web-apps => sharp-apps}/03.html (100%) rename src/wwwroot/gfm/{web-apps => sharp-apps}/03.md (98%) create mode 100644 src/wwwroot/gfm/sharp-apps/04.html rename src/wwwroot/gfm/{web-apps => sharp-apps}/04.md (98%) create mode 100644 src/wwwroot/gfm/sharp-apps/05.html rename src/wwwroot/gfm/{web-apps => sharp-apps}/05.md (99%) rename src/wwwroot/gfm/{web-apps => sharp-apps}/06.html (97%) rename src/wwwroot/gfm/{web-apps => sharp-apps}/06.md (93%) create mode 100644 src/wwwroot/gfm/sharp-apps/07.html rename src/wwwroot/gfm/{web-apps => sharp-apps}/07.md (89%) rename src/wwwroot/gfm/{web-apps => sharp-apps}/08.html (89%) rename src/wwwroot/gfm/{web-apps => sharp-apps}/08.md (99%) rename src/wwwroot/gfm/{web-apps => sharp-apps}/09.html (100%) rename src/wwwroot/gfm/{web-apps => sharp-apps}/09.md (99%) rename src/wwwroot/gfm/{web-apps => sharp-apps}/10.html (100%) rename src/wwwroot/gfm/{web-apps => sharp-apps}/10.md (96%) rename src/wwwroot/gfm/{web-apps => sharp-apps}/11.html (100%) rename src/wwwroot/gfm/{web-apps => sharp-apps}/11.md (98%) rename src/wwwroot/gfm/{web-apps => sharp-apps}/12.html (100%) rename src/wwwroot/gfm/{web-apps => sharp-apps}/12.md (99%) rename src/wwwroot/gfm/{web-apps => sharp-apps}/13.html (90%) rename src/wwwroot/gfm/{web-apps => sharp-apps}/13.md (85%) rename src/wwwroot/gfm/{web-apps => sharp-apps}/14.html (96%) rename src/wwwroot/gfm/{web-apps => sharp-apps}/14.md (91%) rename src/wwwroot/gfm/{view-engine => sharp-pages}/01.html (78%) rename src/wwwroot/gfm/{view-engine => sharp-pages}/01.md (54%) create mode 100644 src/wwwroot/gfm/sharp-pages/02.html create mode 100644 src/wwwroot/gfm/sharp-pages/02.md rename src/wwwroot/gfm/{view-engine => sharp-pages}/03.html (97%) rename src/wwwroot/gfm/{view-engine => sharp-pages}/03.md (90%) rename src/wwwroot/gfm/{view-engine => sharp-pages}/04.html (97%) rename src/wwwroot/gfm/{view-engine => sharp-pages}/04.md (92%) rename src/wwwroot/gfm/{view-engine => sharp-pages}/05.html (51%) rename src/wwwroot/gfm/{view-engine => sharp-pages}/05.md (99%) rename src/wwwroot/gfm/{view-engine => sharp-pages}/06.html (97%) rename src/wwwroot/gfm/{view-engine => sharp-pages}/06.md (96%) rename src/wwwroot/gfm/{view-engine => sharp-pages}/07.html (97%) rename src/wwwroot/gfm/{view-engine => sharp-pages}/07.md (95%) rename src/wwwroot/gfm/{view-engine => sharp-pages}/08.html (97%) rename src/wwwroot/gfm/{view-engine => sharp-pages}/08.md (95%) create mode 100644 src/wwwroot/gfm/sharp-pages/09.html rename src/wwwroot/gfm/{view-engine => sharp-pages}/09.md (97%) rename src/wwwroot/gfm/{view-engine => sharp-pages}/10.html (100%) rename src/wwwroot/gfm/{view-engine => sharp-pages}/10.md (99%) rename src/wwwroot/gfm/{view-engine => sharp-pages}/11.html (90%) rename src/wwwroot/gfm/{view-engine => sharp-pages}/11.md (77%) delete mode 100644 src/wwwroot/gfm/view-engine/02.html delete mode 100644 src/wwwroot/gfm/view-engine/02.md delete mode 100644 src/wwwroot/gfm/view-engine/09.html delete mode 100644 src/wwwroot/gfm/web-apps/01.html delete mode 100644 src/wwwroot/gfm/web-apps/04.html delete mode 100644 src/wwwroot/gfm/web-apps/05.html delete mode 100644 src/wwwroot/gfm/web-apps/07.html diff --git a/src/AppHost.cs b/src/AppHost.cs index 76c7167..94d2d93 100644 --- a/src/AppHost.cs +++ b/src/AppHost.cs @@ -5,21 +5,21 @@ using System.Collections; using ServiceStack; using ServiceStack.IO; -using ServiceStack.Templates; +using ServiceStack.Script; using ServiceStack.Text; using ServiceStack.Data; using ServiceStack.OrmLite; using ServiceStack.Configuration; using Funq; -namespace TemplatePages +namespace SharpScript { public class AppHost : AppHostBase { public AppHost() - : base("Template Pages", typeof(TemplateServices).Assembly) { } + : base("Sharp Pages", typeof(TemplateServices).Assembly) { } - public TemplateContext LinqContext; + public ScriptContext LinqContext; public override void Configure(Container container) { @@ -57,11 +57,11 @@ public override void Configure(Container container) HostContext.Cache, TimeSpan.FromMinutes(10))) ); - var customFilters = new CustomTemplateFilters(); - Plugins.Add(new TemplatePagesFeature { - TemplateFilters = { + var customFilters = new CustomScriptMethods(); + Plugins.Add(new SharpPagesFeature { + ScriptMethods = { customFilters, - new TemplateDbFiltersAsync() + new DbScriptsAsync() }, Args = { ["products"] = TemplateQueryData.Products @@ -72,7 +72,7 @@ public override void Configure(Container container) }); AfterInitCallbacks.Add(host => { - var feature = GetPlugin(); + var feature = GetPlugin(); var files = GetVirtualFileSources().First(x => x is FileSystemVirtualFiles); foreach (var file in files.GetDirectory("docs").GetAllMatchingFiles("*.html")) @@ -102,9 +102,9 @@ public override void Configure(Container container) } } - LinqContext = new TemplateContext { + LinqContext = new ScriptContext { Args = { - [TemplateConstants.DefaultDateFormat] = "yyyy/MM/dd", + [ScriptConstants.DefaultDateFormat] = "yyyy/MM/dd", ["products"] = TemplateQueryData.Products, ["customers"] = TemplateQueryData.Customers, ["comparer"] = new CaseInsensitiveComparer(), diff --git a/src/AutoDataQueryServices.cs b/src/AutoDataQueryServices.cs index 490b3d6..c511641 100644 --- a/src/AutoDataQueryServices.cs +++ b/src/AutoDataQueryServices.cs @@ -5,7 +5,7 @@ using System.Text; using ServiceStack; -namespace TemplatePages +namespace SharpScript { public class QueryGitHubRepos : QueryData { diff --git a/src/AutoQueryDbServices.cs b/src/AutoQueryDbServices.cs index c1ba56f..dd022ed 100644 --- a/src/AutoQueryDbServices.cs +++ b/src/AutoQueryDbServices.cs @@ -2,7 +2,7 @@ using System.Collections.Generic; using ServiceStack; -namespace TemplatePages +namespace SharpScript { // The entire QueryCustomers AutoQuery Service (no implementation required) public class QueryCustomers : QueryDb diff --git a/src/CodePagePartials.cs b/src/CodePagePartials.cs index 2f58260..96813b3 100644 --- a/src/CodePagePartials.cs +++ b/src/CodePagePartials.cs @@ -1,12 +1,12 @@ using System.Linq; using System.Collections.Generic; using ServiceStack; -using ServiceStack.Templates; +using ServiceStack.Script; -namespace TemplatePages +namespace SharpScript { [Page("navLinks")] - public class NavLinksPartial : TemplateCodePage + public class NavLinksPartial : SharpCodePage { string render(string PathInfo, Dictionary links) => $@"
    @@ -19,7 +19,7 @@ string render(string PathInfo, Dictionary links) => $@" } [Page("customerCard")] - public class CustomerCardPartial : TemplateCodePage + public class CustomerCardPartial : SharpCodePage { public ICustomers Customers { get; set; } diff --git a/src/CustomTemplateFilters.cs b/src/CustomTemplateFilters.cs index 14690af..0f1fc3a 100644 --- a/src/CustomTemplateFilters.cs +++ b/src/CustomTemplateFilters.cs @@ -1,6 +1,6 @@ using System.Threading.Tasks; using ServiceStack; -using ServiceStack.Templates; +using ServiceStack.Script; using System.Collections.Generic; using System.Linq; using System.IO; @@ -9,10 +9,11 @@ using ServiceStack.Redis; using ServiceStack.OrmLite; using System.Reflection; +using ServiceStack.Script; -namespace TemplatePages +namespace SharpScript { - public class CustomTemplateFilters : TemplateFilter + public class CustomScriptMethods : ScriptMethods { public Dictionary> DocsIndex { get; } = new Dictionary>(); public Dictionary> LinqIndex { get; } = new Dictionary>(); @@ -85,7 +86,7 @@ public List> sortLinks(Dictionary{type.Name}.cs"); @@ -160,9 +161,9 @@ public FilterInfo[] filtersAvailable(string name) var to = filters .OrderBy(x => x.Name) .ThenBy(x => x.GetParameters().Count()) - .Where(x => x.DeclaringType != typeof(TemplateFilter) && x.DeclaringType != typeof(object)) + .Where(x => x.DeclaringType != typeof(ScriptMethods) && x.DeclaringType != typeof(object)) .Where(m => !m.IsSpecialName) - .Select(x => FilterInfo.Create(x)); + .Select(FilterInfo.Create); return to.ToArray(); } @@ -179,7 +180,7 @@ public class FilterInfo public static FilterInfo Create(MethodInfo mi) { var paramNames = mi.GetParameters() - .Where(x => x.ParameterType != typeof(TemplateScopeContext)) + .Where(x => x.ParameterType != typeof(ScriptScopeContext)) .Select(x => x.Name) .ToArray(); diff --git a/src/CustomerServices.cs b/src/CustomerServices.cs index fccc2b2..2ea394c 100644 --- a/src/CustomerServices.cs +++ b/src/CustomerServices.cs @@ -1,7 +1,7 @@ using ServiceStack; -using ServiceStack.Templates; +using ServiceStack.Script; -namespace TemplatePages +namespace SharpScript { [Route("/customers/{Id}")] public class ViewCustomer @@ -11,7 +11,7 @@ public class ViewCustomer public class CustomerServices : Service { - public ITemplatePages Pages { get; set; } + public ISharpPages Pages { get; set; } public object Any(ViewCustomer request) => new PageResult(Pages.GetPage("examples/customer")) { diff --git a/src/EmailTemplatesService.cs b/src/EmailTemplatesService.cs index 0d9a778..1f0e8ff 100644 --- a/src/EmailTemplatesService.cs +++ b/src/EmailTemplatesService.cs @@ -1,10 +1,11 @@ using System.Linq; using System.Collections.Generic; using ServiceStack; -using ServiceStack.Templates; +using ServiceStack.Script; using ServiceStack.IO; +using ServiceStack.Script; -namespace TemplatePages +namespace SharpScript { [Route("/emails/order-confirmation/preview")] public class PreviewHtmlEmail : IReturn @@ -29,7 +30,7 @@ public object Any(PreviewHtmlEmail request) var customer = Customers.GetCustomer(request.PreviewCustomerId) ?? Customers.GetAllCustomers().First(); - var context = new TemplateContext { + var context = new ScriptContext { PageFormats = { new MarkdownPageFormat() }, Args = { ["customer"] = customer, diff --git a/src/GitHubMarkdownFilters.cs b/src/GitHubMarkdownFilters.cs index 5c4e968..f5ef400 100644 --- a/src/GitHubMarkdownFilters.cs +++ b/src/GitHubMarkdownFilters.cs @@ -7,12 +7,13 @@ using System.Collections.Generic; using ServiceStack; using ServiceStack.IO; +using ServiceStack.Script; using ServiceStack.Text; -using ServiceStack.Templates; +using ServiceStack.Script; -namespace TemplatePages +namespace SharpScript { - public class GitHubMarkdownFilters : TemplateFilter + public class GitHubMarkdownFilters : ScriptMethods { public string ApiBaseUrl { get; set; } = "https://api.github.com"; @@ -22,15 +23,15 @@ public class GitHubMarkdownFilters : TemplateFilter public string RepositoryContext { get; set; } - public IRawString markdown(TemplateScopeContext scope, string markdown) + public IRawString markdown(ScriptScopeContext scope, string markdown) { var html = MarkdownConfig.Transformer.Transform(markdown); return html.ToRawString(); } - public async Task githubMarkdown(TemplateScopeContext scope, string markdownPath) + public async Task githubMarkdown(ScriptScopeContext scope, string markdownPath) { - var file = Context.ProtectedFilters.ResolveFile(nameof(githubMarkdown), scope, markdownPath); + var file = Context.ProtectedMethods.ResolveFile(nameof(githubMarkdown), scope, markdownPath); var htmlFilePath = file.VirtualPath.LastLeftPart('.') + ".html"; var cacheKey = nameof(GitHubMarkdownFilters) + ">" + htmlFilePath; @@ -98,7 +99,7 @@ public async Task githubMarkdown(TemplateScopeContext scope, string markdownPath if (Context.VirtualFiles is IVirtualFiles vfs) { - vfs.WriteFile(htmlFilePath, wrappedBytes); + vfs.GetFileSystemVirtualFiles().WriteFile(htmlFilePath, wrappedBytes); } if (UseMemoryCache) diff --git a/src/IntrospectStateServices.cs b/src/IntrospectStateServices.cs index 1f1d2e9..58640f2 100644 --- a/src/IntrospectStateServices.cs +++ b/src/IntrospectStateServices.cs @@ -5,9 +5,10 @@ using System.Collections.Generic; using ServiceStack; using ServiceStack.IO; -using ServiceStack.Templates; +using ServiceStack.Script; +using ServiceStack.Script; -namespace TemplatePages +namespace SharpScript { [Route("/introspect/state")] public class IntrospectState @@ -17,7 +18,7 @@ public class IntrospectState public string DriveInfo { get; set; } } - public class StateTemplateFilters : TemplateFilter + public class StateScriptMethods : ScriptMethods { bool HasAccess(Process process) { @@ -35,8 +36,8 @@ public class IntrospectStateServices : Service { public object Any(IntrospectState request) { - var context = new TemplateContext { - ScanTypes = { typeof(StateTemplateFilters) }, //Autowires (if needed) + var context = new ScriptContext { + ScanTypes = { typeof(StateScriptMethods) }, //Autowires (if needed) RenderExpressionExceptions = true }.Init(); diff --git a/src/LinqServices.cs b/src/LinqServices.cs index ee09a5a..1d38308 100644 --- a/src/LinqServices.cs +++ b/src/LinqServices.cs @@ -4,12 +4,13 @@ using System.Collections.Generic; using System.IO; using ServiceStack; -using ServiceStack.Templates; +using ServiceStack.Script; using ServiceStack.IO; using ServiceStack.DataAnnotations; using System.Threading.Tasks; +using ServiceStack.Script; -namespace TemplatePages +namespace SharpScript { [Route("/linq/eval")] public class EvaluateLinq : IReturn diff --git a/src/NuGet.Config b/src/NuGet.Config index c00d271..d095f9f 100644 --- a/src/NuGet.Config +++ b/src/NuGet.Config @@ -1,7 +1,8 @@ - + + \ No newline at end of file diff --git a/src/ProductServices.cs b/src/ProductServices.cs index df5cb07..3ee44d0 100644 --- a/src/ProductServices.cs +++ b/src/ProductServices.cs @@ -1,8 +1,8 @@ #if false //stay within free-quota limit using ServiceStack; -using ServiceStack.Templates; +using ServiceStack.Script; -namespace TemplatePages +namespace SharpScript { [Route("/products/view")] public class ViewProducts diff --git a/src/ProductsPage.cs b/src/ProductsPage.cs index db7692d..0f8f667 100644 --- a/src/ProductsPage.cs +++ b/src/ProductsPage.cs @@ -1,13 +1,13 @@ using System.Linq; using System.Collections.Generic; using ServiceStack; -using ServiceStack.Templates; +using ServiceStack.Script; -namespace TemplatePages +namespace SharpScript { [Page("products")] [PageArg("title", "Products")] - public class ProductsPage : TemplateCodePage + public class ProductsPage : SharpCodePage { string render(Product[] products) => $@" diff --git a/src/Program.cs b/src/Program.cs index e611be9..3b7cc15 100644 --- a/src/Program.cs +++ b/src/Program.cs @@ -6,7 +6,7 @@ using Microsoft.AspNetCore; using Microsoft.AspNetCore.Hosting; -namespace TemplatePages +namespace SharpScript { public class Program { diff --git a/src/TemplatePages.csproj b/src/SharpScript.csproj similarity index 98% rename from src/TemplatePages.csproj rename to src/SharpScript.csproj index 03e9e05..4678862 100644 --- a/src/TemplatePages.csproj +++ b/src/SharpScript.csproj @@ -1,4 +1,4 @@ - + netcoreapp2.1 diff --git a/src/Startup.cs b/src/Startup.cs index 8210a07..9133a6b 100644 --- a/src/Startup.cs +++ b/src/Startup.cs @@ -7,7 +7,7 @@ using System.Net; using System.Web; -namespace TemplatePages +namespace SharpScript { public class Startup { diff --git a/src/TemplateServices.cs b/src/TemplateServices.cs index 4e1b247..e672a6d 100644 --- a/src/TemplateServices.cs +++ b/src/TemplateServices.cs @@ -1,18 +1,19 @@ using System.Collections.Generic; using ServiceStack; -using ServiceStack.Templates; +using ServiceStack.Script; using ServiceStack.IO; using System.Threading.Tasks; using System; using ServiceStack.Web; using ServiceStack.OrmLite; using ServiceStack.Data; +using ServiceStack.Script; using ServiceStack.Text; -namespace TemplatePages +namespace SharpScript { [Route("/pages/eval")] - public class EvaluateTemplates : IReturn + public class EvaluateScripts : IReturn { public Dictionary Files { get; set; } public Dictionary Args { get; set; } @@ -21,7 +22,7 @@ public class EvaluateTemplates : IReturn } [Route("/template/eval")] - public class EvaluateTemplate + public class EvaluateScript { public string Template { get; set; } } @@ -42,11 +43,11 @@ public class EvalExpressionResponse [ReturnExceptionsInJson] public class TemplateServices : Service { - public async Task Any(EvaluateTemplates request) + public async Task Any(EvaluateScripts request) { - var context = new TemplateContext { - TemplateFilters = { - new TemplateProtectedFilters(), + var context = new ScriptContext { + ScriptMethods = { + new ProtectedScripts(), }, ExcludeFiltersNamed = { "fileWrite","fileAppend","fileDelete","dirDelete" } }.Init(); @@ -66,14 +67,14 @@ public async Task Any(EvaluateTemplates request) return await pageResult.RenderToStringAsync(); // render to string so [ReturnExceptionsInJson] can detect Exceptions and return JSON } - public async Task Any(EvaluateTemplate request) + public async Task Any(EvaluateScript request) { - var context = new TemplateContext { - TemplateFilters = { - new TemplateDbFilters(), - new TemplateAutoQueryFilters(), - new TemplateServiceStackFilters(), - new CustomTemplateFilters(), + var context = new ScriptContext { + ScriptMethods = { + new DbScripts(), + new AutoQueryScripts(), + new ServiceStackScripts(), + new CustomScriptMethods(), } }; //Register any dependencies filters need: diff --git a/src/wwwroot/_layout.html b/src/wwwroot/_layout.html index 478fbbb..bdc1a47 100644 --- a/src/wwwroot/_layout.html +++ b/src/wwwroot/_layout.html @@ -2,27 +2,23 @@ - {{ title }} + {{ title ?? "Script" }} - {{ ifDebug | select: }} - - {{ eq(PathInfo,'/') | ifShow: Fork me on GitHub - | raw }} + - + - + - +
    ifError - Only execute the filter if there's an error:
    + Only execute the expression if there's an error:
    {{ ifError | select: FAIL! { it.Message } }}
    lastError - Returns the last error on the page or null if there was no error, that's passed to the next filter:
    + Returns the last error on the page or null if there was no error, that's passed to the next method:
    {{ lastError | ifExists | select: FAIL! { it.Message } }}
    htmlErrorhtmlError - Display detailed htmlErrorDebug when in DebugMode + Display detailed htmlErrorDebug when in DebugMode otherwise display a more appropriate end-user htmlErrorMessage.
    htmlErrorMessagehtmlErrorMessage Display the Exception Message
    htmlErrorDebughtmlErrorDebug Display a detailed Exception
    @@ -84,13 +84,13 @@

    Unhandled Exceptions Behavior

    This behavior provides the optimal UX for developers and end-users as HTML Pages with Exceptions are still rendered well-formed whilst still being easily able to display a curated error messages for end-users without developers needing to guard against - executing filters when Exceptions occur. + executing methods when Exceptions occur.

    Controlling Unhandled Exception Behavior

    - We can also enable this behavior on a per-page basis using the skipExecutingFiltersOnError filter: + We can also enable this behavior on a per-page basis using the skipExecutingFiltersOnError method:

    {{ 'live-template' | partial({ rows:5, template: `{{ skipExecutingFiltersOnError }} @@ -100,14 +100,14 @@

    {{ 'After Exception' }}

    {{ htmlErrorMessage }}` }) }}

    - Here we can see that any normal filters after exceptions are never evaluated unless they're specifically for handling Errors. + Here we can see that any normal methods after exceptions are never evaluated unless they're specifically for handling Errors.

    -

    Continue Executing Filters on Unhandled Exceptions

    +

    Continue Executing Methods on Unhandled Exceptions

    - We can also specify that we want to continue executing filters in which case you'll need to manually guard filters you want to - control the execution of using the ifNoError or ifError filters: + We can also specify that we want to continue executing methods in which case you'll need to manually guard methods you want to + control the execution of using the ifNoError or ifError methods:

    {{ 'live-template' | partial({ rows:8, template: `{{ continueExecutingFiltersOnError }} @@ -121,7 +121,7 @@

    {{ 'After Exception' }}

    Accessing Page Exceptions

    - The last Exception thrown are accessible using the lastError* filters: + The last Exception thrown are accessible using the lastError* methods:

    {{ 'live-template' | partial({ rows:6, template: `{{ continueExecutingFiltersOnError }} @@ -134,13 +134,13 @@

    Accessing Page Exceptions

    Throwing Exceptions

    - We've included a few of the popular Exception Types, filters prefixed with throw always throws the Exceptions below: + We've included a few of the popular Exception Types, methods prefixed with throw always throws the Exceptions below:

    - + @@ -181,19 +181,19 @@

    Throwing Exceptions

    FilterMethod Exception
    - You can extend this list with your own custom filters, see the - Error Handling Filters + You can extend this list with your own custom methods, see the + Error Handling Methods for examples.

    - These filters will only throw if a condition is met: + These methods will only throw if a condition is met:

    - + @@ -227,7 +227,7 @@

    Throwing Exceptions

    Ensure Argument Helpers

    - The ensureAll* filters assert the state of multiple arguments where it will either throw an Exception unless + The ensureAll* methods assert the state of multiple arguments where it will either throw an Exception unless all the arguments meet the condition or return the Object Dictionary if all conditions are met:

    @@ -238,7 +238,7 @@

    Ensure Argument Helpers

    {{ ex | htmlErrorMessage }}` }) }}

    - The ensureAny* filters only requires one of the arguments meet the condition to return the Object Dictionary: + The ensureAny* methods only requires one of the arguments meet the condition to return the Object Dictionary:

    {{ 'live-template' | partial({ rows:5, template: `{{ '' | assignTo: empty }} @@ -251,9 +251,9 @@

    Fatal Exceptions

    The Exception Types below are reserved for Exceptions that should never happen, such as incorrect usage of an API where it would've - resulted in a compile error in C#. When these Exceptions are thrown in a filter or a page they'll immediately short-circuit execution of + resulted in a compile error in C#. When these Exceptions are thrown in a method or a page they'll immediately short-circuit execution of the page and write the Exception details to the output. - The Fatal Exceptions include: + The Fatal Exceptions include:

      @@ -267,16 +267,16 @@

      Rendering Exceptions

      The OnExpressionException delegate in Page Formats - are able to control how Exceptions in filter expressions are rendered where if preferred exceptions can be rendered in-line - in the filter expression where they occured with: + are able to control how Exceptions in template expressions are rendered where if preferred exceptions can be rendered in-line + in the template expression where they occured with:

      {{ 'gfm/error-handling/02.md' | githubMarkdown }}

      The OnExpressionException can also suppress Exceptions from being displayed by capturing any naked Exception Types registered in - TemplateConfig.CaptureAndEvaluateExceptionsToNull - and evaluate the filter expression to null which by default will suppress the following naked Exceptions thrown in filters: + TemplateConfig.CaptureAndEvaluateExceptionsToNull + and evaluate the template expression to null which by default will suppress the following naked Exceptions thrown in methods:

        @@ -284,29 +284,29 @@

        Rendering Exceptions

      • ArgumentNullException
      -

      Implementing Filter Exceptions

      +

      Implementing Method Exceptions

      - In order for your own Filter Exceptions to participate in the above Template Error Handling they'll need to be wrapped in an - StopFilterExecutionException including both the Template's scope and an optional options object + In order for your own Method Exceptions to participate in the above Script Error Handling they'll need to be wrapped in an + StopFilterExecutionException including both the Script's scope and an optional options object which is used to check if the assignError binding was provided so it can automatically populate it with the Exception.

      - The easiest way to Implement Exception handling in filters is to call a managed function which catches all Exceptions and + The easiest way to Implement Exception handling in methods is to call a managed function which catches all Exceptions and throws them in a StopFilterExecutionException as seen in OrmLite's - TemplateDbFilters: + DbScripts:

      {{ 'gfm/error-handling/03.md' | githubMarkdown }}

      - The overloads are so the filters can be called without specifying any filter options. + The overloads are so the methods can be called without specifying any method options.

      For more examples of different error handling features and strategies checkout: - TemplateErrorHandlingTests.cs + ErrorHandlingTests.cs

      {{ "doc-links" | partial({ order }) }} diff --git a/src/wwwroot/docs/filters-reference.html b/src/wwwroot/docs/filters-reference.html index 8da2391..474c98e 100644 --- a/src/wwwroot/docs/filters-reference.html +++ b/src/wwwroot/docs/filters-reference.html @@ -1,5 +1,5 @@ @@ -10,7 +10,7 @@
      - @@ -23,30 +23,30 @@ {{ ` -``` \ No newline at end of file +``` diff --git a/src/wwwroot/gfm/web-apps/09.html b/src/wwwroot/gfm/sharp-apps/09.html similarity index 100% rename from src/wwwroot/gfm/web-apps/09.html rename to src/wwwroot/gfm/sharp-apps/09.html diff --git a/src/wwwroot/gfm/web-apps/09.md b/src/wwwroot/gfm/sharp-apps/09.md similarity index 99% rename from src/wwwroot/gfm/web-apps/09.md rename to src/wwwroot/gfm/sharp-apps/09.md index 5337bba..4fbb4b3 100644 --- a/src/wwwroot/gfm/web-apps/09.md +++ b/src/wwwroot/gfm/sharp-apps/09.md @@ -12,4 +12,4 @@ {{/if}}
      -``` \ No newline at end of file +``` diff --git a/src/wwwroot/gfm/web-apps/10.html b/src/wwwroot/gfm/sharp-apps/10.html similarity index 100% rename from src/wwwroot/gfm/web-apps/10.html rename to src/wwwroot/gfm/sharp-apps/10.html diff --git a/src/wwwroot/gfm/web-apps/10.md b/src/wwwroot/gfm/sharp-apps/10.md similarity index 96% rename from src/wwwroot/gfm/web-apps/10.md rename to src/wwwroot/gfm/sharp-apps/10.md index 98b2ed5..d021fc7 100644 --- a/src/wwwroot/gfm/web-apps/10.md +++ b/src/wwwroot/gfm/sharp-apps/10.md @@ -3,4 +3,4 @@ {{ `${q ?? ''}*` | redisSearchKeys({ limit }) | return }} -``` \ No newline at end of file +``` diff --git a/src/wwwroot/gfm/web-apps/11.html b/src/wwwroot/gfm/sharp-apps/11.html similarity index 100% rename from src/wwwroot/gfm/web-apps/11.html rename to src/wwwroot/gfm/sharp-apps/11.html diff --git a/src/wwwroot/gfm/web-apps/11.md b/src/wwwroot/gfm/sharp-apps/11.md similarity index 98% rename from src/wwwroot/gfm/web-apps/11.md rename to src/wwwroot/gfm/sharp-apps/11.md index f0f681c..5e6af4c 100644 --- a/src/wwwroot/gfm/web-apps/11.md +++ b/src/wwwroot/gfm/sharp-apps/11.md @@ -9,4 +9,4 @@ {{ command | redisCall | assignTo: contents }} {{ contents | return({ 'Content-Type': 'text/plain' }) }} -``` \ No newline at end of file +``` diff --git a/src/wwwroot/gfm/web-apps/12.html b/src/wwwroot/gfm/sharp-apps/12.html similarity index 100% rename from src/wwwroot/gfm/web-apps/12.html rename to src/wwwroot/gfm/sharp-apps/12.html diff --git a/src/wwwroot/gfm/web-apps/12.md b/src/wwwroot/gfm/sharp-apps/12.md similarity index 99% rename from src/wwwroot/gfm/web-apps/12.md rename to src/wwwroot/gfm/sharp-apps/12.md index fab8d52..b979077 100644 --- a/src/wwwroot/gfm/web-apps/12.md +++ b/src/wwwroot/gfm/sharp-apps/12.md @@ -32,4 +32,3 @@ public class StartupServices : Service public object Any(GetStartupDep request) => StartupDep.Name; } ``` - diff --git a/src/wwwroot/gfm/web-apps/13.html b/src/wwwroot/gfm/sharp-apps/13.html similarity index 90% rename from src/wwwroot/gfm/web-apps/13.html rename to src/wwwroot/gfm/sharp-apps/13.html index 78615c1..bcd8074 100644 --- a/src/wwwroot/gfm/web-apps/13.html +++ b/src/wwwroot/gfm/sharp-apps/13.html @@ -1,4 +1,4 @@ -

      The Parcel WebApp template is maintained in the following directory structure:

      +

      The Parcel SharpApp template is maintained in the following directory structure:

      • /app - Your Web App's published source code and any plugins
      • @@ -24,11 +24,11 @@

        Then to start the ServiceStack Server to host your Web App run:

        $ npm run server
         
        -

        Which will host your App at http://localhost:5000 which in debug mode will enable hot reloading +

        Which will host your App at http://localhost:5000 which in debug mode will enable hot reloading which will automatically reload your web page as it detects any file changes made by parcel.

        server

        -

        To enable even richer functionality, this Web Apps template is also pre-configured with a custom Server project where you can extend your Web App with Plugins where all Plugins, Services, Filters, etc are automatically wired and made available to your Web App.

        +

        To enable even richer functionality, this Sharp Apps template is also pre-configured with a custom Server project where you can extend your Web App with Plugins where all Plugins, Services, Filters, etc are automatically wired and made available to your Web App.

        This template includes a simple ServerPlugin.cs which contains an Empty ServerPlugin and Hello Service:

        public class ServerPlugin : IPlugin
         {
        @@ -59,7 +59,7 @@ 

        $ npm run server
         

        This will automatically load any Plugins, Services, Filters, etc and make them available to your Web App.

        -

        One benefit of creating your APIs with C# ServiceStack Services instead of API Pages is that you can generate TypeScript DTOs with:

        +

        One benefit of creating your APIs with C# ServiceStack Services instead of API Pages is that you can generate TypeScript DTOs with:

        $ npm run dtos
         

        Which saves generate DTOs for all your ServiceStack Services in dtos.ts which can then be accessed in your TypeScript source code.

        @@ -91,6 +91,6 @@

        $ npm run build
         

        Which will bundle and minify all .css, .js and .html assets and publish to /app/wwwroot.

        -

        Then to deploy Web Apps you just need to copy the /app and /web folders to any server with .NET Core 2.1 runtime installed. -The Deploying Web Apps docs.

        +

        Then to deploy Sharp Apps you just need to copy the /app and /web folders to any server with .NET Core 2.1 runtime installed. +The Deploying Sharp Apps docs.

        \ No newline at end of file diff --git a/src/wwwroot/gfm/web-apps/13.md b/src/wwwroot/gfm/sharp-apps/13.md similarity index 85% rename from src/wwwroot/gfm/web-apps/13.md rename to src/wwwroot/gfm/sharp-apps/13.md index cfdc435..c8889ac 100644 --- a/src/wwwroot/gfm/web-apps/13.md +++ b/src/wwwroot/gfm/sharp-apps/13.md @@ -1,4 +1,4 @@ -The Parcel WebApp template is maintained in the following directory structure: +The Parcel SharpApp template is maintained in the following directory structure: - `/app` - Your Web App's published source code and any plugins - `/client` - The Parcel managed Client App where client source code is maintained @@ -27,12 +27,12 @@ Then to start the ServiceStack Server to host your Web App run: $ npm run server -Which will host your App at `http://localhost:5000` which in `debug` mode will enable [hot reloading](http://templates.servicestack.net/docs/hot-reloading) +Which will host your App at `http://localhost:5000` which in `debug` mode will enable [hot reloading](http://sharpscript.net/docs/hot-reloading) which will automatically reload your web page as it detects any file changes made by parcel. ### server -To enable even richer functionality, this Web Apps template is also pre-configured with a custom Server project where you can extend your Web App with [Plugins](http://templates.servicestack.net/docs/web-apps#plugins) where all `Plugins`, `Services`, `Filters`, etc are automatically wired and made available to your Web App. +To enable even richer functionality, this Sharp Apps template is also pre-configured with a custom Server project where you can extend your Web App with [Plugins](http://sharpscript.net/docs/sharp-apps#plugins) where all `Plugins`, `Services`, `Filters`, etc are automatically wired and made available to your Web App. This template includes a simple [ServerPlugin.cs](https://github.com/NetCoreTemplates/parcel-webapp/blob/master/server/ServerPlugin.cs) which contains an Empty `ServerPlugin` and `Hello` Service: @@ -70,7 +70,7 @@ To build the `server.csproj` project and copy the resulting `server.dll` to `/ap This will automatically load any `Plugins`, `Services`, `Filters`, etc and make them available to your Web App. -One benefit of creating your APIs with C# ServiceStack Services instead of [API Pages](http://templates.servicestack.net/docs/api-pages) is that you can generate TypeScript DTOs with: +One benefit of creating your APIs with C# ServiceStack Services instead of [API Pages](http://sharpscript.net/docs/sharp-apis) is that you can generate TypeScript DTOs with: $ npm run dtos @@ -116,5 +116,5 @@ During development Parcel maintains a debug and source-code friendly version of Which will bundle and minify all `.css`, `.js` and `.html` assets and publish to `/app/wwwroot`. -Then to deploy Web Apps you just need to copy the `/app` and `/web` folders to any server with .NET Core 2.1 runtime installed. -The [Deploying Web Apps](http://templates.servicestack.net/docs/deploying-web-apps) docs. \ No newline at end of file +Then to deploy Sharp Apps you just need to copy the `/app` and `/web` folders to any server with .NET Core 2.1 runtime installed. +The [Deploying Sharp Apps](http://sharpscript.net/docs/deploying-sharp-apps) docs. diff --git a/src/wwwroot/gfm/web-apps/14.html b/src/wwwroot/gfm/sharp-apps/14.html similarity index 96% rename from src/wwwroot/gfm/web-apps/14.html rename to src/wwwroot/gfm/sharp-apps/14.html index d504aa7..fa91bed 100644 --- a/src/wwwroot/gfm/web-apps/14.html +++ b/src/wwwroot/gfm/sharp-apps/14.html @@ -7,8 +7,8 @@ JS App has been dispensed and you can just focus on the App you want to create, using a plain-text editor on the left, a live updating browser on the right and nothing else to interrupt your flow.

        Any infrastructure dependencies have also been avoided by using SQLite by default which is -automatically created an populated on first run if no database exists, or if preferred can be -changed to use any other popular RDBMS using just config.

        +automatically created an populated on first run if no database exists, or if preferred can be +changed to use any other popular RDBMS using just config.

        Multi User Blogging Platform

        Any number of users can Sign In via Twitter and publish content under their Twitter Username where only they'll be able to modify their own Content. @@ -42,7 +42,7 @@

        the database instead of localStorage.

        Server Validation

        -

        The new.html and edit.html pages shows examples of performing server validation with ServiceStack Templates:

        +

        The new.html and edit.html pages shows examples of performing server validation with SharpScript:

        {{ assignErrorAndContinueExecuting: ex }}
         
         {{ 'Title must be between 5 and 200 characters'      
        @@ -89,9 +89,9 @@ 

        }

        To the /preview.html API Page which just renders and captures any Template Content its sent and returns the output:

        -
        {{ content  | evalTemplate({use:{plugins:'MarkdownTemplatePlugin'}}) | assignTo:response }}
        +
        {{ content  | evalTemplate({use:{plugins:'MarkdownScriptPlugin'}}) | assignTo:response }}
         {{ response | return({ contentType:'text/plain' }) }}
        -

        By default the evalTemplate filter renders Templates in a new TemplateContext which can be customized to utilize any additional -plugins, filters and blocks available in the configured TemplatePagesFeature, +

        By default the evalTemplate filter renders Templates in a new ScriptContext which can be customized to utilize any additional +plugins, filters and blocks available in the configured SharpPagesFeature, or for full access you can use {use:{context:true}} to evaluate any Template content under the same context that evalTemplate is run on.

        \ No newline at end of file diff --git a/src/wwwroot/gfm/web-apps/14.md b/src/wwwroot/gfm/sharp-apps/14.md similarity index 91% rename from src/wwwroot/gfm/web-apps/14.md rename to src/wwwroot/gfm/sharp-apps/14.md index aeb46f2..6053d4a 100644 --- a/src/wwwroot/gfm/web-apps/14.md +++ b/src/wwwroot/gfm/sharp-apps/14.md @@ -8,8 +8,8 @@ JS App has been dispensed and you can just focus on the App you want to create, the right and nothing else to interrupt your flow. Any infrastructure dependencies have also been avoided by using SQLite by default which is -[automatically created an populated on first run](/docs/view-engine#init-pages) if no database exists, or if preferred can be -[changed to use any other popular RDBMS](/docs/web-apps#multi-platform-configurations) using just config. +[automatically created an populated on first run](/docs/sharp-pages#init-pages) if no database exists, or if preferred can be +[changed to use any other popular RDBMS](/docs/sharp-apps#multi-platform-configurations) using just config. ### Multi User Blogging Platform @@ -56,7 +56,7 @@ the database instead of `localStorage`. ### Server Validation -The [new.html](https://github.com/NetCoreWebApps/Blog/blob/master/app/posts/new.html) and [edit.html](https://github.com/NetCoreWebApps/Blog/blob/master/app/posts/_slug/edit.html) pages shows examples of performing server validation with ServiceStack Templates: +The [new.html](https://github.com/NetCoreWebApps/Blog/blob/master/app/posts/new.html) and [edit.html](https://github.com/NetCoreWebApps/Blog/blob/master/app/posts/_slug/edit.html) pages shows examples of performing server validation with SharpScript: ```hbs {{ assignErrorAndContinueExecuting: ex }} @@ -120,10 +120,11 @@ To the [/preview.html](https://github.com/NetCoreWebApps/Blog/blob/master/app/pr Template Content its sent and returns the output: ```hbs -{{ content | evalTemplate({use:{plugins:'MarkdownTemplatePlugin'}}) | assignTo:response }} +{{ content | evalTemplate({use:{plugins:'MarkdownScriptPlugin'}}) | assignTo:response }} {{ response | return({ contentType:'text/plain' }) }} ``` -By default the `evalTemplate` filter renders Templates in a new `TemplateContext` which can be customized to utilize any additional -`plugins`, `filters` and `blocks` available in the configured [TemplatePagesFeature](/docs/view-engine), +By default the `evalTemplate` filter renders Templates in a new `ScriptContext` which can be customized to utilize any additional +`plugins`, `filters` and `blocks` available in the configured [SharpPagesFeature](/docs/sharp-pages), or for full access you can use `{use:{context:true}}` to evaluate any Template content under the same context that `evalTemplate` is run on. + diff --git a/src/wwwroot/gfm/view-engine/01.html b/src/wwwroot/gfm/sharp-pages/01.html similarity index 78% rename from src/wwwroot/gfm/view-engine/01.html rename to src/wwwroot/gfm/sharp-pages/01.html index 30e2f97..47bce98 100644 --- a/src/wwwroot/gfm/view-engine/01.html +++ b/src/wwwroot/gfm/sharp-pages/01.html @@ -1,5 +1,5 @@
        public void Configure(Container container)
         {
        -    Plugins.Add(new TemplatePagesFeature());
        +    Plugins.Add(new SharpPagesFeature());
         }
        \ No newline at end of file diff --git a/src/wwwroot/gfm/view-engine/01.md b/src/wwwroot/gfm/sharp-pages/01.md similarity index 54% rename from src/wwwroot/gfm/view-engine/01.md rename to src/wwwroot/gfm/sharp-pages/01.md index 4d5848a..0b57de5 100644 --- a/src/wwwroot/gfm/view-engine/01.md +++ b/src/wwwroot/gfm/sharp-pages/01.md @@ -1,6 +1,6 @@ ```csharp public void Configure(Container container) { - Plugins.Add(new TemplatePagesFeature()); + Plugins.Add(new SharpPagesFeature()); } -``` \ No newline at end of file +``` diff --git a/src/wwwroot/gfm/sharp-pages/02.html b/src/wwwroot/gfm/sharp-pages/02.html new file mode 100644 index 0000000..11648b2 --- /dev/null +++ b/src/wwwroot/gfm/sharp-pages/02.html @@ -0,0 +1,2 @@ +
        Plugins.Add(new SharpPagesFeature { HtmlExtension = "htm" });
        +
        \ No newline at end of file diff --git a/src/wwwroot/gfm/sharp-pages/02.md b/src/wwwroot/gfm/sharp-pages/02.md new file mode 100644 index 0000000..2e643c6 --- /dev/null +++ b/src/wwwroot/gfm/sharp-pages/02.md @@ -0,0 +1,3 @@ +```csharp +Plugins.Add(new SharpPagesFeature { HtmlExtension = "htm" }); +``` diff --git a/src/wwwroot/gfm/view-engine/03.html b/src/wwwroot/gfm/sharp-pages/03.html similarity index 97% rename from src/wwwroot/gfm/view-engine/03.html rename to src/wwwroot/gfm/sharp-pages/03.html index 3c7e491..748e382 100644 --- a/src/wwwroot/gfm/view-engine/03.html +++ b/src/wwwroot/gfm/sharp-pages/03.html @@ -1,4 +1,4 @@
        <!--
         layout: mobile-layout
         -->
        -
        +
      \ No newline at end of file diff --git a/src/wwwroot/gfm/view-engine/03.md b/src/wwwroot/gfm/sharp-pages/03.md similarity index 90% rename from src/wwwroot/gfm/view-engine/03.md rename to src/wwwroot/gfm/sharp-pages/03.md index a86ebf8..b76a123 100644 --- a/src/wwwroot/gfm/view-engine/03.md +++ b/src/wwwroot/gfm/sharp-pages/03.md @@ -2,4 +2,4 @@ -``` \ No newline at end of file +``` diff --git a/src/wwwroot/gfm/view-engine/04.html b/src/wwwroot/gfm/sharp-pages/04.html similarity index 97% rename from src/wwwroot/gfm/view-engine/04.html rename to src/wwwroot/gfm/sharp-pages/04.html index d3b39ad..467882c 100644 --- a/src/wwwroot/gfm/view-engine/04.html +++ b/src/wwwroot/gfm/sharp-pages/04.html @@ -1,4 +1,4 @@
      <!--
       layout: templates/mobile-layout
       -->
      -
      +
      \ No newline at end of file diff --git a/src/wwwroot/gfm/view-engine/04.md b/src/wwwroot/gfm/sharp-pages/04.md similarity index 92% rename from src/wwwroot/gfm/view-engine/04.md rename to src/wwwroot/gfm/sharp-pages/04.md index 9c94528..3f754b6 100644 --- a/src/wwwroot/gfm/view-engine/04.md +++ b/src/wwwroot/gfm/sharp-pages/04.md @@ -2,4 +2,4 @@ -``` \ No newline at end of file +``` diff --git a/src/wwwroot/gfm/view-engine/05.html b/src/wwwroot/gfm/sharp-pages/05.html similarity index 51% rename from src/wwwroot/gfm/view-engine/05.html rename to src/wwwroot/gfm/sharp-pages/05.html index 32a8909..9e27366 100644 --- a/src/wwwroot/gfm/view-engine/05.html +++ b/src/wwwroot/gfm/sharp-pages/05.html @@ -6,7 +6,7 @@ <h5>docs</h5> <ul> {{#each docLinks}} - <li {{ {active: matchesPathInfo(Key)} | htmlClass }}><a href="{{Key}}">{{Value}}</a></li> + <li {{ {active: matchesPathInfo(Key)} | htmlClass }}><a href="{{Key}}">{{Value}}</a></li> {{/each}} </ul> </li> @@ -15,7 +15,7 @@ <h5>use cases</h5> <ul> {{#each useCaseLinks}} - <li {{ {active: matchesPathInfo(Key)} | htmlClass }}><a href="{{Key}}">{{Value}}</a></li> + <li {{ {active: matchesPathInfo(Key)} | htmlClass }}><a href="{{Key}}">{{Value}}</a></li> {{/each}} </ul> </li> @@ -24,7 +24,7 @@ <h5>linq examples</h5> <ul> {{#each linqLinks}} - <li {{ {active: matchesPathInfo(Key)} | htmlClass }}><a href="{{Key}}">{{Value}}</a></li> + <li {{ {active: matchesPathInfo(Key)} | htmlClass }}><a href="{{Key}}">{{Value}}</a></li> {{/each}} </ul> </li> diff --git a/src/wwwroot/gfm/view-engine/05.md b/src/wwwroot/gfm/sharp-pages/05.md similarity index 99% rename from src/wwwroot/gfm/view-engine/05.md rename to src/wwwroot/gfm/sharp-pages/05.md index ddda834..b52b7e3 100644 --- a/src/wwwroot/gfm/view-engine/05.md +++ b/src/wwwroot/gfm/sharp-pages/05.md @@ -33,4 +33,4 @@
      -``` \ No newline at end of file +``` diff --git a/src/wwwroot/gfm/view-engine/06.html b/src/wwwroot/gfm/sharp-pages/06.html similarity index 97% rename from src/wwwroot/gfm/view-engine/06.html rename to src/wwwroot/gfm/sharp-pages/06.html index 70454cc..2c730bc 100644 --- a/src/wwwroot/gfm/view-engine/06.html +++ b/src/wwwroot/gfm/sharp-pages/06.html @@ -3,4 +3,4 @@ --> Nothing in this page will be evaluated but will still be rendered inside the closest Layout. - + \ No newline at end of file diff --git a/src/wwwroot/gfm/view-engine/06.md b/src/wwwroot/gfm/sharp-pages/06.md similarity index 96% rename from src/wwwroot/gfm/view-engine/06.md rename to src/wwwroot/gfm/sharp-pages/06.md index 9ad7093..ae222bb 100644 --- a/src/wwwroot/gfm/view-engine/06.md +++ b/src/wwwroot/gfm/sharp-pages/06.md @@ -4,4 +4,4 @@ ignore: page --> Nothing in this page will be evaluated but will still be rendered inside the closest Layout. -``` \ No newline at end of file +``` diff --git a/src/wwwroot/gfm/view-engine/07.html b/src/wwwroot/gfm/sharp-pages/07.html similarity index 97% rename from src/wwwroot/gfm/view-engine/07.html rename to src/wwwroot/gfm/sharp-pages/07.html index c048a1d..5cf4921 100644 --- a/src/wwwroot/gfm/view-engine/07.html +++ b/src/wwwroot/gfm/sharp-pages/07.html @@ -3,4 +3,4 @@ --> This page will not be evaluated nor will it have a Layout. - + \ No newline at end of file diff --git a/src/wwwroot/gfm/view-engine/07.md b/src/wwwroot/gfm/sharp-pages/07.md similarity index 95% rename from src/wwwroot/gfm/view-engine/07.md rename to src/wwwroot/gfm/sharp-pages/07.md index c4db490..af36cd4 100644 --- a/src/wwwroot/gfm/view-engine/07.md +++ b/src/wwwroot/gfm/sharp-pages/07.md @@ -4,4 +4,4 @@ ignore: template --> This page will not be evaluated nor will it have a Layout. -``` \ No newline at end of file +``` diff --git a/src/wwwroot/gfm/view-engine/08.html b/src/wwwroot/gfm/sharp-pages/08.html similarity index 97% rename from src/wwwroot/gfm/view-engine/08.html rename to src/wwwroot/gfm/sharp-pages/08.html index 8177528..da4efcb 100644 --- a/src/wwwroot/gfm/view-engine/08.html +++ b/src/wwwroot/gfm/sharp-pages/08.html @@ -3,4 +3,4 @@ --> This page will be evaluated but rendered without a layout. - + \ No newline at end of file diff --git a/src/wwwroot/gfm/view-engine/08.md b/src/wwwroot/gfm/sharp-pages/08.md similarity index 95% rename from src/wwwroot/gfm/view-engine/08.md rename to src/wwwroot/gfm/sharp-pages/08.md index fd51f26..b4f4d86 100644 --- a/src/wwwroot/gfm/view-engine/08.md +++ b/src/wwwroot/gfm/sharp-pages/08.md @@ -4,4 +4,4 @@ layout: none --> This page will be evaluated but rendered without a layout. -``` \ No newline at end of file +``` diff --git a/src/wwwroot/gfm/sharp-pages/09.html b/src/wwwroot/gfm/sharp-pages/09.html new file mode 100644 index 0000000..9169595 --- /dev/null +++ b/src/wwwroot/gfm/sharp-pages/09.html @@ -0,0 +1,10 @@ +
      public object Any(MyRequest request)
      +{
      +    ...
      +    return new HttpResult(response)
      +    {
      +        View = "CustomPage",
      +        Template = "_custom-layout",
      +    };
      +}
      +
      \ No newline at end of file diff --git a/src/wwwroot/gfm/view-engine/09.md b/src/wwwroot/gfm/sharp-pages/09.md similarity index 97% rename from src/wwwroot/gfm/view-engine/09.md rename to src/wwwroot/gfm/sharp-pages/09.md index 296a56f..e57daf3 100644 --- a/src/wwwroot/gfm/view-engine/09.md +++ b/src/wwwroot/gfm/sharp-pages/09.md @@ -8,4 +8,4 @@ public object Any(MyRequest request) Template = "_custom-layout", }; } -``` \ No newline at end of file +``` diff --git a/src/wwwroot/gfm/view-engine/10.html b/src/wwwroot/gfm/sharp-pages/10.html similarity index 100% rename from src/wwwroot/gfm/view-engine/10.html rename to src/wwwroot/gfm/sharp-pages/10.html diff --git a/src/wwwroot/gfm/view-engine/10.md b/src/wwwroot/gfm/sharp-pages/10.md similarity index 99% rename from src/wwwroot/gfm/view-engine/10.md rename to src/wwwroot/gfm/sharp-pages/10.md index bf36cee..19938d6 100644 --- a/src/wwwroot/gfm/view-engine/10.md +++ b/src/wwwroot/gfm/sharp-pages/10.md @@ -38,4 +38,4 @@ The output of the `_init` page is captured in the `initout` argument which can b ``` If there was an Exception with any of the SQL Statements it will be displayed in the `{{ htmlError }}` filter which can be -later viewed in the [/log](http://blog.web-app.io/log) page above. \ No newline at end of file +later viewed in the [/log](http://blog.web-app.io/log) page above. diff --git a/src/wwwroot/gfm/view-engine/11.html b/src/wwwroot/gfm/sharp-pages/11.html similarity index 90% rename from src/wwwroot/gfm/view-engine/11.html rename to src/wwwroot/gfm/sharp-pages/11.html index ee7d0de..222f2a3 100644 --- a/src/wwwroot/gfm/view-engine/11.html +++ b/src/wwwroot/gfm/sharp-pages/11.html @@ -1,11 +1,11 @@ -
      Plugins.Add(new TemplatePagesFeature {
      +
      Plugins.Add(new SharpPagesFeature {
           TemplatesAdminRole = RoleNames.AllowAnyUser, // Allow any Authenticated User to call /templates/admin
           //TemplatesAdminRole = RoleNames.AllowAnon,  // Allow anyone
           //TemplatesAdminRole = null,                 // Do not register /templates/admin service
       });
      -

      This also the preferred way to enable the Metadata Debug Template +

      This also the preferred way to enable the Metadata Debug Inspector in production, which is only available in DebugMode and Admin Role by default:

      -
      Plugins.Add(new TemplatePagesFeature {
      +
      Plugins.Add(new SharpPagesFeature {
           MetadataDebugAdminRole = RoleNames.Admin,          // Only allow Admin users to call /metadata/debug
           //MetadataDebugAdminRole = RoleNames.AllowAnyUser, // Allow Authenticated Users
           //MetadataDebugAdminRole = RoleNames.AllowAnon,    // Allow anyone
      diff --git a/src/wwwroot/gfm/view-engine/11.md b/src/wwwroot/gfm/sharp-pages/11.md
      similarity index 77%
      rename from src/wwwroot/gfm/view-engine/11.md
      rename to src/wwwroot/gfm/sharp-pages/11.md
      index 53b5d33..b729a33 100644
      --- a/src/wwwroot/gfm/view-engine/11.md
      +++ b/src/wwwroot/gfm/sharp-pages/11.md
      @@ -1,19 +1,19 @@
       ```csharp
      -Plugins.Add(new TemplatePagesFeature {
      +Plugins.Add(new SharpPagesFeature {
           TemplatesAdminRole = RoleNames.AllowAnyUser, // Allow any Authenticated User to call /templates/admin
           //TemplatesAdminRole = RoleNames.AllowAnon,  // Allow anyone
           //TemplatesAdminRole = null,                 // Do not register /templates/admin service
       });
       ```
       
      -This also the preferred way to enable the [Metadata Debug Template](https://docs.servicestack.net/debugging#metadata-debug-template) 
      +This also the preferred way to enable the [Metadata Debug Inspector](https://docs.servicestack.net/debugging#metadata-debug-template) 
       in production, which is only available in `DebugMode` and **Admin** Role by default:
       
       ```csharp
      -Plugins.Add(new TemplatePagesFeature {
      +Plugins.Add(new SharpPagesFeature {
           MetadataDebugAdminRole = RoleNames.Admin,          // Only allow Admin users to call /metadata/debug
           //MetadataDebugAdminRole = RoleNames.AllowAnyUser, // Allow Authenticated Users
           //MetadataDebugAdminRole = RoleNames.AllowAnon,    // Allow anyone
           //MetadataDebugAdminRole = null,                   // Default. Do not register /metadata/debug service
       });
      -```
      +```
      \ No newline at end of file
      diff --git a/src/wwwroot/gfm/syntax/01.html b/src/wwwroot/gfm/syntax/01.html
      index 0c067f1..70d65af 100644
      --- a/src/wwwroot/gfm/syntax/01.html
      +++ b/src/wwwroot/gfm/syntax/01.html
      @@ -1,2 +1,2 @@
      -
      public string upper(string text) => text?.ToUpper();
      +
      public string upper(string text) => text?.ToUpper();
      \ No newline at end of file diff --git a/src/wwwroot/gfm/syntax/01.md b/src/wwwroot/gfm/syntax/01.md index 053c22b..3b3e772 100644 --- a/src/wwwroot/gfm/syntax/01.md +++ b/src/wwwroot/gfm/syntax/01.md @@ -1,3 +1,3 @@ ```csharp public string upper(string text) => text?.ToUpper(); -``` \ No newline at end of file +``` diff --git a/src/wwwroot/gfm/syntax/02.html b/src/wwwroot/gfm/syntax/02.html index 6fb5fe5..847a00d 100644 --- a/src/wwwroot/gfm/syntax/02.html +++ b/src/wwwroot/gfm/syntax/02.html @@ -1,3 +1,3 @@ -
      public string substring(string text, int startIndex) => text.SafeSubstring(startIndex);
      -public string padRight(string text, int totalWidth, char padChar) => text?.PadRight(totalWidth, padChar);
      +
      public string substring(string text, int startIndex) => text.SafeSubstring(startIndex);
      +public string padRight(string text, int totalWidth, char padChar) => text?.PadRight(totalWidth, padChar);
      \ No newline at end of file diff --git a/src/wwwroot/gfm/syntax/02.md b/src/wwwroot/gfm/syntax/02.md index 39fc9e7..9dc00b5 100644 --- a/src/wwwroot/gfm/syntax/02.md +++ b/src/wwwroot/gfm/syntax/02.md @@ -1,4 +1,4 @@ ```csharp public string substring(string text, int startIndex) => text.SafeSubstring(startIndex); public string padRight(string text, int totalWidth, char padChar) => text?.PadRight(totalWidth, padChar); -``` \ No newline at end of file +``` diff --git a/src/wwwroot/gfm/transformers/01.html b/src/wwwroot/gfm/transformers/01.html index e69de29..663f417 100644 --- a/src/wwwroot/gfm/transformers/01.html +++ b/src/wwwroot/gfm/transformers/01.html @@ -0,0 +1,21 @@ +
      public class MarkdownPageFormat : PageFormat
      +{
      +    private static readonly MarkdownDeep.Markdown markdown = new MarkdownDeep.Markdown();
      +
      +    public MarkdownPageFormat()
      +    {
      +        Extension = "md";
      +        ContentType = MimeTypes.MarkdownText;
      +    }
      +
      +    public static async Task<Stream> TransformToHtml(Stream markdownStream)
      +    {
      +        using (var reader = new StreamReader(markdownStream))
      +        {
      +            var md = await reader.ReadToEndAsync();
      +            var html = markdown.Transform(md);
      +            return MemoryStreamFactory.GetStream(html.ToUtf8Bytes());
      +        }
      +    }
      +}
      +
      \ No newline at end of file diff --git a/src/wwwroot/gfm/transformers/01.md b/src/wwwroot/gfm/transformers/01.md index cd45730..d8f477e 100644 --- a/src/wwwroot/gfm/transformers/01.md +++ b/src/wwwroot/gfm/transformers/01.md @@ -19,4 +19,4 @@ public class MarkdownPageFormat : PageFormat } } } -``` \ No newline at end of file +``` diff --git a/src/wwwroot/gfm/transformers/02.html b/src/wwwroot/gfm/transformers/02.html index f667e95..5596fea 100644 --- a/src/wwwroot/gfm/transformers/02.html +++ b/src/wwwroot/gfm/transformers/02.html @@ -1,14 +1,14 @@ -
      var context = new TemplateContext {
      -    PageFormats = { new MarkdownPageFormat() }
      +
      var context = new ScriptContext {
      +    PageFormats = { new MarkdownPageFormat() }
       }.Init();
       
      -context.VirtualFiles.WriteFile("_layout.md", @"
      -The Header
      -
      -{{ page }}");
      +context.VirtualFiles.WriteFile("_layout.md", @"
      +The Header
       
      -context.VirtualFiles.WriteFile("page.md",  @"
      -## {{ title }}
      +{{ page }}");
       
      -The Content");
      +context.VirtualFiles.WriteFile("page.md", @" +## {{ title }} + +The Content");
      \ No newline at end of file diff --git a/src/wwwroot/gfm/transformers/02.md b/src/wwwroot/gfm/transformers/02.md index 1a042ff..7baa80c 100644 --- a/src/wwwroot/gfm/transformers/02.md +++ b/src/wwwroot/gfm/transformers/02.md @@ -1,5 +1,5 @@ ```csharp -var context = new TemplateContext { +var context = new ScriptContext { PageFormats = { new MarkdownPageFormat() } }.Init(); @@ -12,4 +12,4 @@ context.VirtualFiles.WriteFile("page.md", @" ## {{ title }} The Content"); -``` \ No newline at end of file +``` diff --git a/src/wwwroot/gfm/transformers/03.html b/src/wwwroot/gfm/transformers/03.html index b88a49c..f918f19 100644 --- a/src/wwwroot/gfm/transformers/03.html +++ b/src/wwwroot/gfm/transformers/03.html @@ -1,9 +1,9 @@ -
      var result = new PageResult(context.GetPage("page")) 
      +
      var result = new PageResult(context.GetPage("page")) 
       {
      -    Args = { {"title", "The Title"} },
      -    ContentType = MimeTypes.Html,
      -    OutputTransformers = { MarkdownPageFormat.TransformToHtml },
      +    Args = { {"title", "The Title"} },
      +    ContentType = MimeTypes.Html,
      +    OutputTransformers = { MarkdownPageFormat.TransformToHtml },
       };
       
      -var html = await result.RenderToStringAsync();
      +var html = await result.RenderToStringAsync();
      \ No newline at end of file diff --git a/src/wwwroot/gfm/transformers/03.md b/src/wwwroot/gfm/transformers/03.md index c3f880b..e471afc 100644 --- a/src/wwwroot/gfm/transformers/03.md +++ b/src/wwwroot/gfm/transformers/03.md @@ -7,4 +7,4 @@ var result = new PageResult(context.GetPage("page")) }; var html = await result.RenderToStringAsync(); -``` \ No newline at end of file +``` diff --git a/src/wwwroot/gfm/transformers/04.html b/src/wwwroot/gfm/transformers/04.html index 9450ef9..2522580 100644 --- a/src/wwwroot/gfm/transformers/04.html +++ b/src/wwwroot/gfm/transformers/04.html @@ -1,17 +1,17 @@ -
      var context = new TemplateContext {
      -    PageFormats = { new MarkdownPageFormat() }
      +
      var context = new ScriptContext {
      +    PageFormats = { new MarkdownPageFormat() }
       }.Init();
       
      -context.VirtualFiles.WriteFile("_layout.html", @"
      -<html>
      -  <title>{{ title }}</title>
      -</head>
      -<body>
      -  {{ page }}
      -</body>");
      +context.VirtualFiles.WriteFile("_layout.html", @"
      +<html>
      +  <title>{{ title }}</title>
      +</head>
      +<body>
      +  {{ page }}
      +</body>");
       
      -context.VirtualFiles.WriteFile("page.md",  @"
      -## Transformers
      -
      -The Content");
      +context.VirtualFiles.WriteFile("page.md", @" +## Transformers + +The Content");
      \ No newline at end of file diff --git a/src/wwwroot/gfm/transformers/04.md b/src/wwwroot/gfm/transformers/04.md index ee5a4d4..552c1b4 100644 --- a/src/wwwroot/gfm/transformers/04.md +++ b/src/wwwroot/gfm/transformers/04.md @@ -1,5 +1,5 @@ ```csharp -var context = new TemplateContext { +var context = new ScriptContext { PageFormats = { new MarkdownPageFormat() } }.Init(); @@ -15,4 +15,4 @@ context.VirtualFiles.WriteFile("page.md", @" ## Transformers The Content"); -``` \ No newline at end of file +``` diff --git a/src/wwwroot/gfm/transformers/05.html b/src/wwwroot/gfm/transformers/05.html index fa2c577..00fc39b 100644 --- a/src/wwwroot/gfm/transformers/05.html +++ b/src/wwwroot/gfm/transformers/05.html @@ -1,9 +1,9 @@ -
      var result = new PageResult(context.GetPage("page")) 
      +
      var result = new PageResult(context.GetPage("page")) 
       {
      -    Args = { {"title", "The Title"} },
      -    ContentType = MimeTypes.Html,
      -    PageTransformers = { MarkdownPageFormat.TransformToHtml },
      +    Args = { {"title", "The Title"} },
      +    ContentType = MimeTypes.Html,
      +    PageTransformers = { MarkdownPageFormat.TransformToHtml },
       };
       
      -var html = await result.RenderToStringAsync();
      +var html = await result.RenderToStringAsync();
      \ No newline at end of file diff --git a/src/wwwroot/gfm/transformers/05.md b/src/wwwroot/gfm/transformers/05.md index bc9920a..e9a7fb0 100644 --- a/src/wwwroot/gfm/transformers/05.md +++ b/src/wwwroot/gfm/transformers/05.md @@ -7,4 +7,4 @@ var result = new PageResult(context.GetPage("page")) }; var html = await result.RenderToStringAsync(); -``` \ No newline at end of file +``` diff --git a/src/wwwroot/gfm/transformers/06.html b/src/wwwroot/gfm/transformers/06.html index dd71de1..ab37d81 100644 --- a/src/wwwroot/gfm/transformers/06.html +++ b/src/wwwroot/gfm/transformers/06.html @@ -1,18 +1,14 @@ -
      var context = new TemplateContext
      +
      var context = new ScriptContext
       {
      -    TemplateFilters = { new TemplateProtectedFilters() },
      -    FilterTransformers =
      +    ScriptMethods = { new ProtectedScripts() },
      +    FilterTransformers =
           {
      -        ["markdown"] = MarkdownPageFormat.TransformToHtml
      +        ["markdown"] = MarkdownPageFormat.TransformToHtml
           }
       }.Init();
       
      -context.VirtualFiles.WriteFile("doc.md", "## The Heading
      +context.VirtualFiles.WriteFile("doc.md", "## The Heading\nThe Content");
       
      -The Content");
      -
      -context.VirtualFiles.WriteFile("page.html", "
      -<div id="content">
      -    {{ 'doc.md' | includeFile | markdown }}
      -</div>");
      +context.VirtualFiles.WriteFile("page.html", + "<div id="content">{{ 'doc.md' | includeFile | markdown }}</div>");
      \ No newline at end of file diff --git a/src/wwwroot/gfm/transformers/06.md b/src/wwwroot/gfm/transformers/06.md index 99edde5..1c2ccca 100644 --- a/src/wwwroot/gfm/transformers/06.md +++ b/src/wwwroot/gfm/transformers/06.md @@ -1,19 +1,15 @@ ```csharp -var context = new TemplateContext +var context = new ScriptContext { - TemplateFilters = { new TemplateProtectedFilters() }, + ScriptMethods = { new ProtectedScripts() }, FilterTransformers = { ["markdown"] = MarkdownPageFormat.TransformToHtml } }.Init(); -context.VirtualFiles.WriteFile("doc.md", "## The Heading +context.VirtualFiles.WriteFile("doc.md", "## The Heading\nThe Content"); -The Content"); - -context.VirtualFiles.WriteFile("page.html", " -
      - {{ 'doc.md' | includeFile | markdown }} -
      "); -``` \ No newline at end of file +context.VirtualFiles.WriteFile("page.html", + "
      {{ 'doc.md' | includeFile | markdown }}
      "); +``` diff --git a/src/wwwroot/gfm/transformers/07.html b/src/wwwroot/gfm/transformers/07.html index 452aaed..22acbbc 100644 --- a/src/wwwroot/gfm/transformers/07.html +++ b/src/wwwroot/gfm/transformers/07.html @@ -1,2 +1,2 @@ -
      var html = new PageResult(context.GetPage("page")).Result;
      +
      var html = new PageResult(context.GetPage("page")).Result;
      \ No newline at end of file diff --git a/src/wwwroot/gfm/transformers/07.md b/src/wwwroot/gfm/transformers/07.md index 40565c5..cb0b7f6 100644 --- a/src/wwwroot/gfm/transformers/07.md +++ b/src/wwwroot/gfm/transformers/07.md @@ -1,3 +1,3 @@ ```csharp var html = new PageResult(context.GetPage("page")).Result; -``` \ No newline at end of file +``` diff --git a/src/wwwroot/gfm/view-engine/02.html b/src/wwwroot/gfm/view-engine/02.html deleted file mode 100644 index 7ef77c5..0000000 --- a/src/wwwroot/gfm/view-engine/02.html +++ /dev/null @@ -1,2 +0,0 @@ -
      Plugins.Add(new TemplatePagesFeature { HtmlExtension = "htm" });
      -
      \ No newline at end of file diff --git a/src/wwwroot/gfm/view-engine/02.md b/src/wwwroot/gfm/view-engine/02.md deleted file mode 100644 index 15597bb..0000000 --- a/src/wwwroot/gfm/view-engine/02.md +++ /dev/null @@ -1,3 +0,0 @@ -```csharp -Plugins.Add(new TemplatePagesFeature { HtmlExtension = "htm" }); -``` \ No newline at end of file diff --git a/src/wwwroot/gfm/view-engine/09.html b/src/wwwroot/gfm/view-engine/09.html deleted file mode 100644 index 7b04116..0000000 --- a/src/wwwroot/gfm/view-engine/09.html +++ /dev/null @@ -1,10 +0,0 @@ -
      public object Any(MyRequest request)
      -{
      -    ...
      -    return new HttpResult(response)
      -    {
      -        View = "CustomPage",
      -        Template = "_custom-layout",
      -    };
      -}
      -
      \ No newline at end of file diff --git a/src/wwwroot/gfm/web-apps/01.html b/src/wwwroot/gfm/web-apps/01.html deleted file mode 100644 index 85e2b93..0000000 --- a/src/wwwroot/gfm/web-apps/01.html +++ /dev/null @@ -1,16 +0,0 @@ -
      {{#if id}}
      -    {{ `select o.Id, 
      -            ${sqlConcat(["e.FirstName", "' '", "e.LastName"])} Employee, 
      -            OrderDate, ShipCountry, ShippedDate, 
      -            ${sqlCurrency("sum((d.Unitprice * d.Quantity) - d.discount)")} Total 
      -        from ${sqlQuote("Order")} o
      -            inner join
      -            OrderDetail d on o.Id = d.OrderId
      -            inner join 
      -            Employee e on o.EmployeeId = e.Id
      -        where CustomerId = @id
      -        group by o.Id, EmployeeId, FirstName, LastName, OrderDate, ShipCountry, ShippedDate`
      -        | dbSelect({ id }) 
      -        | assignTo: orders }}
      -{{/if}}
      -
      \ No newline at end of file diff --git a/src/wwwroot/gfm/web-apps/04.html b/src/wwwroot/gfm/web-apps/04.html deleted file mode 100644 index 8b6d584..0000000 --- a/src/wwwroot/gfm/web-apps/04.html +++ /dev/null @@ -1,6 +0,0 @@ -
      Plugins.Add(new CustomPlugin { ShowProcessLinks = true });
      -Plugins.Add(new OpenApiFeature());
      -Plugins.Add(new PostmanFeature());
      -Plugins.Add(new CorsFeature());
      -Plugins.Add(new ValidationFeature { ScanAppHostAssemblies = true });
      -
      \ No newline at end of file diff --git a/src/wwwroot/gfm/web-apps/05.html b/src/wwwroot/gfm/web-apps/05.html deleted file mode 100644 index cc24d7b..0000000 --- a/src/wwwroot/gfm/web-apps/05.html +++ /dev/null @@ -1,25 +0,0 @@ -
      public class CustomPlugin : IPlugin
      -{
      -    public bool ShowDrivesLinks { get; set; } = true;
      -    
      -    public bool ShowProcessLinks { get; set; }
      -
      -    public void Register(IAppHost appHost)
      -    {
      -        if (ShowDrivesLinks)
      -        {
      -            var diskFormat = Env.IsWindows ? "NTFS" : "ext2";
      -            appHost.GetPlugin<MetadataFeature>()
      -                .AddPluginLink("/drives", "All Disks")
      -                .AddPluginLink($"/drives?DriveFormatIn={diskFormat}", $"{diskFormat} Disks");
      -        }
      -
      -        if (ShowProcessLinks)
      -        {
      -            appHost.GetPlugin<MetadataFeature>()
      -                .AddPluginLink("/processes", "All Processes")
      -                .AddPluginLink("/process/current", "Current Process");
      -        }
      -    }
      -}
      -
      \ No newline at end of file diff --git a/src/wwwroot/gfm/web-apps/07.html b/src/wwwroot/gfm/web-apps/07.html deleted file mode 100644 index 083578b..0000000 --- a/src/wwwroot/gfm/web-apps/07.html +++ /dev/null @@ -1,22 +0,0 @@ -
      <html>
      -<head>
      -<title>{{ title ?? 'Redis Vue WebApp' }}</title>
      -<i hidden>{{ '/js/hot-loader.js' | ifDebugIncludeScript }}</i>
      -
      -...
      -<link rel="stylesheet" href="../assets/css/bootstrap.css">
      -<link rel="stylesheet" href="../assets/css/default.css">
      -</head>
      -<body>
      -    <h2 id="title"><a href="/"><img src="/assets/img/redis-logo.png" /> {{ title }}</a></h2>
      -
      -    {{ page }}
      -
      -    <script src="../assets/js/html-utils.js"></script>
      -    <script src="../assets/js/vue{{ '.min' | if(!debug) }}.js"></script>
      -    <script src="../assets/js/axios.min.js"></script>
      -    
      -    {{ scripts | raw }} 
      -</body>
      -</html>
      -
      \ No newline at end of file diff --git a/src/wwwroot/index.html b/src/wwwroot/index.html index 423a2dc..50cfbc5 100644 --- a/src/wwwroot/index.html +++ b/src/wwwroot/index.html @@ -1,41 +1,74 @@ - + +

      + SharpScriptScript +

      + +

      + SharpScript is a simple, fast, highly versatile and + embeddable scripting language for .NET Core and .NET Apps that utilizes a + familiar expressive Syntax + to enable dynamic scripting of .NET Apps via controlled access to pluggable + methods and arguments + within a sandbox environment - ensuring scripts are encapsulated and developed using clean, reusable and testable components. +

      + +
      Language

      - At its core ServiceStack Templates is a simple, fast, and extremely versatile - dynamic templating language for .NET and .NET Core that utilizes an Expressive Syntax to compose high-level - functionality using .NET filters and arguments. + The language itself a familiar combination of + JavaScript expressions contained within Template Expressions popularized in + Vue.js filters and + Angular's Template Expressions + whilst statements adopt Handlebars block helpers syntax. The language is also highly extensible which is pre-configured + comprehensive suite of safe methods and blocks by default, but are all registered via its + rich plugin functionality which can be completely removed or "shadowed" (aka overridden) where it could be used to form the basis of your + own completely different DSL.

      +
      Ecosystem
      + +

      + In addition to the language, SharpScript includes a surrounding ecosystem of rich functionality adapting SharpScript + to power a number of fun and exciting scenarios. E.g. Scripts are lazily loaded from its Virtual File System + which can be configured to use any of the available Virtual File Systems + and is what enables Pure Cloud Apps where entire Sharp Apps could be hosted within + your AWS S3 Bucket or Azure Blob Storage. +

      + +
      Dynamic
      +

      It's small, lightweight footprint and built-in Hot Reloading provides a fun, clean and productive alternative to MVC Razor that's easily - integrated into any web framework and runs identically in - every platform ServiceStack runs on, it can also - be returned in ASP.NET MVC and ASP.NET MVC Core + integrated into any web framework and runs identically in + every platform ServiceStack runs on, as well as + within ASP.NET MVC and ASP.NET MVC Core Controllers - in all cases, using the same high-performance implementation to asynchronously write to a forward-only OutputStream for max performance and maximum potential reuse of your Source Code.

      - Templates are lazily loaded and late-bound for Instant Startup, doesn't require any - pre-compilation, have coupling to any external configuration files, build tools, designer tooling or have - any special deployment requirements. - It can be used as a general purpose templating language to enhance any text format and - includes built-in support for .html. + SharpScripts are lazily loaded and late-bound for Instant Startup, doesn't require any + pre-compilation, coupling to any external configuration files, build tools, designer tooling or have any special deployment requirements. + It can be used as a general purpose scripting or templating language to generate any text format + and includes built-in support for .html.

      +
      Encapsulated
      +

      - Templates are evaluated in an Isolated Sandboxed that enables fine-grained control over exactly - what functionality and instances are available to different Templates. They're pre-configured with a comprehensive suite of safe - Default Filters which when running in trusted contexts can easily be granted access to + Similar to Vue.js Components, Sharp Script's are evaluated in an + Encapsulated Sandbox that enables fine-grained control over exactly what functionality and instances + are available to different Scripts. They're pre-configured with a comprehensive suite of safe + Default Scripts which when running in trusted contexts can easily be granted access to enhanced functionality.

      +
      Simple
      +

      - Templates are designed to be incrementally adoptable where its initial form is - ideal for non-programmers, + SharpScript is designed to be incrementally adoptable where its initial form is + ideal for non-programmers, that can gradually adopt more power and functionality when needed where they can leverage existing Services or MVC Controllers to enable an MVC programming model or have .html pages upgraded to use @@ -44,48 +77,54 @@ normal .html pages are requested making them a non-invasive alternative whenever advanced functionality is required.

      -

      Surrounding Ecosystem

      +

      Popular Use Cases

      - These qualities opens Templates up to a number of new use-cases that's better suited than Razor for maintaining + These qualities opens SharpScripts up to a number of new use-cases that's better suited than Razor for maintaining content-heavy websites, live documents, Email Templates and can easily introspect the state of running .NET Apps where they provide - valuable insight at a glance with support for + valuable insight at a glance with support for Adhoc querying.

      -

      Web Apps

      +

      SharpApps

      + +

      + One use-case made possible by SharpScript we're extremely excited about is SharpApps - a new approach to + dramatically simplify .NET Web App development and provide the most productive development experience possible. +

      - One use-case made possible by Templates we're extremely excited about is Web Apps - a new approach to - dramatically simplify .NET Web App development and provide the most productive development experience possible whilst maximizing - reuse and component sharing. + SharpApps leverages SharpScript to develop entire content-rich, data-driven websites in a pure live development model + without needing to write any C#, compile projects or manually refresh pages.

      - Web Apps leverages Templates to develop entire content-rich, data-driven websites without needing to write any C#, compile projects - or manually refresh pages - resulting in the easiest and fastest way to develop Web Apps in .NET! + The integrated development experience extends to end-to-end to provide a seamless installation and deployment experience where the + same web dotnet tool used to develop SharpApps is all users need to find and install Apps locally or hosted as a server App + where they're easily deployed and hosted on Linux or run within Docker - + the overall SharpApps experience results in the easiest and fastest way to develop and deploy Web Apps in .NET!

      Pure Cloud Apps

      - Web Apps also enable the development of Pure Cloud Apps where the same Web App + SharpApps also enable the development of Pure Cloud Apps where the same SharpApp can be developed and run entirely on AWS S3 and RDS or Azure Blob Storage and SQL Server by just changing - the app.settings that's deployed with the pre-compiled Web App Binary. + its app.settings.

      -

      Learn ServiceStack Templates

      +

      Learn SharpScript

      - Get started learning ServiceStack Templates by going through the Interactive Guide and creating + Get started learning SharpScript by going through the Interactive Guide and creating a Starter Project.

      View the source code of this - Template Pages generated website to see how clean a website built with it is. + Sharp Pages website to see how clean a website built with it is.

      @@ -95,19 +134,17 @@

      Learn ServiceStack Templates

      {{ "linq-preview" | partial({ rows: 7, example: "linq01" }) }} -

      Free for OSS and Commercial Projects

      +

      Free!

      - We believe we've only just scratched the surface of what's possible with Templates and we'd love to see what new - use-cases it can help achieve and help encourage an ecosystem of pluggable and reusable filters, so whilst ServiceStack is a - dual-licensed platform with a - commercial license for closed-source projects, the core of Templates is - being developed in ServiceStack.Common which is an unrestricted - library that's free for OSS and commercial usage. + We believe we've only scratched the surface of what's possible with SharpScript and we'd love to see what new + use-cases it can help achieve and help encourage an ecosystem of pluggable and reusable scripts, SharpScript is + being developed in ServiceStack.Common which is a + free library for commercial or non-commercial use.

      - ServiceStack's existing free-quota restrictions + ServiceStack's existing free-quota restrictions only applies if you're using OrmLite, ServiceStack.Redis or exceed the allowed free-quota of ServiceStack Services. diff --git a/src/wwwroot/linq/index.html b/src/wwwroot/linq/index.html index 37066fe..3058723 100644 --- a/src/wwwroot/linq/index.html +++ b/src/wwwroot/linq/index.html @@ -4,13 +4,13 @@ -->

      - All LINQ Examples are executed using the same TemplateContext instance below: + All LINQ Examples are executed using the same ScriptContext instance below:

      
      -    LinqContext = new TemplateContext {
      +    LinqContext = new ScriptContext {
               Args = {
      -            [TemplateConstants.DefaultDateFormat] = "yyyy/MM/dd",
      +            [ScriptConstants.DefaultDateFormat] = "yyyy/MM/dd",
                   ["products"] = TemplateQueryData.Products,
                   ["customers"] = TemplateQueryData.Customers,
                   ["comparer"] = new CaseInsensitiveComparer(),
      diff --git a/src/wwwroot/usecases/adhoc-querying.html b/src/wwwroot/usecases/adhoc-querying.html
      index 6a3bb9f..d048d2e 100644
      --- a/src/wwwroot/usecases/adhoc-querying.html
      +++ b/src/wwwroot/usecases/adhoc-querying.html
      @@ -4,10 +4,10 @@
       --> 
       
       

      - The same qualities that make Templates great at + The same qualities that make SharpScript great at querying State of a running .NET App also makes it excel at executing adhoc queries against providers which allow free text queries like - OrmLite's Database Filters which enables access to its + OrmLite's Database Scripts which enables access to its Dynamic Result Set APIs:

      @@ -23,28 +23,28 @@
      -

      Register DB Filters

      +

      Register DB Scripts

      - To access OrmLite's Database Filters install the + To access OrmLite's Database Scripts install the OrmLite NuGet package for your RDBMS then add the - TemplateDbFiltersAsync - to your TemplateContext and register its required IDbConnectionFactory dependency in its IOC, e.g. for SQL Server: + DbScriptsAsync + to your ScriptContext and register its required IDbConnectionFactory dependency in its IOC, e.g. for SQL Server:

      {{ 'gfm/adhoc-querying/01.md' | githubMarkdown }}
      - If using ServiceStack's TemplatePagesFeature it's Container has already been reassigned to use - ServiceStack's Funq IOC so it only needs to be registered once in ServiceStack's IOC. + If using ServiceStack's SharpPagesFeature it's Container has already been reassigned to use + ServiceStack's Funq IOC so it only needs to be registered once in ServiceStack's IOC.

      Always protect free text APIs

      - If you're exposing filters enabling a free text API against a production database it should never be accessible by untrusted parties + If you're exposing script methods enabling a free text API against a production database it should never be accessible by untrusted parties so you'll want to at a minimum ensure Services are protected with the [Authenticate] attribute so it's only available to - Authenticated Users and + Authenticated Users and ideally configure it to use a Read Only connection, e.g. for SQLite:

      @@ -54,7 +54,7 @@

      Populating the database

      The database queried above was populated in the - AppHost + AppHost where it re-uses the LINQ data sources to create and populate an In Memory SQLite database on Startup:

      diff --git a/src/wwwroot/usecases/content-websites.html b/src/wwwroot/usecases/content-websites.html index a1015e4..6f2987e 100644 --- a/src/wwwroot/usecases/content-websites.html +++ b/src/wwwroot/usecases/content-websites.html @@ -4,13 +4,13 @@ -->

      - In its simplest form Templates is just plain HTML marked up with variable place holders, coupled with - partials just being HTML pages themselves and the intuitive Cascading Layout Selection - and using Templates becomes a natural solution for building complete websites without any code. + In its simplest form SharpScript is just plain HTML marked up with variable place holders, coupled with + partials just being HTML pages themselves and the intuitive Cascading Layout Selection + and using SharpScript becomes a natural solution for building complete websites without any code.

      - The flexibility of Templates allows for several different solutions for generating websites: + The flexibility of SharpScript allows for several different solutions for generating websites:

      Website Sub Directory

      @@ -19,7 +19,7 @@

      Website Sub Directory

      A lightweight solution that can be embedded in any existing ASP.NET or ASP.NET Core Web Application is to embed and maintain an entire Website in a stand-alone sub directory. To showcase an example we've ported the content in the .NET Core Razor Rockstars into the - /usecases/rockstar-files + /usecases/rockstar-files sub directory with the entire website viewable from:

      @@ -47,9 +47,8 @@

      Porting an existing Razor Website

      -->

      - You won't need to specify a custom layout as Templates will automatically select the closest layout from the - page at: - /rockstar-files/dead/_layout.html. + You won't need to specify a custom layout as Sharp Pages will automatically select the closest layout from the page at: + /rockstar-files/dead/_layout.html. You'll also no longer need to maintain your Layout pages and partials in /Views/Shared separate from your views as they all get to all live cohesively in the same logical folder structure.

      @@ -58,13 +57,13 @@

      Porting an existing Razor Website

      The declarative {{ pass: page }} is used to embed a page in a layout instead of the imperative @RenderBody(). Likewise the syntax for partials changes to {{ pass: "menu-alive" | partial }} from @Html.Partial("MenuAlive"). - Templates also alleviates the need for bespoke partials like @Html.PartialMarkdown("Content") as it can instead leverage the + Sharp Pages also alleviates the need for bespoke partials like @Html.PartialMarkdown("Content") as it can instead leverage the flexibility of chaining existing filters to achieve the same result like {{ pass: "content.md" | includeFile | markdown }}.

      - To get a feel for what an equivalent Razor Website looks like compared to Templates checkout: + To get a feel for what an equivalent Razor Website looks like compared to Sharp Pages checkout:

    FilterMethod Exception
    @@ -79,7 +78,7 @@

    Porting an existing Razor Website

    /Views/Shared/DeadLayout.cshtml @@ -88,7 +87,7 @@

    Porting an existing Razor Website

    /Views/Shared/MenuAlive.cshtml @@ -97,7 +96,7 @@

    Porting an existing Razor Website

    /alive/vedder/default.cshtml
    - /dead/_layout.html + /dead/_layout.html
    - /menu-alive.html + /menu-alive.html
    - /alive/vedder/index.html + /alive/vedder/index.html
    @@ -106,14 +105,14 @@

    Embedding Remote Content

    A useful alternative to embedding static file content in pages is to source the content from a external url. Using the - includeUrlWithCache filter this is easy to do with great + includeUrlWithCache method this is easy to do with great performance where you can embed remote url content, cache it for 1 minute and convert from markdown with:

    {{ pass: url | includeUrlWithCache | markdown }}

    - As seen in /grohl-url/index.html + As seen in /grohl-url/index.html which is viewable at:

    diff --git a/src/wwwroot/usecases/email-templates.html b/src/wwwroot/usecases/email-templates.html index 96984e8..ec2934d 100644 --- a/src/wwwroot/usecases/email-templates.html +++ b/src/wwwroot/usecases/email-templates.html @@ -4,7 +4,7 @@ -->

    - Since Templates are intuitive and approachable to non-programmers it's useful in several Business Activities that are better + Since SharpScript is intuitive and approachable to non-programmers it's useful in several Business Activities that are better served by non-technical Business employees like Marketers, Designers, Copywriters, etc. Generating and Previewing email templates are one example of this:

    @@ -87,13 +87,13 @@

    Implementation

    This example uses this simple Service below to generate the HTML and plain-text email previews of this email template:

    -

    EmailTemplatesService.cs

    +

    EmailTemplatesService.cs

    {{ 'gfm/email-templates/01.md' | githubMarkdown }}

    The source code for this - email-templates.html + email-templates.html page shows the client preview itself is just using Bootstrap Tabs that only uses this custom javascript:

    @@ -102,7 +102,7 @@

    default.js + default.js to make an ajax request on every text box change.

    diff --git a/src/wwwroot/usecases/index.html b/src/wwwroot/usecases/index.html index 6d62521..e96f769 100644 --- a/src/wwwroot/usecases/index.html +++ b/src/wwwroot/usecases/index.html @@ -4,11 +4,11 @@ -->

    - The ease-of-use, performance, no precompilation, interactivity and sandbox features of Templates makes it useful for a + The ease-of-use, performance, no precompilation, interactivity and sandbox features of SharpScript makes it useful for a wide range of use cases. To help stimulate some potential ideas we've included some example use cases below:

    -

    Web Apps

    +

    Sharp Apps

    Build entire content-rich and data-driven .NET Websites in real-time for each major server platform without compilation.

    @@ -25,12 +25,12 @@

    Email Templates

    Live Documents

    - Use Templates built-in functionality and live interactivity experience to author and preview smart documents in real-time. + Use SharpScript's built-in functionality and live interactivity experience to author and preview smart documents in real-time.

    Introspect State

    - Use Templates to execute adhoc queries on the state of a running .NET App. + Use SharpScript to execute adhoc queries on the state of a running .NET App.

    Adhoc Querying

    diff --git a/src/wwwroot/usecases/introspect-state.html b/src/wwwroot/usecases/introspect-state.html index 73980a9..4f139f5 100644 --- a/src/wwwroot/usecases/introspect-state.html +++ b/src/wwwroot/usecases/introspect-state.html @@ -4,7 +4,7 @@ -->

    - Since templates are executable at runtime without precompilation it's a great tool for running + Since SharpScript's are executable at runtime without precompilation it's a great tool for running live queries to inspect the state of a running .NET App within a controlled window sandbox. Here's an example of querying a Server's state:

    @@ -50,12 +50,12 @@

    Implementation

    - To implement IntrospectStateServices.cs - we created a separate Service using a new TemplateContext instance with a custom set of filters which just exposes the APIs + To implement IntrospectStateServices.cs + we created a separate Service using a new ScriptContext instance with a custom set of filters which just exposes the APIs we want to be able to query:

    -

    IntrospectStateServices.cs

    +

    IntrospectStateServices.cs

    {{ 'gfm/introspect-state/01.md' | githubMarkdown }} @@ -63,7 +63,7 @@

    Client UI

    Then to implement the - Client UI + Client UI we just used a FORM containing Bootstrap Tabs that only uses this custom javascript:

    @@ -71,15 +71,15 @@

    Client UI

    Which calls the generic ajaxPreview jQuery plugin in - default.js + default.js to make an ajax request on every text box change.

    -

    Debug Template

    +

    Debug Inspector

    As this feature is an extremely useful way to inspect the state of a remote .NET or .NET Core App it's an embedded feature in - ServiceStack which is automatically registered in DebugMode + ServiceStack which is automatically registered in DebugMode which can optionally be made available to everyone with:

    diff --git a/src/wwwroot/usecases/live-documents.html b/src/wwwroot/usecases/live-documents.html index f855f5f..eb52c25 100644 --- a/src/wwwroot/usecases/live-documents.html +++ b/src/wwwroot/usecases/live-documents.html @@ -18,3 +18,4 @@ {{ "usecase-links" | partial({ order }) }} + From 5fa6c512d166b8bcc127f62e9343040191dfed79 Mon Sep 17 00:00:00 2001 From: Demis Bellot Date: Sat, 2 Mar 2019 02:38:57 -0500 Subject: [PATCH 002/206] Update deploy scripts --- Dockerfile | 2 +- README.md | 8 ++++---- deploy-envs.sh | 4 ++-- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/Dockerfile b/Dockerfile index a0e853a..e42c6b4 100644 --- a/Dockerfile +++ b/Dockerfile @@ -10,4 +10,4 @@ FROM microsoft/dotnet:2.1-aspnetcore-runtime WORKDIR /app COPY --from=build-env /app/out . ENV ASPNETCORE_URLS http://*:5000 -ENTRYPOINT ["dotnet", "TemplatePages.dll"] \ No newline at end of file +ENTRYPOINT ["dotnet", "SharpScript.dll"] \ No newline at end of file diff --git a/README.md b/README.md index e7c7e31..42b6a19 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ -# ServiceStack Templates +# SharpScript -Comprehensive Documentation complete with Live Interactive Examples on using ServiceStack Templates. +Comprehensive Documentation complete with Live Interactive Examples on using SharpScript. -> Live Demo: [templates.servicestack.net](http://templates.servicestack.net) +> Live Demo: [sharpscript.net](http://sharpscript.net) -[![](https://raw.githubusercontent.com/NetCoreApps/TemplatePages/master/src/wwwroot/assets/img/screenshot.png)](https://github.com/NetCoreApps/TemplatePages/tree/master/src) +[![](https://raw.githubusercontent.com/NetCoreApps/TemplatePages/master/src/wwwroot/assets/img/screenshot.png)](https://github.com/ServiceStack/sharpscript/tree/master/src) diff --git a/deploy-envs.sh b/deploy-envs.sh index 6a7a007..1876264 100644 --- a/deploy-envs.sh +++ b/deploy-envs.sh @@ -1,12 +1,12 @@ #!/bin/bash # set environment variables used in deploy.sh and AWS task-definition.json: -export IMAGE_NAME=netcoreapps-templates +export IMAGE_NAME=netcoreapps-sharpscript export IMAGE_VERSION=latest export AWS_DEFAULT_REGION=us-east-1 export AWS_ECS_CLUSTER_NAME=default -export AWS_VIRTUAL_HOST=templates.servicestack.net +export AWS_VIRTUAL_HOST=sharpscript.net # set any sensitive information in travis-ci encrypted project settings: # required: AWS_ACCOUNT_NUMBER, AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY From a6af9434abf2b07f347d46fe337f9035150a1930 Mon Sep 17 00:00:00 2001 From: Demis Bellot Date: Sat, 2 Mar 2019 02:56:59 -0500 Subject: [PATCH 003/206] fix build warnings --- src/{CustomTemplateFilters.cs => CustomScriptMethods.cs} | 1 - src/EmailTemplatesService.cs | 2 -- src/GitHubMarkdownFilters.cs | 6 ------ src/IntrospectStateServices.cs | 2 -- src/LinqServices.cs | 3 --- src/TemplateServices.cs | 3 --- 6 files changed, 17 deletions(-) rename src/{CustomTemplateFilters.cs => CustomScriptMethods.cs} (99%) diff --git a/src/CustomTemplateFilters.cs b/src/CustomScriptMethods.cs similarity index 99% rename from src/CustomTemplateFilters.cs rename to src/CustomScriptMethods.cs index 0f1fc3a..2389614 100644 --- a/src/CustomTemplateFilters.cs +++ b/src/CustomScriptMethods.cs @@ -9,7 +9,6 @@ using ServiceStack.Redis; using ServiceStack.OrmLite; using System.Reflection; -using ServiceStack.Script; namespace SharpScript { diff --git a/src/EmailTemplatesService.cs b/src/EmailTemplatesService.cs index 1f0e8ff..c973de3 100644 --- a/src/EmailTemplatesService.cs +++ b/src/EmailTemplatesService.cs @@ -1,9 +1,7 @@ using System.Linq; -using System.Collections.Generic; using ServiceStack; using ServiceStack.Script; using ServiceStack.IO; -using ServiceStack.Script; namespace SharpScript { diff --git a/src/GitHubMarkdownFilters.cs b/src/GitHubMarkdownFilters.cs index f5ef400..efeb68c 100644 --- a/src/GitHubMarkdownFilters.cs +++ b/src/GitHubMarkdownFilters.cs @@ -1,15 +1,9 @@ -using System; -using System.IO; -using System.Linq; -using System.Reflection; -using System.Diagnostics; using System.Threading.Tasks; using System.Collections.Generic; using ServiceStack; using ServiceStack.IO; using ServiceStack.Script; using ServiceStack.Text; -using ServiceStack.Script; namespace SharpScript { diff --git a/src/IntrospectStateServices.cs b/src/IntrospectStateServices.cs index 58640f2..0abb1bc 100644 --- a/src/IntrospectStateServices.cs +++ b/src/IntrospectStateServices.cs @@ -4,8 +4,6 @@ using System.Diagnostics; using System.Collections.Generic; using ServiceStack; -using ServiceStack.IO; -using ServiceStack.Script; using ServiceStack.Script; namespace SharpScript diff --git a/src/LinqServices.cs b/src/LinqServices.cs index 1d38308..81634f9 100644 --- a/src/LinqServices.cs +++ b/src/LinqServices.cs @@ -1,14 +1,11 @@ using System; using System.Linq; -using System.Collections; using System.Collections.Generic; -using System.IO; using ServiceStack; using ServiceStack.Script; using ServiceStack.IO; using ServiceStack.DataAnnotations; using System.Threading.Tasks; -using ServiceStack.Script; namespace SharpScript { diff --git a/src/TemplateServices.cs b/src/TemplateServices.cs index e672a6d..2b14fac 100644 --- a/src/TemplateServices.cs +++ b/src/TemplateServices.cs @@ -4,11 +4,8 @@ using ServiceStack.IO; using System.Threading.Tasks; using System; -using ServiceStack.Web; using ServiceStack.OrmLite; using ServiceStack.Data; -using ServiceStack.Script; -using ServiceStack.Text; namespace SharpScript { From 44ddeeec3f79a5718f4d269781dc873ea69dd6f2 Mon Sep 17 00:00:00 2001 From: Demis Bellot Date: Sat, 2 Mar 2019 03:58:10 -0500 Subject: [PATCH 004/206] Update to SharpScript --- .vscode/launch.json | 4 ++-- README.md | 2 +- src/GitHubMarkdownFilters.cs | 4 ++-- src/SharpScript.csproj | 4 ++-- src/wwwroot/docs/protected-scripts.html | 2 +- src/wwwroot/gfm/sharp-apps/14.html | 2 +- src/wwwroot/gfm/sharp-apps/14.md | 2 +- .../usecases/rockstar-files/alive/grohl-url/index.html | 2 +- 8 files changed, 11 insertions(+), 11 deletions(-) diff --git a/.vscode/launch.json b/.vscode/launch.json index e4b2363..ed180eb 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -6,7 +6,7 @@ "type": "coreclr", "request": "launch", "preLaunchTask": "build", - "program": "${workspaceRoot}/src/bin/Debug/netcoreapp2.1/TemplatePages.dll", + "program": "${workspaceRoot}/src/bin/Debug/netcoreapp2.1/SharpScript.dll", "args": [], "cwd": "${workspaceRoot}/src", "stopAtEntry": false, @@ -17,7 +17,7 @@ "type": "coreclr", "request": "launch", "preLaunchTask": "build", - "program": "${workspaceRoot}/src/bin/Debug/netcoreapp2.1/TemplatePages.dll", + "program": "${workspaceRoot}/src/bin/Debug/netcoreapp2.1/SharpScript.dll", "args": [], "cwd": "${workspaceRoot}/src", "stopAtEntry": false, diff --git a/README.md b/README.md index 42b6a19..b6debde 100644 --- a/README.md +++ b/README.md @@ -4,4 +4,4 @@ Comprehensive Documentation complete with Live Interactive Examples on using Sha > Live Demo: [sharpscript.net](http://sharpscript.net) -[![](https://raw.githubusercontent.com/NetCoreApps/TemplatePages/master/src/wwwroot/assets/img/screenshot.png)](https://github.com/ServiceStack/sharpscript/tree/master/src) +[![](https://raw.githubusercontent.com/ServiceStack/sharpscript/master/src/wwwroot/assets/img/screenshot.png)](https://github.com/ServiceStack/sharpscript/tree/master/src) diff --git a/src/GitHubMarkdownFilters.cs b/src/GitHubMarkdownFilters.cs index efeb68c..c63fe51 100644 --- a/src/GitHubMarkdownFilters.cs +++ b/src/GitHubMarkdownFilters.cs @@ -78,10 +78,10 @@ public async Task githubMarkdown(ScriptScopeContext scope, string markdownPath) var htmlBytes = RepositoryContext == null ? await ApiBaseUrl.CombineWith("markdown", "raw") - .PostBytesToUrlAsync(bytes, contentType:MimeTypes.PlainText, requestFilter:x => x.UserAgent = "TemplatePages") + .PostBytesToUrlAsync(bytes, contentType:MimeTypes.PlainText, requestFilter:x => x.UserAgent = "SharpScript") : await ApiBaseUrl.CombineWith("markdown") .PostBytesToUrlAsync(new Dictionary { {"text", bytes.FromUtf8Bytes() }, {"mode", Mode}, {"context", RepositoryContext} }.ToJson().ToUtf8Bytes(), - contentType:MimeTypes.Json, requestFilter:x => x.UserAgent = "TemplatePages"); + contentType:MimeTypes.Json, requestFilter:x => x.UserAgent = "SharpScript"); var headerBytes = "
    ".ToUtf8Bytes(); var footerBytes = "
    ".ToUtf8Bytes(); diff --git a/src/SharpScript.csproj b/src/SharpScript.csproj index 4678862..365a962 100644 --- a/src/SharpScript.csproj +++ b/src/SharpScript.csproj @@ -2,8 +2,8 @@ netcoreapp2.1 - TemplatePages - TemplatePages + SharpScript + SharpScript diff --git a/src/wwwroot/docs/protected-scripts.html b/src/wwwroot/docs/protected-scripts.html index 78f4614..78aa918 100644 --- a/src/wwwroot/docs/protected-scripts.html +++ b/src/wwwroot/docs/protected-scripts.html @@ -82,7 +82,7 @@

    includeUrl

    page: 'page', files: { - 'page.html' : '{{ "https://raw.githubusercontent.com/NetCoreApps/TemplatePages/master/src" | assignTo: src }} + 'page.html' : '{{ "https://raw.githubusercontent.com/ServiceStack/sharpscript/master/src" | assignTo: src }} {{ `${src}/wwwroot/code/linq01.txt` | includeUrl }}' } }) diff --git a/src/wwwroot/gfm/sharp-apps/14.html b/src/wwwroot/gfm/sharp-apps/14.html index fa91bed..a7db5a5 100644 --- a/src/wwwroot/gfm/sharp-apps/14.html +++ b/src/wwwroot/gfm/sharp-apps/14.html @@ -1,4 +1,4 @@ -

    +

    Live Demo: blog.web-app.io

    diff --git a/src/wwwroot/gfm/sharp-apps/14.md b/src/wwwroot/gfm/sharp-apps/14.md index 6053d4a..001fd54 100644 --- a/src/wwwroot/gfm/sharp-apps/14.md +++ b/src/wwwroot/gfm/sharp-apps/14.md @@ -1,4 +1,4 @@ -[![](https://raw.githubusercontent.com/NetCoreApps/TemplatePages/master/src/wwwroot/assets/img/screenshots/blog.png)](http://blog.web-app.io) +[![](https://raw.githubusercontent.com/ServiceStack/sharpscript/master/src/wwwroot/assets/img/screenshots/blog.png)](http://blog.web-app.io) > Live Demo: [blog.web-app.io](http://blog.web-app.io) diff --git a/src/wwwroot/usecases/rockstar-files/alive/grohl-url/index.html b/src/wwwroot/usecases/rockstar-files/alive/grohl-url/index.html index 224cdde..1103193 100644 --- a/src/wwwroot/usecases/rockstar-files/alive/grohl-url/index.html +++ b/src/wwwroot/usecases/rockstar-files/alive/grohl-url/index.html @@ -31,7 +31,7 @@ - {{ "https://raw.githubusercontent.com/NetCoreApps/TemplatePages/master/src/wwwroot/usecases/rockstar-files/alive/grohl/content.md" + {{ "https://raw.githubusercontent.com/ServiceStack/sharpscript/master/src/wwwroot/usecases/rockstar-files/alive/grohl/content.md" | includeUrlWithCache | markdown }}
    From 27d1a11d3e5ac178b7d0adc243d0e304e59203b8 Mon Sep 17 00:00:00 2001 From: Demis Bellot Date: Sat, 2 Mar 2019 17:10:34 -0500 Subject: [PATCH 005/206] Update db-scripts.html --- src/wwwroot/docs/db-scripts.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/wwwroot/docs/db-scripts.html b/src/wwwroot/docs/db-scripts.html index 377d870..ffaa4fb 100644 --- a/src/wwwroot/docs/db-scripts.html +++ b/src/wwwroot/docs/db-scripts.html @@ -1,5 +1,5 @@ From ddf03ede5ee7e7873236fe86c8ca515e7f16b64e Mon Sep 17 00:00:00 2001 From: Demis Bellot Date: Sat, 2 Mar 2019 17:36:42 -0500 Subject: [PATCH 006/206] Rename SharpScript to #Script --- src/GitHubMarkdownFilters.cs | 4 +-- src/wwwroot/_layout.html | 2 +- src/wwwroot/docs/api-reference.html | 2 +- src/wwwroot/docs/blocks.html | 6 ++-- src/wwwroot/docs/default-scripts.html | 2 +- src/wwwroot/docs/error-handling.html | 2 +- src/wwwroot/docs/hot-reloading.html | 2 +- src/wwwroot/docs/installation.html | 6 ++-- src/wwwroot/docs/introduction.html | 26 ++++++++--------- src/wwwroot/docs/methods.html | 2 +- src/wwwroot/docs/model-view-controller.html | 2 +- src/wwwroot/docs/mvc-netcore.html | 2 +- src/wwwroot/docs/page-formats.html | 4 +-- src/wwwroot/docs/protected-scripts.html | 10 +++---- src/wwwroot/docs/redis-scripts.html | 8 +++--- src/wwwroot/docs/sandbox.html | 4 +-- src/wwwroot/docs/sharp-apis.html | 2 +- src/wwwroot/docs/sharp-apps.html | 32 ++++++++++----------- src/wwwroot/docs/sharp-pages.html | 10 +++---- src/wwwroot/docs/syntax.html | 10 +++---- src/wwwroot/gfm/sharp-apis/04.html | 6 ++-- src/wwwroot/gfm/sharp-apis/04.md | 6 ++-- src/wwwroot/gfm/sharp-apps/14.html | 2 +- src/wwwroot/gfm/sharp-apps/14.md | 2 +- src/wwwroot/index.html | 32 ++++++++++----------- src/wwwroot/usecases/adhoc-querying.html | 2 +- src/wwwroot/usecases/content-websites.html | 6 ++-- src/wwwroot/usecases/email-templates.html | 2 +- src/wwwroot/usecases/index.html | 6 ++-- src/wwwroot/usecases/introspect-state.html | 2 +- 30 files changed, 102 insertions(+), 102 deletions(-) diff --git a/src/GitHubMarkdownFilters.cs b/src/GitHubMarkdownFilters.cs index c63fe51..d81dffe 100644 --- a/src/GitHubMarkdownFilters.cs +++ b/src/GitHubMarkdownFilters.cs @@ -78,10 +78,10 @@ public async Task githubMarkdown(ScriptScopeContext scope, string markdownPath) var htmlBytes = RepositoryContext == null ? await ApiBaseUrl.CombineWith("markdown", "raw") - .PostBytesToUrlAsync(bytes, contentType:MimeTypes.PlainText, requestFilter:x => x.UserAgent = "SharpScript") + .PostBytesToUrlAsync(bytes, contentType:MimeTypes.PlainText, requestFilter:x => x.UserAgent = "#Script") : await ApiBaseUrl.CombineWith("markdown") .PostBytesToUrlAsync(new Dictionary { {"text", bytes.FromUtf8Bytes() }, {"mode", Mode}, {"context", RepositoryContext} }.ToJson().ToUtf8Bytes(), - contentType:MimeTypes.Json, requestFilter:x => x.UserAgent = "SharpScript"); + contentType:MimeTypes.Json, requestFilter:x => x.UserAgent = "#Script"); var headerBytes = "
    ".ToUtf8Bytes(); var footerBytes = "
    ".ToUtf8Bytes(); diff --git a/src/wwwroot/_layout.html b/src/wwwroot/_layout.html index bdc1a47..553df41 100644 --- a/src/wwwroot/_layout.html +++ b/src/wwwroot/_layout.html @@ -15,7 +15,7 @@
diff --git a/src/wwwroot/docs/introduction.html b/src/wwwroot/docs/introduction.html index 3469b07..e95215d 100644 --- a/src/wwwroot/docs/introduction.html +++ b/src/wwwroot/docs/introduction.html @@ -4,7 +4,7 @@ -->

- SharpScript is a simple and elegant, highly-extensible, sandboxed, + #Script is a simple and elegant, highly-extensible, sandboxed, high-performance general-purpose templating engine for .NET 4.5 and .NET Core. It's designed from the ground-up to be incrementally adoptable where its basic usage is simple enough for non-technical users to use whilst it progressively enables access to more power and functionality allowing it to scale up to @@ -66,7 +66,7 @@

Multi page Scripts

- Typically you'll want to use SharpScript to render entire pages which are sourced from its configured + Typically you'll want to use #Script to render entire pages which are sourced from its configured Virtual File System which uses an In Memory Virtual File System by default that we can programmatically populate:

@@ -92,7 +92,7 @@

Multi page Scripts

{{ 'gfm/introduction/06.md' | githubMarkdown }}

- All I/O within SharpScript is non-blocking, but if you're evaluating an adhoc script or using the default In Memory Virtual FileSystem + All I/O within #Script is non-blocking, but if you're evaluating an adhoc script or using the default In Memory Virtual FileSystem there's no I/O so you can safely block to get the generated output with:

@@ -107,7 +107,7 @@

Cascading Resolution

There's no forced special centralized folders like /Views or /Views/Shared required to store layouts or share partials or artificial "Areas" concept to isolate website sections. Different websites or sections are intuitively grouped into different - folders and SharpScript automatically resolves the closest layout it finds for each page. Cascading resolution also applies to + folders and #Script automatically resolves the closest layout it finds for each page. Cascading resolution also applies to including files or partial pages where you can use just its name to resolve the closest one, or an absolute path from the WebRootPath to include a specific partial or file from a different folder.

@@ -127,7 +127,7 @@

High-level, Declarative and Intent-based

{{ 'gfm/introduction/08.md' | githubMarkdown }}

- The intent of SharpScript code should be clear even if it's the first time reading it. From left-to-right we can deduce that it + The intent of #Script code should be clear even if it's the first time reading it. From left-to-right we can deduce that it retrieves a url from the quote table, downloads its contents of and converts it to markdown before replacing the text "Razor" and "2010" and displaying the raw non-HTML encoded html output.

@@ -139,9 +139,9 @@
Implementation using ASP.NET MVC
that is handed off to MVC to execute the View inside the default Layout.

-
Implementation using SharpScript
+
Implementation using #Script

- What does SharpScript do? lets step through the first filter: + What does #Script do? lets step through the first filter:

{{ "live-template" | partial({ output:'no-scroll', rows:1, template: "{{ 'select url from quote where id= @id' | dbScalar({ id:1 }) | htmlLink }}" }) }} @@ -153,7 +153,7 @@
Implementation using SharpScript
to execute all queries asynchronously, otherwise if using an RDBMS whose ADO.NET Provider doesn't support async you can register the DbScripts to execute each DB request synchronously without paying for any pseudo-async overhead, in each case the exact same code executes - the most optimal ADO.NET APIs. SharpScript also benefits from using the much faster + the most optimal ADO.NET APIs. #Script also benefits from using the much faster OrmLite and also saves on the abstraction cost from generating a parameterized SQL Statement from a Typed Expression-based API.

@@ -177,7 +177,7 @@
Async I/O

urlContents is a Block Method which instead of returning a value writes its response to the OutputStream it's given. But then how could we convert it to Markdown if it's already written to the Response Stream? - SharpScript analyzes the Expression's AST to determine if there's any filters remaining, if there is it gives the urlContents + #Script analyzes the Expression's AST to determine if there's any filters remaining, if there is it gives the urlContents Block filter a MemoryStream to write to, then forwards its buffered output to the next filter. Since they don't return values, the only thing that can come after a Block Filter are other Block filters or Stream Transformers. markdown is one such Filter Transformer which takes in a stream of Markdown @@ -207,14 +207,14 @@

Using the most efficient implementation allowable
So whilst it conceptually looks like each filter is transforming large buffered strings inside every filter, the expression is inspected to utilize the most efficient implementation allowable. At the same time filters are not statically bound to any implementation so you could for instance insert a Custom Filter before the Default - Filters containing the same name and arguments count to have SharpScript execute your custom script methods instead, all whilst the + Filters containing the same name and arguments count to have #Script execute your custom script methods instead, all whilst the script source code and intent remains untouched.

Intent-based code is easier to augment

If it was later discovered that some URLs were slow or rate-limited and you needed to introduce caching, your original C# code - would require a fair amount of rework, in SharpScript you can simply add WithCache to call the + would require a fair amount of rework, in #Script you can simply add WithCache to call the urlContentsWithCache filter to return locally cached contents on subsequent requests.

@@ -224,7 +224,7 @@
Intent-based code is easier to augment

Simplified Language

- As there's very little ceremony in SharpScript, a chain of filters looks like it's using its own DSL to accomplish each task and + As there's very little ceremony in #Script, a chain of filters looks like it's using its own DSL to accomplish each task and given implementing and registering custom filters is trivial you're encouraged to write the intent of your code first then implement any filters that are missing to realize its intent. Once you've captured the intent of what you want to do, it's less likely to ever need to change, focus is instead on @@ -232,7 +232,7 @@

Simplified Language

- To improve readability and make it more approachable, SharpScript aims to normalize the mechanics of the underlying implementation from + To improve readability and make it more approachable, #Script aims to normalize the mechanics of the underlying implementation from the code's intent so you can use the same syntax to access an argument, e.g. {{ pass: arg }} as you would a filter without arguments {{ pass: now }} and just like JavaScript you can use obj.Property syntax to access both a public property on a Type or an entry in a Dictionary. diff --git a/src/wwwroot/docs/methods.html b/src/wwwroot/docs/methods.html index f89cbc3..14d6193 100644 --- a/src/wwwroot/docs/methods.html +++ b/src/wwwroot/docs/methods.html @@ -95,7 +95,7 @@

Autowired using ScriptContext IOC
Method Resolution

- SharpScript will use the first matching method with the same name and argument count it can find by searching through all + #Script will use the first matching method with the same name and argument count it can find by searching through all registered methods in the ScriptMethods collection, so you could override default methods with the same name by inserting your ScriptMethods as the first item in the collection, e.g:

diff --git a/src/wwwroot/docs/model-view-controller.html b/src/wwwroot/docs/model-view-controller.html index 82fd48c..78372d6 100644 --- a/src/wwwroot/docs/model-view-controller.html +++ b/src/wwwroot/docs/model-view-controller.html @@ -4,7 +4,7 @@ -->

- Simplicity is a driving goal behind the design of SharpScript where in its simplest form it's usable by non-programmers who just + Simplicity is a driving goal behind the design of #Script where in its simplest form it's usable by non-programmers who just know HTML as they're able to embed dynamic content in their HTML pages using intuitive Mustache syntax and with the intuitive way in how Sharp Pages works they're able to develop entire content-heavy websites without needing to write any code. diff --git a/src/wwwroot/docs/mvc-netcore.html b/src/wwwroot/docs/mvc-netcore.html index 2d3c133..075c2dd 100644 --- a/src/wwwroot/docs/mvc-netcore.html +++ b/src/wwwroot/docs/mvc-netcore.html @@ -4,7 +4,7 @@ -->

- The easiest way to enable SharpScript support in a .NET Core App is to register an empty ServiceStack AppHost + The easiest way to enable #Script support in a .NET Core App is to register an empty ServiceStack AppHost with the SharpPagesFeature plugin enabled:

diff --git a/src/wwwroot/docs/page-formats.html b/src/wwwroot/docs/page-formats.html index 962cfb2..edf8694 100644 --- a/src/wwwroot/docs/page-formats.html +++ b/src/wwwroot/docs/page-formats.html @@ -4,13 +4,13 @@ -->

- SharpScript is a general purpose text templating language which doesn't have any notion of HTML or any other format embedded in the language itself. + #Script is a general purpose text templating language which doesn't have any notion of HTML or any other format embedded in the language itself. It simply emits text outside of mustaches verbatim and inside mustaches evaluates the expression and emits the result. All custom behavior pertinent to specific text formats are instead extrapolated in its Page Format.

- SharpScript supports rendering multiple different text formats simultaneously within the same ScriptContext, + #Script supports rendering multiple different text formats simultaneously within the same ScriptContext, but the only Page Format pre-registered by default is the HtmlPageFormat which is defined below:

diff --git a/src/wwwroot/docs/protected-scripts.html b/src/wwwroot/docs/protected-scripts.html index 78aa918..3ff5aa6 100644 --- a/src/wwwroot/docs/protected-scripts.html +++ b/src/wwwroot/docs/protected-scripts.html @@ -4,16 +4,16 @@ -->

- One of the goals of SharpScript is that its defaults should be safe enough to be able to execute arbitrary scripts by untrusted 3rd parties. + One of the goals of #Script is that its defaults should be safe enough to be able to execute arbitrary scripts by untrusted 3rd parties. Given this constraint, only default scripts are pre-registered which contain a comprehensive set of filters we deem safe for use by anyone. - Other methods available in SharpScript which are useful to have in a server-generated website environment but we don't want 3rd Parties to + Other methods available in #Script which are useful to have in a server-generated website environment but we don't want 3rd Parties to access are filters in ProtectedScripts.cs.

ProtectedScripts are not pre-registered when creating a new ScriptContext but they are pre-registered when - registering the SharpPagesFeature ServiceStack Plugin as that's designed to use SharpScript within a View Engine where access + registering the SharpPagesFeature ServiceStack Plugin as that's designed to use #Script within a View Engine where access to its context is limited to the server's Web Application.

@@ -103,10 +103,10 @@

includeUrl

{{ url | includeUrl({ dataType: 'json' }) }}

- Send data as form-urlencoded in a HTTP PUT Request with a SharpScript User-Agent: + Send data as form-urlencoded in a HTTP PUT Request with a #Script User-Agent:

-
{{ url | includeUrl({ method:'PUT', data: { id: 1, name: 'foo' }, userAgent:"SharpScript" }) }}
+
{{ url | includeUrl({ method:'PUT', data: { id: 1, name: 'foo' }, userAgent:"#Script" }) }}

Send data as JSON in a HTTP POST request and Accept JSON response: diff --git a/src/wwwroot/docs/redis-scripts.html b/src/wwwroot/docs/redis-scripts.html index e672b56..6dbc39c 100644 --- a/src/wwwroot/docs/redis-scripts.html +++ b/src/wwwroot/docs/redis-scripts.html @@ -103,19 +103,19 @@

Redis Vue

Redis HTML

- redis-html.web-app.io is a version of Redis UI built using just SharpScript and Redis + redis-html.web-app.io is a version of Redis UI built using just #Script and Redis methods where all functionality is maintained in a single index.html weighing under <200 LOC including HTML and JavaScript. It's a good example of how the declarative style of programming - that SharpScript encourages a highly-readable code-base that packs a lot of functionality in a tiny foot print. + that #Script encourages a highly-readable code-base that packs a lot of functionality in a tiny foot print.

Server Generated HTML

- It's not immediately obvious when running this locally since both SharpScript and Redis are super fast, but Redis HTML was developed as + It's not immediately obvious when running this locally since both #Script and Redis are super fast, but Redis HTML was developed as a traditional Website where all HTML is server-generated and every search box key stroke and click on search results performs a full-page reload. There's a slight sub-second delay that causes a noticeable flicker when hosted on the Internet due to network lag, but - otherwise server-generated SharpScript Websites can enable a highly responsive UI (especially in Intranets) with great SEO and deep-linking + otherwise server-generated #Script Websites can enable a highly responsive UI (especially in Intranets) with great SEO and deep-linking and back-button support working as expected without the complexity of adopting a client-side JavaScript SPA framework and build-system.

diff --git a/src/wwwroot/docs/sandbox.html b/src/wwwroot/docs/sandbox.html index f2e9055..bce9d5e 100644 --- a/src/wwwroot/docs/sandbox.html +++ b/src/wwwroot/docs/sandbox.html @@ -4,7 +4,7 @@ -->

- Another useful feature of SharpScript is that it operates within a controlled sandbox where each ScriptContext instance is + Another useful feature of #Script is that it operates within a controlled sandbox where each ScriptContext instance is isolated and defines the entire execution environment on which scripts are executed within as such it should be safe to run scripts from untrusted 3rd Party sources as they're confined to what's available within their allowed ScriptContext instance.

@@ -14,7 +14,7 @@

ScriptContext

The only functionality a new ScriptContext instance has access to are the safe set of default scripts and the htmlencode Filter Transformer. - SharpScript can't call methods on instances or have any other way to invoke a method unless it's explicitly registered. + #Script can't call methods on instances or have any other way to invoke a method unless it's explicitly registered.

diff --git a/src/wwwroot/docs/sharp-apis.html b/src/wwwroot/docs/sharp-apis.html index 7c1e057..cbcb890 100644 --- a/src/wwwroot/docs/sharp-apis.html +++ b/src/wwwroot/docs/sharp-apis.html @@ -4,7 +4,7 @@ -->

- In addition to being productive high-level .NET scripting language for generating dynamic HTML pages, SharpScript can also + In addition to being productive high-level .NET scripting language for generating dynamic HTML pages, #Script can also be used to rapidly develop Web APIs which can take advantage of the new support for Dynamic Page Based Routes to rapidly develop data-driven JSON APIs and make them available under the ideal "pretty" URLs whilst utilizing the same Live Development workflow diff --git a/src/wwwroot/docs/sharp-apps.html b/src/wwwroot/docs/sharp-apps.html index 0fb1957..77c5f35 100644 --- a/src/wwwroot/docs/sharp-apps.html +++ b/src/wwwroot/docs/sharp-apps.html @@ -13,7 +13,7 @@

- Sharp Apps leverages SharpScript to develop entire content-rich, data-driven websites without needing to write any C#, + Sharp Apps leverages #Script to develop entire content-rich, data-driven websites without needing to write any C#, compile projects or manually refresh pages - resulting in the easiest and fastest way to develop Web Apps in .NET!

@@ -21,9 +21,9 @@

Ultimate Simplicity

Not having to write any C# code or perform any app builds dramatically reduces the cognitive overhead and conceptual knowledge - required for development where the only thing front-end Web developers need to know is SharpScript's familiar syntax + required for development where the only thing front-end Web developers need to know is #Script's familiar syntax and what methods are available to call. - Because of SharpScript's JavaScript compatibility, developing a Website with SharpScript will be instantly familiar to JavaScript + Because of #Script's JavaScript compatibility, developing a Website with #Script will be instantly familiar to JavaScript devs despite calling and binding directly to .NET APIs behind the scenes.

@@ -71,7 +71,7 @@

Getting Started

1. redis           Redis Admin Viewer developed as Vue Client Single Page App
 2. bare            Bootstrap + jQuery multi-page Website with dynamic Menu Navigation + API pages
 3. chat            Highly extensible App with custom AppHost leveraging OAuth + SSE for real-time Chat
-4. plugins         Extend SharpApps with Plugins, Filters, ServiceStack Services and other C# extensions
+4. plugins         Extend Sharp Apps with Plugins, Filters, ServiceStack Services and other C# extensions
 5. blog            Minimal multi-user Twitter OAuth blogging platform that creates living powerful pages
 6. rockwind-aws    Rockwind Cloud Web App on AWS
 7. rockwind-azure  Rockwind Cloud Web App on Azure
@@ -232,7 +232,7 @@ 

Ideal for Web Designers and Content Authors

The other primary benefit is that this is an example of a website that can be maintained by employees who don't have any - programming experience as SharpScript in their basic form are intuitive and approachable to non-developers, e.g: + programming experience as #Script in their basic form are intuitive and approachable to non-developers, e.g: The title of each page is maintained as metadata HTML comments:

@@ -242,7 +242,7 @@

Ideal for Web Designers and Content Authors

- SharpScript's syntax is also the ideal way to convey variable substitution, e.g: <title>{{ pass: title }}</title> + #Script's syntax is also the ideal way to convey variable substitution, e.g: <title>{{ pass: title }}</title> and even embedding a partial reads like english {{ pass: 'menu' | partial }} which is both intuitive and works well with GUI HTML designers.

@@ -293,7 +293,7 @@

Redis HTML

For the Redis Browser Web App, we wanted to implement an App that was an ideal candidate for a Single Page App but constrain ourselves to do all HTML rendering on the server and have each interaction request a full-page reload to see how a traditional server-generated - Web App feels like with the performance of .NET Core 2.1 and SharpScript. We're pleasantly surprised with the result as when + Web App feels like with the performance of .NET Core 2.1 and #Script. We're pleasantly surprised with the result as when the App is run locally the responsiveness is effectively indistinguishable from an Ajax App. When hosted on the Internet there is a sub-second delay which causes a noticeable flicker but it still retains a pleasant UX that's faster than most websites.

@@ -328,7 +328,7 @@
Beautiful, succinct, declarative code
all Template and JavaScript Source Code in < 200 lines which also includes all as server logic as it doesn't rely on any back-end Services and just uses the Redis Scripts to interface with Redis directly. The source code also serves as a good - demonstration of the declarative coding style that SharpScript encourages that in addition to being highly-readable requires orders + demonstration of the declarative coding style that #Script encourages that in addition to being highly-readable requires orders of magnitude less code than our previous Redis JavaScript SPA's with a comparable feature-set.

@@ -361,7 +361,7 @@

Redis Vue

- Whilst the above server-generated HTML Redis UI shows how you can easily develop traditional Web Apps using SharpScript, we've also + Whilst the above server-generated HTML Redis UI shows how you can easily develop traditional Web Apps using #Script, we've also rewritten the Redis UI as a Single Page App which is the more suitable choice for an App like this as it provides a more optimal and responsive UX by only loading the HTML page once on Startup then utilizes Ajax to only download and update the incremental parts of the App's UI that needs changing. @@ -380,7 +380,7 @@

Redis Vue

Simple Vue App

- SharpScript also provides a great development experience for Single Page Apps which for the most part gets out of your way letting you + #Script also provides a great development experience for Single Page Apps which for the most part gets out of your way letting you develop the Single Page App as if it were a static .html file, but also benefits from the flexibility of a dynamic web page when needed.

@@ -413,7 +413,7 @@

Server Pages

Whilst most of index.html is a static Vue - app, SharpScript is leveraged to generate the body of the <redis-info/> Component on the initial home page render: + app, #Script is leveraged to generate the body of the <redis-info/> Component on the initial home page render:

{{ 'gfm/sharp-apps/08.md' | githubMarkdown }} @@ -426,7 +426,7 @@

Server Pages

Server Handling

- Another area SharpScript is used is to handle the HTTP POST where it calls the redisChangeConnection filter to change + Another area #Script is used is to handle the HTTP POST where it calls the redisChangeConnection filter to change the current Redis connection before rendering the connection-info.html partial with the current connection info: @@ -511,7 +511,7 @@

Northwind

form to filter results, multi-nested detail pages and deep-linking for quickly navigating between - referenced data. SharpScript is also a great solution for rapidly developing Web APIs where the + referenced data. #Script is also a great solution for rapidly developing Web APIs where the /api/customers.html API Page below:

@@ -683,8 +683,8 @@

app.azure.settings

Multi-RDBMS SQL

- As SharpScript is unable to use a Typed ORM like OrmLite - to hide the nuances of each database, we need to be a bit more diligent in SharpScript to use parameterized SQL that works across + As #Script is unable to use a Typed ORM like OrmLite + to hide the nuances of each database, we need to be a bit more diligent in #Script to use parameterized SQL that works across multiple databases by using the sql* DB Filters to avoid using RDBMS-specific SQL syntax. The @@ -979,7 +979,7 @@

Develop back-end using .NET IDE's

includes removing MVC and Razor dependencies and configuration, extracting its _layout.html and converting index.html - to use SharpScript from its original + to use #Script from its original default.cshtml. It's also been enhanced with the ability to evaluate scripts from the Chat window, as seen in the screenshot above.

diff --git a/src/wwwroot/docs/sharp-pages.html b/src/wwwroot/docs/sharp-pages.html index 9922ac6..bc07425 100644 --- a/src/wwwroot/docs/sharp-pages.html +++ b/src/wwwroot/docs/sharp-pages.html @@ -4,7 +4,7 @@ -->

- One of the most popular use-cases for a high-performance and versatile scripting language like SharpScript is as a server-side + One of the most popular use-cases for a high-performance and versatile scripting language like #Script is as a server-side HTML Sharp Pages for .NET Web Applications where it can provide a simpler, cleaner and portable alternative than Razor and Razor Pages in ASP.NET and ASP.NET Core Web Apps.

@@ -49,9 +49,9 @@

Sharp Pages in ServiceStack

Runs Everywhere

- The beauty of SharpScript working natively with ServiceStack is that it runs everywhere ServiceStack does - which is in all major .NET Server Platforms. That is, your same SharpScript-based Web Application is able to use - the same SharpScript implementation, "flavour" and feature-set and is portable across whichever platform you choose to host it on: + The beauty of #Script working natively with ServiceStack is that it runs everywhere ServiceStack does + which is in all major .NET Server Platforms. That is, your same #Script-based Web Application is able to use + the same #Script implementation, "flavour" and feature-set and is portable across whichever platform you choose to host it on:

- The sql* filters are used to enable + The sql* filters are used to enable cross-platform RDBMS support where it encapsulates the differences behind each RDBMS and returns the appropriate SQL for the RDBMS that's registered.

@@ -301,7 +301,7 @@

PostgreSQL Support

See the Scripts API Reference for the - full list of DB filters available. + full list of DB filters available.

diff --git a/src/wwwroot/docs/default-scripts.html b/src/wwwroot/docs/default-scripts.html index d32a339..828cd7d 100644 --- a/src/wwwroot/docs/default-scripts.html +++ b/src/wwwroot/docs/default-scripts.html @@ -280,10 +280,10 @@

Control Execution

The end* methods short-circuits the execution of a method and discard any results. They're useful to use in combination with the - use* methods + use* methods which discards the old value and creates a new value to be used from that point on in the expression. - show is an alias + show is an alias for use that reads better when used at the end of an expression.

@@ -315,9 +315,9 @@

Control Execution

There's also an - only* method + only* method for each - end* method + end* method above with the inverse behavior, e.g:

@@ -540,7 +540,7 @@

Filter API Reference

See the Filter API Reference for the - full list of default scripts available. + full list of default scripts available.

{{ "doc-links" | partial({ order }) }} diff --git a/src/wwwroot/docs/html-scripts.html b/src/wwwroot/docs/html-scripts.html index 9cd9385..9bba8e9 100644 --- a/src/wwwroot/docs/html-scripts.html +++ b/src/wwwroot/docs/html-scripts.html @@ -88,7 +88,7 @@

htmlErrorDebug

See the Scripts API Reference for the - full list of HTML methods available. + full list of HTML methods available.

diff --git a/src/wwwroot/docs/protected-scripts.html b/src/wwwroot/docs/protected-scripts.html index 07b04c6..e281723 100644 --- a/src/wwwroot/docs/protected-scripts.html +++ b/src/wwwroot/docs/protected-scripts.html @@ -256,7 +256,7 @@

Virtual File System APIs

See the Scripts API Reference for the - full list of ServiceStack scripts available. + full list of ServiceStack scripts available.

{{ "doc-links" | partial({ order }) }} diff --git a/src/wwwroot/docs/redis-scripts.html b/src/wwwroot/docs/redis-scripts.html index 1e0fab7..139b3bb 100644 --- a/src/wwwroot/docs/redis-scripts.html +++ b/src/wwwroot/docs/redis-scripts.html @@ -21,7 +21,7 @@ {{ 'gfm/redis-scripts/02.md' | githubMarkdown }}

- Your scripts are now able to use the available Redis Scripts. + Your scripts are now able to use the available Redis Scripts.

redisCall

@@ -147,7 +147,7 @@
Populate redis with the Northwind database

See the Scripts API Reference for the - full list of Redis scripts available. + full list of Redis scripts available.

diff --git a/src/wwwroot/docs/filters-reference.html b/src/wwwroot/docs/scripts-reference.html similarity index 85% rename from src/wwwroot/docs/filters-reference.html rename to src/wwwroot/docs/scripts-reference.html index c79fe96..3c15b88 100644 --- a/src/wwwroot/docs/filters-reference.html +++ b/src/wwwroot/docs/scripts-reference.html @@ -81,57 +81,57 @@
{{ "live-template" | partial({ rows, className, template:`{{ 'DefaultScripts' | assignTo: filter }} -{{ filter | filtersAvailable | where => contains(lower(it.Name), lower(nameContains ?? '')) +{{ filter | methodsAvailable | where => contains(lower(it.Name), lower(nameContains ?? '')) | assignTo: filters }} - +
{{ filter | filterLinkToSrc }}
{{#each filters}}{{/each}}
{{ filter | methodLinkToSrc }}
{{FirstParam}}{{Body}}{{Return}}
` }) }}
{{ "live-template" | partial({ rows, className, template:`{{ 'HtmlScripts' | assignTo: filter }} -{{ filter | filtersAvailable | where => contains(lower(it.Name), lower(nameContains ?? '')) +{{ filter | methodsAvailable | where => contains(lower(it.Name), lower(nameContains ?? '')) | assignTo: filters }} - +
{{ filter | filterLinkToSrc }}
{{#each filters}}{{/each}}
{{ filter | methodLinkToSrc }}
{{FirstParam}}{{Body}}{{Return}}
` }) }}
{{ "live-template" | partial({ rows, className, template:`{{ 'ProtectedScripts' | assignTo: filter }} -{{ filter | filtersAvailable | where => contains(lower(it.Name), lower(nameContains ?? '')) +{{ filter | methodsAvailable | where => contains(lower(it.Name), lower(nameContains ?? '')) | assignTo: filters }} - +
{{ filter | filterLinkToSrc }}
{{#each filters}}{{/each}}
{{ filter | methodLinkToSrc }}
{{FirstParam}}{{Body}}{{Return}}
` }) }}
{{ "live-template" | partial({ rows, className, template:`{{ 'InfoScripts' | assignTo: filter }} -{{ filter | filtersAvailable | where => contains(lower(it.Name), lower(nameContains ?? '')) +{{ filter | methodsAvailable | where => contains(lower(it.Name), lower(nameContains ?? '')) | assignTo: filters }} - +
{{ filter | filterLinkToSrc }}
{{#each filters}}{{/each}}
{{ filter | methodLinkToSrc }}
{{FirstParam}}{{Body}}{{Return}}
` }) }}
{{ "live-template" | partial({ rows, className, template:`{{ 'RedisScripts' | assignTo: filter }} -{{ filter | filtersAvailable | where => contains(lower(it.Name), lower(nameContains ?? '')) +{{ filter | methodsAvailable | where => contains(lower(it.Name), lower(nameContains ?? '')) | assignTo: filters }} - +
{{ filter | filterLinkToSrc }}
{{#each filters}}{{/each}}
{{ filter | methodLinkToSrc }}
{{FirstParam}}{{Body}}{{Return}}
` }) }}
{{ "live-template" | partial({ rows, className, template:`{{ 'DbScriptsAsync' | assignTo: filter }} -{{ filter | filtersAvailable | where => contains(lower(it.Name), lower(nameContains ?? '')) +{{ filter | methodsAvailable | where => contains(lower(it.Name), lower(nameContains ?? '')) | assignTo: filters }} - +
{{ filter | filterLinkToSrc }}
{{#each filters}}{{/each}}
{{ filter | methodLinkToSrc }}
{{FirstParam}}{{Body}}{{Return}}
` }) }}
{{ "live-template" | partial({ rows, className, template:`{{ 'ServiceStackScripts' | assignTo: filter }} -{{ filter | filtersAvailable | where => contains(lower(it.Name), lower(nameContains ?? '')) +{{ filter | methodsAvailable | where => contains(lower(it.Name), lower(nameContains ?? '')) | assignTo: filters }} - +
{{ filter | filterLinkToSrc }}
{{#each filters}}{{/each}}
{{ filter | methodLinkToSrc }}
{{FirstParam}}{{Body}}{{Return}}
` }) }}
diff --git a/src/wwwroot/docs/servicestack-scripts.html b/src/wwwroot/docs/servicestack-scripts.html index f6df9a8..399f4f4 100644 --- a/src/wwwroot/docs/servicestack-scripts.html +++ b/src/wwwroot/docs/servicestack-scripts.html @@ -150,7 +150,7 @@

sendToAutoQuery

See the Scripts API Reference for the - full list of ServiceStack Scripts available. + full list of ServiceStack Scripts available.

Info Scripts

@@ -357,7 +357,7 @@

/metadata/debug

See the Scripts API Reference for the - full list of Info scripts available. + full list of Info scripts available.

diff --git a/src/wwwroot/docs/sharp-apps.html b/src/wwwroot/docs/sharp-apps.html index c4e4042..4dbf650 100644 --- a/src/wwwroot/docs/sharp-apps.html +++ b/src/wwwroot/docs/sharp-apps.html @@ -22,7 +22,7 @@

Ultimate Simplicity

Not having to write any C# code or perform any app builds dramatically reduces the cognitive overhead and conceptual knowledge required for development where the only thing front-end Web developers need to know is #Script's familiar syntax - and what methods are available to call. + and what methods are available to call. Because of [#Script's JavaScript compatibility](/docs/expression-viewer), developing a Website with #Script will be instantly familiar to JavaScript devs despite calling and binding directly to .NET APIs behind the scenes.

@@ -686,7 +686,7 @@

Multi-RDBMS SQL

As #Script is unable to use a Typed ORM like OrmLite to hide the nuances of each database, we need to be a bit more diligent in #Script to use parameterized SQL that works across multiple databases by using the - sql* DB Filters to avoid using RDBMS-specific + sql* DB Filters to avoid using RDBMS-specific SQL syntax. The /northwind/customer.html contains a good example containing a number of things to watch out for: diff --git a/src/wwwroot/index.html b/src/wwwroot/index.html index 9cd6cce..85307b1 100644 --- a/src/wwwroot/index.html +++ b/src/wwwroot/index.html @@ -174,7 +174,7 @@
Encapsulated
Encapsulated Sandbox that enables fine-grained control over exactly what functionality and instances are available to different Scripts. They're pre-configured with a comprehensive suite of safe Default Scripts which when running in trusted contexts can easily be granted access to - enhanced functionality. + enhanced functionality.

Simple
From 3e151515a369971bcae1dd7beba919f89f229ce4 Mon Sep 17 00:00:00 2001 From: Demis Bellot Date: Thu, 4 Jul 2019 18:53:35 -0400 Subject: [PATCH 032/206] Update scripts-reference.html --- src/wwwroot/docs/scripts-reference.html | 74 ++++++++++++------------- 1 file changed, 37 insertions(+), 37 deletions(-) diff --git a/src/wwwroot/docs/scripts-reference.html b/src/wwwroot/docs/scripts-reference.html index 3c15b88..edc0f4e 100644 --- a/src/wwwroot/docs/scripts-reference.html +++ b/src/wwwroot/docs/scripts-reference.html @@ -9,7 +9,7 @@
- + - +

Scripting .NET Types

+ +

+ To find out how to enable unrestricted access to existing .NET Types see Scripting .NET Types. +

+

Autowired using ScriptContext IOC

diff --git a/src/wwwroot/docs/model-view-controller.html b/src/wwwroot/docs/model-view-controller.html index 98548d2..2cc0959 100644 --- a/src/wwwroot/docs/model-view-controller.html +++ b/src/wwwroot/docs/model-view-controller.html @@ -1,6 +1,6 @@

diff --git a/src/wwwroot/docs/mvc-netcore.html b/src/wwwroot/docs/mvc-netcore.html index ffae942..7393a15 100644 --- a/src/wwwroot/docs/mvc-netcore.html +++ b/src/wwwroot/docs/mvc-netcore.html @@ -1,6 +1,6 @@

diff --git a/src/wwwroot/docs/partials.html b/src/wwwroot/docs/partials.html index 458d337..eb89503 100644 --- a/src/wwwroot/docs/partials.html +++ b/src/wwwroot/docs/partials.html @@ -1,6 +1,6 @@

diff --git a/src/wwwroot/docs/protected-scripts.html b/src/wwwroot/docs/protected-scripts.html index 21d3de0..f4d27ec 100644 --- a/src/wwwroot/docs/protected-scripts.html +++ b/src/wwwroot/docs/protected-scripts.html @@ -1,6 +1,6 @@

diff --git a/src/wwwroot/docs/redis-scripts.html b/src/wwwroot/docs/redis-scripts.html index 7be1179..e1af8a0 100644 --- a/src/wwwroot/docs/redis-scripts.html +++ b/src/wwwroot/docs/redis-scripts.html @@ -1,6 +1,6 @@

diff --git a/src/wwwroot/docs/sandbox.html b/src/wwwroot/docs/sandbox.html index fd7e853..6d2e5bf 100644 --- a/src/wwwroot/docs/sandbox.html +++ b/src/wwwroot/docs/sandbox.html @@ -1,6 +1,6 @@

@@ -12,20 +12,95 @@

ScriptContext

- The only functionality a new ScriptContext instance has access to are the - safe set of default scripts and the htmlencode Filter Transformer. - #Script can't call methods on instances or have any other way to invoke a method unless it's explicitly registered. + By default the only functionality and objects #Script has access to is what's pre-configured within a new + ScriptContext instance which has access to + Default Scripts, HTML Scripts and + default Script Blocks. + #Script can't call methods on instances or have any other way to invoke a method unless it's explicitly registered.

+

+ To give additional functionality to your Scripts extend the ScriptContext that executes your script with additional: +

+ + + + + + +

Protected Scripts

+ +

+ When running in a trusted contexts like Server Scripts in #Script Pages, scripts have elevated + privileges with access to Protected Scripts, + ServiceStack and Info Scripts, + Bootstrap Scripts and + ServiceStack Blocks. +

+ +

+ Protected Scripts allows your Scripts to escape the default sandbox and create new instances + on existing .NET Types, populate them and call their methods as documented in Scripting .NET. + There are 2 levels of access available: +

+ +

Script Assemblies and Types

+ +

+ You can limit which Types are scriptable by specifying just the individual Types: +

+ +{{ 'gfm/sandbox/04.md' | githubMarkdown }} + +

AllowScriptingOfAllTypes

+ +

+ To give your Scripts maximum accessibility, you can give them access to all .NET Types in loaded assemblies: +

+ +{{ 'gfm/sandbox/05.md' | githubMarkdown }} + +

+ Use ScriptNamespaces to include additional Lookup namespaces for resolving Types similar to C# using statements. +

+ +

+ See Scripting .NET for how to access .NET Types in #Script. +

+ +

Running untrusted Scripts

+

If running a script from an untrusted source we recommend running them within a new ScriptContext instance so they're kept isolated from any other ScriptContext instance. Context's are cheap to create, so there won't be a noticeable delay when executing in a new instance but they're used to cache compiled lambda expressions which will need to be recreated if executing script in new ScriptContext instances. For improved performance you can instead have all untrusted templates use the same - ScriptContext instance that way they're able to reuse compiled expressions. + ScriptContext instance that way they're able to reuse existing caches and compiled expressions.

-

Remove default scripts

+

Remove Default Scripts

If you want to start from a clean slate, the default scripts can be removed by clearing the ScriptMethods collection: @@ -57,4 +132,13 @@

Instance creation and MaxQuota

{{ 'gfm/sandbox/03.md' | githubMarkdown }} +

Max Stack Depth

+ +

+ To prevent user-defined functions from causing out of memory Exceptions its execution is limited to + a Maximum Stack Depth which can be configured with: +

+ +
ScriptConfig.MaxStackDepth = 25; //default
+ {{ "doc-links" | partial({ order }) }} diff --git a/src/wwwroot/docs/script-net.html b/src/wwwroot/docs/script-net.html new file mode 100644 index 0000000..1f80628 --- /dev/null +++ b/src/wwwroot/docs/script-net.html @@ -0,0 +1,105 @@ + + +{{#markdown}} + +> this page refers to features available in the latest [app](/netcore-windows-desktop) or [web](/web-tool) tools or **v5.6.1** of +[ServiceStack.Common on MyGet](https://docs.servicestack.net/myget) + +The recommended way to add functionality to your scripts is by [extending your ScriptContext](/docs/introduction#net-usage) +with arguments, scripts, blocks and transformers - encapsulating it in a well-defined scope akin to +[Vue Components](https://vuejs.org/v2/guide/components.html) where all functionality is clearly defined in a componentized +design that promotes decoupling and reuse of testable components where the same environment can be easily simulated and +re-created to allow reuse of scripts in multiple contexts. + +> When scripting .NET Types or C# delegates directly it adds coupling to those Types making it harder to substitute and potentially +limits reuse where if the Types were defined in a Host project, it can prevent reuse in different Apps or Test projects, +and Types defined in .NET Framework-only or .NET Core-only dll's can prohibit platform portability + +Although being able to Script .NET Types directly gives your Scripts greater capabilities and opens it up to a lot more use-cases that's +especially useful in predominantly #Script-heavy contexts like [Sharp Apps](/docs/sharp-apps) and [Shell Scripts](/docs/sharp-scripts) +giving them maximum power that would otherwise require the usage of [Plugins](/docs/sharp-apps#plugins). + +We can visualize the different scriptability options from the diagram below where +by default scripts are limited to functionality [defined within their ScriptContext](/docs/introduction#net-usage), +whether to [limit access to specific Types and Assemblies](#script-assemblies-and-types) or whether to lift the escape hatch +and [allow scripting of any .NET Types](#allowscriptingofalltypes). + +![](/assets/img/sandbox.svg) + +### Delegate Arguments + +In addition to [Script Methods](/docs/methods), you can also call delegates that are registered as arguments: + +{{/markdown}} + +{{ 'gfm/script-net/01.md' | githubMarkdown }} + +{{#markdown}} + +Which just like [user-defined functions](/docs/blocks#function) and other Script Methods can be called positionally or as an +[extension method](/docs/syntax#extension-methods): + + fn(1,2) + 1.fn(2) + +## Scripting .NET Types + +Scripting of .NET Types is only available to Script's executed within **trusted contexts** +like [#Script Pages](/docs/sharp-pages), [Sharp Apps](/docs/sharp-apps) and [Shell Scripts](/docs/sharp-scripts) +that are registered with [Protected Scripts](/docs/protected-scripts). The different ways to allow scripting of .NET Types include: + +#### Script Assemblies and Types + +Using `ScriptTypes` to limit scriptability to **only specific Types**: + +{{/markdown}} + +{{ 'gfm/sandbox/04.md' | githubMarkdown }} + +{{#markdown}} + +#### AllowScriptingOfAllTypes + +To give your Scripts maximum accessibility where they're able to pierce the well-defined [ScriptContext sandbox](/docs/sandbox), +you can set `AllowScriptingOfAllTypes` to allow scripting of **all .NET Types** available in loaded assemblies: + +{{/markdown}} + +{{ 'gfm/sandbox/05.md' | githubMarkdown }} + +{{#markdown}} + +`ScriptNamespaces` is used to include additional Lookup namespaces for resolving Types akin to C# `using` statements. + +Using `AllowScriptingOfAllTypes` also allows access to both public and **non-public** Types. + +## Scripting .NET APIs + +The following [Protected Scripts](/docs/protected-scripts) are all that's needed to create new instances, call methods and +populate instances of .NET Types, including generic Types and generic Methods but only a Type's **public** members can be accessed in `#Script`. + +{{/markdown}} + +{{ 'gfm/script-net/02.md' | githubMarkdown }} + +

Type Resolution

+ +{{ 'gfm/script-net/type-resolution.md' | githubMarkdown }} + +

Creating Instances

+ +{{ 'gfm/script-net/create-instances.md' | githubMarkdown }} + +

Calling Methods

+ +{{ 'gfm/script-net/call-methods.md' | githubMarkdown }} + +{{#markdown}} + +{{/markdown}} + + +{{ "doc-links" | partial({ order }) }} diff --git a/src/wwwroot/docs/scripts-reference.html b/src/wwwroot/docs/scripts-reference.html index 4e6bd98..6d8b115 100644 --- a/src/wwwroot/docs/scripts-reference.html +++ b/src/wwwroot/docs/scripts-reference.html @@ -1,6 +1,6 @@ {{ 'nameContains,tab' | importRequestParams }} diff --git a/src/wwwroot/docs/servicestack-scripts.html b/src/wwwroot/docs/servicestack-scripts.html index 0fbd57f..2b6494a 100644 --- a/src/wwwroot/docs/servicestack-scripts.html +++ b/src/wwwroot/docs/servicestack-scripts.html @@ -1,6 +1,6 @@

diff --git a/src/wwwroot/docs/sharp-apis.html b/src/wwwroot/docs/sharp-apis.html index 598a6b3..3596f3b 100644 --- a/src/wwwroot/docs/sharp-apis.html +++ b/src/wwwroot/docs/sharp-apis.html @@ -1,6 +1,6 @@

diff --git a/src/wwwroot/docs/sharp-apps.html b/src/wwwroot/docs/sharp-apps.html index d797274..312e7f3 100644 --- a/src/wwwroot/docs/sharp-apps.html +++ b/src/wwwroot/docs/sharp-apps.html @@ -1,6 +1,6 @@

diff --git a/src/wwwroot/docs/sharp-pages.html b/src/wwwroot/docs/sharp-pages.html index 226a00e..b8e303c 100644 --- a/src/wwwroot/docs/sharp-pages.html +++ b/src/wwwroot/docs/sharp-pages.html @@ -1,6 +1,6 @@

diff --git a/src/wwwroot/docs/sharp-scripts.html b/src/wwwroot/docs/sharp-scripts.html index f101ed8..e4fc820 100644 --- a/src/wwwroot/docs/sharp-scripts.html +++ b/src/wwwroot/docs/sharp-scripts.html @@ -1,6 +1,6 @@ {{#markdown}} diff --git a/src/wwwroot/docs/transformers.html b/src/wwwroot/docs/transformers.html index 31e8f90..508e6cc 100644 --- a/src/wwwroot/docs/transformers.html +++ b/src/wwwroot/docs/transformers.html @@ -1,6 +1,6 @@

diff --git a/src/wwwroot/examples/monthly-budget.txt b/src/wwwroot/examples/monthly-budget.txt index 2f0da51..e60a136 100644 --- a/src/wwwroot/examples/monthly-budget.txt +++ b/src/wwwroot/examples/monthly-budget.txt @@ -15,18 +15,18 @@ * Misc 200 /keyvalues -monthlyRevenues | values | sum | to => totalRevenues -monthlyExpenses | values | sum | to => totalExpenses +monthlyRevenues | sum => it.Value | to => totalRevenues +monthlyExpenses | sum => it.Value | to => totalExpenses (totalRevenues - totalExpenses) | to => totalSavings ``` Current Balance: {{ balance | currency }} Monthly Revenues: -{{monthlyRevenues | toList | select: {it.Key.padRight(16)} {it.Value.currency()}\n }} +{{monthlyRevenues | select: {it.Key.padRight(16)} {it.Value.currency()}\n }} Total {{ totalRevenues | currency }} Monthly Expenses: -{{monthlyExpenses | toList | select: {it.Key.padRight(16)} {it.Value.currency()}\n }} +{{monthlyExpenses | select: {it.Key.padRight(16)} {it.Value.currency()}\n }} Total {{ totalExpenses | currency }} Monthly Savings: {{ totalSavings | currency }} diff --git a/src/wwwroot/gfm/blocks/21.html b/src/wwwroot/gfm/blocks/21.html index ea297f3..53531cc 100644 --- a/src/wwwroot/gfm/blocks/21.html +++ b/src/wwwroot/gfm/blocks/21.html @@ -12,12 +12,12 @@ { literal = literal.ParseJsToken(out var token); if (!(token is JsLiteral litToken)) - throw new NotSupportedException($"'keyvalues' expected delimiter but was {token.DebugToken()}"); + throw new NotSupportedException($"#keyvalues expected delimiter but was {token.DebugToken()}"); delimiter = litToken.Value.ToString(); } var strFragment = (PageStringFragment)block.Body[0]; - var strDict = strFragment.ValueString.Trim().ParseKeyValueText(delimiter); + var strDict = strFragment.ValueString.Trim().ParseAsKeyValues(delimiter); scope.PageResult.Args[name.ToString()] = strDict; return TypeConstants.EmptyTask; diff --git a/src/wwwroot/gfm/blocks/21.md b/src/wwwroot/gfm/blocks/21.md index ce640b0..434748c 100644 --- a/src/wwwroot/gfm/blocks/21.md +++ b/src/wwwroot/gfm/blocks/21.md @@ -13,12 +13,12 @@ public class KeyValuesBlock : ScriptBlock { literal = literal.ParseJsToken(out var token); if (!(token is JsLiteral litToken)) - throw new NotSupportedException($"'keyvalues' expected delimiter but was {token.DebugToken()}"); + throw new NotSupportedException($"#keyvalues expected delimiter but was {token.DebugToken()}"); delimiter = litToken.Value.ToString(); } var strFragment = (PageStringFragment)block.Body[0]; - var strDict = strFragment.ValueString.Trim().ParseKeyValueText(delimiter); + var strDict = strFragment.ValueString.Trim().ParseAsKeyValues(delimiter); scope.PageResult.Args[name.ToString()] = strDict; return TypeConstants.EmptyTask; diff --git a/src/wwwroot/gfm/blocks/23.html b/src/wwwroot/gfm/blocks/23.html new file mode 100644 index 0000000..1007832 --- /dev/null +++ b/src/wwwroot/gfm/blocks/23.html @@ -0,0 +1,4 @@ +

{{#function hi}}
+    'hello' | return
+{{/function}}
+
\ No newline at end of file diff --git a/src/wwwroot/gfm/blocks/23.md b/src/wwwroot/gfm/blocks/23.md new file mode 100644 index 0000000..b2864cb --- /dev/null +++ b/src/wwwroot/gfm/blocks/23.md @@ -0,0 +1,5 @@ +```hbs +{{#function hi}} + 'hello' | return +{{/function}} +``` \ No newline at end of file diff --git a/src/wwwroot/gfm/blocks/24.html b/src/wwwroot/gfm/blocks/24.html new file mode 100644 index 0000000..b6e64c5 --- /dev/null +++ b/src/wwwroot/gfm/blocks/24.html @@ -0,0 +1,5 @@ +
{{#function calc(a, b) }}
+    a * b | to => c
+    return(a + b + c)
+{{/function}}
+
\ No newline at end of file diff --git a/src/wwwroot/gfm/blocks/24.md b/src/wwwroot/gfm/blocks/24.md new file mode 100644 index 0000000..777eeb7 --- /dev/null +++ b/src/wwwroot/gfm/blocks/24.md @@ -0,0 +1,6 @@ +```hbs +{{#function calc(a, b) }} + a * b | to => c + return(a + b + c) +{{/function}} +``` \ No newline at end of file diff --git a/src/wwwroot/gfm/blocks/25.html b/src/wwwroot/gfm/blocks/25.html new file mode 100644 index 0000000..8c1730c --- /dev/null +++ b/src/wwwroot/gfm/blocks/25.html @@ -0,0 +1,7 @@ +
{{#function fib(num) }}
+    #if num <= 1
+        return(num)
+    /if
+    return (fib(num-1) + fib(num-2))
+{{/function}}
+
\ No newline at end of file diff --git a/src/wwwroot/gfm/blocks/25.md b/src/wwwroot/gfm/blocks/25.md new file mode 100644 index 0000000..66160b3 --- /dev/null +++ b/src/wwwroot/gfm/blocks/25.md @@ -0,0 +1,8 @@ +```js +{{#function fib(num) }} + #if num <= 1 + return(num) + /if + return (fib(num-1) + fib(num-2)) +{{/function}} +``` \ No newline at end of file diff --git a/src/wwwroot/gfm/protected-scripts/02.html b/src/wwwroot/gfm/protected-scripts/02.html index 8391148..2bc4353 100644 --- a/src/wwwroot/gfm/protected-scripts/02.html +++ b/src/wwwroot/gfm/protected-scripts/02.html @@ -95,6 +95,14 @@ vfs.DeleteFolder(virtualPath) +fileContents(virtualPath) +vfs.GetFile(virtualPath).GetContents() + + +fileContents(IFile) +file.GetContents() + + fileTextContents(virtualPath) vfs.GetFile(virtualPath)?.ReadAllText() diff --git a/src/wwwroot/gfm/sandbox/04.html b/src/wwwroot/gfm/sandbox/04.html new file mode 100644 index 0000000..4162a32 --- /dev/null +++ b/src/wwwroot/gfm/sandbox/04.html @@ -0,0 +1,19 @@ +
var context = new ScriptContext {
+    ScriptMethods = {
+        new ProtectedScripts()
+    },
+    ScriptTypes = {
+        typeof(MyType),
+        typeof(MyType2),
+    }
+}.Init();
+

Or you can use ScriptAssemblies to allow scripting of all Types within an Assembly:

+
var context = new ScriptContext {
+    ScriptMethods = {
+        new ProtectedScripts()
+    },
+    ScriptAssemblies = {
+        typeof(MyType).Assembly,
+    }
+}.Init();
+
\ No newline at end of file diff --git a/src/wwwroot/gfm/sandbox/04.md b/src/wwwroot/gfm/sandbox/04.md new file mode 100644 index 0000000..ebc6959 --- /dev/null +++ b/src/wwwroot/gfm/sandbox/04.md @@ -0,0 +1,24 @@ +```csharp +var context = new ScriptContext { + ScriptMethods = { + new ProtectedScripts() + }, + ScriptTypes = { + typeof(MyType), + typeof(MyType2), + } +}.Init(); +``` + +Or you can use `ScriptAssemblies` to allow scripting of **all Types within an Assembly**: + +```csharp +var context = new ScriptContext { + ScriptMethods = { + new ProtectedScripts() + }, + ScriptAssemblies = { + typeof(MyType).Assembly, + } +}.Init(); +``` diff --git a/src/wwwroot/gfm/sandbox/05.html b/src/wwwroot/gfm/sandbox/05.html new file mode 100644 index 0000000..cb9ca3f --- /dev/null +++ b/src/wwwroot/gfm/sandbox/05.html @@ -0,0 +1,10 @@ +
var context = new ScriptContext {
+    ScriptMethods = {
+        new ProtectedScripts()
+    },
+    AllowScriptingOfAllTypes = true,
+    ScriptNamespaces = {
+        typeof(MyType).Namespace,
+    }
+}.Init();
+
\ No newline at end of file diff --git a/src/wwwroot/gfm/sandbox/05.md b/src/wwwroot/gfm/sandbox/05.md new file mode 100644 index 0000000..354bb98 --- /dev/null +++ b/src/wwwroot/gfm/sandbox/05.md @@ -0,0 +1,11 @@ +```csharp +var context = new ScriptContext { + ScriptMethods = { + new ProtectedScripts() + }, + AllowScriptingOfAllTypes = true, + ScriptNamespaces = { + typeof(MyType).Namespace, + } +}.Init(); +``` diff --git a/src/wwwroot/gfm/script-net/01.html b/src/wwwroot/gfm/script-net/01.html new file mode 100644 index 0000000..689d200 --- /dev/null +++ b/src/wwwroot/gfm/script-net/01.html @@ -0,0 +1,8 @@ +
Func<int, int, int> add = (a, b) => a + b;
+
+var context = new ScriptContext {
+    Args = {
+        ["fn"] = add
+    }
+}.Init();
+
\ No newline at end of file diff --git a/src/wwwroot/gfm/script-net/01.md b/src/wwwroot/gfm/script-net/01.md new file mode 100644 index 0000000..aa627cd --- /dev/null +++ b/src/wwwroot/gfm/script-net/01.md @@ -0,0 +1,9 @@ +```csharp +Func add = (a, b) => a + b; + +var context = new ScriptContext { + Args = { + ["fn"] = add + } +}.Init(); +``` \ No newline at end of file diff --git a/src/wwwroot/gfm/script-net/02.html b/src/wwwroot/gfm/script-net/02.html new file mode 100644 index 0000000..defe7bf --- /dev/null +++ b/src/wwwroot/gfm/script-net/02.html @@ -0,0 +1,18 @@ +
// Resolve Types
+Type typeof(string typeName);
+
+// Call Methods
+object call(object instance, string name);
+object call(object instance, string name, List<object> args);
+Delegate Function(string qualifiedMethodName);
+
+// Create Instances
+object new(string typeName);
+object new(string typeName, List<object> constructorArgs);
+object createInstance(Type type);
+object createInstance(Type type, List<object> constructorArgs);
+ObjectActivator Constructor(string qualifiedConstructorName);
+
+// Populate Instance
+object set(object instance, Dictionary<string, object> args);
+
\ No newline at end of file diff --git a/src/wwwroot/gfm/script-net/02.md b/src/wwwroot/gfm/script-net/02.md new file mode 100644 index 0000000..887a53f --- /dev/null +++ b/src/wwwroot/gfm/script-net/02.md @@ -0,0 +1,19 @@ +```csharp +// Resolve Types +Type typeof(string typeName); + +// Call Methods +object call(object instance, string name); +object call(object instance, string name, List args); +Delegate Function(string qualifiedMethodName); + +// Create Instances +object new(string typeName); +object new(string typeName, List constructorArgs); +object createInstance(Type type); +object createInstance(Type type, List constructorArgs); +ObjectActivator Constructor(string qualifiedConstructorName); + +// Populate Instance +object set(object instance, Dictionary args); +``` diff --git a/src/wwwroot/gfm/script-net/call-methods.html b/src/wwwroot/gfm/script-net/call-methods.html new file mode 100644 index 0000000..6a8659b --- /dev/null +++ b/src/wwwroot/gfm/script-net/call-methods.html @@ -0,0 +1,74 @@ +

Use the call and Function APIs to call methods on .NET Types:

+
    +
  • +call - invoke a method on an instance
  • +
  • +Function - create a Function delegate that can invoke methods via normal method invocation
  • +
+

+call

+

In its most simplest form you can invoke an instance method that doesn't have any arguments using just its name:

+
'Ints'.new([1,2]) | to => ints
+ints.call('GetMethod')
+

Any arguments can be specified in an arguments list:

+
'Adder'.new([1.0,2.0]) | to => adder3
+adder3.call('Add',[3.0]) //= 6.0
+

+Method Resolution

+

The same Resolution rules in Constructor Resolution also applies when calling methods where any ambiguous methods needs to be +called with arguments containing the exact types (as above), or you can specify the argument types of the method you want to call, +in which case it will let you use the built-in Auto Mapping to call a method expecting a double with an int argument:

+
adder3.call('Add(double)',[3])
+

+Generic Methods

+

You can call generic methods by specifying the Generic Type in the method name:

+
'Ints'.new([1,2]).call('GenericMethod<string>',['A'])
+

+Function

+

call only invokes instance methods, to call static methods you'll need to create a delegate of it first using the +Function method

+
Function('Console.WriteLine(string)') | to => writeln
+

Which lets you call it like a regular Script method:

+
writeln('A')
+'A'.writeln()
+Function('Console.WriteLine(string)')('A')
+

Examples below uses classes in ScriptTypes.cs.

+

+Instance Methods

+

Function create delegates that lets you genericize the different types of method invocations in .NET, including instance methods, +instance generic methods and void Action methods:

+
'InstanceLog'.new(['A']) | to => o
+Function('InstanceLog.Log') | to => log              // instance void method
+Function('InstanceLog.AllLogs') | to => allLogs      // instance method
+Function('InstanceLog.Log<int>') | to => genericLog  // instance generic method
+
+o.log('B')
+log(o,'C')
+o.genericLog(1)
+o | genericLog(2)    
+o.allLogs() | to => snapshotLogs
+

+Static Type Methods

+

Examples of using Function to call static methods and static action methods on a static Type:

+
Function('StaticLog.Clear')()
+Function('StaticLog.Log') | to => log                // static void method
+Function('StaticLog.AllLogs') | to => allLogs        // static method
+Function('StaticLog.Log<int>') | to => genericLog    // static generic method
+
+log('A')
+'B'.log()
+genericLog('C')
+allLogs() | to => snapshotLogs
+

+Generic Static Type Methods

+

Examples of using Function to call static methods and void action methods on a generic static Type:

+
Function('GenericStaticLog<string>.Clear()')()
+Function('GenericStaticLog<string>.Log(string)') | to => log      // generic type static void method
+Function('GenericStaticLog<string>.AllLogs') | to => allLogs      // generic type static method
+Function('GenericStaticLog<string>.Log<int>') | to => genericLog  // generic type generic static method
+
+log('A')
+'B'.log()
+genericLog('C')
+allLogs() | to => snapshotLogs
+
\ No newline at end of file diff --git a/src/wwwroot/gfm/script-net/call-methods.md b/src/wwwroot/gfm/script-net/call-methods.md new file mode 100644 index 0000000..7af3f50 --- /dev/null +++ b/src/wwwroot/gfm/script-net/call-methods.md @@ -0,0 +1,107 @@ +Use the `call` and `Function` APIs to call methods on .NET Types: + +- `call` - invoke a method on an instance +- `Function` - create a Function **delegate** that can invoke methods via normal method invocation + +## call + +In its most simplest form you can invoke an instance method that doesn't have any arguments using just its name: + +```js +'Ints'.new([1,2]) | to => ints +ints.call('GetMethod') +``` + +Any arguments can be specified in an arguments list: + +```js +'Adder'.new([1.0,2.0]) | to => adder3 +adder3.call('Add',[3.0]) //= 6.0 +``` + +### Method Resolution + +The same Resolution rules in **Constructor Resolution** also applies when calling methods where any ambiguous methods needs to be +called with arguments containing the exact types (as above), or you can specify the argument types of the method you want to call, +in which case it will let you use the built-in Auto Mapping to call a method expecting a `double` with an `int` argument: + +```js +adder3.call('Add(double)',[3]) +``` + +### Generic Methods + +You can call generic methods by specifying the Generic Type in the method name: + +```js +'Ints'.new([1,2]).call('GenericMethod',['A']) +``` + +## Function + +`call` only invokes instance methods, to call static methods you'll need to create a **delegate** of it first using the +`Function` method + +```js +Function('Console.WriteLine(string)') | to => writeln +``` + +Which lets you call it like a regular Script method: + +```js +writeln('A') +'A'.writeln() +Function('Console.WriteLine(string)')('A') +``` + +Examples below uses classes in [ScriptTypes.cs](https://github.com/ServiceStack/sharpscript/blob/master/src/ScriptTypes.cs). + +### Instance Methods + +`Function` create delegates that lets you genericize the different types of method invocations in .NET, including instance methods, +instance generic methods and `void` Action methods: + +```js +'InstanceLog'.new(['A']) | to => o +Function('InstanceLog.Log') | to => log // instance void method +Function('InstanceLog.AllLogs') | to => allLogs // instance method +Function('InstanceLog.Log') | to => genericLog // instance generic method + +o.log('B') +log(o,'C') +o.genericLog(1) +o | genericLog(2) +o.allLogs() | to => snapshotLogs +``` + +### Static Type Methods + +Examples of using `Function` to call static methods and static action methods on a static Type: + +```js +Function('StaticLog.Clear')() +Function('StaticLog.Log') | to => log // static void method +Function('StaticLog.AllLogs') | to => allLogs // static method +Function('StaticLog.Log') | to => genericLog // static generic method + +log('A') +'B'.log() +genericLog('C') +allLogs() | to => snapshotLogs +``` + +### Generic Static Type Methods + +Examples of using `Function` to call static methods and void action methods on a generic static Type: + +```js +Function('GenericStaticLog.Clear()')() +Function('GenericStaticLog.Log(string)') | to => log // generic type static void method +Function('GenericStaticLog.AllLogs') | to => allLogs // generic type static method +Function('GenericStaticLog.Log') | to => genericLog // generic type generic static method + +log('A') +'B'.log() +genericLog('C') +allLogs() | to => snapshotLogs +``` diff --git a/src/wwwroot/gfm/script-net/create-instances.html b/src/wwwroot/gfm/script-net/create-instances.html new file mode 100644 index 0000000..842d542 --- /dev/null +++ b/src/wwwroot/gfm/script-net/create-instances.html @@ -0,0 +1,68 @@ +

There are 3 different APIs for creating instances of Types:

+
    +
  • +new - create instances from Type name with specified List of arguments
  • +
  • +createInstance - create instance of Type with specified List of arguments
  • +
  • +Constructor - create a Constructor delegate that can create instances via method invocation
  • +
+

Built-in .NET Types and Types in ScriptTypes, ScriptAssemblies or ScriptNamespaces can be created using their Type Name, +including generic Types:

+
'int'.new()
+'DateTime'.new()
+'Dictionary<string,DateTime>'.new()
+

Otherwise new instances of Types can be created using their full Type Name:

+
'System.Int32'.new()
+'System.Text.StringBuilder'.new()
+

A list of arguments can be passed to the new method to call the constructor with the specified arguments:

+
'Ints'.new([1,2])
+'Adder'.new([1.0])
+'KeyValuePair<string,int>'.new(['A',1])
+

+Constructor Resolution

+

#Script will use the constructor that matches the same number of specified arguments, when needed it uses +ServiceStack's Auto Mapping to convert instances when their Types don't match, e.g:

+
'Ints'.new([1.0,2.0])
+'KeyValuePair<char,double>'.new(['A',1])
+

However if there are multiple constructors with the same number of arguments, it will only use the constructor where all its argument Types +match with the supplied arguments. Attempting to create an instance of the Adder class which only has constructors for string or doublewill fail with an **Ambiguous Match Exception** when trying to create it with anint`:

+
'Adder'.new([1])  // FAIL: Ambiguous Constructor
+

In this case you'll need to convert the arguments so its Types matches one of the available constructors:

+
'Adder'.new([1.0])
+'Adder'.new([intArg.toDouble()])
+'Adder'.new(['A'])
+'Adder'.new([`${instance}`]) // or 'Adder'.new([instance.toString()]) 
+

+Constructor function

+

Alternatively you can use the Constructor method to specify the constructor you want by specifying the argument types of the +constructor you want to use, which will return a delegate that lets you call a method to create instances using that Type's constructor:

+
Constructor('Adder(double)') | to => doubleAdder
+Constructor('Adder(string)') | to => stringAdder
+

In this case you will be able to create instances of Adder using an int argument as the built-in automapping will convert it to +the Argument Type of the Constructor you've chosen:

+
doubleAdder(1)
+stringAdder(1)
+
+// equivalent to:
+Constructor('Adder(double)')(1)
+Constructor('Adder(string)')(1)
+

As the Constructor Function returns a delegate you will be able to invoke it like a normal method where it can also be invoked as +an extension method or inside a filter expression:

+
1.doubleAdder()
+1 | doubleAdder
+

+createInstance

+

The createInstance is like new except it's used to create instances from a Type instead of its string Type Name:

+
typeof('Ints').createInstance([1,2])
+typeof('Adder').createInstance([1.0])
+typeof('KeyValuePair<string,int>').createInstance(['A',1])
+

+set

+

Once you've created instance you can further populate it using the set method which will let you populate public properties +with a JS Object literal, performing any auto-mapping conversions as needed:

+
'Ints'.new([1,2]).set({ C:3, D:4 })
+Constructor('Ints(int,int)')(1,2).set({ C:3, D:4 })
+

As set returns the instance, it can be used within a chained expression:

+
instance.set({ C:3 }).set({ D:4 }).call('GetTotal')
+
\ No newline at end of file diff --git a/src/wwwroot/gfm/script-net/create-instances.md b/src/wwwroot/gfm/script-net/create-instances.md new file mode 100644 index 0000000..fe405d3 --- /dev/null +++ b/src/wwwroot/gfm/script-net/create-instances.md @@ -0,0 +1,112 @@ +There are 3 different APIs for creating instances of Types: + + - `new` - create instances from Type **name** with specified List of arguments + - `createInstance` - create instance of **Type** with specified List of arguments + - `Constructor` - create a Constructor **delegate** that can create instances via method invocation + +Built-in .NET Types and Types in `ScriptTypes`, `ScriptAssemblies` or `ScriptNamespaces` can be created using their Type Name, +including generic Types: + +```js +'int'.new() +'DateTime'.new() +'Dictionary'.new() +``` + +Otherwise new instances of Types can be created using their **full Type Name**: + +```js +'System.Int32'.new() +'System.Text.StringBuilder'.new() +``` + +A list of arguments can be passed to the `new` method to call the constructor with the specified arguments: + +```js +'Ints'.new([1,2]) +'Adder'.new([1.0]) +'KeyValuePair'.new(['A',1]) +``` + +### Constructor Resolution + +`#Script` will use the constructor that matches the same number of specified arguments, when needed it uses +[ServiceStack's Auto Mapping](https://docs.servicestack.net/auto-mapping) to convert instances when their Types don't match, e.g: + +```js +'Ints'.new([1.0,2.0]) +'KeyValuePair'.new(['A',1]) +``` + +However if there are multiple constructors with the same number of arguments, it will only use the constructor where all its argument Types +match with the supplied arguments. Attempting to create an instance of the `Adder` class which only has constructors for `string` or` +`double` will fail with an **Ambiguous Match Exception** when trying to create it with an `int`: + +```js +'Adder'.new([1]) // FAIL: Ambiguous Constructor +``` + +In this case you'll need to convert the arguments so its Types matches one of the available constructors: + +```js +'Adder'.new([1.0]) +'Adder'.new([intArg.toDouble()]) +'Adder'.new(['A']) +'Adder'.new([`${instance}`]) // or 'Adder'.new([instance.toString()]) +``` + +### Constructor function + +Alternatively you can use the `Constructor` method to specify the constructor you want by specifying the argument types of the +constructor you want to use, which will return a delegate that lets you call a method to create instances using that Type's constructor: + +```js +Constructor('Adder(double)') | to => doubleAdder +Constructor('Adder(string)') | to => stringAdder +``` + +In this case you will be able to create instances of `Adder` using an `int` argument as the built-in automapping will convert it to +the Argument Type of the Constructor you've chosen: + +```js +doubleAdder(1) +stringAdder(1) + +// equivalent to: +Constructor('Adder(double)')(1) +Constructor('Adder(string)')(1) +``` + +As the Constructor Function returns a delegate you will be able to invoke it like a normal method where it can also be invoked as +an extension method or inside a filter expression: + +```csharp +1.doubleAdder() +1 | doubleAdder +``` + +### createInstance + +The `createInstance` is like `new` except it's used to create instances from a `Type` instead of its `string` Type Name: + +```js +typeof('Ints').createInstance([1,2]) +typeof('Adder').createInstance([1.0]) +typeof('KeyValuePair').createInstance(['A',1]) +``` + +## set + +Once you've created instance you can further populate it using the `set` method which will let you populate **public properties** +with a JS Object literal, performing any auto-mapping conversions as needed: + +```js +'Ints'.new([1,2]).set({ C:3, D:4 }) +Constructor('Ints(int,int)')(1,2).set({ C:3, D:4 }) +``` + +As `set` returns the instance, it can be used within a chained expression: + +```js +instance.set({ C:3 }).set({ D:4 }).call('GetTotal') +``` diff --git a/src/wwwroot/gfm/script-net/type-resolution.html b/src/wwwroot/gfm/script-net/type-resolution.html new file mode 100644 index 0000000..47249a1 --- /dev/null +++ b/src/wwwroot/gfm/script-net/type-resolution.html @@ -0,0 +1,52 @@ +

If you've registered Types using either ScriptTypes or ScriptAssemblies than you'll be able to reference the Type using +just the Type Name, unless multiple Types of the same name are registered in which case the typeof() will return the first Type +registered, all other subsequent Types with the same Name will need to be referenced with their Full Name.

+
typeof('MyType')
+typeof('My.Namespace.MyType')
+

When AllowScriptingOfAllTypes=true is enabled, you can use ScriptNamespaces to add Lookup Namespaces for resolving Types, +which for #Script Pages, Sharp Apps and Sharp Scripts are pre-configured with:

+
var context = new ScriptContext {
+    //...
+    ScriptNamespaces = {
+        "System",
+        "System.Collections.Generic",
+        "ServiceStack",
+    }
+}.Init();
+

All other Types (other than .NET built-in types) +not registered in ScriptTypes, ScriptAssemblies or have their namespace defined in ScriptNamespaces will need to be referenced using +their Full Type Name. This same Type resolution applies for all references of Types in #Script.

+

+Examples Configuration

+

The examples below assumes a ScriptContext configured with:

+
var context = new ScriptContext {
+    ScriptMethods = { new ProtectedScripts() },
+    AllowScriptingOfAllTypes = true,
+    ScriptNamespaces = {
+        "System",
+        "System.Collections.Generic",
+    },
+    ScriptTypes = {
+        typeof(Ints),
+        typeof(Adder),
+        typeof(StaticLog),
+        typeof(InstanceLog),
+        typeof(GenericStaticLog<>),
+    },
+}.Init();
+

With the types for the above classes defined in ScriptTypes.cs. +This is the definition of the Adder class that's referenced frequently in the examples below:

+
public class Adder
+{
+    public string String { get; set; }
+    public double Double { get; set; }
+
+    public Adder(string str) => String = str;
+    public Adder(double num) => Double = num;
+
+    public string Add(string str) => String += str;
+    public double Add(double num) => Double += num;
+
+    public override string ToString() => String != null ? $"string: {String}" : $"double: {Double}";
+}
+
\ No newline at end of file diff --git a/src/wwwroot/gfm/script-net/type-resolution.md b/src/wwwroot/gfm/script-net/type-resolution.md new file mode 100644 index 0000000..cd9ac48 --- /dev/null +++ b/src/wwwroot/gfm/script-net/type-resolution.md @@ -0,0 +1,67 @@ +If you've registered Types using either `ScriptTypes` or `ScriptAssemblies` than you'll be able to reference the Type using +**just the Type Name**, unless multiple Types of the same name are registered in which case the `typeof()` will return the **first Type** +registered, all other subsequent Types with the same Name will need to be referenced with their **Full Name**. + +```js +typeof('MyType') +typeof('My.Namespace.MyType') +``` + +When `AllowScriptingOfAllTypes=true` is enabled, you can use `ScriptNamespaces` to add Lookup Namespaces for resolving Types, +which for #Script Pages, Sharp Apps and Sharp Scripts are pre-configured with: + +```csharp +var context = new ScriptContext { + //... + ScriptNamespaces = { + "System", + "System.Collections.Generic", + "ServiceStack", + } +}.Init(); +``` + +All other Types (other than [.NET built-in types](https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/built-in-types-table)) +not registered in `ScriptTypes`, `ScriptAssemblies` or have their namespace defined in `ScriptNamespaces` will need to be referenced using +their Full Type Name. This same Type resolution applies for all references of Types in `#Script`. + +#### Examples Configuration + +The examples below assumes a `ScriptContext` configured with: + +```csharp +var context = new ScriptContext { + ScriptMethods = { new ProtectedScripts() }, + AllowScriptingOfAllTypes = true, + ScriptNamespaces = { + "System", + "System.Collections.Generic", + }, + ScriptTypes = { + typeof(Ints), + typeof(Adder), + typeof(StaticLog), + typeof(InstanceLog), + typeof(GenericStaticLog<>), + }, +}.Init(); +``` + +With the types for the above classes defined in [ScriptTypes.cs](https://github.com/ServiceStack/sharpscript/blob/master/src/ScriptTypes.cs). +This is the definition of the `Adder` class that's referenced frequently in the examples below: + +```csharp +public class Adder +{ + public string String { get; set; } + public double Double { get; set; } + + public Adder(string str) => String = str; + public Adder(double num) => Double = num; + + public string Add(string str) => String += str; + public double Add(double num) => Double += num; + + public override string ToString() => String != null ? $"string: {String}" : $"double: {Double}"; +} +``` diff --git a/src/wwwroot/gfm/script-plugins/01.html b/src/wwwroot/gfm/script-plugins/01.html index 17d3edc..79392f7 100644 --- a/src/wwwroot/gfm/script-plugins/01.html +++ b/src/wwwroot/gfm/script-plugins/01.html @@ -72,6 +72,9 @@

new PartialScriptBlock(), new WithScriptBlock(), new NoopScriptBlock(), + new KeyValuesBlock(), + new CsvBlock(), + new FunctionBlock(), }); } } @@ -115,6 +118,10 @@

new ScriptBBlock(), new ScriptIBlock(), new ScriptStrongBlock(), + new ScriptScriptBlock(), + new ScriptStyleBlock(), + new ScriptLinkBlock(), + new ScriptMetaBlock(), }); } } @@ -135,6 +142,7 @@

new MinifyJsScriptBlock(), new MinifyCssScriptBlock(), new MinifyHtmlScriptBlock(), + new SvgScriptBlock(), }); } } @@ -171,7 +179,7 @@

public void Register(ScriptContext context) { context.ScriptBlocks.AddRange(new ScriptBlock[] { - new EvalScriptBlock(), // evalTemplate script method has same functionality + new EvalScriptBlock(), // evalScript has same functionality and is registered by default }); } } diff --git a/src/wwwroot/gfm/script-plugins/01.md b/src/wwwroot/gfm/script-plugins/01.md index b4ee9c0..76bcadd 100644 --- a/src/wwwroot/gfm/script-plugins/01.md +++ b/src/wwwroot/gfm/script-plugins/01.md @@ -93,6 +93,9 @@ public class DefaultScriptBlocks : IScriptPlugin new PartialScriptBlock(), new WithScriptBlock(), new NoopScriptBlock(), + new KeyValuesBlock(), + new CsvBlock(), + new FunctionBlock(), }); } } @@ -140,6 +143,10 @@ public class HtmlScriptBlocks : IScriptPlugin new ScriptBBlock(), new ScriptIBlock(), new ScriptStrongBlock(), + new ScriptScriptBlock(), + new ScriptStyleBlock(), + new ScriptLinkBlock(), + new ScriptMetaBlock(), }); } } @@ -164,6 +171,7 @@ public class ServiceStackScriptBlocks : IScriptPlugin new MinifyJsScriptBlock(), new MinifyCssScriptBlock(), new MinifyHtmlScriptBlock(), + new SvgScriptBlock(), }); } } @@ -212,7 +220,7 @@ public class ProtectedScriptBlocks : IScriptPlugin public void Register(ScriptContext context) { context.ScriptBlocks.AddRange(new ScriptBlock[] { - new EvalScriptBlock(), // evalTemplate script method has same functionality + new EvalScriptBlock(), // evalScript has same functionality and is registered by default }); } } diff --git a/src/wwwroot/gfm/sharp-apps/06.html b/src/wwwroot/gfm/sharp-apps/06.html index 1ead499..b0729a1 100644 --- a/src/wwwroot/gfm/sharp-apps/06.html +++ b/src/wwwroot/gfm/sharp-apps/06.html @@ -10,7 +10,7 @@ public class AppHost : AppHostBase { - public AppHost() : base("Chat Web App", typeof(ServerEventsServices).GetAssembly()) {} + public AppHost() : base("Chat Web App", typeof(ServerEventsServices).Assembly) {} public AppHost(IAppSettings appSettings) : this() => AppSettings = appSettings; public override void Configure(Container container) diff --git a/src/wwwroot/index.html b/src/wwwroot/index.html index 016522f..00ff63f 100644 --- a/src/wwwroot/index.html +++ b/src/wwwroot/index.html @@ -44,6 +44,10 @@

Embed in .NET

See Embedding in .NET docs for how to customize and add additional functionality to your ScriptContext.

+

+ To find out how to enable unrestricted access to existing .NET Types see Scripting .NET Types. +

+

Sharp Scripts

diff --git a/src/wwwroot/usecases/live-documents.html b/src/wwwroot/usecases/live-documents.html index 67a9d97..b1ec264 100644 --- a/src/wwwroot/usecases/live-documents.html +++ b/src/wwwroot/usecases/live-documents.html @@ -17,7 +17,32 @@ } -

new keyvalues block is available from v5.6.1 on MyGet
+

csv

+ +

+ Similar to keyvalues, you can specify a multi-column inline data set using the #csv block, e.g: +

+ +
+
+ +
+
+
+
+
+ +
+ new keyvalues and csv + blocks are available from v5.6.1 on MyGet +

Live Document Blog

From fd665b5bbb3ccf3e8909f06e0383570e59530173 Mon Sep 17 00:00:00 2001 From: Demis Bellot Date: Tue, 27 Aug 2019 22:16:29 -0400 Subject: [PATCH 087/206] Update script-net.html --- src/wwwroot/docs/script-net.html | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/wwwroot/docs/script-net.html b/src/wwwroot/docs/script-net.html index 1f80628..f8b8a03 100644 --- a/src/wwwroot/docs/script-net.html +++ b/src/wwwroot/docs/script-net.html @@ -5,7 +5,8 @@ {{#markdown}} -> this page refers to features available in the latest [app](/netcore-windows-desktop) or [web](/web-tool) tools or **v5.6.1** of +> this page refers to features available in the latest [app](https://docs.servicestack.net/netcore-windows-desktop) or +[web](https://docs.servicestack.net/web-tool) tools or **v5.6.1** of [ServiceStack.Common on MyGet](https://docs.servicestack.net/myget) The recommended way to add functionality to your scripts is by [extending your ScriptContext](/docs/introduction#net-usage) From b855bb49cd4cbd6843bcf3ae4b15c487dfebe282 Mon Sep 17 00:00:00 2001 From: Demis Bellot Date: Wed, 28 Aug 2019 07:54:19 -0400 Subject: [PATCH 088/206] Update servicestack-scripts.html --- src/wwwroot/docs/servicestack-scripts.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/wwwroot/docs/servicestack-scripts.html b/src/wwwroot/docs/servicestack-scripts.html index 2b6494a..db5fada 100644 --- a/src/wwwroot/docs/servicestack-scripts.html +++ b/src/wwwroot/docs/servicestack-scripts.html @@ -210,7 +210,7 @@

Info Scripts

Env.ServiceStackVersion - envIsWindows + envIsWindows (isWin) Env.IsWindows (net45) / IsOSPlatform(Windows) (netcore) From 7cd19966fd167864cf6d621e09a73f37315288b9 Mon Sep 17 00:00:00 2001 From: Demis Bellot Date: Wed, 28 Aug 2019 10:31:10 -0400 Subject: [PATCH 089/206] Update create-instances.md --- src/wwwroot/gfm/script-net/create-instances.md | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/wwwroot/gfm/script-net/create-instances.md b/src/wwwroot/gfm/script-net/create-instances.md index fe405d3..f9aa760 100644 --- a/src/wwwroot/gfm/script-net/create-instances.md +++ b/src/wwwroot/gfm/script-net/create-instances.md @@ -81,8 +81,15 @@ As the Constructor Function returns a delegate you will be able to invoke it lik an extension method or inside a filter expression: ```csharp -1.doubleAdder() -1 | doubleAdder +Constructor('Uri(string)') | to => url + +url('http://example.org') +'http://example.org'.url() +'http://example.org' | url + +// equivalent to: +'Uri'.new(['http://example.org']) +Constructor('Uri(string)')('http://example.org') ``` ### createInstance From c05066f4a2d383693a1dd862c49431d0983a6d3e Mon Sep 17 00:00:00 2001 From: Demis Bellot Date: Wed, 28 Aug 2019 10:49:20 -0400 Subject: [PATCH 090/206] Update create-instances.html --- src/wwwroot/gfm/script-net/create-instances.html | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/wwwroot/gfm/script-net/create-instances.html b/src/wwwroot/gfm/script-net/create-instances.html index 842d542..f6b7e58 100644 --- a/src/wwwroot/gfm/script-net/create-instances.html +++ b/src/wwwroot/gfm/script-net/create-instances.html @@ -49,8 +49,15 @@

Constructor('Adder(string)')(1)

As the Constructor Function returns a delegate you will be able to invoke it like a normal method where it can also be invoked as an extension method or inside a filter expression:

-
1.doubleAdder()
-1 | doubleAdder
+
Constructor('Uri(string)') | to => url
+
+url('http://example.org')
+'http://example.org'.url()
+'http://example.org' | url
+
+// equivalent to:
+'Uri'.new(['http://example.org'])
+Constructor('Uri(string)')('http://example.org')

createInstance

The createInstance is like new except it's used to create instances from a Type instead of its string Type Name:

From 432c1166aaea1141f7b73b021cfc40f24ee6a587 Mon Sep 17 00:00:00 2001 From: Demis Bellot Date: Wed, 28 Aug 2019 11:19:56 -0400 Subject: [PATCH 091/206] tweaks --- src/wwwroot/docs/script-net.html | 2 +- src/wwwroot/gfm/script-net/02.html | 3 +++ src/wwwroot/gfm/script-net/02.md | 2 ++ src/wwwroot/gfm/script-net/call-methods.html | 10 +++++----- src/wwwroot/gfm/script-net/call-methods.md | 10 +++++----- src/wwwroot/gfm/script-net/create-instances.html | 9 +++++---- src/wwwroot/gfm/script-net/create-instances.md | 8 ++++---- src/wwwroot/gfm/script-net/type-resolution.html | 2 +- src/wwwroot/gfm/script-net/type-resolution.md | 2 +- 9 files changed, 27 insertions(+), 21 deletions(-) diff --git a/src/wwwroot/docs/script-net.html b/src/wwwroot/docs/script-net.html index f8b8a03..e369e96 100644 --- a/src/wwwroot/docs/script-net.html +++ b/src/wwwroot/docs/script-net.html @@ -80,7 +80,7 @@ ## Scripting .NET APIs The following [Protected Scripts](/docs/protected-scripts) are all that's needed to create new instances, call methods and -populate instances of .NET Types, including generic Types and generic Methods but only a Type's **public** members can be accessed in `#Script`. +populate instances of .NET Types, including generic types and generic methods. {{/markdown}} diff --git a/src/wwwroot/gfm/script-net/02.html b/src/wwwroot/gfm/script-net/02.html index defe7bf..f63ccab 100644 --- a/src/wwwroot/gfm/script-net/02.html +++ b/src/wwwroot/gfm/script-net/02.html @@ -15,4 +15,7 @@ // Populate Instance object set(object instance, Dictionary<string, object> args); +
+

Note: only a Type's public members can be accessed in #Script

+
\ No newline at end of file diff --git a/src/wwwroot/gfm/script-net/02.md b/src/wwwroot/gfm/script-net/02.md index 887a53f..094e13c 100644 --- a/src/wwwroot/gfm/script-net/02.md +++ b/src/wwwroot/gfm/script-net/02.md @@ -17,3 +17,5 @@ ObjectActivator Constructor(string qualifiedConstructorName); // Populate Instance object set(object instance, Dictionary args); ``` + +> Note: only a Type's **public** members can be accessed in `#Script` \ No newline at end of file diff --git a/src/wwwroot/gfm/script-net/call-methods.html b/src/wwwroot/gfm/script-net/call-methods.html index 6a8659b..16332ae 100644 --- a/src/wwwroot/gfm/script-net/call-methods.html +++ b/src/wwwroot/gfm/script-net/call-methods.html @@ -15,8 +15,8 @@

adder3.call('Add',[3.0]) //= 6.0

Method Resolution

-

The same Resolution rules in Constructor Resolution also applies when calling methods where any ambiguous methods needs to be -called with arguments containing the exact types (as above), or you can specify the argument types of the method you want to call, +

The same Resolution rules in Constructor Resolution also applies when calling methods where any ambiguous methods needs to be +called with arguments containing the exact types (as above), or you can specify the argument types of the method you want to call, in which case it will let you use the built-in Auto Mapping to call a method expecting a double with an int argument:

adder3.call('Add(double)',[3])

@@ -36,7 +36,7 @@

Instance Methods

Function create delegates that lets you genericize the different types of method invocations in .NET, including instance methods, -instance generic methods and void Action methods:

+generic methods and void Action methods on an instance:

'InstanceLog'.new(['A']) | to => o
 Function('InstanceLog.Log') | to => log              // instance void method
 Function('InstanceLog.AllLogs') | to => allLogs      // instance method
@@ -49,7 +49,7 @@ 

o.allLogs() | to => snapshotLogs

Static Type Methods

-

Examples of using Function to call static methods and static action methods on a static Type:

+

As well as calling static methods and static void Action methods on a static Type:

Function('StaticLog.Clear')()
 Function('StaticLog.Log') | to => log                // static void method
 Function('StaticLog.AllLogs') | to => allLogs        // static method
@@ -61,7 +61,7 @@ 

allLogs() | to => snapshotLogs

Generic Static Type Methods

-

Examples of using Function to call static methods and void action methods on a generic static Type:

+

Including calling generic static methods on a generic static Type:

Function('GenericStaticLog<string>.Clear()')()
 Function('GenericStaticLog<string>.Log(string)') | to => log      // generic type static void method
 Function('GenericStaticLog<string>.AllLogs') | to => allLogs      // generic type static method
diff --git a/src/wwwroot/gfm/script-net/call-methods.md b/src/wwwroot/gfm/script-net/call-methods.md
index 7af3f50..e08a64c 100644
--- a/src/wwwroot/gfm/script-net/call-methods.md
+++ b/src/wwwroot/gfm/script-net/call-methods.md
@@ -21,8 +21,8 @@ adder3.call('Add',[3.0]) //= 6.0
 
 ### Method Resolution    
 
-The same Resolution rules in **Constructor Resolution** also applies when calling methods where any ambiguous methods needs to be
-called with arguments containing the exact types (as above), or you can specify the argument types of the method you want to call,
+The same Resolution rules in **Constructor Resolution** also applies when calling methods where any **ambiguous methods** needs to be
+called with arguments containing the **exact types** (as above), or you can specify the argument types of the method you want to call,
 in which case it will let you use the built-in Auto Mapping to call a method expecting a `double` with an `int` argument:
 
 ```js
@@ -59,7 +59,7 @@ Examples below uses classes in [ScriptTypes.cs](https://github.com/ServiceStack/
 ### Instance Methods
 
 `Function` create delegates that lets you genericize the different types of method invocations in .NET, including instance methods, 
-instance generic methods and `void` Action methods:
+generic methods and `void` Action methods on an **instance**:
 
 ```js
 'InstanceLog'.new(['A']) | to => o
@@ -76,7 +76,7 @@ o.allLogs() | to => snapshotLogs
 
 ### Static Type Methods
 
-Examples of using `Function` to call static methods and static action methods on a static Type:
+As well as calling static methods and static `void` Action methods on a **static Type**:
 
 ```js
 Function('StaticLog.Clear')()
@@ -92,7 +92,7 @@ allLogs() | to => snapshotLogs
 
 ### Generic Static Type Methods
 
-Examples of using `Function` to call static methods and void action methods on a generic static Type:
+Including calling generic static methods on a **generic static Type**:
 
 ```js
 Function('GenericStaticLog.Clear()')()
diff --git a/src/wwwroot/gfm/script-net/create-instances.html b/src/wwwroot/gfm/script-net/create-instances.html
index f6b7e58..7a3630d 100644
--- a/src/wwwroot/gfm/script-net/create-instances.html
+++ b/src/wwwroot/gfm/script-net/create-instances.html
@@ -26,7 +26,8 @@ 

'Ints'.new([1.0,2.0])
 'KeyValuePair<char,double>'.new(['A',1])

However if there are multiple constructors with the same number of arguments, it will only use the constructor where all its argument Types -match with the supplied arguments. Attempting to create an instance of the Adder class which only has constructors for string or doublewill fail with an **Ambiguous Match Exception** when trying to create it with anint`:

+match with the supplied arguments. Attempting to create an instance of the Adder class which only has constructors for string or +double will fail with an Ambiguous Match Exception when trying to create it with an int:

'Adder'.new([1])  // FAIL: Ambiguous Constructor

In this case you'll need to convert the arguments so its Types matches one of the available constructors:

'Adder'.new([1.0])
@@ -68,8 +69,8 @@ 

set

Once you've created instance you can further populate it using the set method which will let you populate public properties with a JS Object literal, performing any auto-mapping conversions as needed:

-
'Ints'.new([1,2]).set({ C:3, D:4 })
-Constructor('Ints(int,int)')(1,2).set({ C:3, D:4 })
+
'Ints'.new([1,2]).set({ C:3, D:4.0 })
+Constructor('Ints(int,int)')(1,2).set({ C:3, D:4.0 })

As set returns the instance, it can be used within a chained expression:

-
instance.set({ C:3 }).set({ D:4 }).call('GetTotal')
+
instance.set({ C:3 }).set({ D:4.0 }).call('GetTotal')
\ No newline at end of file diff --git a/src/wwwroot/gfm/script-net/create-instances.md b/src/wwwroot/gfm/script-net/create-instances.md index f9aa760..c7ab7ae 100644 --- a/src/wwwroot/gfm/script-net/create-instances.md +++ b/src/wwwroot/gfm/script-net/create-instances.md @@ -39,7 +39,7 @@ A list of arguments can be passed to the `new` method to call the constructor wi ``` However if there are multiple constructors with the same number of arguments, it will only use the constructor where all its argument Types -match with the supplied arguments. Attempting to create an instance of the `Adder` class which only has constructors for `string` or` +match with the supplied arguments. Attempting to create an instance of the `Adder` class which only has constructors for `string` or `double` will fail with an **Ambiguous Match Exception** when trying to create it with an `int`: ```js @@ -108,12 +108,12 @@ Once you've created instance you can further populate it using the `set` method with a JS Object literal, performing any auto-mapping conversions as needed: ```js -'Ints'.new([1,2]).set({ C:3, D:4 }) -Constructor('Ints(int,int)')(1,2).set({ C:3, D:4 }) +'Ints'.new([1,2]).set({ C:3, D:4.0 }) +Constructor('Ints(int,int)')(1,2).set({ C:3, D:4.0 }) ``` As `set` returns the instance, it can be used within a chained expression: ```js -instance.set({ C:3 }).set({ D:4 }).call('GetTotal') +instance.set({ C:3 }).set({ D:4.0 }).call('GetTotal') ``` diff --git a/src/wwwroot/gfm/script-net/type-resolution.html b/src/wwwroot/gfm/script-net/type-resolution.html index 47249a1..6e1ca50 100644 --- a/src/wwwroot/gfm/script-net/type-resolution.html +++ b/src/wwwroot/gfm/script-net/type-resolution.html @@ -15,7 +15,7 @@ }.Init();

All other Types (other than .NET built-in types) not registered in ScriptTypes, ScriptAssemblies or have their namespace defined in ScriptNamespaces will need to be referenced using -their Full Type Name. This same Type resolution applies for all references of Types in #Script.

+their Full Type Name. This same Type resolution applies for all references of Types in #Script.

Examples Configuration

The examples below assumes a ScriptContext configured with:

diff --git a/src/wwwroot/gfm/script-net/type-resolution.md b/src/wwwroot/gfm/script-net/type-resolution.md index cd9ac48..e08b501 100644 --- a/src/wwwroot/gfm/script-net/type-resolution.md +++ b/src/wwwroot/gfm/script-net/type-resolution.md @@ -23,7 +23,7 @@ var context = new ScriptContext { All other Types (other than [.NET built-in types](https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/built-in-types-table)) not registered in `ScriptTypes`, `ScriptAssemblies` or have their namespace defined in `ScriptNamespaces` will need to be referenced using -their Full Type Name. This same Type resolution applies for all references of Types in `#Script`. +their **Full Type Name**. This same Type resolution applies for all references of Types in `#Script`. #### Examples Configuration From 4942fac72ad3ac4633b407a9605c2ba31e4cc31d Mon Sep 17 00:00:00 2001 From: Demis Bellot Date: Wed, 28 Aug 2019 17:06:42 -0400 Subject: [PATCH 092/206] Add docs for ServiceStack Blocks --- src/Configure.Nuglify.cs | 29 +++++++++ src/SharpScript.csproj | 1 + src/Startup.cs | 11 ++-- src/TemplateServices.cs | 4 ++ src/wwwroot/docs/blocks.html | 111 ++++++++++++++++++++++++++++++----- 5 files changed, 135 insertions(+), 21 deletions(-) create mode 100644 src/Configure.Nuglify.cs diff --git a/src/Configure.Nuglify.cs b/src/Configure.Nuglify.cs new file mode 100644 index 0000000..657081f --- /dev/null +++ b/src/Configure.Nuglify.cs @@ -0,0 +1,29 @@ +using ServiceStack; +using ServiceStack.Html; +using NUglify; + +namespace SharpScript +{ + public class NUglifyJsMinifier : ICompressor + { + public string Compress(string js) => Uglify.Js(js).Code; + } + public class NUglifyCssMinifier : ICompressor + { + public string Compress(string css) => Uglify.Css(css).Code; + } + public class NUglifyHtmlMinifier : ICompressor + { + public string Compress(string html) => Uglify.Html(html).Code; + } + + public class ConfigureNUglify : IConfigureAppHost + { + public void Configure(IAppHost appHost) + { + Minifiers.JavaScript = new NUglifyJsMinifier(); + Minifiers.Css = new NUglifyCssMinifier(); + Minifiers.Html = new NUglifyHtmlMinifier(); + } + } +} \ No newline at end of file diff --git a/src/SharpScript.csproj b/src/SharpScript.csproj index 365a962..fcce1c8 100644 --- a/src/SharpScript.csproj +++ b/src/SharpScript.csproj @@ -10,6 +10,7 @@ + diff --git a/src/Startup.cs b/src/Startup.cs index 9133a6b..d611215 100644 --- a/src/Startup.cs +++ b/src/Startup.cs @@ -3,19 +3,18 @@ using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Configuration; using ServiceStack; -using System.Net; -using System.Web; namespace SharpScript { - public class Startup + public class Startup : ModularStartup { + public Startup(IConfiguration configuration) : base(configuration){} + // This method gets called by the runtime. Use this method to add services to the container. // For more information on how to configure your application, visit http://go.microsoft.com/fwlink/?LinkID=398940 - public void ConfigureServices(IServiceCollection services) - { - } + public new void ConfigureServices(IServiceCollection services) {} // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory) diff --git a/src/TemplateServices.cs b/src/TemplateServices.cs index 00b4b63..63258e3 100644 --- a/src/TemplateServices.cs +++ b/src/TemplateServices.cs @@ -67,11 +67,15 @@ public async Task Any(EvaluateScripts request) public async Task Any(EvaluateScript request) { var context = new ScriptContext { + DebugMode = false, ScriptMethods = { new DbScripts(), new AutoQueryScripts(), new ServiceStackScripts(), new CustomScriptMethods(), + }, + Plugins = { + new ServiceStackScriptBlocks(), } }; //Register any dependencies filters need: diff --git a/src/wwwroot/docs/blocks.html b/src/wwwroot/docs/blocks.html index 9d9c5f4..0a7e883 100644 --- a/src/wwwroot/docs/blocks.html +++ b/src/wwwroot/docs/blocks.html @@ -23,10 +23,10 @@ ## ServiceStack Blocks - - [minifyjs](https://github.com/ServiceStack/ServiceStack/blob/master/src/ServiceStack/ServiceStackScripts.cs) - - [minifycss](https://github.com/ServiceStack/ServiceStack/blob/master/src/ServiceStack/ServiceStackScripts.cs) - - [minifyhtml](https://github.com/ServiceStack/ServiceStack/blob/master/src/ServiceStack/ServiceStackScripts.cs) - - [svg](https://github.com/ServiceStack/ServiceStack/blob/master/src/ServiceStack/ServiceStackScripts.cs) + - [minifyjs](#minifyjs) + - [minifycss](#minifycss) + - [minifyhtml](#minifyhtml) + - [svg](#svg) The syntax for blocks follows the familiar [handlebars block helpers](https://handlebarsjs.com/block_helpers.html) in both syntax and functionality. `#Script` also includes most of handlebars.js block helpers which are useful in a HTML template language whilst minimizing any porting efforts if @@ -398,21 +398,17 @@
Autowired using ScriptContext IOC
Similar to `keyvalues`, you can specify a multi-column inline data set using the `{{#csv}}` block, e.g: -
-
- -
-
-
-
-
+Total Cost: {{ cars | sum => it[2] | currency }}" }) }} + +{{#markdown}} The [CsvBlock.cs](https://github.com/ServiceStack/ServiceStack/blob/master/src/ServiceStack.Common/Script/Blocks/CsvBlock.cs) implementation is similar to `keyvalues` except passes the trimmed string body to `FromCsv` into a string List and assigns the result to the specified name: @@ -471,10 +467,95 @@
Autowired using ScriptContext IOC
{{ 'gfm/blocks/19.md' | githubMarkdown }} +

ServiceStack Blocks

+ + +{{#markdown}} + +ServiceStack's Blocks are registered by default in [#Script Pages](/docs/sharp-pages) that can be registered in a new `ScriptContext` +by adding the `ServiceStackScriptBlocks` plugin: + + var context = new ScriptContext { + Plugins = { + new ServiceStackScriptBlocks(), + } + }.Init(); + +### Mix in NUglify + +You can configure ServiceStack and `#Script` to use Nuglify's Advanced HTML, CSS, JS Minifiers using [mix](https://docs.servicestack.net/mix-tool) with: + + $ mix nuglify + +Which will add [Configure.Nuglify.cs](https://gist.github.com/gistlyn/4bdb79d21f199c22b8a86f032c186e2d) to your **HOST** project. + +To assist with debugging during development, **no minification** is applied when `DebugMode=true`. + +All minifier Blocks supports an additional `` argument to store the captured output of the minifier block into, e.g: + + {{#minifier capturedMinification}} ... {{/minifier}} + {{capturedMinification}} + +That also supports using the `appendTo` modifier to **concatenate** the minified output instead of replacing it, e.g: + + {{#minifier appendTo capturedMinification}} ... {{/minifier}} + {{#minifier appendTo capturedMinification}} ... {{/minifier}} + {{capturedMinification}} + +### minifyjs + +Use the `minifyjs` block to minify inline JavaScript: + +{{/markdown}} + +{{ "live-template" | partial({ rows:6, template: "{{#minifyjs}} +function add(left, right) { + return left + right; +} +add(1, 2); +{{/minifyjs}}" }) }} + +{{#markdown}} +### minifycss + +Use the `minifycss` block to minify inline CSS: + +{{/markdown}} + +{{ "live-template" | partial({ rows:5, template: "{{#minifycss}} +body { + background-color: yellow; +} +{{/minifycss}}" }) }} + +{{#markdown}} +### minifyhtml + +Use the `minifyhtml` block to minify HTML: + +{{/markdown}} + +{{ "live-template" | partial({ rows:8, template: "{{#minifyhtml capturedHtml}} +

+ Title +

+

+ Content +


+{{/minifyhtml}} {{capturedHtml}}" }) }} + +{{#markdown}} +### svg + +Use the `svg` block in your `_init.html` Startup Script to [register SVG Images with ServiceStack](https://docs.servicestack.net/svg#registering-svgs-from-_inithtml). + +{{/markdown}} + +

Removing Blocks

{{#markdown}} -Like everything else in SharpScript, all built-in Blocks can be removed. To make it easy to remove groups of related blocks you can just remove the +Like everything else in `#Script`, all built-in Blocks can be removed. To make it easy to remove groups of related blocks you can just remove the plugin that registered them using the `RemovePlugins()` API, e.g: {{/markdown}} From cf061a45fb1ceb1eff38ef5f9d29ad5a0f460a24 Mon Sep 17 00:00:00 2001 From: Demis Bellot Date: Fri, 30 Aug 2019 16:02:47 -0400 Subject: [PATCH 093/206] Update Scripting .NET docs --- src/ScriptTypes.cs | 24 ++++ src/wwwroot/docs/blocks.html | 6 +- src/wwwroot/docs/script-net.html | 4 + src/wwwroot/gfm/blocks/21.html | 2 +- src/wwwroot/gfm/blocks/21.md | 2 +- src/wwwroot/gfm/blocks/22.html | 2 +- src/wwwroot/gfm/blocks/22.md | 2 +- src/wwwroot/gfm/script-net/02.html | 4 +- src/wwwroot/gfm/script-net/02.md | 6 +- src/wwwroot/gfm/script-net/Function.html | 103 ++++++++++++++ src/wwwroot/gfm/script-net/Function.md | 133 ++++++++++++++++++ src/wwwroot/gfm/script-net/call-methods.html | 48 ------- src/wwwroot/gfm/script-net/call-methods.md | 68 --------- .../gfm/script-net/create-instances.html | 5 + .../gfm/script-net/create-instances.md | 9 ++ src/wwwroot/gfm/script-plugins/01.html | 7 +- src/wwwroot/gfm/script-plugins/01.md | 7 +- 17 files changed, 298 insertions(+), 134 deletions(-) create mode 100644 src/wwwroot/gfm/script-net/Function.html create mode 100644 src/wwwroot/gfm/script-net/Function.md diff --git a/src/ScriptTypes.cs b/src/ScriptTypes.cs index 585c3a0..c839c32 100644 --- a/src/ScriptTypes.cs +++ b/src/ScriptTypes.cs @@ -45,6 +45,30 @@ public class StaticLog public static string AllLogs() => sb.ToString(); public static void Clear() => sb.Clear(); + + public static string Prop { get; } = "StaticLog.Prop"; + public static string Field = "StaticLog.Field"; + public const string Const = "StaticLog.Const"; + + public string InstanceProp { get; } = "StaticLog.InstanceProp"; + public string InstanceField = "StaticLog.InstanceField"; + + public class Inner1 + { + public static string Prop1 { get; } = "StaticLog.Inner1.Prop1"; + public static string Field1 = "StaticLog.Inner1.Field1"; + public const string Const1 = "StaticLog.Inner1.Const1"; + + public string InstanceProp1 { get; } = "StaticLog.Inner1.InstanceProp1"; + public string InstanceField1 = "StaticLog.Inner1.InstanceField1"; + + public static class Inner2 + { + public static string Prop2 { get; } = "StaticLog.Inner1.Inner2.Prop2"; + public static string Field2 = "StaticLog.Inner1.Inner2.Field2"; + public const string Const2 = "StaticLog.Inner1.Inner2.Const2"; + } + } } public class InstanceLog diff --git a/src/wwwroot/docs/blocks.html b/src/wwwroot/docs/blocks.html index 0a7e883..16d8ba1 100644 --- a/src/wwwroot/docs/blocks.html +++ b/src/wwwroot/docs/blocks.html @@ -311,7 +311,7 @@
Autowired using ScriptContext IOC
{{#markdown}} Although their limited to the configured [MaxStackDepth](/docs/sandbox#max-stack-depth). -Source code for [FunctionBlock.cs](https://github.com/ServiceStack/ServiceStack/blob/master/src/ServiceStack.Common/Script/Blocks/FunctionBlock.cs). +Source code for [FunctionScriptBlock.cs](https://github.com/ServiceStack/ServiceStack/blob/master/src/ServiceStack.Common/Script/Blocks/FunctionScriptBlock.cs). > `function` block is available from [v5.6.1 on MyGet](https://docs.servicestack.net/myget) @@ -384,7 +384,7 @@
Autowired using ScriptContext IOC
{{/keyvalues}} -The [KeyValuesBlock.cs](https://github.com/ServiceStack/ServiceStack/blob/master/src/ServiceStack.Common/Script/Blocks/KeyValuesBlock.cs) +The [KeyValuesScriptBlock.cs](https://github.com/ServiceStack/ServiceStack/blob/master/src/ServiceStack.Common/Script/Blocks/KeyValuesScriptBlock.cs) implementation is fairly straight forward where it passes the string body to `ParseKeyValueText()` method with an optional delimiter and assigns the results to the specified variable name: {{/markdown}} @@ -410,7 +410,7 @@
Autowired using ScriptContext IOC
{{#markdown}} -The [CsvBlock.cs](https://github.com/ServiceStack/ServiceStack/blob/master/src/ServiceStack.Common/Script/Blocks/CsvBlock.cs) +The [CsvScriptBlock.cs](https://github.com/ServiceStack/ServiceStack/blob/master/src/ServiceStack.Common/Script/Blocks/CsvScriptBlock.cs) implementation is similar to `keyvalues` except passes the trimmed string body to `FromCsv` into a string List and assigns the result to the specified name: {{/markdown}} diff --git a/src/wwwroot/docs/script-net.html b/src/wwwroot/docs/script-net.html index e369e96..b723f49 100644 --- a/src/wwwroot/docs/script-net.html +++ b/src/wwwroot/docs/script-net.html @@ -98,6 +98,10 @@

Calling Methods

{{ 'gfm/script-net/call-methods.md' | githubMarkdown }} +

Function

+ +{{ 'gfm/script-net/Function.md' | githubMarkdown }} + {{#markdown}} {{/markdown}} diff --git a/src/wwwroot/gfm/blocks/21.html b/src/wwwroot/gfm/blocks/21.html index 53531cc..f2b3746 100644 --- a/src/wwwroot/gfm/blocks/21.html +++ b/src/wwwroot/gfm/blocks/21.html @@ -1,4 +1,4 @@ -
public class KeyValuesBlock : ScriptBlock
+
public class KeyValuesScriptBlock : ScriptBlock
 {
     public override string Name => "keyvalues";
     
diff --git a/src/wwwroot/gfm/blocks/21.md b/src/wwwroot/gfm/blocks/21.md
index 434748c..4132c86 100644
--- a/src/wwwroot/gfm/blocks/21.md
+++ b/src/wwwroot/gfm/blocks/21.md
@@ -1,5 +1,5 @@
 ```csharp
-public class KeyValuesBlock : ScriptBlock
+public class KeyValuesScriptBlock : ScriptBlock
 {
     public override string Name => "keyvalues";
     
diff --git a/src/wwwroot/gfm/blocks/22.html b/src/wwwroot/gfm/blocks/22.html
index 7dd3647..4155836 100644
--- a/src/wwwroot/gfm/blocks/22.html
+++ b/src/wwwroot/gfm/blocks/22.html
@@ -1,4 +1,4 @@
-
public class CsvBlock : ScriptBlock
+
public class CsvScriptBlock : ScriptBlock
 {
     public override string Name => "csv";
     
diff --git a/src/wwwroot/gfm/blocks/22.md b/src/wwwroot/gfm/blocks/22.md
index 1dba6a1..078181f 100644
--- a/src/wwwroot/gfm/blocks/22.md
+++ b/src/wwwroot/gfm/blocks/22.md
@@ -1,5 +1,5 @@
 ```csharp
-public class CsvBlock : ScriptBlock
+public class CsvScriptBlock : ScriptBlock
 {
     public override string Name => "csv";
     
diff --git a/src/wwwroot/gfm/script-net/02.html b/src/wwwroot/gfm/script-net/02.html
index f63ccab..c9a7a93 100644
--- a/src/wwwroot/gfm/script-net/02.html
+++ b/src/wwwroot/gfm/script-net/02.html
@@ -4,14 +4,14 @@
 // Call Methods
 object call(object instance, string name);
 object call(object instance, string name, List<object> args);
-Delegate Function(string qualifiedMethodName);
+Delegate Function(string qualifiedMethodName);                    // alias F(string)
 
 // Create Instances
 object new(string typeName);
 object new(string typeName, List<object> constructorArgs);
 object createInstance(Type type);
 object createInstance(Type type, List<object> constructorArgs);
-ObjectActivator Constructor(string qualifiedConstructorName);
+ObjectActivator Constructor(string qualifiedConstructorName);     // alias C(string)
 
 // Populate Instance
 object set(object instance, Dictionary<string, object> args);
diff --git a/src/wwwroot/gfm/script-net/02.md b/src/wwwroot/gfm/script-net/02.md index 094e13c..33a73d6 100644 --- a/src/wwwroot/gfm/script-net/02.md +++ b/src/wwwroot/gfm/script-net/02.md @@ -5,17 +5,17 @@ Type typeof(string typeName); // Call Methods object call(object instance, string name); object call(object instance, string name, List args); -Delegate Function(string qualifiedMethodName); +Delegate Function(string qualifiedMethodName); // alias F(string) // Create Instances object new(string typeName); object new(string typeName, List constructorArgs); object createInstance(Type type); object createInstance(Type type, List constructorArgs); -ObjectActivator Constructor(string qualifiedConstructorName); +ObjectActivator Constructor(string qualifiedConstructorName); // alias C(string) // Populate Instance object set(object instance, Dictionary args); ``` -> Note: only a Type's **public** members can be accessed in `#Script` \ No newline at end of file +> Note: only a Type's **public** members can be accessed in `#Script` diff --git a/src/wwwroot/gfm/script-net/Function.html b/src/wwwroot/gfm/script-net/Function.html new file mode 100644 index 0000000..7ef828b --- /dev/null +++ b/src/wwwroot/gfm/script-net/Function.html @@ -0,0 +1,103 @@ +

call only invokes instance methods, to call static methods you'll need to create a delegate of it first using the +Function method

+
Function('Console.WriteLine(string)') | to => writeln
+

Which lets you call it like a regular Script method:

+
writeln('A')
+'A'.writeln()
+Function('Console.WriteLine(string)')('A')
+

Examples below uses classes in ScriptTypes.cs.

+

+Instance Methods

+

Function create delegates that lets you genericize the different types of method invocations in .NET, including instance methods, +generic methods and void Action methods on an instance:

+
'InstanceLog'.new(['A']) | to => o
+Function('InstanceLog.Log') | to => log              // instance void method
+Function('InstanceLog.AllLogs') | to => allLogs      // instance method
+Function('InstanceLog.Log<int>') | to => genericLog  // instance generic method
+
+o.log('B')
+log(o,'C')
+o.genericLog(1)
+o | genericLog(2)    
+o.allLogs() | to => snapshotLogs
+

+Static Type Methods

+

As well as calling static methods and static void Action methods on a static Type:

+
Function('StaticLog.Clear')()
+Function('StaticLog.Log') | to => log                // static void method
+Function('StaticLog.AllLogs') | to => allLogs        // static method
+Function('StaticLog.Log<int>') | to => genericLog    // static generic method
+
+log('A')
+'B'.log()
+genericLog('C')
+allLogs() | to => snapshotLogs
+

+Generic Static Type Methods

+

Including calling generic static methods on a generic static Type:

+
Function('GenericStaticLog<string>.Clear()')()
+Function('GenericStaticLog<string>.Log(string)') | to => log      // generic type static void method
+Function('GenericStaticLog<string>.AllLogs') | to => allLogs      // generic type static method
+Function('GenericStaticLog<string>.Log<int>') | to => genericLog  // generic type generic static method
+
+log('A')
+'B'.log()
+genericLog('C')
+allLogs() | to => snapshotLogs
+

+F() alias

+

You can use the shorter F() alias to reduce syntax noise when writing #Script that heavily interops directly with .NET Classes.

+

+Instance and Static Properties, Fields and Constants

+

In addition to being able to create Delegates that genericize access to .NET Methods, it can also be used to create a delegate +for accessing Instance and Static Properties, Fields and Constants including members of Inner Classes, e.g:

+

Each of the members of the following Type definition:

+
public class StaticLog
+{
+    public static string Prop { get; } = "StaticLog.Prop";
+    public static string Field = "StaticLog.Field";
+    public const string Const = "StaticLog.Const";
+
+    public string InstanceProp { get; } = "StaticLog.InstanceProp";
+    public string InstanceField = "StaticLog.InstanceField";
+
+    public class Inner1
+    {
+        public static string Prop1 { get; } = "StaticLog.Inner1.Prop1";
+        public static string Field1 = "StaticLog.Inner1.Field1";
+        public const string Const1 = "StaticLog.Inner1.Const1";
+
+        public string InstanceProp1 { get; } = "StaticLog.Inner1.InstanceProp1";
+        public string InstanceField1 = "StaticLog.Inner1.InstanceField1";
+
+        public static class Inner2
+        {
+            public static string Prop2 { get; } = "StaticLog.Inner1.Inner2.Prop2";
+            public static string Field2 = "StaticLog.Inner1.Inner2.Field2";
+            public const string Const2 = "StaticLog.Inner1.Inner2.Const2";
+        }
+    }
+}
+

Can be accessed the same way, where you can use Function to create a zero-argument delegate for static members that can be immediately invoked, +or a 1 argument Delegate for instance members.

+

Examples below uses Function's shorter F() alias:

+
F('StaticLog.Prop')()
+F('StaticLog.Field')()
+F('StaticLog.Const')()
+
+F('StaticLog.Inner1.Prop1')()
+F('StaticLog.Inner1.Field1')()
+F('StaticLog.Inner1.Const1')()
+
+F('StaticLog.Inner1.Inner2.Prop2')()
+F('StaticLog.Inner1.Inner2.Field2')()
+F('StaticLog.Inner1.Inner2.Const2')()
+
+'StaticLog'.new() | to => o
+F('StaticLog.InstanceProp')(o)
+F('StaticLog.InstanceField')(o)
+
+'StaticLog.Inner1'.new() | to => o
+F('StaticLog.Inner1.InstanceProp1')(o)
+F('StaticLog.Inner1.InstanceField1')(o)
+
\ No newline at end of file diff --git a/src/wwwroot/gfm/script-net/Function.md b/src/wwwroot/gfm/script-net/Function.md new file mode 100644 index 0000000..12107f2 --- /dev/null +++ b/src/wwwroot/gfm/script-net/Function.md @@ -0,0 +1,133 @@ +`call` only invokes instance methods, to call static methods you'll need to create a **delegate** of it first using the +`Function` method + +```js +Function('Console.WriteLine(string)') | to => writeln +``` + +Which lets you call it like a regular Script method: + +```js +writeln('A') +'A'.writeln() +Function('Console.WriteLine(string)')('A') +``` + +Examples below uses classes in [ScriptTypes.cs](https://github.com/ServiceStack/sharpscript/blob/master/src/ScriptTypes.cs). + +### Instance Methods + +`Function` create delegates that lets you genericize the different types of method invocations in .NET, including instance methods, +generic methods and `void` Action methods on an **instance**: + +```js +'InstanceLog'.new(['A']) | to => o +Function('InstanceLog.Log') | to => log // instance void method +Function('InstanceLog.AllLogs') | to => allLogs // instance method +Function('InstanceLog.Log') | to => genericLog // instance generic method + +o.log('B') +log(o,'C') +o.genericLog(1) +o | genericLog(2) +o.allLogs() | to => snapshotLogs +``` + +### Static Type Methods + +As well as calling static methods and static `void` Action methods on a **static Type**: + +```js +Function('StaticLog.Clear')() +Function('StaticLog.Log') | to => log // static void method +Function('StaticLog.AllLogs') | to => allLogs // static method +Function('StaticLog.Log') | to => genericLog // static generic method + +log('A') +'B'.log() +genericLog('C') +allLogs() | to => snapshotLogs +``` + +### Generic Static Type Methods + +Including calling generic static methods on a **generic static Type**: + +```js +Function('GenericStaticLog.Clear()')() +Function('GenericStaticLog.Log(string)') | to => log // generic type static void method +Function('GenericStaticLog.AllLogs') | to => allLogs // generic type static method +Function('GenericStaticLog.Log') | to => genericLog // generic type generic static method + +log('A') +'B'.log() +genericLog('C') +allLogs() | to => snapshotLogs +``` + +### F() alias + +You can use the shorter `F()` alias to reduce syntax noise when writing #Script that heavily interops directly with .NET Classes. + +### Instance and Static Properties, Fields and Constants + +In addition to being able to create Delegates that genericize access to .NET Methods, it can also be used to create a delegate +for accessing Instance and Static Properties, Fields and Constants including members of Inner Classes, e.g: + +Each of the members of the following Type definition: + +```csharp +public class StaticLog +{ + public static string Prop { get; } = "StaticLog.Prop"; + public static string Field = "StaticLog.Field"; + public const string Const = "StaticLog.Const"; + + public string InstanceProp { get; } = "StaticLog.InstanceProp"; + public string InstanceField = "StaticLog.InstanceField"; + + public class Inner1 + { + public static string Prop1 { get; } = "StaticLog.Inner1.Prop1"; + public static string Field1 = "StaticLog.Inner1.Field1"; + public const string Const1 = "StaticLog.Inner1.Const1"; + + public string InstanceProp1 { get; } = "StaticLog.Inner1.InstanceProp1"; + public string InstanceField1 = "StaticLog.Inner1.InstanceField1"; + + public static class Inner2 + { + public static string Prop2 { get; } = "StaticLog.Inner1.Inner2.Prop2"; + public static string Field2 = "StaticLog.Inner1.Inner2.Field2"; + public const string Const2 = "StaticLog.Inner1.Inner2.Const2"; + } + } +} +``` + +Can be accessed the same way, where you can use `Function` to create a zero-argument delegate for static members that can be immediately invoked, +or a 1 argument Delegate for instance members. + +Examples below uses Function's shorter `F()` alias: + +```js +F('StaticLog.Prop')() +F('StaticLog.Field')() +F('StaticLog.Const')() + +F('StaticLog.Inner1.Prop1')() +F('StaticLog.Inner1.Field1')() +F('StaticLog.Inner1.Const1')() + +F('StaticLog.Inner1.Inner2.Prop2')() +F('StaticLog.Inner1.Inner2.Field2')() +F('StaticLog.Inner1.Inner2.Const2')() + +'StaticLog'.new() | to => o +F('StaticLog.InstanceProp')(o) +F('StaticLog.InstanceField')(o) + +'StaticLog.Inner1'.new() | to => o +F('StaticLog.Inner1.InstanceProp1')(o) +F('StaticLog.Inner1.InstanceField1')(o) +``` diff --git a/src/wwwroot/gfm/script-net/call-methods.html b/src/wwwroot/gfm/script-net/call-methods.html index 16332ae..34a3504 100644 --- a/src/wwwroot/gfm/script-net/call-methods.html +++ b/src/wwwroot/gfm/script-net/call-methods.html @@ -23,52 +23,4 @@

Generic Methods

You can call generic methods by specifying the Generic Type in the method name:

'Ints'.new([1,2]).call('GenericMethod<string>',['A'])
-

-Function

-

call only invokes instance methods, to call static methods you'll need to create a delegate of it first using the -Function method

-
Function('Console.WriteLine(string)') | to => writeln
-

Which lets you call it like a regular Script method:

-
writeln('A')
-'A'.writeln()
-Function('Console.WriteLine(string)')('A')
-

Examples below uses classes in ScriptTypes.cs.

-

-Instance Methods

-

Function create delegates that lets you genericize the different types of method invocations in .NET, including instance methods, -generic methods and void Action methods on an instance:

-
'InstanceLog'.new(['A']) | to => o
-Function('InstanceLog.Log') | to => log              // instance void method
-Function('InstanceLog.AllLogs') | to => allLogs      // instance method
-Function('InstanceLog.Log<int>') | to => genericLog  // instance generic method
-
-o.log('B')
-log(o,'C')
-o.genericLog(1)
-o | genericLog(2)    
-o.allLogs() | to => snapshotLogs
-

-Static Type Methods

-

As well as calling static methods and static void Action methods on a static Type:

-
Function('StaticLog.Clear')()
-Function('StaticLog.Log') | to => log                // static void method
-Function('StaticLog.AllLogs') | to => allLogs        // static method
-Function('StaticLog.Log<int>') | to => genericLog    // static generic method
-
-log('A')
-'B'.log()
-genericLog('C')
-allLogs() | to => snapshotLogs
-

-Generic Static Type Methods

-

Including calling generic static methods on a generic static Type:

-
Function('GenericStaticLog<string>.Clear()')()
-Function('GenericStaticLog<string>.Log(string)') | to => log      // generic type static void method
-Function('GenericStaticLog<string>.AllLogs') | to => allLogs      // generic type static method
-Function('GenericStaticLog<string>.Log<int>') | to => genericLog  // generic type generic static method
-
-log('A')
-'B'.log()
-genericLog('C')
-allLogs() | to => snapshotLogs
\ No newline at end of file diff --git a/src/wwwroot/gfm/script-net/call-methods.md b/src/wwwroot/gfm/script-net/call-methods.md index e08a64c..46627f0 100644 --- a/src/wwwroot/gfm/script-net/call-methods.md +++ b/src/wwwroot/gfm/script-net/call-methods.md @@ -37,71 +37,3 @@ You can call generic methods by specifying the Generic Type in the method name: 'Ints'.new([1,2]).call('GenericMethod',['A']) ``` -## Function - -`call` only invokes instance methods, to call static methods you'll need to create a **delegate** of it first using the -`Function` method - -```js -Function('Console.WriteLine(string)') | to => writeln -``` - -Which lets you call it like a regular Script method: - -```js -writeln('A') -'A'.writeln() -Function('Console.WriteLine(string)')('A') -``` - -Examples below uses classes in [ScriptTypes.cs](https://github.com/ServiceStack/sharpscript/blob/master/src/ScriptTypes.cs). - -### Instance Methods - -`Function` create delegates that lets you genericize the different types of method invocations in .NET, including instance methods, -generic methods and `void` Action methods on an **instance**: - -```js -'InstanceLog'.new(['A']) | to => o -Function('InstanceLog.Log') | to => log // instance void method -Function('InstanceLog.AllLogs') | to => allLogs // instance method -Function('InstanceLog.Log') | to => genericLog // instance generic method - -o.log('B') -log(o,'C') -o.genericLog(1) -o | genericLog(2) -o.allLogs() | to => snapshotLogs -``` - -### Static Type Methods - -As well as calling static methods and static `void` Action methods on a **static Type**: - -```js -Function('StaticLog.Clear')() -Function('StaticLog.Log') | to => log // static void method -Function('StaticLog.AllLogs') | to => allLogs // static method -Function('StaticLog.Log') | to => genericLog // static generic method - -log('A') -'B'.log() -genericLog('C') -allLogs() | to => snapshotLogs -``` - -### Generic Static Type Methods - -Including calling generic static methods on a **generic static Type**: - -```js -Function('GenericStaticLog.Clear()')() -Function('GenericStaticLog.Log(string)') | to => log // generic type static void method -Function('GenericStaticLog.AllLogs') | to => allLogs // generic type static method -Function('GenericStaticLog.Log') | to => genericLog // generic type generic static method - -log('A') -'B'.log() -genericLog('C') -allLogs() | to => snapshotLogs -``` diff --git a/src/wwwroot/gfm/script-net/create-instances.html b/src/wwwroot/gfm/script-net/create-instances.html index 7a3630d..22e6182 100644 --- a/src/wwwroot/gfm/script-net/create-instances.html +++ b/src/wwwroot/gfm/script-net/create-instances.html @@ -60,6 +60,11 @@

'Uri'.new(['http://example.org']) Constructor('Uri(string)')('http://example.org')

+C() alias

+

To reduce syntax noise when needing to create a lot of constructors you can use the much shorter alias C instead of Constructor:

+
C('Uri(string)') | to => url
+C('Adder(double)')(1)
+

createInstance

The createInstance is like new except it's used to create instances from a Type instead of its string Type Name:

typeof('Ints').createInstance([1,2])
diff --git a/src/wwwroot/gfm/script-net/create-instances.md b/src/wwwroot/gfm/script-net/create-instances.md
index c7ab7ae..68f372a 100644
--- a/src/wwwroot/gfm/script-net/create-instances.md
+++ b/src/wwwroot/gfm/script-net/create-instances.md
@@ -92,6 +92,15 @@ url('http://example.org')
 Constructor('Uri(string)')('http://example.org')
 ```
 
+### C() alias
+
+To reduce syntax noise when needing to create a lot of constructors you can use the much shorter alias `C` instead of `Constructor`:
+
+```js
+C('Uri(string)') | to => url
+C('Adder(double)')(1)
+```
+
 ### createInstance
 
 The `createInstance` is like `new` except it's used to create instances from a `Type` instead of its `string` Type Name:
diff --git a/src/wwwroot/gfm/script-plugins/01.html b/src/wwwroot/gfm/script-plugins/01.html
index 79392f7..6b4ef0b 100644
--- a/src/wwwroot/gfm/script-plugins/01.html
+++ b/src/wwwroot/gfm/script-plugins/01.html
@@ -72,9 +72,10 @@ 

new PartialScriptBlock(), new WithScriptBlock(), new NoopScriptBlock(), - new KeyValuesBlock(), - new CsvBlock(), - new FunctionBlock(), + new KeyValuesScriptBlock(), + new CsvScriptBlock(), + new FunctionScriptBlock(), + new WhileScriptBlock(), }); } }

diff --git a/src/wwwroot/gfm/script-plugins/01.md b/src/wwwroot/gfm/script-plugins/01.md index 76bcadd..40a8bd0 100644 --- a/src/wwwroot/gfm/script-plugins/01.md +++ b/src/wwwroot/gfm/script-plugins/01.md @@ -93,9 +93,10 @@ public class DefaultScriptBlocks : IScriptPlugin new PartialScriptBlock(), new WithScriptBlock(), new NoopScriptBlock(), - new KeyValuesBlock(), - new CsvBlock(), - new FunctionBlock(), + new KeyValuesScriptBlock(), + new CsvScriptBlock(), + new FunctionScriptBlock(), + new WhileScriptBlock(), }); } } From 69b80fdfab72f73ef2f5a7fe763af4cf3151693d Mon Sep 17 00:00:00 2001 From: Demis Bellot Date: Fri, 30 Aug 2019 16:14:25 -0400 Subject: [PATCH 094/206] Update Function docs --- src/wwwroot/gfm/script-net/Function.html | 7 ++++--- src/wwwroot/gfm/script-net/Function.md | 8 +++++--- src/wwwroot/gfm/script-net/call-methods.html | 1 + src/wwwroot/gfm/script-net/call-methods.md | 1 + 4 files changed, 11 insertions(+), 6 deletions(-) diff --git a/src/wwwroot/gfm/script-net/Function.html b/src/wwwroot/gfm/script-net/Function.html index 7ef828b..96a265f 100644 --- a/src/wwwroot/gfm/script-net/Function.html +++ b/src/wwwroot/gfm/script-net/Function.html @@ -1,11 +1,12 @@ -

call only invokes instance methods, to call static methods you'll need to create a delegate of it first using the -Function method

+

Function is a universal accessor for .NET Types where it can create a cached delegate to access Instance, Static and Generic Static Types - Including Nested Types (aka Inner Classes), Instance, Static and Generic Methods of those Types as well as their Instance and Static Properties, +Fields and Constants.

+

As a simple example we'll use Function to create a delegate to call .NET's System.Console.WriteLine(string) static method:

Function('Console.WriteLine(string)') | to => writeln

Which lets you call it like a regular Script method:

writeln('A')
 'A'.writeln()
 Function('Console.WriteLine(string)')('A')
-

Examples below uses classes in ScriptTypes.cs.

+

All Examples below uses classes defined in ScriptTypes.cs.

Instance Methods

Function create delegates that lets you genericize the different types of method invocations in .NET, including instance methods, diff --git a/src/wwwroot/gfm/script-net/Function.md b/src/wwwroot/gfm/script-net/Function.md index 12107f2..314d881 100644 --- a/src/wwwroot/gfm/script-net/Function.md +++ b/src/wwwroot/gfm/script-net/Function.md @@ -1,5 +1,7 @@ -`call` only invokes instance methods, to call static methods you'll need to create a **delegate** of it first using the -`Function` method +Function is a universal accessor for .NET Types where it can create a cached **delegate** to access Instance, Static and Generic Static Types - Including Nested Types (aka Inner Classes), Instance, Static and Generic Methods of those Types as well as their Instance and Static Properties, +Fields and Constants. + +As a simple example we'll use `Function` to create a delegate to call .NET's `System.Console.WriteLine(string)` static method: ```js Function('Console.WriteLine(string)') | to => writeln @@ -13,7 +15,7 @@ writeln('A') Function('Console.WriteLine(string)')('A') ``` -Examples below uses classes in [ScriptTypes.cs](https://github.com/ServiceStack/sharpscript/blob/master/src/ScriptTypes.cs). +All Examples below uses classes defined in [ScriptTypes.cs](https://github.com/ServiceStack/sharpscript/blob/master/src/ScriptTypes.cs). ### Instance Methods diff --git a/src/wwwroot/gfm/script-net/call-methods.html b/src/wwwroot/gfm/script-net/call-methods.html index 34a3504..4489885 100644 --- a/src/wwwroot/gfm/script-net/call-methods.html +++ b/src/wwwroot/gfm/script-net/call-methods.html @@ -23,4 +23,5 @@

Generic Methods

You can call generic methods by specifying the Generic Type in the method name:

'Ints'.new([1,2]).call('GenericMethod<string>',['A'])
+

call only invokes instance methods, to call static methods you'll need to use Function.

\ No newline at end of file diff --git a/src/wwwroot/gfm/script-net/call-methods.md b/src/wwwroot/gfm/script-net/call-methods.md index 46627f0..dc25c79 100644 --- a/src/wwwroot/gfm/script-net/call-methods.md +++ b/src/wwwroot/gfm/script-net/call-methods.md @@ -37,3 +37,4 @@ You can call generic methods by specifying the Generic Type in the method name: 'Ints'.new([1,2]).call('GenericMethod',['A']) ``` +`call` only invokes instance methods, to call static methods you'll need to use `Function`. From cfe0e1493078b4a7807b4c4d6fbe13e5d119fa0d Mon Sep 17 00:00:00 2001 From: Demis Bellot Date: Sat, 28 Sep 2019 01:33:01 -0400 Subject: [PATCH 095/206] Add #Script Lisp --- src/AppHost.cs | 46 +- src/CustomScriptMethods.cs | 74 +- src/GitHubMarkdownFilters.cs | 26 +- src/LinqServices.cs | 17 +- ...{TemplateServices.cs => ScriptServices.cs} | 3 +- src/SharpScript.csproj | 22 +- src/global.json | 5 + src/wwwroot/_net-usage-partial.html | 29 + src/wwwroot/assets/img/multi-langs.svg | 3 + src/wwwroot/assets/js/default.js | 29 +- src/wwwroot/code/linq01-langs.ss | 11 + src/wwwroot/code/linq01.l | 2 + src/wwwroot/code/linq01.sc | 5 + src/wwwroot/code/{linq01.txt => linq01.ss} | 2 +- src/wwwroot/code/linq02.l | 4 + src/wwwroot/code/linq02.sc | 4 + src/wwwroot/code/{linq02.txt => linq02.ss} | 0 src/wwwroot/code/linq03.l | 3 + src/wwwroot/code/linq03.sc | 4 + src/wwwroot/code/{linq03.txt => linq03.ss} | 0 src/wwwroot/code/linq04-customer.ss | 2 + src/wwwroot/code/linq04-customer.txt | 2 - src/wwwroot/code/linq04-order.ss | 1 + src/wwwroot/code/linq04-order.txt | 1 - src/wwwroot/code/linq04-partials.ss | 3 + src/wwwroot/code/linq04-partials.txt | 3 - src/wwwroot/code/linq04.l | 5 + src/wwwroot/code/linq04.sc | 7 + src/wwwroot/code/{linq04.txt => linq04.ss} | 0 src/wwwroot/code/linq05.l | 4 + src/wwwroot/code/linq05.sc | 5 + src/wwwroot/code/{linq05.txt => linq05.ss} | 2 +- src/wwwroot/code/linq06.l | 3 + src/wwwroot/code/linq06.sc | 5 + src/wwwroot/code/{linq06.txt => linq06.ss} | 2 +- src/wwwroot/code/linq07.l | 2 + src/wwwroot/code/linq07.sc | 4 + src/wwwroot/code/{linq07.txt => linq07.ss} | 2 +- src/wwwroot/code/linq08.l | 4 + src/wwwroot/code/linq08.sc | 6 + src/wwwroot/code/{linq08.txt => linq08.ss} | 0 src/wwwroot/code/linq09.l | 3 + src/wwwroot/code/linq09.sc | 5 + src/wwwroot/code/{linq09.txt => linq09.ss} | 0 src/wwwroot/code/linq10.l | 4 + src/wwwroot/code/linq10.sc | 6 + src/wwwroot/code/{linq10.txt => linq10.ss} | 0 src/wwwroot/code/linq100.l | 4 + src/wwwroot/code/linq100.sc | 3 + src/wwwroot/code/{linq100.txt => linq100.ss} | 0 src/wwwroot/code/linq101.l | 11 + src/wwwroot/code/linq101.sc | 12 + src/wwwroot/code/{linq101.txt => linq101.ss} | 6 +- src/wwwroot/code/linq11.l | 8 + src/wwwroot/code/linq11.sc | 5 + src/wwwroot/code/{linq11.txt => linq11.ss} | 0 src/wwwroot/code/linq12.l | 5 + src/wwwroot/code/linq12.sc | 5 + src/wwwroot/code/{linq12.txt => linq12.ss} | 0 src/wwwroot/code/linq13.l | 4 + src/wwwroot/code/linq13.sc | 6 + src/wwwroot/code/{linq13.txt => linq13.ss} | 0 src/wwwroot/code/linq14.l | 5 + src/wwwroot/code/linq14.sc | 8 + src/wwwroot/code/{linq14.txt => linq14.ss} | 2 +- src/wwwroot/code/linq15.l | 8 + src/wwwroot/code/{linq15.txt => linq15.sc} | 0 src/wwwroot/code/linq15.ss | 5 + src/wwwroot/code/linq16.l | 9 + src/wwwroot/code/{linq16.txt => linq16.sc} | 0 src/wwwroot/code/linq16.ss | 5 + src/wwwroot/code/linq17.l | 8 + src/wwwroot/code/{linq17.txt => linq17.sc} | 0 src/wwwroot/code/linq17.ss | 5 + src/wwwroot/code/linq18.l | 8 + src/wwwroot/code/linq18.sc | 8 + src/wwwroot/code/{linq18.txt => linq18.ss} | 0 src/wwwroot/code/linq19.l | 7 + src/wwwroot/code/{linq19.txt => linq19.sc} | 2 +- src/wwwroot/code/linq19.ss | 5 + src/wwwroot/code/linq20.l | 3 + .../code/{linq01-code.txt => linq20.sc} | 6 +- src/wwwroot/code/{linq20.txt => linq20.ss} | 0 src/wwwroot/code/linq21.l | 9 + src/wwwroot/code/linq21.sc | 7 + src/wwwroot/code/{linq21.txt => linq21.ss} | 0 src/wwwroot/code/linq22.l | 3 + src/wwwroot/code/linq22.sc | 5 + src/wwwroot/code/{linq22.txt => linq22.ss} | 0 src/wwwroot/code/linq23.l | 9 + src/wwwroot/code/linq23.sc | 7 + src/wwwroot/code/{linq23.txt => linq23.ss} | 0 src/wwwroot/code/linq24.l | 3 + src/wwwroot/code/linq24.sc | 3 + src/wwwroot/code/{linq24.txt => linq24.ss} | 2 +- src/wwwroot/code/linq25.l | 4 + src/wwwroot/code/linq25.sc | 3 + src/wwwroot/code/{linq25.txt => linq25.ss} | 2 +- src/wwwroot/code/linq26.l | 3 + src/wwwroot/code/linq26.sc | 3 + src/wwwroot/code/{linq26.txt => linq26.ss} | 2 +- src/wwwroot/code/linq27.l | 4 + src/wwwroot/code/linq27.sc | 3 + src/wwwroot/code/{linq27.txt => linq27.ss} | 2 +- src/wwwroot/code/linq28.l | 3 + src/wwwroot/code/linq28.sc | 5 + src/wwwroot/code/{linq28.txt => linq28.ss} | 0 src/wwwroot/code/linq29.l | 3 + src/wwwroot/code/linq29.sc | 5 + src/wwwroot/code/{linq29.txt => linq29.ss} | 0 src/wwwroot/code/linq30.l | 1 + src/wwwroot/code/linq30.sc | 1 + src/wwwroot/code/{linq30.txt => linq30.ss} | 0 src/wwwroot/code/linq31.l | 2 + src/wwwroot/code/linq31.sc | 2 + src/wwwroot/code/{linq31.txt => linq31.ss} | 2 +- src/wwwroot/code/linq32.l | 3 + src/wwwroot/code/linq32.sc | 5 + src/wwwroot/code/{linq32.txt => linq32.ss} | 0 src/wwwroot/code/linq33.l | 1 + src/wwwroot/code/linq33.sc | 1 + src/wwwroot/code/{linq33.txt => linq33.ss} | 0 src/wwwroot/code/linq34.l | 2 + src/wwwroot/code/linq34.sc | 2 + src/wwwroot/code/{linq34.txt => linq34.ss} | 2 +- src/wwwroot/code/linq35.l | 4 + src/wwwroot/code/linq35.sc | 3 + src/wwwroot/code/{linq35.txt => linq35.ss} | 2 +- src/wwwroot/code/linq36.l | 2 + src/wwwroot/code/linq36.sc | 2 + src/wwwroot/code/{linq36.txt => linq36.ss} | 2 +- src/wwwroot/code/linq37.l | 1 + src/wwwroot/code/linq37.sc | 1 + src/wwwroot/code/{linq37.txt => linq37.ss} | 0 src/wwwroot/code/linq38.l | 2 + src/wwwroot/code/linq38.sc | 2 + src/wwwroot/code/{linq38.txt => linq38.ss} | 2 +- src/wwwroot/code/linq39.l | 3 + src/wwwroot/code/linq39.sc | 3 + src/wwwroot/code/{linq39.txt => linq39.ss} | 2 +- src/wwwroot/code/linq40.l | 7 + src/wwwroot/code/linq40.sc | 7 + src/wwwroot/code/{linq40.txt => linq40.ss} | 2 +- src/wwwroot/code/linq41.l | 7 + src/wwwroot/code/linq41.sc | 6 + src/wwwroot/code/{linq41.txt => linq41.ss} | 2 +- src/wwwroot/code/linq42.l | 1 + src/wwwroot/code/linq42.sc | 1 + src/wwwroot/code/{linq42.txt => linq42.ss} | 0 src/wwwroot/code/linq43.l | 12 + src/wwwroot/code/{linq43.txt => linq43.sc} | 0 src/wwwroot/code/linq43.ss | 12 + src/wwwroot/code/linq44.l | 3 + src/wwwroot/code/linq44.sc | 4 + src/wwwroot/code/{linq44.txt => linq44.ss} | 0 src/wwwroot/code/linq45.l | 3 + src/wwwroot/code/linq45.sc | 4 + src/wwwroot/code/{linq45.txt => linq45.ss} | 0 src/wwwroot/code/linq46.l | 3 + src/wwwroot/code/linq46.sc | 3 + src/wwwroot/code/{linq46.txt => linq46.ss} | 2 +- src/wwwroot/code/linq47.l | 2 + src/wwwroot/code/linq47.sc | 2 + src/wwwroot/code/{linq47.txt => linq47.ss} | 2 +- src/wwwroot/code/linq48.l | 4 + src/wwwroot/code/linq48.sc | 6 + src/wwwroot/code/{linq48.txt => linq48.ss} | 0 src/wwwroot/code/linq49.l | 5 + src/wwwroot/code/linq49.sc | 6 + src/wwwroot/code/{linq49.txt => linq49.ss} | 0 src/wwwroot/code/linq50.l | 4 + src/wwwroot/code/linq50.sc | 4 + src/wwwroot/code/{linq50.txt => linq50.ss} | 2 +- src/wwwroot/code/linq51.l | 4 + src/wwwroot/code/linq51.sc | 6 + src/wwwroot/code/{linq51.txt => linq51.ss} | 0 src/wwwroot/code/linq52.l | 4 + src/wwwroot/code/linq52.sc | 4 + src/wwwroot/code/{linq52.txt => linq52.ss} | 2 +- src/wwwroot/code/linq53.l | 4 + src/wwwroot/code/linq53.sc | 6 + src/wwwroot/code/{linq53.txt => linq53.ss} | 0 src/wwwroot/code/linq54.l | 3 + src/wwwroot/code/linq54.sc | 3 + src/wwwroot/code/{linq54.txt => linq54.ss} | 2 +- src/wwwroot/code/linq55.l | 3 + src/wwwroot/code/linq55.sc | 3 + src/wwwroot/code/{linq55.txt => linq55.ss} | 2 +- src/wwwroot/code/linq56.l | 4 + src/wwwroot/code/linq56.sc | 3 + src/wwwroot/code/{linq56.txt => linq56.ss} | 0 src/wwwroot/code/linq57.l | 3 + src/wwwroot/code/linq57.sc | 3 + src/wwwroot/code/{linq57.txt => linq57.ss} | 0 src/wwwroot/code/linq58.l | 1 + src/wwwroot/code/linq58.sc | 1 + src/wwwroot/code/{linq58.txt => linq58.ss} | 0 src/wwwroot/code/linq59.l | 2 + src/wwwroot/code/linq59.sc | 4 + src/wwwroot/code/{linq59.txt => linq59.ss} | 0 src/wwwroot/code/linq61.l | 1 + src/wwwroot/code/linq61.sc | 1 + src/wwwroot/code/{linq61.txt => linq61.ss} | 0 src/wwwroot/code/linq62.l | 2 + src/wwwroot/code/linq62.sc | 2 + src/wwwroot/code/{linq62.txt => linq62.ss} | 0 src/wwwroot/code/linq64.l | 2 + src/wwwroot/code/linq64.sc | 3 + src/wwwroot/code/{linq64.txt => linq64.ss} | 0 src/wwwroot/code/linq65.l | 2 + src/wwwroot/code/linq65.sc | 3 + src/wwwroot/code/{linq65.txt => linq65.ss} | 0 src/wwwroot/code/linq66.l | 1 + src/wwwroot/code/linq66.sc | 1 + src/wwwroot/code/linq66.ss | 1 + src/wwwroot/code/linq66.txt | 1 - src/wwwroot/code/linq67.l | 3 + src/wwwroot/code/linq67.sc | 3 + src/wwwroot/code/{linq67.txt => linq67.ss} | 0 src/wwwroot/code/linq69.l | 3 + src/wwwroot/code/{linq69.txt => linq69.sc} | 0 src/wwwroot/code/linq69.ss | 5 + src/wwwroot/code/linq70.l | 2 + src/wwwroot/code/linq70.sc | 3 + src/wwwroot/code/{linq70.txt => linq70.ss} | 0 src/wwwroot/code/linq72.l | 3 + src/wwwroot/code/{linq72.txt => linq72.sc} | 0 src/wwwroot/code/linq72.ss | 5 + src/wwwroot/code/linq73.l | 2 + src/wwwroot/code/linq73.sc | 3 + src/wwwroot/code/{linq73.txt => linq73.ss} | 0 src/wwwroot/code/linq74.l | 2 + src/wwwroot/code/linq74.sc | 3 + src/wwwroot/code/{linq74.txt => linq74.ss} | 0 src/wwwroot/code/linq76.l | 5 + src/wwwroot/code/{linq76.txt => linq76.sc} | 2 +- src/wwwroot/code/linq76.ss | 3 + src/wwwroot/code/linq77.l | 4 + src/wwwroot/code/{linq77.txt => linq77.sc} | 0 src/wwwroot/code/linq77.ss | 4 + src/wwwroot/code/linq78.l | 2 + src/wwwroot/code/linq78.sc | 3 + src/wwwroot/code/{linq78.txt => linq78.ss} | 0 src/wwwroot/code/linq79.l | 2 + src/wwwroot/code/linq79.sc | 3 + src/wwwroot/code/{linq79.txt => linq79.ss} | 0 src/wwwroot/code/linq80.l | 4 + src/wwwroot/code/{linq80.txt => linq80.sc} | 0 src/wwwroot/code/linq80.ss | 4 + src/wwwroot/code/linq81.l | 2 + src/wwwroot/code/linq81.sc | 3 + src/wwwroot/code/{linq81.txt => linq81.ss} | 0 src/wwwroot/code/linq82.l | 2 + src/wwwroot/code/linq82.sc | 3 + src/wwwroot/code/{linq82.txt => linq82.ss} | 0 src/wwwroot/code/linq83.l | 4 + src/wwwroot/code/{linq83.txt => linq83.sc} | 0 src/wwwroot/code/linq83.ss | 4 + src/wwwroot/code/linq84.l | 6 + src/wwwroot/code/{linq84.txt => linq84.sc} | 0 src/wwwroot/code/linq84.ss | 8 + src/wwwroot/code/linq85.l | 2 + src/wwwroot/code/linq85.sc | 3 + src/wwwroot/code/{linq85.txt => linq85.ss} | 0 src/wwwroot/code/linq86.l | 2 + src/wwwroot/code/linq86.sc | 3 + src/wwwroot/code/{linq86.txt => linq86.ss} | 0 src/wwwroot/code/linq87.l | 5 + src/wwwroot/code/{linq87.txt => linq87.sc} | 0 src/wwwroot/code/linq87.ss | 4 + src/wwwroot/code/linq88.l | 6 + src/wwwroot/code/{linq88.txt => linq88.sc} | 0 src/wwwroot/code/linq88.ss | 8 + src/wwwroot/code/linq89.l | 2 + src/wwwroot/code/linq89.sc | 3 + src/wwwroot/code/{linq89.txt => linq89.ss} | 0 src/wwwroot/code/linq90.l | 2 + src/wwwroot/code/linq90.sc | 3 + src/wwwroot/code/{linq90.txt => linq90.ss} | 0 src/wwwroot/code/linq91.l | 5 + src/wwwroot/code/{linq91.txt => linq91.sc} | 0 src/wwwroot/code/linq91.ss | 4 + src/wwwroot/code/linq92.l | 2 + src/wwwroot/code/linq92.sc | 3 + src/wwwroot/code/{linq92.txt => linq92.ss} | 0 src/wwwroot/code/linq93.l | 6 + src/wwwroot/code/linq93.sc | 5 + src/wwwroot/code/{linq93.txt => linq93.ss} | 0 src/wwwroot/code/linq94.l | 4 + src/wwwroot/code/linq94.sc | 4 + src/wwwroot/code/{linq94.txt => linq94.ss} | 2 +- src/wwwroot/code/linq95.l | 4 + src/wwwroot/code/linq95.sc | 4 + src/wwwroot/code/{linq95.txt => linq95.ss} | 2 +- src/wwwroot/code/linq96.l | 3 + src/wwwroot/code/linq96.sc | 4 + src/wwwroot/code/{linq96.txt => linq96.ss} | 0 src/wwwroot/code/linq97.l | 3 + src/wwwroot/code/linq97.sc | 4 + src/wwwroot/code/{linq97.txt => linq97.ss} | 0 src/wwwroot/code/linq99.l | 3 + src/wwwroot/code/linq99.sc | 3 + src/wwwroot/code/{linq99.txt => linq99.ss} | 0 src/wwwroot/code/mv.sc | 7 + src/wwwroot/code/rss.l | 8 + src/wwwroot/doc-links.html | 9 +- src/wwwroot/docs/blocks.html | 57 +- src/wwwroot/docs/default-scripts.html | 4 +- src/wwwroot/docs/introduction.html | 31 +- src/wwwroot/docs/partials.html | 6 +- src/wwwroot/docs/protected-scripts.html | 2 +- src/wwwroot/docs/servicestack-scripts.html | 8 + src/wwwroot/docs/syntax.html | 93 ++- src/wwwroot/docs/test.html | 9 + src/wwwroot/docs/transformers.html | 33 + src/wwwroot/examples/monthly-budget.txt | 40 +- src/wwwroot/examples/query-github.txt | 8 +- src/wwwroot/gfm/blocks/00.html | 168 +++++ src/wwwroot/gfm/blocks/00.md | 143 ++++ src/wwwroot/gfm/blocks/13.html | 2 + src/wwwroot/gfm/blocks/13.md | 2 + src/wwwroot/gfm/blocks/14.html | 52 +- src/wwwroot/gfm/blocks/14.md | 28 +- src/wwwroot/gfm/blocks/26.html | 16 + src/wwwroot/gfm/blocks/26.md | 17 + src/wwwroot/gfm/blocks/27.html | 39 ++ src/wwwroot/gfm/blocks/27.md | 40 ++ src/wwwroot/gfm/installation/01.html | 2 +- src/wwwroot/gfm/installation/01.md | 2 +- src/wwwroot/gfm/introduction/02.html | 4 +- src/wwwroot/gfm/introduction/02.md | 4 +- src/wwwroot/gfm/linq/01.html | 19 + src/wwwroot/gfm/linq/01.md | 20 + src/wwwroot/gfm/lisp/01.html | 4 + src/wwwroot/gfm/lisp/01.md | 5 + src/wwwroot/gfm/lisp/02.html | 28 + src/wwwroot/gfm/lisp/02.md | 28 + src/wwwroot/gfm/lisp/03.html | 110 +++ src/wwwroot/gfm/lisp/03.md | 111 +++ src/wwwroot/gfm/lisp/04.html | 9 + src/wwwroot/gfm/lisp/04.md | 10 + src/wwwroot/gfm/lisp/05.html | 14 + src/wwwroot/gfm/lisp/05.md | 15 + src/wwwroot/gfm/lisp/06.html | 80 +++ src/wwwroot/gfm/lisp/06.md | 81 +++ src/wwwroot/gfm/lisp/07.html | 27 + src/wwwroot/gfm/lisp/07.md | 41 ++ src/wwwroot/gfm/lisp/08.html | 12 + src/wwwroot/gfm/lisp/08.md | 21 + src/wwwroot/gfm/lisp/09.html | 79 +++ src/wwwroot/gfm/lisp/09.md | 85 +++ src/wwwroot/gfm/lisp/10.html | 7 + src/wwwroot/gfm/lisp/10.md | 9 + src/wwwroot/gfm/lisp/11.html | 64 ++ src/wwwroot/gfm/lisp/11.md | 105 +++ src/wwwroot/gfm/methods/05.html | 2 +- src/wwwroot/gfm/methods/05.md | 2 +- src/wwwroot/gfm/methods/07.html | 2 +- src/wwwroot/gfm/methods/07.md | 2 +- src/wwwroot/gfm/scode/01.html | 5 + src/wwwroot/gfm/scode/01.md | 6 + src/wwwroot/gfm/scode/02.html | 13 + src/wwwroot/gfm/scode/02.md | 19 + src/wwwroot/gfm/scode/03.html | 20 + src/wwwroot/gfm/scode/03.md | 29 + src/wwwroot/gfm/scode/04.html | 11 + src/wwwroot/gfm/scode/04.md | 20 + src/wwwroot/gfm/servicestack-scripts/09.html | 2 + src/wwwroot/gfm/servicestack-scripts/09.md | 3 + src/wwwroot/gfm/syntax/05.html | 2 +- src/wwwroot/gfm/syntax/05.md | 2 +- src/wwwroot/gfm/syntax/07.html | 8 + src/wwwroot/gfm/syntax/07.md | 9 + src/wwwroot/gfm/syntax/08.html | 9 + src/wwwroot/gfm/syntax/08.md | 10 + src/wwwroot/gfm/syntax/09.html | 9 + src/wwwroot/gfm/syntax/09.md | 10 + src/wwwroot/gfm/syntax/10.html | 4 + src/wwwroot/gfm/syntax/10.md | 5 + src/wwwroot/index.html | 5 +- src/wwwroot/lang-select.html | 19 + src/wwwroot/linq-links.html | 10 +- src/wwwroot/linq-preview.html | 9 +- src/wwwroot/linq/Miscellaneous-operators.html | 2 + src/wwwroot/linq/aggregate-operators.html | 2 + src/wwwroot/linq/conversion-operators.html | 2 + src/wwwroot/linq/element-operators.html | 2 + src/wwwroot/linq/generation-operators.html | 2 + src/wwwroot/linq/grouping-operators.html | 2 + src/wwwroot/linq/index.html | 21 +- src/wwwroot/linq/ordering-operators.html | 2 + src/wwwroot/linq/partitioning-operators.html | 6 +- src/wwwroot/linq/projection-operators.html | 18 +- src/wwwroot/linq/quantifiers.html | 2 + src/wwwroot/linq/query-execution.html | 7 +- src/wwwroot/linq/restriction-operators.html | 9 +- src/wwwroot/linq/set-operators.html | 2 + src/wwwroot/lisp/index.html | 638 ++++++++++++++++++ src/wwwroot/scode/index.html | 109 +++ src/wwwroot/sidebar.html | 20 +- src/wwwroot/usecases/live-documents.html | 4 +- wip/script-net.ss | 19 + 402 files changed, 3763 insertions(+), 265 deletions(-) rename src/{TemplateServices.cs => ScriptServices.cs} (97%) create mode 100644 src/global.json create mode 100644 src/wwwroot/_net-usage-partial.html create mode 100644 src/wwwroot/assets/img/multi-langs.svg create mode 100644 src/wwwroot/code/linq01-langs.ss create mode 100644 src/wwwroot/code/linq01.l create mode 100644 src/wwwroot/code/linq01.sc rename src/wwwroot/code/{linq01.txt => linq01.ss} (53%) create mode 100644 src/wwwroot/code/linq02.l create mode 100644 src/wwwroot/code/linq02.sc rename src/wwwroot/code/{linq02.txt => linq02.ss} (100%) create mode 100644 src/wwwroot/code/linq03.l create mode 100644 src/wwwroot/code/linq03.sc rename src/wwwroot/code/{linq03.txt => linq03.ss} (100%) create mode 100644 src/wwwroot/code/linq04-customer.ss delete mode 100644 src/wwwroot/code/linq04-customer.txt create mode 100644 src/wwwroot/code/linq04-order.ss delete mode 100644 src/wwwroot/code/linq04-order.txt create mode 100644 src/wwwroot/code/linq04-partials.ss delete mode 100644 src/wwwroot/code/linq04-partials.txt create mode 100644 src/wwwroot/code/linq04.l create mode 100644 src/wwwroot/code/linq04.sc rename src/wwwroot/code/{linq04.txt => linq04.ss} (100%) create mode 100644 src/wwwroot/code/linq05.l create mode 100644 src/wwwroot/code/linq05.sc rename src/wwwroot/code/{linq05.txt => linq05.ss} (85%) create mode 100644 src/wwwroot/code/linq06.l create mode 100644 src/wwwroot/code/linq06.sc rename src/wwwroot/code/{linq06.txt => linq06.ss} (50%) create mode 100644 src/wwwroot/code/linq07.l create mode 100644 src/wwwroot/code/linq07.sc rename src/wwwroot/code/{linq07.txt => linq07.ss} (83%) create mode 100644 src/wwwroot/code/linq08.l create mode 100644 src/wwwroot/code/linq08.sc rename src/wwwroot/code/{linq08.txt => linq08.ss} (100%) create mode 100644 src/wwwroot/code/linq09.l create mode 100644 src/wwwroot/code/linq09.sc rename src/wwwroot/code/{linq09.txt => linq09.ss} (100%) create mode 100644 src/wwwroot/code/linq10.l create mode 100644 src/wwwroot/code/linq10.sc rename src/wwwroot/code/{linq10.txt => linq10.ss} (100%) create mode 100644 src/wwwroot/code/linq100.l create mode 100644 src/wwwroot/code/linq100.sc rename src/wwwroot/code/{linq100.txt => linq100.ss} (100%) create mode 100644 src/wwwroot/code/linq101.l create mode 100644 src/wwwroot/code/linq101.sc rename src/wwwroot/code/{linq101.txt => linq101.ss} (74%) create mode 100644 src/wwwroot/code/linq11.l create mode 100644 src/wwwroot/code/linq11.sc rename src/wwwroot/code/{linq11.txt => linq11.ss} (100%) create mode 100644 src/wwwroot/code/linq12.l create mode 100644 src/wwwroot/code/linq12.sc rename src/wwwroot/code/{linq12.txt => linq12.ss} (100%) create mode 100644 src/wwwroot/code/linq13.l create mode 100644 src/wwwroot/code/linq13.sc rename src/wwwroot/code/{linq13.txt => linq13.ss} (100%) create mode 100644 src/wwwroot/code/linq14.l create mode 100644 src/wwwroot/code/linq14.sc rename src/wwwroot/code/{linq14.txt => linq14.ss} (79%) create mode 100644 src/wwwroot/code/linq15.l rename src/wwwroot/code/{linq15.txt => linq15.sc} (100%) create mode 100644 src/wwwroot/code/linq15.ss create mode 100644 src/wwwroot/code/linq16.l rename src/wwwroot/code/{linq16.txt => linq16.sc} (100%) create mode 100644 src/wwwroot/code/linq16.ss create mode 100644 src/wwwroot/code/linq17.l rename src/wwwroot/code/{linq17.txt => linq17.sc} (100%) create mode 100644 src/wwwroot/code/linq17.ss create mode 100644 src/wwwroot/code/linq18.l create mode 100644 src/wwwroot/code/linq18.sc rename src/wwwroot/code/{linq18.txt => linq18.ss} (100%) create mode 100644 src/wwwroot/code/linq19.l rename src/wwwroot/code/{linq19.txt => linq19.sc} (84%) create mode 100644 src/wwwroot/code/linq19.ss create mode 100644 src/wwwroot/code/linq20.l rename src/wwwroot/code/{linq01-code.txt => linq20.sc} (53%) rename src/wwwroot/code/{linq20.txt => linq20.ss} (100%) create mode 100644 src/wwwroot/code/linq21.l create mode 100644 src/wwwroot/code/linq21.sc rename src/wwwroot/code/{linq21.txt => linq21.ss} (100%) create mode 100644 src/wwwroot/code/linq22.l create mode 100644 src/wwwroot/code/linq22.sc rename src/wwwroot/code/{linq22.txt => linq22.ss} (100%) create mode 100644 src/wwwroot/code/linq23.l create mode 100644 src/wwwroot/code/linq23.sc rename src/wwwroot/code/{linq23.txt => linq23.ss} (100%) create mode 100644 src/wwwroot/code/linq24.l create mode 100644 src/wwwroot/code/linq24.sc rename src/wwwroot/code/{linq24.txt => linq24.ss} (86%) create mode 100644 src/wwwroot/code/linq25.l create mode 100644 src/wwwroot/code/linq25.sc rename src/wwwroot/code/{linq25.txt => linq25.ss} (88%) create mode 100644 src/wwwroot/code/linq26.l create mode 100644 src/wwwroot/code/linq26.sc rename src/wwwroot/code/{linq26.txt => linq26.ss} (89%) create mode 100644 src/wwwroot/code/linq27.l create mode 100644 src/wwwroot/code/linq27.sc rename src/wwwroot/code/{linq27.txt => linq27.ss} (89%) create mode 100644 src/wwwroot/code/linq28.l create mode 100644 src/wwwroot/code/linq28.sc rename src/wwwroot/code/{linq28.txt => linq28.ss} (100%) create mode 100644 src/wwwroot/code/linq29.l create mode 100644 src/wwwroot/code/linq29.sc rename src/wwwroot/code/{linq29.txt => linq29.ss} (100%) create mode 100644 src/wwwroot/code/linq30.l create mode 100644 src/wwwroot/code/linq30.sc rename src/wwwroot/code/{linq30.txt => linq30.ss} (100%) create mode 100644 src/wwwroot/code/linq31.l create mode 100644 src/wwwroot/code/linq31.sc rename src/wwwroot/code/{linq31.txt => linq31.ss} (87%) create mode 100644 src/wwwroot/code/linq32.l create mode 100644 src/wwwroot/code/linq32.sc rename src/wwwroot/code/{linq32.txt => linq32.ss} (100%) create mode 100644 src/wwwroot/code/linq33.l create mode 100644 src/wwwroot/code/linq33.sc rename src/wwwroot/code/{linq33.txt => linq33.ss} (100%) create mode 100644 src/wwwroot/code/linq34.l create mode 100644 src/wwwroot/code/linq34.sc rename src/wwwroot/code/{linq34.txt => linq34.ss} (88%) create mode 100644 src/wwwroot/code/linq35.l create mode 100644 src/wwwroot/code/linq35.sc rename src/wwwroot/code/{linq35.txt => linq35.ss} (90%) create mode 100644 src/wwwroot/code/linq36.l create mode 100644 src/wwwroot/code/linq36.sc rename src/wwwroot/code/{linq36.txt => linq36.ss} (89%) create mode 100644 src/wwwroot/code/linq37.l create mode 100644 src/wwwroot/code/linq37.sc rename src/wwwroot/code/{linq37.txt => linq37.ss} (100%) create mode 100644 src/wwwroot/code/linq38.l create mode 100644 src/wwwroot/code/linq38.sc rename src/wwwroot/code/{linq38.txt => linq38.ss} (90%) create mode 100644 src/wwwroot/code/linq39.l create mode 100644 src/wwwroot/code/linq39.sc rename src/wwwroot/code/{linq39.txt => linq39.ss} (91%) create mode 100644 src/wwwroot/code/linq40.l create mode 100644 src/wwwroot/code/linq40.sc rename src/wwwroot/code/{linq40.txt => linq40.ss} (93%) create mode 100644 src/wwwroot/code/linq41.l create mode 100644 src/wwwroot/code/linq41.sc rename src/wwwroot/code/{linq41.txt => linq41.ss} (91%) create mode 100644 src/wwwroot/code/linq42.l create mode 100644 src/wwwroot/code/linq42.sc rename src/wwwroot/code/{linq42.txt => linq42.ss} (100%) create mode 100644 src/wwwroot/code/linq43.l rename src/wwwroot/code/{linq43.txt => linq43.sc} (100%) create mode 100644 src/wwwroot/code/linq43.ss create mode 100644 src/wwwroot/code/linq44.l create mode 100644 src/wwwroot/code/linq44.sc rename src/wwwroot/code/{linq44.txt => linq44.ss} (100%) create mode 100644 src/wwwroot/code/linq45.l create mode 100644 src/wwwroot/code/linq45.sc rename src/wwwroot/code/{linq45.txt => linq45.ss} (100%) create mode 100644 src/wwwroot/code/linq46.l create mode 100644 src/wwwroot/code/linq46.sc rename src/wwwroot/code/{linq46.txt => linq46.ss} (60%) create mode 100644 src/wwwroot/code/linq47.l create mode 100644 src/wwwroot/code/linq47.sc rename src/wwwroot/code/{linq47.txt => linq47.ss} (79%) create mode 100644 src/wwwroot/code/linq48.l create mode 100644 src/wwwroot/code/linq48.sc rename src/wwwroot/code/{linq48.txt => linq48.ss} (100%) create mode 100644 src/wwwroot/code/linq49.l create mode 100644 src/wwwroot/code/linq49.sc rename src/wwwroot/code/{linq49.txt => linq49.ss} (100%) create mode 100644 src/wwwroot/code/linq50.l create mode 100644 src/wwwroot/code/linq50.sc rename src/wwwroot/code/{linq50.txt => linq50.ss} (72%) create mode 100644 src/wwwroot/code/linq51.l create mode 100644 src/wwwroot/code/linq51.sc rename src/wwwroot/code/{linq51.txt => linq51.ss} (100%) create mode 100644 src/wwwroot/code/linq52.l create mode 100644 src/wwwroot/code/linq52.sc rename src/wwwroot/code/{linq52.txt => linq52.ss} (75%) create mode 100644 src/wwwroot/code/linq53.l create mode 100644 src/wwwroot/code/linq53.sc rename src/wwwroot/code/{linq53.txt => linq53.ss} (100%) create mode 100644 src/wwwroot/code/linq54.l create mode 100644 src/wwwroot/code/linq54.sc rename src/wwwroot/code/{linq54.txt => linq54.ss} (89%) create mode 100644 src/wwwroot/code/linq55.l create mode 100644 src/wwwroot/code/linq55.sc rename src/wwwroot/code/{linq55.txt => linq55.ss} (86%) create mode 100644 src/wwwroot/code/linq56.l create mode 100644 src/wwwroot/code/linq56.sc rename src/wwwroot/code/{linq56.txt => linq56.ss} (100%) create mode 100644 src/wwwroot/code/linq57.l create mode 100644 src/wwwroot/code/linq57.sc rename src/wwwroot/code/{linq57.txt => linq57.ss} (100%) create mode 100644 src/wwwroot/code/linq58.l create mode 100644 src/wwwroot/code/linq58.sc rename src/wwwroot/code/{linq58.txt => linq58.ss} (100%) create mode 100644 src/wwwroot/code/linq59.l create mode 100644 src/wwwroot/code/linq59.sc rename src/wwwroot/code/{linq59.txt => linq59.ss} (100%) create mode 100644 src/wwwroot/code/linq61.l create mode 100644 src/wwwroot/code/linq61.sc rename src/wwwroot/code/{linq61.txt => linq61.ss} (100%) create mode 100644 src/wwwroot/code/linq62.l create mode 100644 src/wwwroot/code/linq62.sc rename src/wwwroot/code/{linq62.txt => linq62.ss} (100%) create mode 100644 src/wwwroot/code/linq64.l create mode 100644 src/wwwroot/code/linq64.sc rename src/wwwroot/code/{linq64.txt => linq64.ss} (100%) create mode 100644 src/wwwroot/code/linq65.l create mode 100644 src/wwwroot/code/linq65.sc rename src/wwwroot/code/{linq65.txt => linq65.ss} (100%) create mode 100644 src/wwwroot/code/linq66.l create mode 100644 src/wwwroot/code/linq66.sc create mode 100644 src/wwwroot/code/linq66.ss delete mode 100644 src/wwwroot/code/linq66.txt create mode 100644 src/wwwroot/code/linq67.l create mode 100644 src/wwwroot/code/linq67.sc rename src/wwwroot/code/{linq67.txt => linq67.ss} (100%) create mode 100644 src/wwwroot/code/linq69.l rename src/wwwroot/code/{linq69.txt => linq69.sc} (100%) create mode 100644 src/wwwroot/code/linq69.ss create mode 100644 src/wwwroot/code/linq70.l create mode 100644 src/wwwroot/code/linq70.sc rename src/wwwroot/code/{linq70.txt => linq70.ss} (100%) create mode 100644 src/wwwroot/code/linq72.l rename src/wwwroot/code/{linq72.txt => linq72.sc} (100%) create mode 100644 src/wwwroot/code/linq72.ss create mode 100644 src/wwwroot/code/linq73.l create mode 100644 src/wwwroot/code/linq73.sc rename src/wwwroot/code/{linq73.txt => linq73.ss} (100%) create mode 100644 src/wwwroot/code/linq74.l create mode 100644 src/wwwroot/code/linq74.sc rename src/wwwroot/code/{linq74.txt => linq74.ss} (100%) create mode 100644 src/wwwroot/code/linq76.l rename src/wwwroot/code/{linq76.txt => linq76.sc} (56%) create mode 100644 src/wwwroot/code/linq76.ss create mode 100644 src/wwwroot/code/linq77.l rename src/wwwroot/code/{linq77.txt => linq77.sc} (100%) create mode 100644 src/wwwroot/code/linq77.ss create mode 100644 src/wwwroot/code/linq78.l create mode 100644 src/wwwroot/code/linq78.sc rename src/wwwroot/code/{linq78.txt => linq78.ss} (100%) create mode 100644 src/wwwroot/code/linq79.l create mode 100644 src/wwwroot/code/linq79.sc rename src/wwwroot/code/{linq79.txt => linq79.ss} (100%) create mode 100644 src/wwwroot/code/linq80.l rename src/wwwroot/code/{linq80.txt => linq80.sc} (100%) create mode 100644 src/wwwroot/code/linq80.ss create mode 100644 src/wwwroot/code/linq81.l create mode 100644 src/wwwroot/code/linq81.sc rename src/wwwroot/code/{linq81.txt => linq81.ss} (100%) create mode 100644 src/wwwroot/code/linq82.l create mode 100644 src/wwwroot/code/linq82.sc rename src/wwwroot/code/{linq82.txt => linq82.ss} (100%) create mode 100644 src/wwwroot/code/linq83.l rename src/wwwroot/code/{linq83.txt => linq83.sc} (100%) create mode 100644 src/wwwroot/code/linq83.ss create mode 100644 src/wwwroot/code/linq84.l rename src/wwwroot/code/{linq84.txt => linq84.sc} (100%) create mode 100644 src/wwwroot/code/linq84.ss create mode 100644 src/wwwroot/code/linq85.l create mode 100644 src/wwwroot/code/linq85.sc rename src/wwwroot/code/{linq85.txt => linq85.ss} (100%) create mode 100644 src/wwwroot/code/linq86.l create mode 100644 src/wwwroot/code/linq86.sc rename src/wwwroot/code/{linq86.txt => linq86.ss} (100%) create mode 100644 src/wwwroot/code/linq87.l rename src/wwwroot/code/{linq87.txt => linq87.sc} (100%) create mode 100644 src/wwwroot/code/linq87.ss create mode 100644 src/wwwroot/code/linq88.l rename src/wwwroot/code/{linq88.txt => linq88.sc} (100%) create mode 100644 src/wwwroot/code/linq88.ss create mode 100644 src/wwwroot/code/linq89.l create mode 100644 src/wwwroot/code/linq89.sc rename src/wwwroot/code/{linq89.txt => linq89.ss} (100%) create mode 100644 src/wwwroot/code/linq90.l create mode 100644 src/wwwroot/code/linq90.sc rename src/wwwroot/code/{linq90.txt => linq90.ss} (100%) create mode 100644 src/wwwroot/code/linq91.l rename src/wwwroot/code/{linq91.txt => linq91.sc} (100%) create mode 100644 src/wwwroot/code/linq91.ss create mode 100644 src/wwwroot/code/linq92.l create mode 100644 src/wwwroot/code/linq92.sc rename src/wwwroot/code/{linq92.txt => linq92.ss} (100%) create mode 100644 src/wwwroot/code/linq93.l create mode 100644 src/wwwroot/code/linq93.sc rename src/wwwroot/code/{linq93.txt => linq93.ss} (100%) create mode 100644 src/wwwroot/code/linq94.l create mode 100644 src/wwwroot/code/linq94.sc rename src/wwwroot/code/{linq94.txt => linq94.ss} (72%) create mode 100644 src/wwwroot/code/linq95.l create mode 100644 src/wwwroot/code/linq95.sc rename src/wwwroot/code/{linq95.txt => linq95.ss} (76%) create mode 100644 src/wwwroot/code/linq96.l create mode 100644 src/wwwroot/code/linq96.sc rename src/wwwroot/code/{linq96.txt => linq96.ss} (100%) create mode 100644 src/wwwroot/code/linq97.l create mode 100644 src/wwwroot/code/linq97.sc rename src/wwwroot/code/{linq97.txt => linq97.ss} (100%) create mode 100644 src/wwwroot/code/linq99.l create mode 100644 src/wwwroot/code/linq99.sc rename src/wwwroot/code/{linq99.txt => linq99.ss} (100%) create mode 100644 src/wwwroot/code/mv.sc create mode 100644 src/wwwroot/code/rss.l create mode 100644 src/wwwroot/docs/test.html create mode 100644 src/wwwroot/gfm/blocks/00.html create mode 100644 src/wwwroot/gfm/blocks/00.md create mode 100644 src/wwwroot/gfm/blocks/26.html create mode 100644 src/wwwroot/gfm/blocks/26.md create mode 100644 src/wwwroot/gfm/blocks/27.html create mode 100644 src/wwwroot/gfm/blocks/27.md create mode 100644 src/wwwroot/gfm/linq/01.html create mode 100644 src/wwwroot/gfm/linq/01.md create mode 100644 src/wwwroot/gfm/lisp/01.html create mode 100644 src/wwwroot/gfm/lisp/01.md create mode 100644 src/wwwroot/gfm/lisp/02.html create mode 100644 src/wwwroot/gfm/lisp/02.md create mode 100644 src/wwwroot/gfm/lisp/03.html create mode 100644 src/wwwroot/gfm/lisp/03.md create mode 100644 src/wwwroot/gfm/lisp/04.html create mode 100644 src/wwwroot/gfm/lisp/04.md create mode 100644 src/wwwroot/gfm/lisp/05.html create mode 100644 src/wwwroot/gfm/lisp/05.md create mode 100644 src/wwwroot/gfm/lisp/06.html create mode 100644 src/wwwroot/gfm/lisp/06.md create mode 100644 src/wwwroot/gfm/lisp/07.html create mode 100644 src/wwwroot/gfm/lisp/07.md create mode 100644 src/wwwroot/gfm/lisp/08.html create mode 100644 src/wwwroot/gfm/lisp/08.md create mode 100644 src/wwwroot/gfm/lisp/09.html create mode 100644 src/wwwroot/gfm/lisp/09.md create mode 100644 src/wwwroot/gfm/lisp/10.html create mode 100644 src/wwwroot/gfm/lisp/10.md create mode 100644 src/wwwroot/gfm/lisp/11.html create mode 100644 src/wwwroot/gfm/lisp/11.md create mode 100644 src/wwwroot/gfm/scode/01.html create mode 100644 src/wwwroot/gfm/scode/01.md create mode 100644 src/wwwroot/gfm/scode/02.html create mode 100644 src/wwwroot/gfm/scode/02.md create mode 100644 src/wwwroot/gfm/scode/03.html create mode 100644 src/wwwroot/gfm/scode/03.md create mode 100644 src/wwwroot/gfm/scode/04.html create mode 100644 src/wwwroot/gfm/scode/04.md create mode 100644 src/wwwroot/gfm/servicestack-scripts/09.html create mode 100644 src/wwwroot/gfm/servicestack-scripts/09.md create mode 100644 src/wwwroot/gfm/syntax/07.html create mode 100644 src/wwwroot/gfm/syntax/07.md create mode 100644 src/wwwroot/gfm/syntax/08.html create mode 100644 src/wwwroot/gfm/syntax/08.md create mode 100644 src/wwwroot/gfm/syntax/09.html create mode 100644 src/wwwroot/gfm/syntax/09.md create mode 100644 src/wwwroot/gfm/syntax/10.html create mode 100644 src/wwwroot/gfm/syntax/10.md create mode 100644 src/wwwroot/lang-select.html create mode 100644 src/wwwroot/lisp/index.html create mode 100644 src/wwwroot/scode/index.html create mode 100644 wip/script-net.ss diff --git a/src/AppHost.cs b/src/AppHost.cs index f10f9bd..8878990 100644 --- a/src/AppHost.cs +++ b/src/AppHost.cs @@ -17,7 +17,7 @@ namespace SharpScript public class AppHost : AppHostBase { public AppHost() - : base("#Script Pages", typeof(TemplateServices).Assembly) { } + : base("#Script Pages", typeof(ScriptServices).Assembly) { } public ScriptContext LinqContext; @@ -57,12 +57,14 @@ public override void Configure(Container container) var customFilters = new CustomScriptMethods(); Plugins.Add(new SharpPagesFeature { + ScriptLanguages = { ScriptLisp.Language }, ScriptMethods = { customFilters, new DbScriptsAsync() }, FilterTransformers = { ["convertScriptToCodeBlocks"] = GitHubMarkdownScripts.convertScriptToCodeBlocks, + ["convertScriptToLispBlocks"] = GitHubMarkdownScripts.convertScriptToLispBlocks, }, Args = { ["products"] = TemplateQueryData.Products @@ -92,12 +94,22 @@ public override void Configure(Container container) } } - foreach (var file in files.GetDirectory("linq").GetAllMatchingFiles("*.html")) + foreach (var file in files.GetDirectory("scode").GetAllMatchingFiles("*.html")) { var page = feature.GetPage(file.VirtualPath).Init().Result; if (page.Args.TryGetValue("order", out object order) && page.Args.TryGetValue("title", out object title)) { - customFilters.LinqIndex[int.Parse((string)order)] = new KeyValuePair(GetPath(file.VirtualPath), (string)title); + customFilters.CodeIndex[int.Parse((string)order)] = new KeyValuePair(GetPath(file.VirtualPath), (string)title); + } + } + + + foreach (var file in files.GetDirectory("lisp").GetAllMatchingFiles("*.html")) + { + var page = feature.GetPage(file.VirtualPath).Init().Result; + if (page.Args.TryGetValue("order", out object order) && page.Args.TryGetValue("title", out object title)) + { + customFilters.LispIndex[int.Parse((string)order)] = new KeyValuePair(GetPath(file.VirtualPath), (string)title); } } @@ -110,15 +122,39 @@ public override void Configure(Container container) } } + foreach (var file in files.GetDirectory("linq").GetAllMatchingFiles("*.html")) + { + var page = feature.GetPage(file.VirtualPath).Init().Result; + if (page.Args.TryGetValue("order", out object order) && page.Args.TryGetValue("title", out object title)) + { + customFilters.LinqIndex[int.Parse((string)order)] = new KeyValuePair(GetPath(file.VirtualPath), (string)title); + } + } + + var protectedScriptNames = new HashSet(ScriptMethodInfo.GetMethodsAvailable(typeof(ProtectedScripts)).Map(x => x.Name)); + LinqContext = new ScriptContext { + ScriptLanguages = { ScriptLisp.Language }, Args = { [ScriptConstants.DefaultDateFormat] = "yyyy/MM/dd", ["products"] = TemplateQueryData.Products, + ["products-list"] = Lisp.ToCons(TemplateQueryData.Products), ["customers"] = TemplateQueryData.Customers, + ["customers-list"] = Lisp.ToCons(TemplateQueryData.Customers), ["comparer"] = new CaseInsensitiveComparer(), ["anagramComparer"] = new AnagramEqualityComparer(), - } - }.Init(); + }, + ScriptTypes = { + typeof(DateTime), + typeof(CaseInsensitiveComparer), + typeof(AnagramEqualityComparer), + }, + ScriptMethods = { + new ProtectedScripts() + }, + }; + protectedScriptNames.Each(x => LinqContext.ExcludeFiltersNamed.Add(x)); + LinqContext.Init(); }); } diff --git a/src/CustomScriptMethods.cs b/src/CustomScriptMethods.cs index 93e50d2..874b0de 100644 --- a/src/CustomScriptMethods.cs +++ b/src/CustomScriptMethods.cs @@ -15,8 +15,10 @@ namespace SharpScript public class CustomScriptMethods : ScriptMethods { public Dictionary> DocsIndex { get; } = new Dictionary>(); - public Dictionary> LinqIndex { get; } = new Dictionary>(); + public Dictionary> CodeIndex { get; } = new Dictionary>(); + public Dictionary> LispIndex { get; } = new Dictionary>(); public Dictionary> UseCasesIndex { get; } = new Dictionary>(); + public Dictionary> LinqIndex { get; } = new Dictionary>(); public object prevDocLink(int order) { @@ -32,16 +34,30 @@ public object nextDocLink(int order) return null; } - public object prevLinqLink(int order) + public object prevCodeLink(int order) { - if (LinqIndex.TryGetValue(order - 1, out KeyValuePair entry)) + if (CodeIndex.TryGetValue(order - 1, out KeyValuePair entry)) return entry; return null; } - public object nextLinqLink(int order) + public object nextCodeLink(int order) { - if (LinqIndex.TryGetValue(order + 1, out KeyValuePair entry)) + if (CodeIndex.TryGetValue(order + 1, out KeyValuePair entry)) + return entry; + return null; + } + + public object prevLispLink(int order) + { + if (LispIndex.TryGetValue(order - 1, out KeyValuePair entry)) + return entry; + return null; + } + + public object nextLispLink(int order) + { + if (LispIndex.TryGetValue(order + 1, out KeyValuePair entry)) return entry; return null; } @@ -60,15 +76,34 @@ public object nextUseCaseLink(int order) return null; } + public object prevLinqLink(int order) + { + if (LinqIndex.TryGetValue(order - 1, out KeyValuePair entry)) + return entry; + return null; + } + + public object nextLinqLink(int order) + { + if (LinqIndex.TryGetValue(order + 1, out KeyValuePair entry)) + return entry; + return null; + } + List> sortedDocLinks; public object docLinks() => sortedDocLinks ?? (sortedDocLinks = sortLinks(DocsIndex)); + List> sortedCodeLinks; + public object codeLinks() => sortedCodeLinks ?? (sortedCodeLinks = sortLinks(CodeIndex)); + List> sortedLispLinks; + public object lispLinks() => sortedLispLinks ?? (sortedLispLinks = sortLinks(LispIndex)); + + List> sortedUseCaseLinks; + public object useCaseLinks() => sortedUseCaseLinks ?? (sortedUseCaseLinks = sortLinks(UseCasesIndex)); + List> sortedLinqLinks; public object linqLinks() => sortedLinqLinks ?? (sortedLinqLinks = sortLinks(LinqIndex)); - List> sorteUseCaseLinks; - public object useCaseLinks() => sorteUseCaseLinks ?? (sorteUseCaseLinks = sortLinks(UseCasesIndex)); - public List> sortLinks(Dictionary> links) { var sortedKeys = links.Keys.ToList(); @@ -153,9 +188,19 @@ public IRawString methodLinkToSrc(string name) return new RawString($"{type.Name}.cs"); } - public ScriptMethodInfo[] methodsAvailable(string name) + public ScriptMethodInfo[] methodsAvailable(string name) => ScriptMethodInfo.GetMethodsAvailable(GetFilterType(name)); + } + + public class ScriptMethodInfo + { + public string Name { get; set; } + public string FirstParam { get; set; } + public string ReturnType { get; set; } + public int ParamCount { get; set; } + public string[] RemainingParams { get; set; } + + public static ScriptMethodInfo[] GetMethodsAvailable(Type filterType) { - var filterType = GetFilterType(name); var filters = filterType.GetMethods(BindingFlags.Instance | BindingFlags.Public); var to = filters .OrderBy(x => x.Name) @@ -166,15 +211,6 @@ public ScriptMethodInfo[] methodsAvailable(string name) return to.ToArray(); } - } - - public class ScriptMethodInfo - { - public string Name { get; set; } - public string FirstParam { get; set; } - public string ReturnType { get; set; } - public int ParamCount { get; set; } - public string[] RemainingParams { get; set; } public static ScriptMethodInfo Create(MethodInfo mi) { diff --git a/src/GitHubMarkdownFilters.cs b/src/GitHubMarkdownFilters.cs index 0bda304..d414492 100644 --- a/src/GitHubMarkdownFilters.cs +++ b/src/GitHubMarkdownFilters.cs @@ -35,11 +35,27 @@ public static async Task convertScriptToCodeBlocks(Stream renderedHtmlMa { var html = await renderedHtmlMarkdownBlock.ReadToEndAsync(); html = html.Replace("<script>", - "```code") - .Replace("</script>", - "```") - .Replace("</script>", - "```"); + "```code") + .Replace("</script>", + "```") + .Replace("</script>", + "```") + .Replace("</script>", + "```"); + return MemoryStreamFactory.GetStream(html.ToUtf8Bytes()); + } + + public static async Task convertScriptToLispBlocks(Stream renderedHtmlMarkdownBlock) + { + var html = await renderedHtmlMarkdownBlock.ReadToEndAsync(); + html = html.Replace("<script>", + "```lisp") + .Replace("</script>", + "```") + .Replace("</script>", + "```") + .Replace("</script>", + "```"); return MemoryStreamFactory.GetStream(html.ToUtf8Bytes()); } diff --git a/src/LinqServices.cs b/src/LinqServices.cs index 81634f9..c43810a 100644 --- a/src/LinqServices.cs +++ b/src/LinqServices.cs @@ -12,7 +12,8 @@ namespace SharpScript [Route("/linq/eval")] public class EvaluateLinq : IReturn { - public string Template { get; set; } + public string Code { get; set; } + public string Lang { get; set; } public Dictionary Files { get; set; } } @@ -28,12 +29,18 @@ public async Task Any(EvaluateLinq request) context.VirtualFiles.WriteFile(entry.Key, entry.Value); } - var pageResult = new PageResult(context.OneTimePage(request.Template)); + var page = request.Lang == "code" + ? context.CodeSharpPage(request.Code) + : request.Lang == "lisp" + ? context.LispSharpPage(request.Code) + : context.OneTimePage(request.Code); + + var pageResult = new PageResult(page); return await pageResult.RenderToStringAsync(); } } - public class AnagramEqualityComparer : IEqualityComparer + public class AnagramEqualityComparer : IEqualityComparer, IEqualityComparer { public bool Equals(string x, string y) => GetCanonicalString(x) == GetCanonicalString(y); public int GetHashCode(string obj) => GetCanonicalString(obj).GetHashCode(); @@ -43,6 +50,10 @@ private string GetCanonicalString(string word) Array.Sort(wordChars); return new string(wordChars); } + + public bool Equals(object x, object y) => Equals((string) x, (string) y); + + public int GetHashCode(object obj) => GetHashCode((string)obj); } public class Customer diff --git a/src/TemplateServices.cs b/src/ScriptServices.cs similarity index 97% rename from src/TemplateServices.cs rename to src/ScriptServices.cs index 63258e3..19839d1 100644 --- a/src/TemplateServices.cs +++ b/src/ScriptServices.cs @@ -38,7 +38,7 @@ public class EvalExpressionResponse } [ReturnExceptionsInJson] - public class TemplateServices : Service + public class ScriptServices : Service { public async Task Any(EvaluateScripts request) { @@ -68,6 +68,7 @@ public async Task Any(EvaluateScript request) { var context = new ScriptContext { DebugMode = false, + ScriptLanguages = { ScriptLisp.Language }, ScriptMethods = { new DbScripts(), new AutoQueryScripts(), diff --git a/src/SharpScript.csproj b/src/SharpScript.csproj index fcce1c8..59ec269 100644 --- a/src/SharpScript.csproj +++ b/src/SharpScript.csproj @@ -22,18 +22,18 @@ + - + + + + + + + + + + diff --git a/src/global.json b/src/global.json new file mode 100644 index 0000000..2be3e86 --- /dev/null +++ b/src/global.json @@ -0,0 +1,5 @@ +{ + "sdk": { + "version": "2.2.105" + } +} \ No newline at end of file diff --git a/src/wwwroot/_net-usage-partial.html b/src/wwwroot/_net-usage-partial.html new file mode 100644 index 0000000..87a84f3 --- /dev/null +++ b/src/wwwroot/_net-usage-partial.html @@ -0,0 +1,29 @@ +{{ 'gfm/introduction/11.md' | githubMarkdown }} + +

+ Where you can customize the pure sandboxed ScriptContext your Script is executed within by extending it with: +

+ + diff --git a/src/wwwroot/assets/img/multi-langs.svg b/src/wwwroot/assets/img/multi-langs.svg new file mode 100644 index 0000000..13ea05d --- /dev/null +++ b/src/wwwroot/assets/img/multi-langs.svg @@ -0,0 +1,3 @@ + + +
page text {{ 1 + 2 + 3 }}
page text {{ 1 + 2 + 3 }}
```lisp
(+ 1 2 3)
```
[Not supported by viewer]
```code
1 + 2 + 3
```
[Not supported by viewer]
more text and whitespace
more text and whitespace
{|lisp (* 1 2 3) |}
{|lisp (* 1 2 3) |}
template
template
code
code
lisp
lisp
fragment
fragment
fragment (JsStatement[])
fragment (JsStatement[])
fragment
fragment
fragment (SExpression[])
fragment (SExpression[])
fragment
fragment
fragment (SExpression[])
fragment (SExpression[])
template
template
code
code
lisp
lisp
page output
page output
PageResult
PageResult
\ No newline at end of file diff --git a/src/wwwroot/assets/js/default.js b/src/wwwroot/assets/js/default.js index 34b5113..3a66d9e 100644 --- a/src/wwwroot/assets/js/default.js +++ b/src/wwwroot/assets/js/default.js @@ -1,3 +1,26 @@ +$('.lang-select').each(function(){ + var qs = queryStringParams(); + var lang = qs['lang'] || 'template'; + var el = $(this); + el.find("input[type=radio]").on("change", function(){ + qs['lang'] = this.id === 'template' ? null : this.id; + var url = location.href.split('?')[0]; + for (var k in qs) { + if (!qs[k]) continue; + url += url.indexOf('?' >= 0) ? "?" : "&"; + url += k + "=" + encodeURIComponent(qs[k]); + } + location.href = url; + }); + el.find("input[type=radio]").each(function() { + this.checked = this.id === lang; + if (this.checked) + $(this).parents("label").addClass('active'); + else + $(this).parents("label").removeClass('active'); + }) +}) + $(".live-pages").each(function(){ var el = $(this) @@ -52,6 +75,7 @@ $(".linq-preview").each(function(){ var files = {} var el = $(this) + var lang = $(this).data('lang'); el.find("textarea").on("input", function(){ var files = {} @@ -61,11 +85,12 @@ $(".linq-preview").each(function(){ files[name] = contents }) - var request = { template: el.find(".template textarea").val(), files: files } + var request = { code: el.find(".template textarea").val(), files: files } + var qsLang = lang ? "?lang=" + lang : ""; $.ajax({ type: "POST", - url: "/linq/eval", + url: "/linq/eval" + qsLang, data: JSON.stringify(request), contentType: "application/json", dataType: "html" diff --git a/src/wwwroot/code/linq01-langs.ss b/src/wwwroot/code/linq01-langs.ss new file mode 100644 index 0000000..346bdfa --- /dev/null +++ b/src/wwwroot/code/linq01-langs.ss @@ -0,0 +1,11 @@ +{{ [5, 4, 1, 3, 9, 8, 6, 7, 2, 0] | to => numbers }} + +code: +```code +#each numbers where it < 5 + it +/each +``` + +lisp: +{|lisp (joinln (where #(< % 5) numbers)) |} \ No newline at end of file diff --git a/src/wwwroot/code/linq01.l b/src/wwwroot/code/linq01.l new file mode 100644 index 0000000..6446a7d --- /dev/null +++ b/src/wwwroot/code/linq01.l @@ -0,0 +1,2 @@ +(let ( (numbers [5, 4, 1, 3, 9, 8, 6, 7, 2, 0]) ) + (joinln (where #(< % 5) numbers))) diff --git a/src/wwwroot/code/linq01.sc b/src/wwwroot/code/linq01.sc new file mode 100644 index 0000000..92937fe --- /dev/null +++ b/src/wwwroot/code/linq01.sc @@ -0,0 +1,5 @@ +[5, 4, 1, 3, 9, 8, 6, 7, 2, 0] |> to => numbers +`Numbers < 5:` +#each numbers where it < 5 + it +/each diff --git a/src/wwwroot/code/linq01.txt b/src/wwwroot/code/linq01.ss similarity index 53% rename from src/wwwroot/code/linq01.txt rename to src/wwwroot/code/linq01.ss index 85540c7..3a51c82 100644 --- a/src/wwwroot/code/linq01.txt +++ b/src/wwwroot/code/linq01.ss @@ -1,4 +1,4 @@ -{{ [5, 4, 1, 3, 9, 8, 6, 7, 2, 0] | to => numbers }} +{{ [5, 4, 1, 3, 9, 8, 6, 7, 2, 0] |> to => numbers }} Numbers < 5: {{#each numbers where it < 5}} {{it}} diff --git a/src/wwwroot/code/linq02.l b/src/wwwroot/code/linq02.l new file mode 100644 index 0000000..cf01178 --- /dev/null +++ b/src/wwwroot/code/linq02.l @@ -0,0 +1,4 @@ +(let ( (sold-out-products (where #(= 0 (.UnitsInStock %)) products-list)) ) + (println "Sold out products:") + (doseq (p sold-out-products) + (println (.ProductName p) " is sold out") )) \ No newline at end of file diff --git a/src/wwwroot/code/linq02.sc b/src/wwwroot/code/linq02.sc new file mode 100644 index 0000000..93acaeb --- /dev/null +++ b/src/wwwroot/code/linq02.sc @@ -0,0 +1,4 @@ +`Sold out products:` +#each products where UnitsInStock = 0 + `${ProductName} is sold out!` +/each diff --git a/src/wwwroot/code/linq02.txt b/src/wwwroot/code/linq02.ss similarity index 100% rename from src/wwwroot/code/linq02.txt rename to src/wwwroot/code/linq02.ss diff --git a/src/wwwroot/code/linq03.l b/src/wwwroot/code/linq03.l new file mode 100644 index 0000000..d76c7a5 --- /dev/null +++ b/src/wwwroot/code/linq03.l @@ -0,0 +1,3 @@ +(println "In-stock products that cost more than 3.00:") +(doseq (p (where #(and (> (.UnitsInStock %) 0) (> (.UnitPrice %) 3)) products-list)) + (println (.ProductName p) " is in stock and costs more than 3.00")) \ No newline at end of file diff --git a/src/wwwroot/code/linq03.sc b/src/wwwroot/code/linq03.sc new file mode 100644 index 0000000..b77aebd --- /dev/null +++ b/src/wwwroot/code/linq03.sc @@ -0,0 +1,4 @@ +`In-stock products that cost more than 3.00:` +#each products where UnitsInStock > 0 && UnitPrice > 3.00 + `${ProductName} is in stock and costs more than 3.00.` +/each \ No newline at end of file diff --git a/src/wwwroot/code/linq03.txt b/src/wwwroot/code/linq03.ss similarity index 100% rename from src/wwwroot/code/linq03.txt rename to src/wwwroot/code/linq03.ss diff --git a/src/wwwroot/code/linq04-customer.ss b/src/wwwroot/code/linq04-customer.ss new file mode 100644 index 0000000..5886a9b --- /dev/null +++ b/src/wwwroot/code/linq04-customer.ss @@ -0,0 +1,2 @@ +Customer {{ it.CustomerId }} {{ it.CompanyName |> raw }} +{{ it.Orders |> selectPartial: order }} \ No newline at end of file diff --git a/src/wwwroot/code/linq04-customer.txt b/src/wwwroot/code/linq04-customer.txt deleted file mode 100644 index 74f9ca2..0000000 --- a/src/wwwroot/code/linq04-customer.txt +++ /dev/null @@ -1,2 +0,0 @@ -Customer {{ it.CustomerId }} {{ it.CompanyName | raw }} -{{ it.Orders | selectPartial: order }} \ No newline at end of file diff --git a/src/wwwroot/code/linq04-order.ss b/src/wwwroot/code/linq04-order.ss new file mode 100644 index 0000000..1a640de --- /dev/null +++ b/src/wwwroot/code/linq04-order.ss @@ -0,0 +1 @@ + Order {{ it.OrderId }}: {{ it.OrderDate |> dateFormat }} diff --git a/src/wwwroot/code/linq04-order.txt b/src/wwwroot/code/linq04-order.txt deleted file mode 100644 index 039f91f..0000000 --- a/src/wwwroot/code/linq04-order.txt +++ /dev/null @@ -1 +0,0 @@ - Order {{ it.OrderId }}: {{ it.OrderDate | dateFormat }} diff --git a/src/wwwroot/code/linq04-partials.ss b/src/wwwroot/code/linq04-partials.ss new file mode 100644 index 0000000..a97bcc1 --- /dev/null +++ b/src/wwwroot/code/linq04-partials.ss @@ -0,0 +1,3 @@ +{{ customers |> where => it.Region = 'WA' |> to => waCustomers }} +Customers from Washington and their orders: +{{ waCustomers |> selectPartial: customer }} \ No newline at end of file diff --git a/src/wwwroot/code/linq04-partials.txt b/src/wwwroot/code/linq04-partials.txt deleted file mode 100644 index 098c964..0000000 --- a/src/wwwroot/code/linq04-partials.txt +++ /dev/null @@ -1,3 +0,0 @@ -{{ customers | where => it.Region = 'WA' | to => waCustomers }} -Customers from Washington and their orders: -{{ waCustomers | selectPartial: customer }} \ No newline at end of file diff --git a/src/wwwroot/code/linq04.l b/src/wwwroot/code/linq04.l new file mode 100644 index 0000000..dddc8f4 --- /dev/null +++ b/src/wwwroot/code/linq04.l @@ -0,0 +1,5 @@ +(println "Customers from Washington and their orders:") +(doseq (c (where #(= (.Region %) "WA") customers-list)) + (println "Customer " (.CustomerId c) ": " (.CompanyName c) ": ") + (doseq (o (.Orders c)) + (println " Order " (.OrderId o) ": " (.OrderDate o)) )) \ No newline at end of file diff --git a/src/wwwroot/code/linq04.sc b/src/wwwroot/code/linq04.sc new file mode 100644 index 0000000..152fce2 --- /dev/null +++ b/src/wwwroot/code/linq04.sc @@ -0,0 +1,7 @@ +`Customers from Washington and their orders:` +#each c in customers where c.Region == 'WA' +` Customer ${c.CustomerId} ${c.CompanyName}` + #each c.Orders + ` Order ${OrderId}: ${OrderDate.dateFormat()}` + /each +/each \ No newline at end of file diff --git a/src/wwwroot/code/linq04.txt b/src/wwwroot/code/linq04.ss similarity index 100% rename from src/wwwroot/code/linq04.txt rename to src/wwwroot/code/linq04.ss diff --git a/src/wwwroot/code/linq05.l b/src/wwwroot/code/linq05.l new file mode 100644 index 0000000..e2c3453 --- /dev/null +++ b/src/wwwroot/code/linq05.l @@ -0,0 +1,4 @@ +(let ( (digits ["zero" "one" "two" "three" "four" "five" "six" "seven" "eight" "nine"]) ) + (println "Short digits:") + (doseq (d (filter-index #(< (length %1) %2) digits)) + (println "The word " d " is shorter than its value"))) \ No newline at end of file diff --git a/src/wwwroot/code/linq05.sc b/src/wwwroot/code/linq05.sc new file mode 100644 index 0000000..163bf7e --- /dev/null +++ b/src/wwwroot/code/linq05.sc @@ -0,0 +1,5 @@ +['zero','one','two','three','four','five','six','seven','eight','nine'] |> to => digits +`Short digits:` +#each d in digits where d.Length < index + `The word ${d} is shorter than its value.` +/each diff --git a/src/wwwroot/code/linq05.txt b/src/wwwroot/code/linq05.ss similarity index 85% rename from src/wwwroot/code/linq05.txt rename to src/wwwroot/code/linq05.ss index d4b24ee..8cff9dd 100644 --- a/src/wwwroot/code/linq05.txt +++ b/src/wwwroot/code/linq05.ss @@ -1,4 +1,4 @@ -{{ ['zero','one','two','three','four','five','six','seven','eight','nine'] | to => digits }} +{{ ['zero','one','two','three','four','five','six','seven','eight','nine'] |> to => digits }} Short digits: {{#each d in digits where d.Length < index}} The word {{d}} is shorter than its value. diff --git a/src/wwwroot/code/linq06.l b/src/wwwroot/code/linq06.l new file mode 100644 index 0000000..9d2ea79 --- /dev/null +++ b/src/wwwroot/code/linq06.l @@ -0,0 +1,3 @@ +(let ( (numbers [5 4 1 3 9 8 6 7 2 0]) ) + (println "Numbers + 1:") + (joinln (map inc numbers))) \ No newline at end of file diff --git a/src/wwwroot/code/linq06.sc b/src/wwwroot/code/linq06.sc new file mode 100644 index 0000000..55564d1 --- /dev/null +++ b/src/wwwroot/code/linq06.sc @@ -0,0 +1,5 @@ +[5, 4, 1, 3, 9, 8, 6, 7, 2, 0] |> to => numbers +`Numbers + 1:` +#each numbers + it + 1 +/each \ No newline at end of file diff --git a/src/wwwroot/code/linq06.txt b/src/wwwroot/code/linq06.ss similarity index 50% rename from src/wwwroot/code/linq06.txt rename to src/wwwroot/code/linq06.ss index 09d4db8..a3e3fe0 100644 --- a/src/wwwroot/code/linq06.txt +++ b/src/wwwroot/code/linq06.ss @@ -1,4 +1,4 @@ -{{ [5, 4, 1, 3, 9, 8, 6, 7, 2, 0] | to => numbers }} +{{ [5, 4, 1, 3, 9, 8, 6, 7, 2, 0] |> to => numbers }} Numbers + 1: {{#each numbers}} {{ it + 1 }} diff --git a/src/wwwroot/code/linq07.l b/src/wwwroot/code/linq07.l new file mode 100644 index 0000000..f8b8ca2 --- /dev/null +++ b/src/wwwroot/code/linq07.l @@ -0,0 +1,2 @@ +(println "Product Names:") +(joinln (map .ProductName products-list)) \ No newline at end of file diff --git a/src/wwwroot/code/linq07.sc b/src/wwwroot/code/linq07.sc new file mode 100644 index 0000000..2ecc5f4 --- /dev/null +++ b/src/wwwroot/code/linq07.sc @@ -0,0 +1,4 @@ +`Product Names:` +#each products + ProductName +/each \ No newline at end of file diff --git a/src/wwwroot/code/linq07.txt b/src/wwwroot/code/linq07.ss similarity index 83% rename from src/wwwroot/code/linq07.txt rename to src/wwwroot/code/linq07.ss index 5d17b14..077731a 100644 --- a/src/wwwroot/code/linq07.txt +++ b/src/wwwroot/code/linq07.ss @@ -1,4 +1,4 @@ Product Names: {{#each products}} {{ProductName}} -{{/each}} +{{/each}} \ No newline at end of file diff --git a/src/wwwroot/code/linq08.l b/src/wwwroot/code/linq08.l new file mode 100644 index 0000000..5859d6e --- /dev/null +++ b/src/wwwroot/code/linq08.l @@ -0,0 +1,4 @@ +(let ( (numbers [5 4 1 3 9 8 6 7 2 0]) + (strings ["zero" "one" "two" "three" "four" "five" "six" "seven" "eight" "nine"]) ) + (println "Number strings:") + (joinln (map #(nth strings %) numbers))) \ No newline at end of file diff --git a/src/wwwroot/code/linq08.sc b/src/wwwroot/code/linq08.sc new file mode 100644 index 0000000..09c4baa --- /dev/null +++ b/src/wwwroot/code/linq08.sc @@ -0,0 +1,6 @@ +[5, 4, 1, 3, 9, 8, 6, 7, 2, 0] | to => numbers +['zero','one','two','three','four','five','six','seven','eight','nine'] | to => strings +`Number strings:` +#each n in numbers + strings[n] +/each \ No newline at end of file diff --git a/src/wwwroot/code/linq08.txt b/src/wwwroot/code/linq08.ss similarity index 100% rename from src/wwwroot/code/linq08.txt rename to src/wwwroot/code/linq08.ss diff --git a/src/wwwroot/code/linq09.l b/src/wwwroot/code/linq09.l new file mode 100644 index 0000000..52adddc --- /dev/null +++ b/src/wwwroot/code/linq09.l @@ -0,0 +1,3 @@ +(let ( (words ["aPPLE" "BlUeBeRrY" "cHeRry"]) ) + (doseq (ul (map #(it { :lower (lower-case %) :upper (upper-case %) } ) words)) + (println "Uppercase: " (:upper ul) ", Lowercase: " (:lower ul)))) \ No newline at end of file diff --git a/src/wwwroot/code/linq09.sc b/src/wwwroot/code/linq09.sc new file mode 100644 index 0000000..bb21f2f --- /dev/null +++ b/src/wwwroot/code/linq09.sc @@ -0,0 +1,5 @@ +['aPPLE', 'BlUeBeRrY', 'cHeRry'] | to => words +words | map => { Uppercase: it.upper(), Lowercase: it.lower() } | to => upperLowerWords +#each ul in upperLowerWords + `Uppercase: ${ul.Uppercase}, Lowercase: ${ul.Lowercase}` +/each \ No newline at end of file diff --git a/src/wwwroot/code/linq09.txt b/src/wwwroot/code/linq09.ss similarity index 100% rename from src/wwwroot/code/linq09.txt rename to src/wwwroot/code/linq09.ss diff --git a/src/wwwroot/code/linq10.l b/src/wwwroot/code/linq10.l new file mode 100644 index 0000000..30e4393 --- /dev/null +++ b/src/wwwroot/code/linq10.l @@ -0,0 +1,4 @@ +(let ( (numbers [5 4 1 3 9 8 6 7 2 0]) + (strings ["zero" "one" "two" "three" "four" "five" "six" "seven" "eight" "nine"]) ) + (doseq (d (map #(it { :digit (nth strings %) :even (even? %) }) numbers)) + (println "The digit " (:digit d) " is " (if (:even d) "even" "odd")) )) \ No newline at end of file diff --git a/src/wwwroot/code/linq10.sc b/src/wwwroot/code/linq10.sc new file mode 100644 index 0000000..e9d4812 --- /dev/null +++ b/src/wwwroot/code/linq10.sc @@ -0,0 +1,6 @@ +[5, 4, 1, 3, 9, 8, 6, 7, 2, 0] | to => numbers +['zero','one','two','three','four','five','six','seven','eight','nine'] | to => strings +numbers | map => { Digit: strings[it], Even: it % 2 == 0 } | to => digitOddEvens +#each digitOddEvens + `The digit ${Digit} is ${Even ? "even" : "odd"}.` +/each \ No newline at end of file diff --git a/src/wwwroot/code/linq10.txt b/src/wwwroot/code/linq10.ss similarity index 100% rename from src/wwwroot/code/linq10.txt rename to src/wwwroot/code/linq10.ss diff --git a/src/wwwroot/code/linq100.l b/src/wwwroot/code/linq100.l new file mode 100644 index 0000000..e9dab8c --- /dev/null +++ b/src/wwwroot/code/linq100.l @@ -0,0 +1,4 @@ +(let ( (numbers [5 4 1 3 9 8 6 7 2 0]) + (i 0) ) + (setq q (map #(it (f++ i)) numbers)) + (doseq (v q) (println "v = " v ", i = " i))) \ No newline at end of file diff --git a/src/wwwroot/code/linq100.sc b/src/wwwroot/code/linq100.sc new file mode 100644 index 0000000..8f6ca69 --- /dev/null +++ b/src/wwwroot/code/linq100.sc @@ -0,0 +1,3 @@ +[5, 4, 1, 3, 9, 8, 6, 7, 2, 0] | to => numbers +0 | to => i +numbers | let => { i: i + 1 } | toList | map => `v = ${index + 1}, i = ${i}` | joinln \ No newline at end of file diff --git a/src/wwwroot/code/linq100.txt b/src/wwwroot/code/linq100.ss similarity index 100% rename from src/wwwroot/code/linq100.txt rename to src/wwwroot/code/linq100.ss diff --git a/src/wwwroot/code/linq101.l b/src/wwwroot/code/linq101.l new file mode 100644 index 0000000..0d2f605 --- /dev/null +++ b/src/wwwroot/code/linq101.l @@ -0,0 +1,11 @@ +(let ( (numbers [5 4 1 3 9 8 6 7 2 0]) ) + + (defn low-numbers [] (where #(<= % 3) numbers)) + + (println "First run numbers <= 3:") + (doseq (n (low-numbers)) (println n)) + + (setq numbers (map #(- %) numbers)) + + (println "Second run numbers <= 3") + (doseq (n (low-numbers)) (println n))) \ No newline at end of file diff --git a/src/wwwroot/code/linq101.sc b/src/wwwroot/code/linq101.sc new file mode 100644 index 0000000..db21959 --- /dev/null +++ b/src/wwwroot/code/linq101.sc @@ -0,0 +1,12 @@ +[5, 4, 1, 3, 9, 8, 6, 7, 2, 0] | to => numbers +numbers | where => it <= 3 | to => lowNumbers + +`First run numbers <= 3:` +lowNumbers | joinln +10 | times | do => assign('numbers[index]', -numbers[index]) + +`Second run numbers <= 3:` +lowNumbers | joinln + +`Contents of numbers:` +numbers | joinln \ No newline at end of file diff --git a/src/wwwroot/code/linq101.txt b/src/wwwroot/code/linq101.ss similarity index 74% rename from src/wwwroot/code/linq101.txt rename to src/wwwroot/code/linq101.ss index 3bbf9e9..b97f13a 100644 --- a/src/wwwroot/code/linq101.txt +++ b/src/wwwroot/code/linq101.ss @@ -3,9 +3,9 @@ | where => it <= 3 | to => lowNumbers }} First run numbers <= 3: -{{ lowNumbers | join(`\n`) }} +{{ lowNumbers | joinln }} {{ 10 | times | do => assign('numbers[index]', -numbers[index]) }} Second run numbers <= 3: -{{ lowNumbers | join(`\n`) }} +{{ lowNumbers | joinln }} Contents of numbers: -{{ numbers | join(`\n`) }} \ No newline at end of file +{{ numbers | joinln }} \ No newline at end of file diff --git a/src/wwwroot/code/linq11.l b/src/wwwroot/code/linq11.l new file mode 100644 index 0000000..be466cf --- /dev/null +++ b/src/wwwroot/code/linq11.l @@ -0,0 +1,8 @@ +(let ( (product-infos (map #(it { + :ProductName (.ProductName %) + :Category (.Category %) + :Price (.UnitPrice %) }) + products-list)) ) + (println "Product Info:") + (doseq (p product-infos) + (println (:ProductName p) " is in the category " (:Category p) " and costs " (:Price p)))) \ No newline at end of file diff --git a/src/wwwroot/code/linq11.sc b/src/wwwroot/code/linq11.sc new file mode 100644 index 0000000..54650d1 --- /dev/null +++ b/src/wwwroot/code/linq11.sc @@ -0,0 +1,5 @@ +`Product Info:` +products | map => { it.ProductName, it.Category, Price: it.UnitPrice } | to => productInfos +#each productInfos +`${ProductName} is in the Category ${Category} and costs ${Price.currency()} per unit.` +/each \ No newline at end of file diff --git a/src/wwwroot/code/linq11.txt b/src/wwwroot/code/linq11.ss similarity index 100% rename from src/wwwroot/code/linq11.txt rename to src/wwwroot/code/linq11.ss diff --git a/src/wwwroot/code/linq12.l b/src/wwwroot/code/linq12.l new file mode 100644 index 0000000..665a187 --- /dev/null +++ b/src/wwwroot/code/linq12.l @@ -0,0 +1,5 @@ +(let ( (numbers [5 4 1 3 9 8 6 7 2 0]) + (i 0) ) + (println "Number: In-place?") + (doseq (n (map #(it { :num % :in-place (= % (f++ i)) }) numbers)) + (println (:num n) ": " (if (:in-place n) 'true 'false)) )) \ No newline at end of file diff --git a/src/wwwroot/code/linq12.sc b/src/wwwroot/code/linq12.sc new file mode 100644 index 0000000..8993d82 --- /dev/null +++ b/src/wwwroot/code/linq12.sc @@ -0,0 +1,5 @@ +[5, 4, 1, 3, 9, 8, 6, 7, 2, 0] | to => numbers +`Number: In-place?` +#each n in numbers + `${n}: ${ n == index }` +/each diff --git a/src/wwwroot/code/linq12.txt b/src/wwwroot/code/linq12.ss similarity index 100% rename from src/wwwroot/code/linq12.txt rename to src/wwwroot/code/linq12.ss diff --git a/src/wwwroot/code/linq13.l b/src/wwwroot/code/linq13.l new file mode 100644 index 0000000..a21a9f1 --- /dev/null +++ b/src/wwwroot/code/linq13.l @@ -0,0 +1,4 @@ +(let ( (numbers [5 4 1 3 9 8 6 7 2 0]) + (digits ["zero" "one" "two" "three" "four" "five" "six" "seven" "eight" "nine"]) ) + (println "Numbers < 5:") + (joinln (map #(nth digits %) (where #(< % 5) numbers)))) \ No newline at end of file diff --git a/src/wwwroot/code/linq13.sc b/src/wwwroot/code/linq13.sc new file mode 100644 index 0000000..4904a24 --- /dev/null +++ b/src/wwwroot/code/linq13.sc @@ -0,0 +1,6 @@ +[5, 4, 1, 3, 9, 8, 6, 7, 2, 0] | to => numbers +['zero','one','two','three','four','five','six','seven','eight','nine'] | to => digits +`Numbers < 5:` +#each numbers where it < 5 + digits[it] +/each diff --git a/src/wwwroot/code/linq13.txt b/src/wwwroot/code/linq13.ss similarity index 100% rename from src/wwwroot/code/linq13.txt rename to src/wwwroot/code/linq13.ss diff --git a/src/wwwroot/code/linq14.l b/src/wwwroot/code/linq14.l new file mode 100644 index 0000000..aa3b3ee --- /dev/null +++ b/src/wwwroot/code/linq14.l @@ -0,0 +1,5 @@ +(let ( (numbers-a [0 2 4 5 6 8 9]) + (numbers-b [1 3 5 7 8]) ) + (println "Pairs where a < b:") + (doseq (pair (zip-where #(< %1 %2) #(it { :a %1 :b %2 }) numbers-a numbers-b)) + (println (:a pair) " is less than " (:b pair)))) \ No newline at end of file diff --git a/src/wwwroot/code/linq14.sc b/src/wwwroot/code/linq14.sc new file mode 100644 index 0000000..81b92f2 --- /dev/null +++ b/src/wwwroot/code/linq14.sc @@ -0,0 +1,8 @@ +[0, 2, 4, 5, 6, 8, 9] | to => numbersA +[1, 3, 5, 7, 8] | to => numbersB +`Pairs where a < b:` +{{ numbersA.zip(numbersB) + | let => { a: it[0], b: it[1] } + | where => a < b + | map => `${a} is less than ${b}` | joinln +}} \ No newline at end of file diff --git a/src/wwwroot/code/linq14.txt b/src/wwwroot/code/linq14.ss similarity index 79% rename from src/wwwroot/code/linq14.txt rename to src/wwwroot/code/linq14.ss index 16fefb9..4c57a0d 100644 --- a/src/wwwroot/code/linq14.txt +++ b/src/wwwroot/code/linq14.ss @@ -4,5 +4,5 @@ Pairs where a < b: {{ numbersA.zip(numbersB) | let => { a: it[0], b: it[1] } | where => a < b - | map => `${a} is less than ${b}` | join(`\n`) + | map => `${a} is less than ${b}` | joinln }} \ No newline at end of file diff --git a/src/wwwroot/code/linq15.l b/src/wwwroot/code/linq15.l new file mode 100644 index 0000000..c3f91d4 --- /dev/null +++ b/src/wwwroot/code/linq15.l @@ -0,0 +1,8 @@ +(let ( (orders (flatmap (fn [c] + (map #(it { + :customer-id (.CustomerId c) + :order-id (.OrderId %) + :total (.Total %) }) + (where #(< (.Total %) 500) (.Orders c)) )) + customers-list)) ) + (htmldump orders)) \ No newline at end of file diff --git a/src/wwwroot/code/linq15.txt b/src/wwwroot/code/linq15.sc similarity index 100% rename from src/wwwroot/code/linq15.txt rename to src/wwwroot/code/linq15.sc diff --git a/src/wwwroot/code/linq15.ss b/src/wwwroot/code/linq15.ss new file mode 100644 index 0000000..5f77aa3 --- /dev/null +++ b/src/wwwroot/code/linq15.ss @@ -0,0 +1,5 @@ +{{ customers | zip => it.Orders + | let => { c: it[0], o: it[1] } + | where => o.Total < 500 + | map => o + | htmlDump }} \ No newline at end of file diff --git a/src/wwwroot/code/linq16.l b/src/wwwroot/code/linq16.l new file mode 100644 index 0000000..3406007 --- /dev/null +++ b/src/wwwroot/code/linq16.l @@ -0,0 +1,9 @@ +(let ( (orders (flatmap (fn [c] + (map-where #(> (.OrderDate %) (DateTime. 1998 1 1)) + #(it { + :customer-id (.CustomerId c) + :order-id (.OrderId %) + :order-date (.OrderDate %) }) + (.Orders c) )) + customers-list) )) + (htmldump orders)) \ No newline at end of file diff --git a/src/wwwroot/code/linq16.txt b/src/wwwroot/code/linq16.sc similarity index 100% rename from src/wwwroot/code/linq16.txt rename to src/wwwroot/code/linq16.sc diff --git a/src/wwwroot/code/linq16.ss b/src/wwwroot/code/linq16.ss new file mode 100644 index 0000000..674e807 --- /dev/null +++ b/src/wwwroot/code/linq16.ss @@ -0,0 +1,5 @@ +{{ customers | zip => it.Orders + | let => { c: it[0], o: it[1] } + | where => o.OrderDate >= '1998-01-01' + | map => o + | htmlDump }} \ No newline at end of file diff --git a/src/wwwroot/code/linq17.l b/src/wwwroot/code/linq17.l new file mode 100644 index 0000000..cc669fc --- /dev/null +++ b/src/wwwroot/code/linq17.l @@ -0,0 +1,8 @@ +(htmldump (flatmap (fn [c] + (map-where #(>= (.Total %) 2000) + #(it { + :customer-id (.CustomerId c) + :order-id (.OrderId %) + :total (.Total %) }) + (.Orders c) )) + customers-list)) diff --git a/src/wwwroot/code/linq17.txt b/src/wwwroot/code/linq17.sc similarity index 100% rename from src/wwwroot/code/linq17.txt rename to src/wwwroot/code/linq17.sc diff --git a/src/wwwroot/code/linq17.ss b/src/wwwroot/code/linq17.ss new file mode 100644 index 0000000..6ee81ae --- /dev/null +++ b/src/wwwroot/code/linq17.ss @@ -0,0 +1,5 @@ +{{ customers | zip => it.Orders + | let => { c: it[0], o: it[1] } + | where => o.Total >= 2000 + | map => o + | htmlDump }} \ No newline at end of file diff --git a/src/wwwroot/code/linq18.l b/src/wwwroot/code/linq18.l new file mode 100644 index 0000000..45022df --- /dev/null +++ b/src/wwwroot/code/linq18.l @@ -0,0 +1,8 @@ +(let ( (cutoff-date (DateTime. 1997 1 1)) ) + (htmldump (flatmap (fn [c] + (map-where #(>= (.OrderDate %) cutoff-date) + #(it { + :customer-id (.CustomerId c) + :order-id (.OrderId %) }) + (.Orders c) )) + (where #(= (.Region %) "WA") customers-list)))) \ No newline at end of file diff --git a/src/wwwroot/code/linq18.sc b/src/wwwroot/code/linq18.sc new file mode 100644 index 0000000..dae7bda --- /dev/null +++ b/src/wwwroot/code/linq18.sc @@ -0,0 +1,8 @@ +'1997-01-01' | to => cutoffDate +{{ customers + | where => it.Region = 'WA' + | zip => it.Orders + | let => { c: it[0], o: it[1] } + | where => o.OrderDate >= cutoffDate + | map => o + | htmlDump }} \ No newline at end of file diff --git a/src/wwwroot/code/linq18.txt b/src/wwwroot/code/linq18.ss similarity index 100% rename from src/wwwroot/code/linq18.txt rename to src/wwwroot/code/linq18.ss diff --git a/src/wwwroot/code/linq19.l b/src/wwwroot/code/linq19.l new file mode 100644 index 0000000..29e670f --- /dev/null +++ b/src/wwwroot/code/linq19.l @@ -0,0 +1,7 @@ +(let ( (customer-orders (map + #(it (str "Customer #" (:i %) " has an order with OrderID " (.OrderId (:o %)))) + (flatten (map-index (fn [c i] (map #(it { + :o % + :i (1+ i) }) + (.Orders c))) customers-list)))) ) + (joinln customer-orders)) \ No newline at end of file diff --git a/src/wwwroot/code/linq19.txt b/src/wwwroot/code/linq19.sc similarity index 84% rename from src/wwwroot/code/linq19.txt rename to src/wwwroot/code/linq19.sc index e323fde..6216d01 100644 --- a/src/wwwroot/code/linq19.txt +++ b/src/wwwroot/code/linq19.sc @@ -2,4 +2,4 @@ | let => { cust: it, custIndex: index } | zip => cust.Orders | let => { o: it[1] } - | map => `Customer #${custIndex + 1} has an order with OrderID ${o.OrderId}` | join(`\n`) }} \ No newline at end of file + | map => `Customer #${custIndex + 1} has an order with OrderID ${o.OrderId}` | joinln }} \ No newline at end of file diff --git a/src/wwwroot/code/linq19.ss b/src/wwwroot/code/linq19.ss new file mode 100644 index 0000000..6216d01 --- /dev/null +++ b/src/wwwroot/code/linq19.ss @@ -0,0 +1,5 @@ +{{ customers + | let => { cust: it, custIndex: index } + | zip => cust.Orders + | let => { o: it[1] } + | map => `Customer #${custIndex + 1} has an order with OrderID ${o.OrderId}` | joinln }} \ No newline at end of file diff --git a/src/wwwroot/code/linq20.l b/src/wwwroot/code/linq20.l new file mode 100644 index 0000000..872941f --- /dev/null +++ b/src/wwwroot/code/linq20.l @@ -0,0 +1,3 @@ +(let ( (numbers [5 4 1 3 9 8 6 7 2 0]) ) + (println "First 3 numbers:") + (joinln (take 3 numbers))) \ No newline at end of file diff --git a/src/wwwroot/code/linq01-code.txt b/src/wwwroot/code/linq20.sc similarity index 53% rename from src/wwwroot/code/linq01-code.txt rename to src/wwwroot/code/linq20.sc index 8545bac..c64848c 100644 --- a/src/wwwroot/code/linq01-code.txt +++ b/src/wwwroot/code/linq20.sc @@ -1,7 +1,5 @@ -Numbers < 5: -```code [5, 4, 1, 3, 9, 8, 6, 7, 2, 0] | to => numbers -#each numbers where it < 5 +`First 3 numbers:` +#each numbers take 3 it /each -``` \ No newline at end of file diff --git a/src/wwwroot/code/linq20.txt b/src/wwwroot/code/linq20.ss similarity index 100% rename from src/wwwroot/code/linq20.txt rename to src/wwwroot/code/linq20.ss diff --git a/src/wwwroot/code/linq21.l b/src/wwwroot/code/linq21.l new file mode 100644 index 0000000..99335b0 --- /dev/null +++ b/src/wwwroot/code/linq21.l @@ -0,0 +1,9 @@ +(println " First 3 orders in WA:") +(htmldump (take 3 + (flatmap (fn [c] + (map #(it { + :customer-id (.CustomerId c) + :order-id (.OrderId %) + :order-date (.OrderDate %) }) + (.Orders c) )) + (where #(= (.Region %) "WA") customers-list) )) ) \ No newline at end of file diff --git a/src/wwwroot/code/linq21.sc b/src/wwwroot/code/linq21.sc new file mode 100644 index 0000000..9ee1bf2 --- /dev/null +++ b/src/wwwroot/code/linq21.sc @@ -0,0 +1,7 @@ +` First 3 orders in WA:` +{{ customers | zip => it.Orders + | let => { c: it[0], o: it[1] } + | where => c.Region = 'WA' + | take(3) + | map => o + | htmlDump }} \ No newline at end of file diff --git a/src/wwwroot/code/linq21.txt b/src/wwwroot/code/linq21.ss similarity index 100% rename from src/wwwroot/code/linq21.txt rename to src/wwwroot/code/linq21.ss diff --git a/src/wwwroot/code/linq22.l b/src/wwwroot/code/linq22.l new file mode 100644 index 0000000..ea9b414 --- /dev/null +++ b/src/wwwroot/code/linq22.l @@ -0,0 +1,3 @@ +(let ( (numbers [5 4 1 3 9 8 6 7 2 0]) ) + (println "All but first 4 numbers:") + (joinln (skip 4 numbers))) \ No newline at end of file diff --git a/src/wwwroot/code/linq22.sc b/src/wwwroot/code/linq22.sc new file mode 100644 index 0000000..64fc9cb --- /dev/null +++ b/src/wwwroot/code/linq22.sc @@ -0,0 +1,5 @@ +[5, 4, 1, 3, 9, 8, 6, 7, 2, 0] | to => numbers +`All but first 4 numbers:` +#each numbers skip 4 + it +/each diff --git a/src/wwwroot/code/linq22.txt b/src/wwwroot/code/linq22.ss similarity index 100% rename from src/wwwroot/code/linq22.txt rename to src/wwwroot/code/linq22.ss diff --git a/src/wwwroot/code/linq23.l b/src/wwwroot/code/linq23.l new file mode 100644 index 0000000..ef38f48 --- /dev/null +++ b/src/wwwroot/code/linq23.l @@ -0,0 +1,9 @@ +(println "All but first 2 orders in WA:") +(htmldump (skip 2 + (flatmap (fn [c] + (map #(it { + :customer-id (.CustomerId c) + :order-id (.OrderId %) + :order-date (.OrderDate %) }) + (.Orders c) )) + (where #(= (.Region %) "WA") customers-list) )) ) \ No newline at end of file diff --git a/src/wwwroot/code/linq23.sc b/src/wwwroot/code/linq23.sc new file mode 100644 index 0000000..93f104d --- /dev/null +++ b/src/wwwroot/code/linq23.sc @@ -0,0 +1,7 @@ +` All but first 2 orders in WA:` +{{ customers | zip => it.Orders + | let => { c: it[0], o: it[1] } + | where => c.Region = 'WA' + | skip(2) + | map => { c.CustomerId, o.OrderId, o.OrderDate } + | htmlDump }} \ No newline at end of file diff --git a/src/wwwroot/code/linq23.txt b/src/wwwroot/code/linq23.ss similarity index 100% rename from src/wwwroot/code/linq23.txt rename to src/wwwroot/code/linq23.ss diff --git a/src/wwwroot/code/linq24.l b/src/wwwroot/code/linq24.l new file mode 100644 index 0000000..9851706 --- /dev/null +++ b/src/wwwroot/code/linq24.l @@ -0,0 +1,3 @@ +(let ( (numbers [5 4 1 3 9 8 6 7 2 0]) ) + (println "First numbers less than 6:") + (joinln (take-while #(< % 6) numbers))) \ No newline at end of file diff --git a/src/wwwroot/code/linq24.sc b/src/wwwroot/code/linq24.sc new file mode 100644 index 0000000..216ea92 --- /dev/null +++ b/src/wwwroot/code/linq24.sc @@ -0,0 +1,3 @@ +[5, 4, 1, 3, 9, 8, 6, 7, 2, 0] | to => numbers +`First numbers less than 6:` +numbers | takeWhile => it < 6 | joinln \ No newline at end of file diff --git a/src/wwwroot/code/linq24.txt b/src/wwwroot/code/linq24.ss similarity index 86% rename from src/wwwroot/code/linq24.txt rename to src/wwwroot/code/linq24.ss index d157afb..c8257fc 100644 --- a/src/wwwroot/code/linq24.txt +++ b/src/wwwroot/code/linq24.ss @@ -2,4 +2,4 @@ First numbers less than 6: {{ numbers | takeWhile => it < 6 - | join(`\n`) }} \ No newline at end of file + | joinln }} \ No newline at end of file diff --git a/src/wwwroot/code/linq25.l b/src/wwwroot/code/linq25.l new file mode 100644 index 0000000..cb55341 --- /dev/null +++ b/src/wwwroot/code/linq25.l @@ -0,0 +1,4 @@ +(let ( (numbers [5 4 1 3 9 8 6 7 2 0] ) + (i 0) ) + (println "First numbers not less than their position:") + (joinln (take-while #(>= % (f++ i)) numbers))) \ No newline at end of file diff --git a/src/wwwroot/code/linq25.sc b/src/wwwroot/code/linq25.sc new file mode 100644 index 0000000..a5a1dcf --- /dev/null +++ b/src/wwwroot/code/linq25.sc @@ -0,0 +1,3 @@ +[5, 4, 1, 3, 9, 8, 6, 7, 2, 0] | to => numbers +`First numbers not less than their position:` +numbers | takeWhile => it >= index | joinln \ No newline at end of file diff --git a/src/wwwroot/code/linq25.txt b/src/wwwroot/code/linq25.ss similarity index 88% rename from src/wwwroot/code/linq25.txt rename to src/wwwroot/code/linq25.ss index 3fe0c5b..dcdb3a8 100644 --- a/src/wwwroot/code/linq25.txt +++ b/src/wwwroot/code/linq25.ss @@ -2,4 +2,4 @@ First numbers not less than their position: {{ numbers | takeWhile => it >= index - | join(`\n`) }} \ No newline at end of file + | joinln }} \ No newline at end of file diff --git a/src/wwwroot/code/linq26.l b/src/wwwroot/code/linq26.l new file mode 100644 index 0000000..d4851a5 --- /dev/null +++ b/src/wwwroot/code/linq26.l @@ -0,0 +1,3 @@ +(let ( (numbers [5 4 1 3 9 8 6 7 2 0]) ) + (println "All elements starting from first element divisible by 3:") + (joinln (skip-while #(not= (mod % 3) 0) numbers))) \ No newline at end of file diff --git a/src/wwwroot/code/linq26.sc b/src/wwwroot/code/linq26.sc new file mode 100644 index 0000000..1ab00b1 --- /dev/null +++ b/src/wwwroot/code/linq26.sc @@ -0,0 +1,3 @@ +[5, 4, 1, 3, 9, 8, 6, 7, 2, 0] | to => numbers +`All elements starting from first element divisible by 3:` +numbers | skipWhile => it % 3 != 0 | joinln \ No newline at end of file diff --git a/src/wwwroot/code/linq26.txt b/src/wwwroot/code/linq26.ss similarity index 89% rename from src/wwwroot/code/linq26.txt rename to src/wwwroot/code/linq26.ss index 6e67077..ab12f35 100644 --- a/src/wwwroot/code/linq26.txt +++ b/src/wwwroot/code/linq26.ss @@ -2,4 +2,4 @@ All elements starting from first element divisible by 3: {{ numbers | skipWhile => it % 3 != 0 - | join(`\n`) }} \ No newline at end of file + | joinln }} \ No newline at end of file diff --git a/src/wwwroot/code/linq27.l b/src/wwwroot/code/linq27.l new file mode 100644 index 0000000..d0710d5 --- /dev/null +++ b/src/wwwroot/code/linq27.l @@ -0,0 +1,4 @@ +(let ( (numbers [5 4 1 3 9 8 6 7 2 0]) + (i 0) ) + (println "All elements starting from first element less than its position:") + (joinln (skip-while #(>= % (f++ i)) numbers))) \ No newline at end of file diff --git a/src/wwwroot/code/linq27.sc b/src/wwwroot/code/linq27.sc new file mode 100644 index 0000000..48255f1 --- /dev/null +++ b/src/wwwroot/code/linq27.sc @@ -0,0 +1,3 @@ +[5, 4, 1, 3, 9, 8, 6, 7, 2, 0] | to => numbers +`All elements starting from first element less than its position:` +numbers | skipWhile => it >= index | joinln \ No newline at end of file diff --git a/src/wwwroot/code/linq27.txt b/src/wwwroot/code/linq27.ss similarity index 89% rename from src/wwwroot/code/linq27.txt rename to src/wwwroot/code/linq27.ss index 950b312..5d33123 100644 --- a/src/wwwroot/code/linq27.txt +++ b/src/wwwroot/code/linq27.ss @@ -2,4 +2,4 @@ All elements starting from first element less than its position: {{ numbers | skipWhile => it >= index - | join(`\n`) }} \ No newline at end of file + | joinln }} \ No newline at end of file diff --git a/src/wwwroot/code/linq28.l b/src/wwwroot/code/linq28.l new file mode 100644 index 0000000..e5f76eb --- /dev/null +++ b/src/wwwroot/code/linq28.l @@ -0,0 +1,3 @@ +(let ( (words ["cherry" "apple" "blueberry"]) ) + (println "The sorted list of words:") + (joinln (sort words))) \ No newline at end of file diff --git a/src/wwwroot/code/linq28.sc b/src/wwwroot/code/linq28.sc new file mode 100644 index 0000000..c6ebb39 --- /dev/null +++ b/src/wwwroot/code/linq28.sc @@ -0,0 +1,5 @@ +['cherry', 'apple', 'blueberry'] | to => words +`The sorted list of words:` +#each words orderby it + it +/each diff --git a/src/wwwroot/code/linq28.txt b/src/wwwroot/code/linq28.ss similarity index 100% rename from src/wwwroot/code/linq28.txt rename to src/wwwroot/code/linq28.ss diff --git a/src/wwwroot/code/linq29.l b/src/wwwroot/code/linq29.l new file mode 100644 index 0000000..7d4b1ea --- /dev/null +++ b/src/wwwroot/code/linq29.l @@ -0,0 +1,3 @@ +(let ( (words ["cherry" "apple" "blueberry"]) ) + (println "The sorted list of words (by length):") + (joinln (sort-by count words))) \ No newline at end of file diff --git a/src/wwwroot/code/linq29.sc b/src/wwwroot/code/linq29.sc new file mode 100644 index 0000000..ed9c33b --- /dev/null +++ b/src/wwwroot/code/linq29.sc @@ -0,0 +1,5 @@ +['cherry', 'apple', 'blueberry'] | to => words +`The sorted list of words (by length):` +#each words orderby it.Length + it +/each diff --git a/src/wwwroot/code/linq29.txt b/src/wwwroot/code/linq29.ss similarity index 100% rename from src/wwwroot/code/linq29.txt rename to src/wwwroot/code/linq29.ss diff --git a/src/wwwroot/code/linq30.l b/src/wwwroot/code/linq30.l new file mode 100644 index 0000000..08884b5 --- /dev/null +++ b/src/wwwroot/code/linq30.l @@ -0,0 +1 @@ +(htmldump (sort-by .ProductName products-list)) \ No newline at end of file diff --git a/src/wwwroot/code/linq30.sc b/src/wwwroot/code/linq30.sc new file mode 100644 index 0000000..106c156 --- /dev/null +++ b/src/wwwroot/code/linq30.sc @@ -0,0 +1 @@ +products | orderBy => it.ProductName | htmlDump \ No newline at end of file diff --git a/src/wwwroot/code/linq30.txt b/src/wwwroot/code/linq30.ss similarity index 100% rename from src/wwwroot/code/linq30.txt rename to src/wwwroot/code/linq30.ss diff --git a/src/wwwroot/code/linq31.l b/src/wwwroot/code/linq31.l new file mode 100644 index 0000000..75d60e2 --- /dev/null +++ b/src/wwwroot/code/linq31.l @@ -0,0 +1,2 @@ +(let ( (words ["aPPLE" "AbAcUs" "bRaNcH" "BlUeBeRrY" "ClOvEr" "cHeRry"]) ) + (joinln (sort-by it (CaseInsensitiveComparer.) words))) \ No newline at end of file diff --git a/src/wwwroot/code/linq31.sc b/src/wwwroot/code/linq31.sc new file mode 100644 index 0000000..b4d47aa --- /dev/null +++ b/src/wwwroot/code/linq31.sc @@ -0,0 +1,2 @@ +['aPPLE', 'AbAcUs', 'bRaNcH', 'BlUeBeRrY', 'ClOvEr', 'cHeRry'] | to => words + words | orderBy(o => o, { comparer }) | joinln \ No newline at end of file diff --git a/src/wwwroot/code/linq31.txt b/src/wwwroot/code/linq31.ss similarity index 87% rename from src/wwwroot/code/linq31.txt rename to src/wwwroot/code/linq31.ss index 57fba4d..25e3016 100644 --- a/src/wwwroot/code/linq31.txt +++ b/src/wwwroot/code/linq31.ss @@ -1,4 +1,4 @@ {{ ['aPPLE', 'AbAcUs', 'bRaNcH', 'BlUeBeRrY', 'ClOvEr', 'cHeRry'] | to => words }} {{ words | orderBy(o => o, { comparer }) - | join(`\n`) }} \ No newline at end of file + | joinln }} \ No newline at end of file diff --git a/src/wwwroot/code/linq32.l b/src/wwwroot/code/linq32.l new file mode 100644 index 0000000..0d41987 --- /dev/null +++ b/src/wwwroot/code/linq32.l @@ -0,0 +1,3 @@ +(let ( (dbls [1.7 2.3 1.9 4.1 2.9]) ) + (println "The doubles from highest to lowest:") + (joinln (reverse (sort dbls))) ) \ No newline at end of file diff --git a/src/wwwroot/code/linq32.sc b/src/wwwroot/code/linq32.sc new file mode 100644 index 0000000..e278f9f --- /dev/null +++ b/src/wwwroot/code/linq32.sc @@ -0,0 +1,5 @@ +[1.7, 2.3, 1.9, 4.1, 2.9] | to => doubles +`The doubles from highest to lowest:` +#each doubles orderby it descending + it +/each diff --git a/src/wwwroot/code/linq32.txt b/src/wwwroot/code/linq32.ss similarity index 100% rename from src/wwwroot/code/linq32.txt rename to src/wwwroot/code/linq32.ss diff --git a/src/wwwroot/code/linq33.l b/src/wwwroot/code/linq33.l new file mode 100644 index 0000000..527fc91 --- /dev/null +++ b/src/wwwroot/code/linq33.l @@ -0,0 +1 @@ +(htmldump (reverse (sort-by .UnitsInStock products-list))) \ No newline at end of file diff --git a/src/wwwroot/code/linq33.sc b/src/wwwroot/code/linq33.sc new file mode 100644 index 0000000..e08fec9 --- /dev/null +++ b/src/wwwroot/code/linq33.sc @@ -0,0 +1 @@ +products | orderByDescending => it.UnitsInStock | htmlDump \ No newline at end of file diff --git a/src/wwwroot/code/linq33.txt b/src/wwwroot/code/linq33.ss similarity index 100% rename from src/wwwroot/code/linq33.txt rename to src/wwwroot/code/linq33.ss diff --git a/src/wwwroot/code/linq34.l b/src/wwwroot/code/linq34.l new file mode 100644 index 0000000..a1ee4b4 --- /dev/null +++ b/src/wwwroot/code/linq34.l @@ -0,0 +1,2 @@ +(let ( (words ["aPPLE" "AbAcUs" "bRaNcH" "BlUeBeRrY" "ClOvEr" "cHeRry"]) ) + (joinln (order-by [{ :comparer (CaseInsensitiveComparer.) :desc true }] words))) \ No newline at end of file diff --git a/src/wwwroot/code/linq34.sc b/src/wwwroot/code/linq34.sc new file mode 100644 index 0000000..580fbd0 --- /dev/null +++ b/src/wwwroot/code/linq34.sc @@ -0,0 +1,2 @@ +['aPPLE', 'AbAcUs', 'bRaNcH', 'BlUeBeRrY', 'ClOvEr', 'cHeRry'] | to => words +words | orderByDescending(o => o, { comparer }) | joinln \ No newline at end of file diff --git a/src/wwwroot/code/linq34.txt b/src/wwwroot/code/linq34.ss similarity index 88% rename from src/wwwroot/code/linq34.txt rename to src/wwwroot/code/linq34.ss index 29fefe3..b45997b 100644 --- a/src/wwwroot/code/linq34.txt +++ b/src/wwwroot/code/linq34.ss @@ -1,4 +1,4 @@ {{ ['aPPLE', 'AbAcUs', 'bRaNcH', 'BlUeBeRrY', 'ClOvEr', 'cHeRry'] | to => words }} {{ words | orderByDescending(o => o, { comparer }) - | join(`\n`) }} \ No newline at end of file + | joinln }} \ No newline at end of file diff --git a/src/wwwroot/code/linq35.l b/src/wwwroot/code/linq35.l new file mode 100644 index 0000000..aa10f39 --- /dev/null +++ b/src/wwwroot/code/linq35.l @@ -0,0 +1,4 @@ +(let ( (digits ["zero" "one" "two" "three" "four" "five" "six" "seven" "eight" "nine"]) + (i 0) ) + (println "Sorted digits:") + (joinln (order-by [#(count %) it] digits ))) \ No newline at end of file diff --git a/src/wwwroot/code/linq35.sc b/src/wwwroot/code/linq35.sc new file mode 100644 index 0000000..1fd6ff4 --- /dev/null +++ b/src/wwwroot/code/linq35.sc @@ -0,0 +1,3 @@ +['zero','one','two','three','four','five','six','seven','eight','nine'] | to => digits +`Sorted digits:` +digits | orderBy => it.length | thenBy => it | joinln \ No newline at end of file diff --git a/src/wwwroot/code/linq35.txt b/src/wwwroot/code/linq35.ss similarity index 90% rename from src/wwwroot/code/linq35.txt rename to src/wwwroot/code/linq35.ss index 38e0652..a87ce66 100644 --- a/src/wwwroot/code/linq35.txt +++ b/src/wwwroot/code/linq35.ss @@ -3,4 +3,4 @@ Sorted digits: {{ digits | orderBy => it.length | thenBy => it - | join(`\n`) }} \ No newline at end of file + | joinln }} \ No newline at end of file diff --git a/src/wwwroot/code/linq36.l b/src/wwwroot/code/linq36.l new file mode 100644 index 0000000..c125a48 --- /dev/null +++ b/src/wwwroot/code/linq36.l @@ -0,0 +1,2 @@ +(let ( (words ["aPPLE" "AbAcUs" "bRaNcH" "BlUeBeRrY" "ClOvEr" "cHeRry"]) ) + (joinln (order-by [#(count %) { :comparer (CaseInsensitiveComparer.) }] words))) \ No newline at end of file diff --git a/src/wwwroot/code/linq36.sc b/src/wwwroot/code/linq36.sc new file mode 100644 index 0000000..858315a --- /dev/null +++ b/src/wwwroot/code/linq36.sc @@ -0,0 +1,2 @@ +['aPPLE', 'AbAcUs', 'bRaNcH', 'BlUeBeRrY', 'ClOvEr', 'cHeRry'] | to => words +words | orderBy => it.length | thenBy(w => w, { comparer }) | joinln \ No newline at end of file diff --git a/src/wwwroot/code/linq36.txt b/src/wwwroot/code/linq36.ss similarity index 89% rename from src/wwwroot/code/linq36.txt rename to src/wwwroot/code/linq36.ss index 4c4c6fa..c4025d0 100644 --- a/src/wwwroot/code/linq36.txt +++ b/src/wwwroot/code/linq36.ss @@ -2,4 +2,4 @@ {{ words | orderBy => it.length | thenBy(w => w, { comparer }) - | join(`\n`) }} \ No newline at end of file + | joinln }} \ No newline at end of file diff --git a/src/wwwroot/code/linq37.l b/src/wwwroot/code/linq37.l new file mode 100644 index 0000000..5d3f43e --- /dev/null +++ b/src/wwwroot/code/linq37.l @@ -0,0 +1 @@ +(htmldump (order-by [ #(.Category %) { :key #(.UnitPrice %) :desc true } ] products-list)) \ No newline at end of file diff --git a/src/wwwroot/code/linq37.sc b/src/wwwroot/code/linq37.sc new file mode 100644 index 0000000..0d8194d --- /dev/null +++ b/src/wwwroot/code/linq37.sc @@ -0,0 +1 @@ +products | orderBy => it.Category | thenByDescending => it.UnitPrice | htmlDump \ No newline at end of file diff --git a/src/wwwroot/code/linq37.txt b/src/wwwroot/code/linq37.ss similarity index 100% rename from src/wwwroot/code/linq37.txt rename to src/wwwroot/code/linq37.ss diff --git a/src/wwwroot/code/linq38.l b/src/wwwroot/code/linq38.l new file mode 100644 index 0000000..b39161e --- /dev/null +++ b/src/wwwroot/code/linq38.l @@ -0,0 +1,2 @@ +(let ( (words ["aPPLE" "AbAcUs" "bRaNcH" "BlUeBeRrY" "ClOvEr" "cHeRry"]) ) + (joinln (order-by [ #(count %) { :comparer (CaseInsensitiveComparer.) :desc true } ] words))) \ No newline at end of file diff --git a/src/wwwroot/code/linq38.sc b/src/wwwroot/code/linq38.sc new file mode 100644 index 0000000..2011c9b --- /dev/null +++ b/src/wwwroot/code/linq38.sc @@ -0,0 +1,2 @@ +['aPPLE', 'AbAcUs', 'bRaNcH', 'BlUeBeRrY', 'ClOvEr', 'cHeRry'] | to => words +words | orderBy => it.length | thenByDescending(w => w, { comparer }) | joinln \ No newline at end of file diff --git a/src/wwwroot/code/linq38.txt b/src/wwwroot/code/linq38.ss similarity index 90% rename from src/wwwroot/code/linq38.txt rename to src/wwwroot/code/linq38.ss index fee779c..48d4e14 100644 --- a/src/wwwroot/code/linq38.txt +++ b/src/wwwroot/code/linq38.ss @@ -2,4 +2,4 @@ {{ words | orderBy => it.length | thenByDescending(w => w, { comparer }) - | join(`\n`) }} \ No newline at end of file + | joinln }} \ No newline at end of file diff --git a/src/wwwroot/code/linq39.l b/src/wwwroot/code/linq39.l new file mode 100644 index 0000000..11d055b --- /dev/null +++ b/src/wwwroot/code/linq39.l @@ -0,0 +1,3 @@ +(let ( (digits ["zero" "one" "two" "three" "four" "five" "six" "seven" "eight" "nine"]) ) + (println "A backwards list of the digits with a second character of 'i':") + (joinln (reverse (where #(= (:1 %) (:0 "i")) digits)))) \ No newline at end of file diff --git a/src/wwwroot/code/linq39.sc b/src/wwwroot/code/linq39.sc new file mode 100644 index 0000000..188f8fb --- /dev/null +++ b/src/wwwroot/code/linq39.sc @@ -0,0 +1,3 @@ +['zero','one','two','three','four','five','six','seven','eight','nine'] | to => digits +`A backwards list of the digits with a second character of 'i':` +digits | where => it[1] = 'i' | reverse | joinln \ No newline at end of file diff --git a/src/wwwroot/code/linq39.txt b/src/wwwroot/code/linq39.ss similarity index 91% rename from src/wwwroot/code/linq39.txt rename to src/wwwroot/code/linq39.ss index 30203b2..cb9743b 100644 --- a/src/wwwroot/code/linq39.txt +++ b/src/wwwroot/code/linq39.ss @@ -3,4 +3,4 @@ A backwards list of the digits with a second character of 'i': {{ digits | where => it[1] = 'i' | reverse - | join(`\n`) }} \ No newline at end of file + | joinln }} \ No newline at end of file diff --git a/src/wwwroot/code/linq40.l b/src/wwwroot/code/linq40.l new file mode 100644 index 0000000..04de9db --- /dev/null +++ b/src/wwwroot/code/linq40.l @@ -0,0 +1,7 @@ +(let ( (numbers [5 4 1 3 9 8 6 7 2 0]) + (number-groups) ) + (setq number-groups + (map #(it { :remainder (.Key %) :numbers % }) (group-by #(mod % 5) numbers))) + (doseq (g number-groups) + (println "Numbers with a remainder of " (:remainder g) " when divided by 5:") + (println (joinln (:numbers g))) )) \ No newline at end of file diff --git a/src/wwwroot/code/linq40.sc b/src/wwwroot/code/linq40.sc new file mode 100644 index 0000000..c7c7e21 --- /dev/null +++ b/src/wwwroot/code/linq40.sc @@ -0,0 +1,7 @@ +[5, 4, 1, 3, 9, 8, 6, 7, 2, 0] | to => nums +{{ nums + | groupBy => it % 5 + | let => { remainder: it.Key, nums: it } + | map => `Numbers with a remainder of ${remainder} when divided by 5:\n${nums.join('\n')}` + | joinln +}} \ No newline at end of file diff --git a/src/wwwroot/code/linq40.txt b/src/wwwroot/code/linq40.ss similarity index 93% rename from src/wwwroot/code/linq40.txt rename to src/wwwroot/code/linq40.ss index 6f4032f..620199b 100644 --- a/src/wwwroot/code/linq40.txt +++ b/src/wwwroot/code/linq40.ss @@ -3,5 +3,5 @@ | groupBy => it % 5 | let => { remainder: it.Key, nums: it } | map => `Numbers with a remainder of ${remainder} when divided by 5:\n${nums.join('\n')}` - | join('\n') + | joinln }} \ No newline at end of file diff --git a/src/wwwroot/code/linq41.l b/src/wwwroot/code/linq41.l new file mode 100644 index 0000000..1d25754 --- /dev/null +++ b/src/wwwroot/code/linq41.l @@ -0,0 +1,7 @@ +(let ( (words ["blueberry" "chimpanzee" "abacus" "banana" "apple" "cheese"]) + (word-groups) ) + (setq word-groups + (map #(it {:first-letter (.Key %) :words % }) (group-by #(:0 %) words) )) + (doseq (g word-groups) + (println "Words that start with the letter: " (:first-letter g)) + (println (joinln (:words g))) )) \ No newline at end of file diff --git a/src/wwwroot/code/linq41.sc b/src/wwwroot/code/linq41.sc new file mode 100644 index 0000000..a6bb72e --- /dev/null +++ b/src/wwwroot/code/linq41.sc @@ -0,0 +1,6 @@ +['blueberry', 'chimpanzee', 'abacus', 'banana', 'apple', 'cheese'] | to => words +words | groupBy => it[0] | map => { firstLetter: it.Key, words: it } | to => groups +#each groups + `Words that start with the letter '${firstLetter}':` + words | joinln +/each diff --git a/src/wwwroot/code/linq41.txt b/src/wwwroot/code/linq41.ss similarity index 91% rename from src/wwwroot/code/linq41.txt rename to src/wwwroot/code/linq41.ss index ec1e7c2..12b84d9 100644 --- a/src/wwwroot/code/linq41.txt +++ b/src/wwwroot/code/linq41.ss @@ -2,5 +2,5 @@ {{ words | groupBy => it[0] | map => { firstLetter: it.Key, words: it } | to => groups }} {{#each groups}} Words that start with the letter '{{firstLetter}}': -{{ words | join(`\n`) }} +{{ words | joinln }} {{/each}} diff --git a/src/wwwroot/code/linq42.l b/src/wwwroot/code/linq42.l new file mode 100644 index 0000000..f744461 --- /dev/null +++ b/src/wwwroot/code/linq42.l @@ -0,0 +1 @@ +(htmldump (map #(it {:category (.Key %), :products % }) (group-by :category products-list))) \ No newline at end of file diff --git a/src/wwwroot/code/linq42.sc b/src/wwwroot/code/linq42.sc new file mode 100644 index 0000000..ae5b35a --- /dev/null +++ b/src/wwwroot/code/linq42.sc @@ -0,0 +1 @@ +products | groupBy => it.Category | let => { Category: it.Key, Products: it } | htmlDump \ No newline at end of file diff --git a/src/wwwroot/code/linq42.txt b/src/wwwroot/code/linq42.ss similarity index 100% rename from src/wwwroot/code/linq42.txt rename to src/wwwroot/code/linq42.ss diff --git a/src/wwwroot/code/linq43.l b/src/wwwroot/code/linq43.l new file mode 100644 index 0000000..390c67c --- /dev/null +++ b/src/wwwroot/code/linq43.l @@ -0,0 +1,12 @@ +(let ( (customer-order-groups + (map (fn [c] { + :company-name (.CompanyName c) + :year-groups (map (fn [yg] { + :year (.Key yg) + :month-groups (map #(it { + :month (.Key %) + :orders % }) + (group-by #(.Month (.OrderDate %)) yg)) }) + (group-by #(it (.Year (.OrderDate %))) (.Orders c))) }) + customers-list)) ) + (htmldump customer-order-groups)) \ No newline at end of file diff --git a/src/wwwroot/code/linq43.txt b/src/wwwroot/code/linq43.sc similarity index 100% rename from src/wwwroot/code/linq43.txt rename to src/wwwroot/code/linq43.sc diff --git a/src/wwwroot/code/linq43.ss b/src/wwwroot/code/linq43.ss new file mode 100644 index 0000000..b4bcd41 --- /dev/null +++ b/src/wwwroot/code/linq43.ss @@ -0,0 +1,12 @@ +{{ customers | map => { + CompanyName: it.CompanyName, + YearGroups: it.Orders.groupBy(it => it.OrderDate.Year).map(yg => + { + Year: yg.Key, + MonthGroups: yg.groupBy(o => o.OrderDate.Month).map(mg => + { Month: mg.Key, Orders: mg } + ) + } + ) + } + | htmlDump }} \ No newline at end of file diff --git a/src/wwwroot/code/linq44.l b/src/wwwroot/code/linq44.l new file mode 100644 index 0000000..53119d1 --- /dev/null +++ b/src/wwwroot/code/linq44.l @@ -0,0 +1,3 @@ +(let ( (anagrams ["from " " salt" " earn " " last " " near " " form "]) ) + (doseq (x (group-by .Trim { :comparer (AnagramEqualityComparer.) } anagrams)) + (println (/json x)) )) \ No newline at end of file diff --git a/src/wwwroot/code/linq44.sc b/src/wwwroot/code/linq44.sc new file mode 100644 index 0000000..3cceae1 --- /dev/null +++ b/src/wwwroot/code/linq44.sc @@ -0,0 +1,4 @@ +['from ', ' salt', ' earn ', ' last ', ' near ', ' form '] | to => anagrams +#each anagrams.groupBy(w => w.trim(), { comparer: anagramComparer }) + it | json +/each diff --git a/src/wwwroot/code/linq44.txt b/src/wwwroot/code/linq44.ss similarity index 100% rename from src/wwwroot/code/linq44.txt rename to src/wwwroot/code/linq44.ss diff --git a/src/wwwroot/code/linq45.l b/src/wwwroot/code/linq45.l new file mode 100644 index 0000000..db14ac7 --- /dev/null +++ b/src/wwwroot/code/linq45.l @@ -0,0 +1,3 @@ +(let ( (anagrams ["from " " salt" " earn " " last " " near " " form "]) ) + (doseq (x (group-by .Trim { :comparer (AnagramEqualityComparer.) :map /upper } anagrams)) + (println (/json x)) )) \ No newline at end of file diff --git a/src/wwwroot/code/linq45.sc b/src/wwwroot/code/linq45.sc new file mode 100644 index 0000000..d8d5112 --- /dev/null +++ b/src/wwwroot/code/linq45.sc @@ -0,0 +1,4 @@ +['from ', ' salt', ' earn ', ' last ', ' near ', ' form '] | to => anagrams +#each anagrams.groupBy(w => w.trim(), { map: a => a.upper(), comparer: anagramComparer }) + it | json +/each \ No newline at end of file diff --git a/src/wwwroot/code/linq45.txt b/src/wwwroot/code/linq45.ss similarity index 100% rename from src/wwwroot/code/linq45.txt rename to src/wwwroot/code/linq45.ss diff --git a/src/wwwroot/code/linq46.l b/src/wwwroot/code/linq46.l new file mode 100644 index 0000000..89958d0 --- /dev/null +++ b/src/wwwroot/code/linq46.l @@ -0,0 +1,3 @@ +(let ( (factors-of-300 [2, 2, 3, 5, 5]) ) + (println "Prime factors of 300:") + (joinln (/distinct factors-of-300))) \ No newline at end of file diff --git a/src/wwwroot/code/linq46.sc b/src/wwwroot/code/linq46.sc new file mode 100644 index 0000000..eabb4f5 --- /dev/null +++ b/src/wwwroot/code/linq46.sc @@ -0,0 +1,3 @@ +[2, 2, 3, 5, 5] | to => factorsOf300 +`Prime factors of 300:` +factorsOf300.distinct() | joinln \ No newline at end of file diff --git a/src/wwwroot/code/linq46.txt b/src/wwwroot/code/linq46.ss similarity index 60% rename from src/wwwroot/code/linq46.txt rename to src/wwwroot/code/linq46.ss index cbe71f1..5adffe6 100644 --- a/src/wwwroot/code/linq46.txt +++ b/src/wwwroot/code/linq46.ss @@ -1,3 +1,3 @@ {{ [2, 2, 3, 5, 5] | to => factorsOf300 }} Prime factors of 300: -{{ factorsOf300.distinct() | join(`\n`) }} \ No newline at end of file +{{ factorsOf300.distinct() | joinln }} \ No newline at end of file diff --git a/src/wwwroot/code/linq47.l b/src/wwwroot/code/linq47.l new file mode 100644 index 0000000..cf3948a --- /dev/null +++ b/src/wwwroot/code/linq47.l @@ -0,0 +1,2 @@ +(println "Category names:") +(joinln (/distinct (map .Category products-list))) \ No newline at end of file diff --git a/src/wwwroot/code/linq47.sc b/src/wwwroot/code/linq47.sc new file mode 100644 index 0000000..a647892 --- /dev/null +++ b/src/wwwroot/code/linq47.sc @@ -0,0 +1,2 @@ +`Category names:` +products | map => it.Category | distinct | joinln \ No newline at end of file diff --git a/src/wwwroot/code/linq47.txt b/src/wwwroot/code/linq47.ss similarity index 79% rename from src/wwwroot/code/linq47.txt rename to src/wwwroot/code/linq47.ss index ca8cb29..0d28fc5 100644 --- a/src/wwwroot/code/linq47.txt +++ b/src/wwwroot/code/linq47.ss @@ -2,4 +2,4 @@ Category names: {{ products | map => it.Category | distinct - | join(`\n`) }} \ No newline at end of file + | joinln }} \ No newline at end of file diff --git a/src/wwwroot/code/linq48.l b/src/wwwroot/code/linq48.l new file mode 100644 index 0000000..5005f95 --- /dev/null +++ b/src/wwwroot/code/linq48.l @@ -0,0 +1,4 @@ +(let ( (numbers-a [0 2 4 5 6 8 9]) + (numbers-b [1 3 5 7 8]) ) + (println "Unique numbers from both arrays:") + (joinln (/union numbers-a numbers-b))) \ No newline at end of file diff --git a/src/wwwroot/code/linq48.sc b/src/wwwroot/code/linq48.sc new file mode 100644 index 0000000..7216e4c --- /dev/null +++ b/src/wwwroot/code/linq48.sc @@ -0,0 +1,6 @@ +[ 0, 2, 4, 5, 6, 8, 9 ] | to => numbersA +[ 1, 3, 5, 7, 8 ] | to => numbersB +`Unique numbers from both arrays:` +#each numbersA.union(numbersB) + it +/each \ No newline at end of file diff --git a/src/wwwroot/code/linq48.txt b/src/wwwroot/code/linq48.ss similarity index 100% rename from src/wwwroot/code/linq48.txt rename to src/wwwroot/code/linq48.ss diff --git a/src/wwwroot/code/linq49.l b/src/wwwroot/code/linq49.l new file mode 100644 index 0000000..740f77d --- /dev/null +++ b/src/wwwroot/code/linq49.l @@ -0,0 +1,5 @@ +(let ( (product-first-chars (map #(:0 (.ProductName %)) products-list)) + (customer-first-chars (map #(:0 (.CompanyName %)) customers-list)) ) + + (println "Unique first letters from Product names and Customer names:") + (joinln (/union product-first-chars customer-first-chars))) \ No newline at end of file diff --git a/src/wwwroot/code/linq49.sc b/src/wwwroot/code/linq49.sc new file mode 100644 index 0000000..c9af1c5 --- /dev/null +++ b/src/wwwroot/code/linq49.sc @@ -0,0 +1,6 @@ +products | map => it.ProductName[0] | to => productFirstChars +customers | map => it.CompanyName[0] | to => customerFirstChars +`Unique first letters from Product names and Customer names:` +#each productFirstChars.union(customerFirstChars) + it +/each \ No newline at end of file diff --git a/src/wwwroot/code/linq49.txt b/src/wwwroot/code/linq49.ss similarity index 100% rename from src/wwwroot/code/linq49.txt rename to src/wwwroot/code/linq49.ss diff --git a/src/wwwroot/code/linq50.l b/src/wwwroot/code/linq50.l new file mode 100644 index 0000000..b08300b --- /dev/null +++ b/src/wwwroot/code/linq50.l @@ -0,0 +1,4 @@ +(let ( (numbers-a [0 2 4 5 6 8 9]) + (numbers-b [1 3 5 7 8]) ) + (println "Common numbers shared by both arrays:") + (joinln (/intersect numbers-a numbers-b))) \ No newline at end of file diff --git a/src/wwwroot/code/linq50.sc b/src/wwwroot/code/linq50.sc new file mode 100644 index 0000000..c1d835f --- /dev/null +++ b/src/wwwroot/code/linq50.sc @@ -0,0 +1,4 @@ +[ 0, 2, 4, 5, 6, 8, 9 ] | to => numbersA +[ 1, 3, 5, 7, 8 ] | to => numbersB +`Common numbers shared by both arrays:` +numbersA.intersect(numbersB) | joinln \ No newline at end of file diff --git a/src/wwwroot/code/linq50.txt b/src/wwwroot/code/linq50.ss similarity index 72% rename from src/wwwroot/code/linq50.txt rename to src/wwwroot/code/linq50.ss index b9fb3bb..385c485 100644 --- a/src/wwwroot/code/linq50.txt +++ b/src/wwwroot/code/linq50.ss @@ -1,4 +1,4 @@ {{ [ 0, 2, 4, 5, 6, 8, 9 ] | to => numbersA }} {{ [ 1, 3, 5, 7, 8 ] | to => numbersB }} Common numbers shared by both arrays: -{{ numbersA.intersect(numbersB) | join(`\n`) }} \ No newline at end of file +{{ numbersA.intersect(numbersB) | joinln }} \ No newline at end of file diff --git a/src/wwwroot/code/linq51.l b/src/wwwroot/code/linq51.l new file mode 100644 index 0000000..d8f3757 --- /dev/null +++ b/src/wwwroot/code/linq51.l @@ -0,0 +1,4 @@ +(let ( (product-first-chars (map #(:0 (.ProductName %)) products-list)) + (customer-first-chars (map #(:0 (.CompanyName %)) customers-list)) ) + (println "Common first letters from Product names and Customer names:") + (joinln (/intersect product-first-chars customer-first-chars))) \ No newline at end of file diff --git a/src/wwwroot/code/linq51.sc b/src/wwwroot/code/linq51.sc new file mode 100644 index 0000000..1c22ec4 --- /dev/null +++ b/src/wwwroot/code/linq51.sc @@ -0,0 +1,6 @@ +products | map => it.ProductName[0] | to => productFirstChars +customers | map => it.CompanyName[0] | to => customerFirstChars +`Common first letters from Product names and Customer names:` +#each productFirstChars.intersect(customerFirstChars) + it +/each \ No newline at end of file diff --git a/src/wwwroot/code/linq51.txt b/src/wwwroot/code/linq51.ss similarity index 100% rename from src/wwwroot/code/linq51.txt rename to src/wwwroot/code/linq51.ss diff --git a/src/wwwroot/code/linq52.l b/src/wwwroot/code/linq52.l new file mode 100644 index 0000000..b6d57ea --- /dev/null +++ b/src/wwwroot/code/linq52.l @@ -0,0 +1,4 @@ +(let ( (numbers-a [0 2 4 5 6 8 9]) + (numbers-b [1 3 5 7 8]) ) + (println "Numbers in first array but not second array:") + (joinln (/except numbers-a numbers-b))) \ No newline at end of file diff --git a/src/wwwroot/code/linq52.sc b/src/wwwroot/code/linq52.sc new file mode 100644 index 0000000..4cb7116 --- /dev/null +++ b/src/wwwroot/code/linq52.sc @@ -0,0 +1,4 @@ +[ 0, 2, 4, 5, 6, 8, 9 ] | to => numbersA +[ 1, 3, 5, 7, 8 ] | to => numbersB +`Numbers in first array but not second array:` +numbersA.except(numbersB) | joinln \ No newline at end of file diff --git a/src/wwwroot/code/linq52.txt b/src/wwwroot/code/linq52.ss similarity index 75% rename from src/wwwroot/code/linq52.txt rename to src/wwwroot/code/linq52.ss index a5b8006..59a7bec 100644 --- a/src/wwwroot/code/linq52.txt +++ b/src/wwwroot/code/linq52.ss @@ -1,4 +1,4 @@ {{ [ 0, 2, 4, 5, 6, 8, 9 ] | to => numbersA }} {{ [ 1, 3, 5, 7, 8 ] | to => numbersB }} Numbers in first array but not second array: -{{ numbersA.except(numbersB) | join(`\n`) }} \ No newline at end of file +{{ numbersA.except(numbersB) | joinln }} \ No newline at end of file diff --git a/src/wwwroot/code/linq53.l b/src/wwwroot/code/linq53.l new file mode 100644 index 0000000..8ad499d --- /dev/null +++ b/src/wwwroot/code/linq53.l @@ -0,0 +1,4 @@ +(let ( (product-first-chars (map #(:0 (.ProductName %)) products-list)) + (customer-first-chars (map #(:0 (.CompanyName %)) customers-list)) ) + (println "First letters from Product names, but not from Customer names:") + (joinln (/except product-first-chars customer-first-chars))) \ No newline at end of file diff --git a/src/wwwroot/code/linq53.sc b/src/wwwroot/code/linq53.sc new file mode 100644 index 0000000..323ff10 --- /dev/null +++ b/src/wwwroot/code/linq53.sc @@ -0,0 +1,6 @@ +products | map => it.ProductName[0] | to => productFirstChars +customers | map => it.CompanyName[0] | to => customerFirstChars +`First letters from Product names, but not from Customer names:` +#each productFirstChars.except(customerFirstChars) + it +/each \ No newline at end of file diff --git a/src/wwwroot/code/linq53.txt b/src/wwwroot/code/linq53.ss similarity index 100% rename from src/wwwroot/code/linq53.txt rename to src/wwwroot/code/linq53.ss diff --git a/src/wwwroot/code/linq54.l b/src/wwwroot/code/linq54.l new file mode 100644 index 0000000..7c8c40c --- /dev/null +++ b/src/wwwroot/code/linq54.l @@ -0,0 +1,3 @@ +(let ( (dbls [1.7 2.3 1.9 4.1 2.9]) ) + (println "Every other double from highest to lowest:") + (joinln (/step (reverse (sort dbls)) { :by 2 }))) \ No newline at end of file diff --git a/src/wwwroot/code/linq54.sc b/src/wwwroot/code/linq54.sc new file mode 100644 index 0000000..b87ae9f --- /dev/null +++ b/src/wwwroot/code/linq54.sc @@ -0,0 +1,3 @@ +[ 1.7, 2.3, 1.9, 4.1, 2.9 ] | to => doubles +`Every other double from highest to lowest:` +doubles | orderByDescending => it | step({ by: 2 }) | joinln \ No newline at end of file diff --git a/src/wwwroot/code/linq54.txt b/src/wwwroot/code/linq54.ss similarity index 89% rename from src/wwwroot/code/linq54.txt rename to src/wwwroot/code/linq54.ss index 834c58e..dcadf28 100644 --- a/src/wwwroot/code/linq54.txt +++ b/src/wwwroot/code/linq54.ss @@ -3,4 +3,4 @@ Every other double from highest to lowest: {{ doubles | orderByDescending => it | step({ by: 2 }) - | join(`\n`) }} \ No newline at end of file + | joinln }} \ No newline at end of file diff --git a/src/wwwroot/code/linq55.l b/src/wwwroot/code/linq55.l new file mode 100644 index 0000000..d3eb5df --- /dev/null +++ b/src/wwwroot/code/linq55.l @@ -0,0 +1,3 @@ +(let ( (words ["cherry" "apple" "blueberry"]) ) + (println "The sorted word list:") + (joinln (to-list (sort words)))) \ No newline at end of file diff --git a/src/wwwroot/code/linq55.sc b/src/wwwroot/code/linq55.sc new file mode 100644 index 0000000..1dc09b7 --- /dev/null +++ b/src/wwwroot/code/linq55.sc @@ -0,0 +1,3 @@ +[ 'cherry', 'apple', 'blueberry' ] | to => words +`The sorted word list:` +words | orderBy => it | toList | joinln \ No newline at end of file diff --git a/src/wwwroot/code/linq55.txt b/src/wwwroot/code/linq55.ss similarity index 86% rename from src/wwwroot/code/linq55.txt rename to src/wwwroot/code/linq55.ss index 180591b..2105590 100644 --- a/src/wwwroot/code/linq55.txt +++ b/src/wwwroot/code/linq55.ss @@ -3,4 +3,4 @@ The sorted word list: {{ words | orderBy => it | toList - | join(`\n`) }} \ No newline at end of file + | joinln }} \ No newline at end of file diff --git a/src/wwwroot/code/linq56.l b/src/wwwroot/code/linq56.l new file mode 100644 index 0000000..515da29 --- /dev/null +++ b/src/wwwroot/code/linq56.l @@ -0,0 +1,4 @@ +(let ( (sorted-records [{ :name "Alice", :score 50 } + { :name "Bob", :score 40 } + { :name "Cathy", :score 45 }]) ) + (println "Bob's score: " (:score (:"Bob" (to-dictionary :name sorted-records))))) \ No newline at end of file diff --git a/src/wwwroot/code/linq56.sc b/src/wwwroot/code/linq56.sc new file mode 100644 index 0000000..b90395b --- /dev/null +++ b/src/wwwroot/code/linq56.sc @@ -0,0 +1,3 @@ +[ {name:'Alice',score:50}, {name:'Bob',score:40}, {name:'Cathy',score:45} ] | to => records +records | toDictionary => it.name | to => scoreRecordsDict +`Bob's score: ${scoreRecordsDict.Bob.score}` \ No newline at end of file diff --git a/src/wwwroot/code/linq56.txt b/src/wwwroot/code/linq56.ss similarity index 100% rename from src/wwwroot/code/linq56.txt rename to src/wwwroot/code/linq56.ss diff --git a/src/wwwroot/code/linq57.l b/src/wwwroot/code/linq57.l new file mode 100644 index 0000000..60ded73 --- /dev/null +++ b/src/wwwroot/code/linq57.l @@ -0,0 +1,3 @@ +(let ( (numbers [nil 1.0 "two" 3 "four" 5 "six" 7.0]) ) + (println "Numbers stored as doubles:") + (joinln (/of numbers { :type "Double" }))) \ No newline at end of file diff --git a/src/wwwroot/code/linq57.sc b/src/wwwroot/code/linq57.sc new file mode 100644 index 0000000..5a4959a --- /dev/null +++ b/src/wwwroot/code/linq57.sc @@ -0,0 +1,3 @@ +[null, 1.0, 'two', 3, 'four', 5, 'six', 7.0] | to => numbers +`Numbers stored as doubles:` +numbers | of({ type: 'Double' }) | map => `${it.format('#.0') }` | joinln \ No newline at end of file diff --git a/src/wwwroot/code/linq57.txt b/src/wwwroot/code/linq57.ss similarity index 100% rename from src/wwwroot/code/linq57.txt rename to src/wwwroot/code/linq57.ss diff --git a/src/wwwroot/code/linq58.l b/src/wwwroot/code/linq58.l new file mode 100644 index 0000000..fe26b92 --- /dev/null +++ b/src/wwwroot/code/linq58.l @@ -0,0 +1 @@ +(dump (first (where #(= (.ProductId %) 12) products-list))) \ No newline at end of file diff --git a/src/wwwroot/code/linq58.sc b/src/wwwroot/code/linq58.sc new file mode 100644 index 0000000..4d02977 --- /dev/null +++ b/src/wwwroot/code/linq58.sc @@ -0,0 +1 @@ +products | where => it.ProductId = 12 | first | dump \ No newline at end of file diff --git a/src/wwwroot/code/linq58.txt b/src/wwwroot/code/linq58.ss similarity index 100% rename from src/wwwroot/code/linq58.txt rename to src/wwwroot/code/linq58.ss diff --git a/src/wwwroot/code/linq59.l b/src/wwwroot/code/linq59.l new file mode 100644 index 0000000..3a999c3 --- /dev/null +++ b/src/wwwroot/code/linq59.l @@ -0,0 +1,2 @@ +(let ( (strings ["zero" "one" "two" "three" "four" "five" "six" "seven" "eight" "nine"]) ) + (println "A string starting with 'o': " (first (where #(/startsWith % "o") strings)))) \ No newline at end of file diff --git a/src/wwwroot/code/linq59.sc b/src/wwwroot/code/linq59.sc new file mode 100644 index 0000000..aad4847 --- /dev/null +++ b/src/wwwroot/code/linq59.sc @@ -0,0 +1,4 @@ +['zero','one','two','three','four','five','six','seven','eight','nine'] | to => strings +#each s in strings where s[0] == 'o' take 1 +`A string starting with 'o': ${s}` +/each \ No newline at end of file diff --git a/src/wwwroot/code/linq59.txt b/src/wwwroot/code/linq59.ss similarity index 100% rename from src/wwwroot/code/linq59.txt rename to src/wwwroot/code/linq59.ss diff --git a/src/wwwroot/code/linq61.l b/src/wwwroot/code/linq61.l new file mode 100644 index 0000000..cd7d1c5 --- /dev/null +++ b/src/wwwroot/code/linq61.l @@ -0,0 +1 @@ +(or (first []) "null") \ No newline at end of file diff --git a/src/wwwroot/code/linq61.sc b/src/wwwroot/code/linq61.sc new file mode 100644 index 0000000..dc1f6d3 --- /dev/null +++ b/src/wwwroot/code/linq61.sc @@ -0,0 +1 @@ +([].first()) ?? 'null' \ No newline at end of file diff --git a/src/wwwroot/code/linq61.txt b/src/wwwroot/code/linq61.ss similarity index 100% rename from src/wwwroot/code/linq61.txt rename to src/wwwroot/code/linq61.ss diff --git a/src/wwwroot/code/linq62.l b/src/wwwroot/code/linq62.l new file mode 100644 index 0000000..8936a05 --- /dev/null +++ b/src/wwwroot/code/linq62.l @@ -0,0 +1,2 @@ +(let ( (product-789 (first (where #(= (.ProductId %) 789) products-list))) ) + (println "Product 789 exists: " (not= product-789 nil))) \ No newline at end of file diff --git a/src/wwwroot/code/linq62.sc b/src/wwwroot/code/linq62.sc new file mode 100644 index 0000000..231f760 --- /dev/null +++ b/src/wwwroot/code/linq62.sc @@ -0,0 +1,2 @@ +products | first => it.ProductId = 789 | to => product789 +`Product 789 exists: ${ product789 != null }` \ No newline at end of file diff --git a/src/wwwroot/code/linq62.txt b/src/wwwroot/code/linq62.ss similarity index 100% rename from src/wwwroot/code/linq62.txt rename to src/wwwroot/code/linq62.ss diff --git a/src/wwwroot/code/linq64.l b/src/wwwroot/code/linq64.l new file mode 100644 index 0000000..61f2271 --- /dev/null +++ b/src/wwwroot/code/linq64.l @@ -0,0 +1,2 @@ +(let ( (numbers [5 4 1 3 9 8 6 7 2 0]) ) + (println "Second number > 5: " (:1 (where #(> % 5) numbers)))) \ No newline at end of file diff --git a/src/wwwroot/code/linq64.sc b/src/wwwroot/code/linq64.sc new file mode 100644 index 0000000..70715b1 --- /dev/null +++ b/src/wwwroot/code/linq64.sc @@ -0,0 +1,3 @@ +[ 5, 4, 1, 3, 9, 8, 6, 7, 2, 0 ] | to => numbers +numbers | where => it > 5 | elementAt(1) | to => fourthLowNum +`Second number > 5: ${fourthLowNum}` \ No newline at end of file diff --git a/src/wwwroot/code/linq64.txt b/src/wwwroot/code/linq64.ss similarity index 100% rename from src/wwwroot/code/linq64.txt rename to src/wwwroot/code/linq64.ss diff --git a/src/wwwroot/code/linq65.l b/src/wwwroot/code/linq65.l new file mode 100644 index 0000000..1a242cf --- /dev/null +++ b/src/wwwroot/code/linq65.l @@ -0,0 +1,2 @@ +(doseq (n (range 100 150)) + (println "The number " n " is " (if (even? n) "even" "odd"))) \ No newline at end of file diff --git a/src/wwwroot/code/linq65.sc b/src/wwwroot/code/linq65.sc new file mode 100644 index 0000000..2aa68cd --- /dev/null +++ b/src/wwwroot/code/linq65.sc @@ -0,0 +1,3 @@ +#each range(100,50) +`The number ${it} is ${ it.isEven() ? 'even' : 'odd' }.` +/each diff --git a/src/wwwroot/code/linq65.txt b/src/wwwroot/code/linq65.ss similarity index 100% rename from src/wwwroot/code/linq65.txt rename to src/wwwroot/code/linq65.ss diff --git a/src/wwwroot/code/linq66.l b/src/wwwroot/code/linq66.l new file mode 100644 index 0000000..2e191b6 --- /dev/null +++ b/src/wwwroot/code/linq66.l @@ -0,0 +1 @@ +(doseq (n (/repeat 7 10)) (println n)) \ No newline at end of file diff --git a/src/wwwroot/code/linq66.sc b/src/wwwroot/code/linq66.sc new file mode 100644 index 0000000..2be0a50 --- /dev/null +++ b/src/wwwroot/code/linq66.sc @@ -0,0 +1 @@ +10.itemsOf(7) | joinln \ No newline at end of file diff --git a/src/wwwroot/code/linq66.ss b/src/wwwroot/code/linq66.ss new file mode 100644 index 0000000..071bab0 --- /dev/null +++ b/src/wwwroot/code/linq66.ss @@ -0,0 +1 @@ +{{ 10.itemsOf(7) | joinln }} \ No newline at end of file diff --git a/src/wwwroot/code/linq66.txt b/src/wwwroot/code/linq66.txt deleted file mode 100644 index 41f4040..0000000 --- a/src/wwwroot/code/linq66.txt +++ /dev/null @@ -1 +0,0 @@ -{{ 10.itemsOf(7) | join(`\n`) }} \ No newline at end of file diff --git a/src/wwwroot/code/linq67.l b/src/wwwroot/code/linq67.l new file mode 100644 index 0000000..99fec4c --- /dev/null +++ b/src/wwwroot/code/linq67.l @@ -0,0 +1,3 @@ +(let ( (words ["believe" "relief" "receipt" "field"]) ) + (println "There is a word that contains in the list that contains 'ei': " + (any? #(.Contains % "ie") words))) \ No newline at end of file diff --git a/src/wwwroot/code/linq67.sc b/src/wwwroot/code/linq67.sc new file mode 100644 index 0000000..89ebce0 --- /dev/null +++ b/src/wwwroot/code/linq67.sc @@ -0,0 +1,3 @@ +['believe', 'relief', 'receipt', 'field'] | to => words +words | any => it.contains('ei') | to => iAfterE +`There is a word that contains in the list that contains 'ei': ${iAfterE.lower()}` \ No newline at end of file diff --git a/src/wwwroot/code/linq67.txt b/src/wwwroot/code/linq67.ss similarity index 100% rename from src/wwwroot/code/linq67.txt rename to src/wwwroot/code/linq67.ss diff --git a/src/wwwroot/code/linq69.l b/src/wwwroot/code/linq69.l new file mode 100644 index 0000000..8f55d89 --- /dev/null +++ b/src/wwwroot/code/linq69.l @@ -0,0 +1,3 @@ +(htmldump (map-where #(any? (fn [p] (= (.UnitsInStock p) 0)) %) + #(it { :category (.Key %), :products % }) + (group-by .Category products-list))) \ No newline at end of file diff --git a/src/wwwroot/code/linq69.txt b/src/wwwroot/code/linq69.sc similarity index 100% rename from src/wwwroot/code/linq69.txt rename to src/wwwroot/code/linq69.sc diff --git a/src/wwwroot/code/linq69.ss b/src/wwwroot/code/linq69.ss new file mode 100644 index 0000000..77e6d53 --- /dev/null +++ b/src/wwwroot/code/linq69.ss @@ -0,0 +1,5 @@ +{{ products + | groupBy => it.Category + | where => it.any(it => it.UnitsInStock = 0) + | let => { Category: it.Key, Products: it } + | htmlDump }} \ No newline at end of file diff --git a/src/wwwroot/code/linq70.l b/src/wwwroot/code/linq70.l new file mode 100644 index 0000000..8c9651f --- /dev/null +++ b/src/wwwroot/code/linq70.l @@ -0,0 +1,2 @@ +(let ( (numbers [1 11 3 19 41 65 19]) ) + (println "The list contains only odd numbers: " (all? odd? numbers))) \ No newline at end of file diff --git a/src/wwwroot/code/linq70.sc b/src/wwwroot/code/linq70.sc new file mode 100644 index 0000000..3485fe8 --- /dev/null +++ b/src/wwwroot/code/linq70.sc @@ -0,0 +1,3 @@ +[1, 11, 3, 19, 41, 65, 19] | to => numbers +numbers | all => it.isOdd() | to => onlyOdd +`The list contains only odd numbers: ${ onlyOdd }` \ No newline at end of file diff --git a/src/wwwroot/code/linq70.txt b/src/wwwroot/code/linq70.ss similarity index 100% rename from src/wwwroot/code/linq70.txt rename to src/wwwroot/code/linq70.ss diff --git a/src/wwwroot/code/linq72.l b/src/wwwroot/code/linq72.l new file mode 100644 index 0000000..3e8fced --- /dev/null +++ b/src/wwwroot/code/linq72.l @@ -0,0 +1,3 @@ +(htmldump (map-where #(all? (fn [p] (> (.UnitsInStock p) 0)) %) + #(it { :category (.Key %), :products % }) + (group-by .Category products-list))) \ No newline at end of file diff --git a/src/wwwroot/code/linq72.txt b/src/wwwroot/code/linq72.sc similarity index 100% rename from src/wwwroot/code/linq72.txt rename to src/wwwroot/code/linq72.sc diff --git a/src/wwwroot/code/linq72.ss b/src/wwwroot/code/linq72.ss new file mode 100644 index 0000000..1a738eb --- /dev/null +++ b/src/wwwroot/code/linq72.ss @@ -0,0 +1,5 @@ +{{ products + | groupBy => it.Category + | where => it.all(it => it.UnitsInStock > 0) + | let => { Category: it.Key, Products: it } + | htmlDump }} \ No newline at end of file diff --git a/src/wwwroot/code/linq73.l b/src/wwwroot/code/linq73.l new file mode 100644 index 0000000..71d0ad0 --- /dev/null +++ b/src/wwwroot/code/linq73.l @@ -0,0 +1,2 @@ +(let ( (factors-of-300 [2 2 3 5 5]) ) + (println "There are " (count (/distinct factors-of-300)) " unique factors of 300.")) \ No newline at end of file diff --git a/src/wwwroot/code/linq73.sc b/src/wwwroot/code/linq73.sc new file mode 100644 index 0000000..bcf76aa --- /dev/null +++ b/src/wwwroot/code/linq73.sc @@ -0,0 +1,3 @@ +[2, 2, 3, 5, 5] | to => factorsOf300 +factorsOf300.distinct().count() | to => uniqueFactors +`There are ${uniqueFactors} unique factors of 300.` \ No newline at end of file diff --git a/src/wwwroot/code/linq73.txt b/src/wwwroot/code/linq73.ss similarity index 100% rename from src/wwwroot/code/linq73.txt rename to src/wwwroot/code/linq73.ss diff --git a/src/wwwroot/code/linq74.l b/src/wwwroot/code/linq74.l new file mode 100644 index 0000000..f16a57d --- /dev/null +++ b/src/wwwroot/code/linq74.l @@ -0,0 +1,2 @@ +(let ( (numbers [4 5 1 3 9 0 6 7 2 0]) ) + (println "There are " (count (where odd? numbers)) " odd numbers in the list.")) \ No newline at end of file diff --git a/src/wwwroot/code/linq74.sc b/src/wwwroot/code/linq74.sc new file mode 100644 index 0000000..0c06823 --- /dev/null +++ b/src/wwwroot/code/linq74.sc @@ -0,0 +1,3 @@ +[5, 4, 1, 3, 9, 8, 6, 7, 2, 0] | to => numbers +numbers | count => it.isOdd() | to => oddNumbers +`There are ${oddNumbers} odd numbers in the list.` \ No newline at end of file diff --git a/src/wwwroot/code/linq74.txt b/src/wwwroot/code/linq74.ss similarity index 100% rename from src/wwwroot/code/linq74.txt rename to src/wwwroot/code/linq74.ss diff --git a/src/wwwroot/code/linq76.l b/src/wwwroot/code/linq76.l new file mode 100644 index 0000000..3b76000 --- /dev/null +++ b/src/wwwroot/code/linq76.l @@ -0,0 +1,5 @@ +(doseq (x (map #(it { + :customer-id (.CustomerId %) + :order-count (count (.Orders %)) }) + customers-list)) + (println (:customer-id x) ", " (:order-count x))) \ No newline at end of file diff --git a/src/wwwroot/code/linq76.txt b/src/wwwroot/code/linq76.sc similarity index 56% rename from src/wwwroot/code/linq76.txt rename to src/wwwroot/code/linq76.sc index 329e6e2..69ab2db 100644 --- a/src/wwwroot/code/linq76.txt +++ b/src/wwwroot/code/linq76.sc @@ -1,3 +1,3 @@ {{ customers | let => { it.CustomerId, OrderCount: it.Orders.count() } - | map => `${CustomerId}, ${OrderCount}` | join(`\n`) }} \ No newline at end of file + | map => `${CustomerId}, ${OrderCount}` | joinln }} \ No newline at end of file diff --git a/src/wwwroot/code/linq76.ss b/src/wwwroot/code/linq76.ss new file mode 100644 index 0000000..69ab2db --- /dev/null +++ b/src/wwwroot/code/linq76.ss @@ -0,0 +1,3 @@ +{{ customers + | let => { it.CustomerId, OrderCount: it.Orders.count() } + | map => `${CustomerId}, ${OrderCount}` | joinln }} \ No newline at end of file diff --git a/src/wwwroot/code/linq77.l b/src/wwwroot/code/linq77.l new file mode 100644 index 0000000..0adc47c --- /dev/null +++ b/src/wwwroot/code/linq77.l @@ -0,0 +1,4 @@ +(htmldump (map #(it { + :category (.Key %) + :product-count (count %) }) + (group-by .Category products-list))) \ No newline at end of file diff --git a/src/wwwroot/code/linq77.txt b/src/wwwroot/code/linq77.sc similarity index 100% rename from src/wwwroot/code/linq77.txt rename to src/wwwroot/code/linq77.sc diff --git a/src/wwwroot/code/linq77.ss b/src/wwwroot/code/linq77.ss new file mode 100644 index 0000000..d51ea1b --- /dev/null +++ b/src/wwwroot/code/linq77.ss @@ -0,0 +1,4 @@ +{{ products + | groupBy => it.Category + | let => { Category: it.Key, ProductCount: it.count() } + | htmlDump }} \ No newline at end of file diff --git a/src/wwwroot/code/linq78.l b/src/wwwroot/code/linq78.l new file mode 100644 index 0000000..f9c2527 --- /dev/null +++ b/src/wwwroot/code/linq78.l @@ -0,0 +1,2 @@ +(let ( (numbers [5 4 1 3 9 8 6 7 2 0]) ) + (println "The sum of the numbers is " (sum numbers))) \ No newline at end of file diff --git a/src/wwwroot/code/linq78.sc b/src/wwwroot/code/linq78.sc new file mode 100644 index 0000000..0d17295 --- /dev/null +++ b/src/wwwroot/code/linq78.sc @@ -0,0 +1,3 @@ +[5, 4, 1, 3, 9, 8, 6, 7, 2, 0] | to => numbers +numbers.sum() | to => numSum +`The sum of the numbers is ${numSum}.` \ No newline at end of file diff --git a/src/wwwroot/code/linq78.txt b/src/wwwroot/code/linq78.ss similarity index 100% rename from src/wwwroot/code/linq78.txt rename to src/wwwroot/code/linq78.ss diff --git a/src/wwwroot/code/linq79.l b/src/wwwroot/code/linq79.l new file mode 100644 index 0000000..5926350 --- /dev/null +++ b/src/wwwroot/code/linq79.l @@ -0,0 +1,2 @@ +(let ( (words ["cherry", "apple", "blueberry"]) ) + (println "There are a total of " (sum (map count words)) " characters in these words.")) \ No newline at end of file diff --git a/src/wwwroot/code/linq79.sc b/src/wwwroot/code/linq79.sc new file mode 100644 index 0000000..e7ea9cc --- /dev/null +++ b/src/wwwroot/code/linq79.sc @@ -0,0 +1,3 @@ +[ 'cherry', 'apple', 'blueberry'] | to => words +words | sum => it.Length | to => totalChars +`There are a total of ${totalChars} characters in these words.` \ No newline at end of file diff --git a/src/wwwroot/code/linq79.txt b/src/wwwroot/code/linq79.ss similarity index 100% rename from src/wwwroot/code/linq79.txt rename to src/wwwroot/code/linq79.ss diff --git a/src/wwwroot/code/linq80.l b/src/wwwroot/code/linq80.l new file mode 100644 index 0000000..8d6c937 --- /dev/null +++ b/src/wwwroot/code/linq80.l @@ -0,0 +1,4 @@ +(htmldump (map #(it { + :category (.Key %) + :total-units-in-stock (sum (map .UnitsInStock %)) }) + (group-by .Category products-list))) \ No newline at end of file diff --git a/src/wwwroot/code/linq80.txt b/src/wwwroot/code/linq80.sc similarity index 100% rename from src/wwwroot/code/linq80.txt rename to src/wwwroot/code/linq80.sc diff --git a/src/wwwroot/code/linq80.ss b/src/wwwroot/code/linq80.ss new file mode 100644 index 0000000..739813c --- /dev/null +++ b/src/wwwroot/code/linq80.ss @@ -0,0 +1,4 @@ +{{ products + | groupBy => it.Category + | map => { Category: it.Key, TotalUnitsInStock: it.sum(p => p.UnitsInStock) } + | htmlDump }} \ No newline at end of file diff --git a/src/wwwroot/code/linq81.l b/src/wwwroot/code/linq81.l new file mode 100644 index 0000000..e6fd7df --- /dev/null +++ b/src/wwwroot/code/linq81.l @@ -0,0 +1,2 @@ +(let ( (numbers [5 4 1 3 9 8 6 7 2 0]) ) + (println "The minimum number is " (apply min numbers))) \ No newline at end of file diff --git a/src/wwwroot/code/linq81.sc b/src/wwwroot/code/linq81.sc new file mode 100644 index 0000000..793a495 --- /dev/null +++ b/src/wwwroot/code/linq81.sc @@ -0,0 +1,3 @@ +[5, 4, 1, 3, 9, 8, 6, 7, 2, 0] | to => numbers +numbers.min() | to => minNum +`The minimum number is ${minNum}.` \ No newline at end of file diff --git a/src/wwwroot/code/linq81.txt b/src/wwwroot/code/linq81.ss similarity index 100% rename from src/wwwroot/code/linq81.txt rename to src/wwwroot/code/linq81.ss diff --git a/src/wwwroot/code/linq82.l b/src/wwwroot/code/linq82.l new file mode 100644 index 0000000..91b5b0e --- /dev/null +++ b/src/wwwroot/code/linq82.l @@ -0,0 +1,2 @@ +(let ( (words ["cherry", "apple", "blueberry"]) ) + (println "The shortest word is " (apply min (map count words)) " characters long.")) \ No newline at end of file diff --git a/src/wwwroot/code/linq82.sc b/src/wwwroot/code/linq82.sc new file mode 100644 index 0000000..076c09b --- /dev/null +++ b/src/wwwroot/code/linq82.sc @@ -0,0 +1,3 @@ +[ 'cherry', 'apple', 'blueberry' ] | to => words +words | min => it.Length | to => shortestWord +`The shortest word is ${shortestWord} characters long.` \ No newline at end of file diff --git a/src/wwwroot/code/linq82.txt b/src/wwwroot/code/linq82.ss similarity index 100% rename from src/wwwroot/code/linq82.txt rename to src/wwwroot/code/linq82.ss diff --git a/src/wwwroot/code/linq83.l b/src/wwwroot/code/linq83.l new file mode 100644 index 0000000..edfcfdc --- /dev/null +++ b/src/wwwroot/code/linq83.l @@ -0,0 +1,4 @@ +(htmldump (map #(it { + :category (.Key %) + :cheapest-price (apply min (map .UnitPrice %)) }) + (group-by .Category products-list))) \ No newline at end of file diff --git a/src/wwwroot/code/linq83.txt b/src/wwwroot/code/linq83.sc similarity index 100% rename from src/wwwroot/code/linq83.txt rename to src/wwwroot/code/linq83.sc diff --git a/src/wwwroot/code/linq83.ss b/src/wwwroot/code/linq83.ss new file mode 100644 index 0000000..1911948 --- /dev/null +++ b/src/wwwroot/code/linq83.ss @@ -0,0 +1,4 @@ +{{ products + | groupBy => it.Category + | map => { Category: it.Key, CheapestPrice: it.min(p => p.UnitPrice) } + | htmlDump }} \ No newline at end of file diff --git a/src/wwwroot/code/linq84.l b/src/wwwroot/code/linq84.l new file mode 100644 index 0000000..d1b86e0 --- /dev/null +++ b/src/wwwroot/code/linq84.l @@ -0,0 +1,6 @@ +(htmldump (map (fn [g] + (let ( (min-price (apply min (map .UnitPrice g))) ) { + :category (.Key g) + :cheapest-products (where #(= (.UnitPrice %) min-price) g) + })) + (group-by .Category products-list))) \ No newline at end of file diff --git a/src/wwwroot/code/linq84.txt b/src/wwwroot/code/linq84.sc similarity index 100% rename from src/wwwroot/code/linq84.txt rename to src/wwwroot/code/linq84.sc diff --git a/src/wwwroot/code/linq84.ss b/src/wwwroot/code/linq84.ss new file mode 100644 index 0000000..fe9b0cb --- /dev/null +++ b/src/wwwroot/code/linq84.ss @@ -0,0 +1,8 @@ +{{ products + | groupBy => it.Category + | let => { + g: it, + MinPrice: it.min(p => p.UnitPrice), + } + | map => { Category: g.Key, CheapestProducts: g.where(p => p.UnitPrice == MinPrice) } + | htmlDump }} diff --git a/src/wwwroot/code/linq85.l b/src/wwwroot/code/linq85.l new file mode 100644 index 0000000..693c86e --- /dev/null +++ b/src/wwwroot/code/linq85.l @@ -0,0 +1,2 @@ +(let ( (numbers [5 4 1 3 9 8 6 7 2 0]) ) + (println "The maximum number is " (apply max numbers))) \ No newline at end of file diff --git a/src/wwwroot/code/linq85.sc b/src/wwwroot/code/linq85.sc new file mode 100644 index 0000000..74e82cf --- /dev/null +++ b/src/wwwroot/code/linq85.sc @@ -0,0 +1,3 @@ +[5, 4, 1, 3, 9, 8, 6, 7, 2, 0] | to => numbers +numbers.max() | to => maxNum +`The maximum number is ${maxNum}.` \ No newline at end of file diff --git a/src/wwwroot/code/linq85.txt b/src/wwwroot/code/linq85.ss similarity index 100% rename from src/wwwroot/code/linq85.txt rename to src/wwwroot/code/linq85.ss diff --git a/src/wwwroot/code/linq86.l b/src/wwwroot/code/linq86.l new file mode 100644 index 0000000..ff7a8a2 --- /dev/null +++ b/src/wwwroot/code/linq86.l @@ -0,0 +1,2 @@ +(let ( (words ["cherry", "apple", "blueberry"]) ) + (println "The longest word is " (apply max (map count words)) " characters long.")) \ No newline at end of file diff --git a/src/wwwroot/code/linq86.sc b/src/wwwroot/code/linq86.sc new file mode 100644 index 0000000..2273477 --- /dev/null +++ b/src/wwwroot/code/linq86.sc @@ -0,0 +1,3 @@ +[ 'cherry', 'apple', 'blueberry' ] | to => words +words | max => it.Length | to => longestLength +`The longest word is ${longestLength} characters long.` \ No newline at end of file diff --git a/src/wwwroot/code/linq86.txt b/src/wwwroot/code/linq86.ss similarity index 100% rename from src/wwwroot/code/linq86.txt rename to src/wwwroot/code/linq86.ss diff --git a/src/wwwroot/code/linq87.l b/src/wwwroot/code/linq87.l new file mode 100644 index 0000000..6005534 --- /dev/null +++ b/src/wwwroot/code/linq87.l @@ -0,0 +1,5 @@ +(htmldump (map #(it { + :category (.Key %) + :most-expensive-price (apply max (map .UnitPrice %)) + }) + (group-by .Category products-list))) \ No newline at end of file diff --git a/src/wwwroot/code/linq87.txt b/src/wwwroot/code/linq87.sc similarity index 100% rename from src/wwwroot/code/linq87.txt rename to src/wwwroot/code/linq87.sc diff --git a/src/wwwroot/code/linq87.ss b/src/wwwroot/code/linq87.ss new file mode 100644 index 0000000..0f56a40 --- /dev/null +++ b/src/wwwroot/code/linq87.ss @@ -0,0 +1,4 @@ +{{ products + | groupBy => it.Category + | map => { Category: it.Key, MostExpensivePrice: it.max(p => p.UnitPrice) } + | htmlDump }} \ No newline at end of file diff --git a/src/wwwroot/code/linq88.l b/src/wwwroot/code/linq88.l new file mode 100644 index 0000000..89b0f48 --- /dev/null +++ b/src/wwwroot/code/linq88.l @@ -0,0 +1,6 @@ +(htmldump (map (fn [g] ( + let ( (max-price (apply max (map .UnitPrice g))) ) { + :category (.Key g) + :most-expensive-products (where #(= (.UnitPrice %) max-price) g) + })) + (group-by .Category products-list))) \ No newline at end of file diff --git a/src/wwwroot/code/linq88.txt b/src/wwwroot/code/linq88.sc similarity index 100% rename from src/wwwroot/code/linq88.txt rename to src/wwwroot/code/linq88.sc diff --git a/src/wwwroot/code/linq88.ss b/src/wwwroot/code/linq88.ss new file mode 100644 index 0000000..b5c12b8 --- /dev/null +++ b/src/wwwroot/code/linq88.ss @@ -0,0 +1,8 @@ +{{ products + | groupBy => it.Category + | let => { + g: it, + MaxPrice: it.max(p => p.UnitPrice), + } + | map => { Category: g.Key, MostExpensiveProducts: g.where(p => p.UnitPrice = MaxPrice) } + | htmlDump }} \ No newline at end of file diff --git a/src/wwwroot/code/linq89.l b/src/wwwroot/code/linq89.l new file mode 100644 index 0000000..0a0fb8c --- /dev/null +++ b/src/wwwroot/code/linq89.l @@ -0,0 +1,2 @@ +(let ( (numbers [5 4 1 3 9 8 6 7 2 0]) ) + (println "The average number is " (average numbers))) \ No newline at end of file diff --git a/src/wwwroot/code/linq89.sc b/src/wwwroot/code/linq89.sc new file mode 100644 index 0000000..e429303 --- /dev/null +++ b/src/wwwroot/code/linq89.sc @@ -0,0 +1,3 @@ +[5, 4, 1, 3, 9, 8, 6, 7, 2, 0] | to => numbers +numbers.average() | to => averageNum +`The average number is ${averageNum}.` \ No newline at end of file diff --git a/src/wwwroot/code/linq89.txt b/src/wwwroot/code/linq89.ss similarity index 100% rename from src/wwwroot/code/linq89.txt rename to src/wwwroot/code/linq89.ss diff --git a/src/wwwroot/code/linq90.l b/src/wwwroot/code/linq90.l new file mode 100644 index 0000000..26fdd87 --- /dev/null +++ b/src/wwwroot/code/linq90.l @@ -0,0 +1,2 @@ +(let ( (words ["cherry", "apple", "blueberry"]) ) + (println "The average word length is " (apply average (map count words)) " characters.")) \ No newline at end of file diff --git a/src/wwwroot/code/linq90.sc b/src/wwwroot/code/linq90.sc new file mode 100644 index 0000000..fc564c5 --- /dev/null +++ b/src/wwwroot/code/linq90.sc @@ -0,0 +1,3 @@ +[ 'cherry', 'apple', 'blueberry' ] | to => words +words | average => it.Length | to => averageLength +`The average word length is ${averageLength} characters.` \ No newline at end of file diff --git a/src/wwwroot/code/linq90.txt b/src/wwwroot/code/linq90.ss similarity index 100% rename from src/wwwroot/code/linq90.txt rename to src/wwwroot/code/linq90.ss diff --git a/src/wwwroot/code/linq91.l b/src/wwwroot/code/linq91.l new file mode 100644 index 0000000..16a696f --- /dev/null +++ b/src/wwwroot/code/linq91.l @@ -0,0 +1,5 @@ +(htmldump (map #(it { + :category (.Key %) + :average-price (apply average (map .UnitPrice %)) + }) + (group-by .Category products-list))) \ No newline at end of file diff --git a/src/wwwroot/code/linq91.txt b/src/wwwroot/code/linq91.sc similarity index 100% rename from src/wwwroot/code/linq91.txt rename to src/wwwroot/code/linq91.sc diff --git a/src/wwwroot/code/linq91.ss b/src/wwwroot/code/linq91.ss new file mode 100644 index 0000000..a8b4962 --- /dev/null +++ b/src/wwwroot/code/linq91.ss @@ -0,0 +1,4 @@ +{{ products + | groupBy => it.Category + | map => { Category: it.Key, AveragePrice: it.average(p => p.UnitPrice) } + | htmlDump }} \ No newline at end of file diff --git a/src/wwwroot/code/linq92.l b/src/wwwroot/code/linq92.l new file mode 100644 index 0000000..1e82212 --- /dev/null +++ b/src/wwwroot/code/linq92.l @@ -0,0 +1,2 @@ +(let ( (dbls [1.7 2.3 1.9 4.1 2.9]) ) + (println "Total product of all numbers: " (reduce * dbls))) \ No newline at end of file diff --git a/src/wwwroot/code/linq92.sc b/src/wwwroot/code/linq92.sc new file mode 100644 index 0000000..6120cc2 --- /dev/null +++ b/src/wwwroot/code/linq92.sc @@ -0,0 +1,3 @@ +[1.7, 2.3, 1.9, 4.1, 2.9] | to => doubles +doubles.reduce((runningProduct, nextFactor) => runningProduct * nextFactor, 1) | to => product +`Total product of all numbers: ${ product }.` \ No newline at end of file diff --git a/src/wwwroot/code/linq92.txt b/src/wwwroot/code/linq92.ss similarity index 100% rename from src/wwwroot/code/linq92.txt rename to src/wwwroot/code/linq92.ss diff --git a/src/wwwroot/code/linq93.l b/src/wwwroot/code/linq93.l new file mode 100644 index 0000000..629b2f8 --- /dev/null +++ b/src/wwwroot/code/linq93.l @@ -0,0 +1,6 @@ +(let ( (start-balance 100) + (attempted-withdrawls [20 10 40 50 10 70 30]) + (end-balance) ) + (setq end-balance (reduce (fn [balance cut] (if (> balance cut) (- balance cut) balance)) + attempted-withdrawls start-balance)) + (println "Ending balance: " end-balance)) \ No newline at end of file diff --git a/src/wwwroot/code/linq93.sc b/src/wwwroot/code/linq93.sc new file mode 100644 index 0000000..0858373 --- /dev/null +++ b/src/wwwroot/code/linq93.sc @@ -0,0 +1,5 @@ +[20, 10, 40, 50, 10, 70, 30] | to => attemptedWithdrawals +{{ attemptedWithdrawals.reduce((balance, nextWithdrawal) => + ((nextWithdrawal <= balance) ? (balance - nextWithdrawal) : balance), 100.0) + | to => endBalance }} +`Ending balance: ${endBalance}.` \ No newline at end of file diff --git a/src/wwwroot/code/linq93.txt b/src/wwwroot/code/linq93.ss similarity index 100% rename from src/wwwroot/code/linq93.txt rename to src/wwwroot/code/linq93.ss diff --git a/src/wwwroot/code/linq94.l b/src/wwwroot/code/linq94.l new file mode 100644 index 0000000..65c375b --- /dev/null +++ b/src/wwwroot/code/linq94.l @@ -0,0 +1,4 @@ +(let ( (numbers-a [0 2 4 5 6 8 9]) + (numbers-b [1 3 5 7 8]) ) + (println "All numbers from both arrays:") + (joinln (flatten [numbers-a numbers-b]))) \ No newline at end of file diff --git a/src/wwwroot/code/linq94.sc b/src/wwwroot/code/linq94.sc new file mode 100644 index 0000000..17848d4 --- /dev/null +++ b/src/wwwroot/code/linq94.sc @@ -0,0 +1,4 @@ +[0, 2, 4, 5, 6, 8, 9] | to => numbersA +[1, 3, 5, 7, 8] | to => numbersB +`All numbers from both arrays:` +numbersA.concat(numbersB) | joinln \ No newline at end of file diff --git a/src/wwwroot/code/linq94.txt b/src/wwwroot/code/linq94.ss similarity index 72% rename from src/wwwroot/code/linq94.txt rename to src/wwwroot/code/linq94.ss index 942be63..55ff8e9 100644 --- a/src/wwwroot/code/linq94.txt +++ b/src/wwwroot/code/linq94.ss @@ -1,4 +1,4 @@ {{ [0, 2, 4, 5, 6, 8, 9] | to => numbersA }} {{ [1, 3, 5, 7, 8] | to => numbersB }} All numbers from both arrays: -{{ numbersA.concat(numbersB) | join(`\n`) }} \ No newline at end of file +{{ numbersA.concat(numbersB) | joinln }} \ No newline at end of file diff --git a/src/wwwroot/code/linq95.l b/src/wwwroot/code/linq95.l new file mode 100644 index 0000000..6d6eaff --- /dev/null +++ b/src/wwwroot/code/linq95.l @@ -0,0 +1,4 @@ +(let ( (customer-names (map .CompanyName customers-list)) + (product-names (map .ProductName products-list)) ) + (println "Customer and product names:") + (joinln (flatten [customer-names product-names]))) \ No newline at end of file diff --git a/src/wwwroot/code/linq95.sc b/src/wwwroot/code/linq95.sc new file mode 100644 index 0000000..05d9b63 --- /dev/null +++ b/src/wwwroot/code/linq95.sc @@ -0,0 +1,4 @@ +customers | map => it.CompanyName | to => customerNames +products | map => it.ProductName | to => productNames +`Customer and product names:` +customerNames.concat(productNames) | joinln \ No newline at end of file diff --git a/src/wwwroot/code/linq95.txt b/src/wwwroot/code/linq95.ss similarity index 76% rename from src/wwwroot/code/linq95.txt rename to src/wwwroot/code/linq95.ss index 5c48cdf..3fde78f 100644 --- a/src/wwwroot/code/linq95.txt +++ b/src/wwwroot/code/linq95.ss @@ -5,4 +5,4 @@ | map => it.ProductName | to => productNames }} Customer and product names: -{{ customerNames.concat(productNames) | join(`\n`) }} \ No newline at end of file +{{ customerNames.concat(productNames) | joinln }} \ No newline at end of file diff --git a/src/wwwroot/code/linq96.l b/src/wwwroot/code/linq96.l new file mode 100644 index 0000000..765b36c --- /dev/null +++ b/src/wwwroot/code/linq96.l @@ -0,0 +1,3 @@ +(let ( (words-a ["cherry" "apple" "blueberry"]) + (words-b ["cherry" "apple" "blueberry"]) ) + (println "The sequences match: " (/sequenceEquals words-a words-b))) \ No newline at end of file diff --git a/src/wwwroot/code/linq96.sc b/src/wwwroot/code/linq96.sc new file mode 100644 index 0000000..7f98bf3 --- /dev/null +++ b/src/wwwroot/code/linq96.sc @@ -0,0 +1,4 @@ +[ 'cherry', 'apple', 'blueberry' ] | to => wordsA +[ 'cherry', 'apple', 'blueberry' ] | to => wordsB +wordsA.equivalentTo(wordsB) | to => match +`The sequences match: ${match.lower()}` \ No newline at end of file diff --git a/src/wwwroot/code/linq96.txt b/src/wwwroot/code/linq96.ss similarity index 100% rename from src/wwwroot/code/linq96.txt rename to src/wwwroot/code/linq96.ss diff --git a/src/wwwroot/code/linq97.l b/src/wwwroot/code/linq97.l new file mode 100644 index 0000000..c7f92b5 --- /dev/null +++ b/src/wwwroot/code/linq97.l @@ -0,0 +1,3 @@ +(let ( (words-a ["cherry" "apple" "blueberry"]) + (words-b ["apple" "blueberry" "cherry"]) ) + (println "The sequences match: " (/sequenceEquals words-a words-b))) \ No newline at end of file diff --git a/src/wwwroot/code/linq97.sc b/src/wwwroot/code/linq97.sc new file mode 100644 index 0000000..49a6010 --- /dev/null +++ b/src/wwwroot/code/linq97.sc @@ -0,0 +1,4 @@ +[ 'cherry', 'apple', 'blueberry' ] | to => wordsA +[ 'apple', 'blueberry', 'cherry' ] | to => wordsB +wordsA.equivalentTo(wordsB) | to => match +`The sequences match: ${match.lower()}` \ No newline at end of file diff --git a/src/wwwroot/code/linq97.txt b/src/wwwroot/code/linq97.ss similarity index 100% rename from src/wwwroot/code/linq97.txt rename to src/wwwroot/code/linq97.ss diff --git a/src/wwwroot/code/linq99.l b/src/wwwroot/code/linq99.l new file mode 100644 index 0000000..7452874 --- /dev/null +++ b/src/wwwroot/code/linq99.l @@ -0,0 +1,3 @@ +(let ( (numbers [5 4 1 3 9 8 6 7 2 0]) + (i 0) ) + (doseq (v (map #(it (fn [] (f++ i))) numbers)) (println "v = " (v) ", i = " i))) \ No newline at end of file diff --git a/src/wwwroot/code/linq99.sc b/src/wwwroot/code/linq99.sc new file mode 100644 index 0000000..bee840e --- /dev/null +++ b/src/wwwroot/code/linq99.sc @@ -0,0 +1,3 @@ +[5, 4, 1, 3, 9, 8, 6, 7, 2, 0] | to => numbers +0 | to => i +numbers | let => { i: i + 1 } | map => `v = ${index + 1}, i = ${i}` | joinln \ No newline at end of file diff --git a/src/wwwroot/code/linq99.txt b/src/wwwroot/code/linq99.ss similarity index 100% rename from src/wwwroot/code/linq99.txt rename to src/wwwroot/code/linq99.ss diff --git a/src/wwwroot/code/mv.sc b/src/wwwroot/code/mv.sc new file mode 100644 index 0000000..ff6c781 --- /dev/null +++ b/src/wwwroot/code/mv.sc @@ -0,0 +1,7 @@ +vfsFileSystem('.') | to => fs + +#each f in fs.findFiles('linq*.txt') + `copy ${f.Name} ${f.Name.lastLeftPart('.')}.sc` | sh + `copy ${f.Name} ${f.Name.lastLeftPart('.')}.l` | sh + `move ${f.Name} ${f.Name.lastLeftPart('.')}.ss` | sh +/each diff --git a/src/wwwroot/code/rss.l b/src/wwwroot/code/rss.l new file mode 100644 index 0000000..6e51e39 --- /dev/null +++ b/src/wwwroot/code/rss.l @@ -0,0 +1,8 @@ +(load "index:parse-rss") + +(def xml (/urlContentsWithCache "https://news.ycombinator.com/rss")) + +(def top-links (take 5 (:items (parse-rss xml)))) +(def n 0) + +(joinln (map #(str (padLeft (incf n) 2) ". " (:title %) "\n " (:link %) "\n") top-links )) diff --git a/src/wwwroot/doc-links.html b/src/wwwroot/doc-links.html index 1e24e08..0cbd7ee 100644 --- a/src/wwwroot/doc-links.html +++ b/src/wwwroot/doc-links.html @@ -1,7 +1,12 @@ \ No newline at end of file diff --git a/src/wwwroot/docs/blocks.html b/src/wwwroot/docs/blocks.html index 16d8ba1..fa2f078 100644 --- a/src/wwwroot/docs/blocks.html +++ b/src/wwwroot/docs/blocks.html @@ -7,26 +7,13 @@ Script Blocks lets you define reusable statements that can be invoked with a new context allowing the creation custom iterators and helpers - making it easy to encapsulate reusable functionality and reduce boilerplate for common functionality. -## Default Blocks - - - [noop](#noop) - - [with](#with) - - [if](#if) - - [raw](#raw) - - [function](#function) - - [capture](#capture) - - [markdown](#markdown) - - [keyvalues](#keyvalues) - - [csv](#csv) - - [partial](#partial) - - [html](#html) - -## ServiceStack Blocks - - - [minifyjs](#minifyjs) - - [minifycss](#minifycss) - - [minifyhtml](#minifyhtml) - - [svg](#svg) +{{/markdown}} + +{{ 'gfm/blocks/00.md' | githubMarkdown | convertScriptToCodeBlocks }} + +{{#markdown}} + +### Syntax The syntax for blocks follows the familiar [handlebars block helpers](https://handlebarsjs.com/block_helpers.html) in both syntax and functionality. `#Script` also includes most of handlebars.js block helpers which are useful in a HTML template language whilst minimizing any porting efforts if @@ -162,13 +149,24 @@
Autowired using ScriptContext IOC
### if Since all blocks are able to execute any number of `{{else}}` statements by calling `base.WriteElseAsync()`, the implementation for -the `{{if}}` block ends up being even simpler which just needs to evaluate the argument to `bool`. +the `#if` block ends up being even simpler which just needs to evaluate the argument to `bool`. If **true** it writes the body with `WriteBodyAsync()` otherwise it evaluates any `else` statements with `WriteElseAsync()`: {{/markdown}} {{ 'gfm/blocks/10.md' | githubMarkdown }} +{{#markdown}} + +### while + +Similar to `#if`, the `#while` block takes a boolean expression, except it keeps evaluating its body until the expression evaluates to `false`. + +The implementation includes a safe-guard to ensure it doesn't exceed the configured `ScriptContext.MaxQuota` to avoid infinite recursion: +{{/markdown}} + +{{ 'gfm/blocks/27.md' | githubMarkdown }} + {{#markdown}} ### each @@ -315,6 +313,23 @@
Autowired using ScriptContext IOC
> `function` block is available from [v5.6.1 on MyGet](https://docs.servicestack.net/myget) +### defn + +Similar to `{{#function}}` above, the `{{#defn}}` script block lets you define a function using lisp. The resulting function is +exported as a C# delegate where it can be invoked like any other Script method. + +An equivalent `calc` and `fib` function in lisp looks like: + +{{/markdown}} + +{{ 'gfm/blocks/26.md' | githubMarkdown }} + +{{#markdown}} + +As in most Lisp expressions, the last expression executed is the implicit return value. + +> The `defn` Script Block is automatically registered when the [Lisp language is registered](/lisp/). + ### capture The `{{#capture}}` block is similar to the raw block except instead of using its raw text contents, it instead evaluates its contents and captures diff --git a/src/wwwroot/docs/default-scripts.html b/src/wwwroot/docs/default-scripts.html index 7b0e076..8705c88 100644 --- a/src/wwwroot/docs/default-scripts.html +++ b/src/wwwroot/docs/default-scripts.html @@ -13,10 +13,10 @@ For examples of querying methods checkout the Live LINQ Examples, we'll show examples for other useful methods below:

-

Filters as bindings

+

Methods as bindings

- Filters with no arguments can be used in-place of an argument binding, now and utcNow are some examples of this: + Script Methods with no arguments can be used in-place of an argument binding, now and utcNow are some examples of this:

{{ 'live-template' | partial({ template: "now: {{ now | dateFormat }} diff --git a/src/wwwroot/docs/introduction.html b/src/wwwroot/docs/introduction.html index c32dd12..11d2d13 100644 --- a/src/wwwroot/docs/introduction.html +++ b/src/wwwroot/docs/introduction.html @@ -59,36 +59,7 @@

Usage in .NET

your Scripts have access to:

-{{ 'gfm/introduction/11.md' | githubMarkdown }} - -

- Where you can customize the pure sandboxed ScriptContext your Script is executed - within by extending it with: -

- - +{{ "net-usage" | partial }}

Scripting .NET Types

diff --git a/src/wwwroot/docs/partials.html b/src/wwwroot/docs/partials.html index eb89503..60a1f15 100644 --- a/src/wwwroot/docs/partials.html +++ b/src/wwwroot/docs/partials.html @@ -38,15 +38,15 @@

Select Partial

customer.html
- +
order.html
- +
- +
diff --git a/src/wwwroot/docs/protected-scripts.html b/src/wwwroot/docs/protected-scripts.html index f4d27ec..ea687a4 100644 --- a/src/wwwroot/docs/protected-scripts.html +++ b/src/wwwroot/docs/protected-scripts.html @@ -83,7 +83,7 @@

includeUrl

files: { 'page.html' : '{{ "https://raw.githubusercontent.com/ServiceStack/sharpscript/master/src" | to => src }} -{{ `${src}/wwwroot/code/linq01.txt` | includeUrl }}' +{{ `${src}/wwwroot/examples/email-template.txt` | includeUrl }}' } }) }} diff --git a/src/wwwroot/docs/servicestack-scripts.html b/src/wwwroot/docs/servicestack-scripts.html index db5fada..5b8d870 100644 --- a/src/wwwroot/docs/servicestack-scripts.html +++ b/src/wwwroot/docs/servicestack-scripts.html @@ -56,6 +56,14 @@

publishToGateway

{{ 'gfm/servicestack-scripts/02.md' | githubMarkdown }} +

publishMessage

+ +

+ publishMessage is used for sending Request DTOs to be processed by the configured MQ Server: +

+ +{{ 'gfm/servicestack-scripts/09.md' | githubMarkdown }} +

sendToAutoQuery

diff --git a/src/wwwroot/docs/syntax.html b/src/wwwroot/docs/syntax.html index 0532d54..5731a8d 100644 --- a/src/wwwroot/docs/syntax.html +++ b/src/wwwroot/docs/syntax.html @@ -312,12 +312,14 @@

Multi-line Comments

- `values` -### Code Blocks + +## Language Blocks and Expressions -We've caught a glimpse of the `code` blocks feature in the JavaScript Array Example above which dramatically reduces -the boilerplate that would've otherwise been needed should each statement have been needed to be wrapped in a template expression. +We've caught a glimpse using language blocks with the `code` JavaScript Array Example above which allows us to invert `#Script` from **"Template Mode"** +where all text is emitted as-is with only code within Template Expressions `{{ ... }}` are evaluated and changed it to **"Code Mode"** where all code +is evaluated a code expression. -They're akin to Razor's statement blocks which inverts Razor's **mode** of emitting text to treating text inside statement blocks as code, e.g: +This is akin to Razor's statement blocks which inverts Razor's **mode** of emitting text to treating text inside statement blocks as code, e.g: {{/markdown}} @@ -325,26 +327,83 @@

Multi-line Comments

{{#markdown}} -The entire **code** feature is implemented within a -[simple TransformCodeBlocks PreProcessor](https://github.com/ServiceStack/ServiceStack/blob/master/src/ServiceStack.Common/Script/ScriptPreprocessors.cs) -that's pre-configured by default and so can be removed by clearing the `Preprocessors` List: +Which is useful in reducing boilerplate when you need to evaluate code blocks with 2+ or more lines without the distracting boilerplate of wrapping +each expression within a `{{ ... }}` Template Expression. - var context = new ScriptContext(); - context.Preprocessors.Clear(); +### Languages -#### Preprocessor Code Transformations +Refer to the languages page to learn about alternative languages you can use within Language Blocks and Expressions: -It performs a basic transformation that assumes every statement is an expression and wraps them in an `{{...}}` -expression block, the exception are expressions which are already within an expression block which are ignored and you still -need to use to wrap multi-line expressions in `code` blocks. + - [#Script Code](/scode/) - use `code` language identifier + - [#Script Lisp](/lisp/) - use `lisp` language identifier -The other transformation `code` blocks perform is collapsing new lines and trimming each line, this is so scripts which are primarily -structured and indented for readability aren't reflected in its text output. +### Language Blocks -We can see an example of how `code` blocks work from the example below: +`code` fragments are executed using `#Script` language blocks in the format: + + ``` + + ``` + +Where `#Script` will parse the statement body with the language registered in its ScriptContext's `ScriptLanguages` collection that's pre-registered +with `ScriptCode.Language` which is used to process `code` block statements, e.g: + +{{/markdown}} + +{{ 'gfm/syntax/07.md' | githubMarkdown | convertScriptToCodeBlocks }} + +

+ Code Statement Blocks are evaluated within the same scope so any arguments that are assigned are also accessible within the containing page + as seen above. +

+ +

Evaluate Lisp

+ +

+ You can use language blocks to embed and evaluate any of the languages registered in your ScriptContext, e,g. to evaluate + #Script Lisp register it in your ScriptContext or SharpPagesFeature: +

+ +{{ 'gfm/lisp/01.md' | githubMarkdown }} + +

+ Where it will let you use Language Blocks to evaluate LISP code within #Script: +

+ +{{ 'gfm/syntax/08.md' | githubMarkdown | convertScriptToLispBlocks }} + +{{#markdown}} + +Unlike `code` blocks, Lisp evaluates its code using its own Symbols table, it's able to reference arguments not in its Global symbols +by resolving them from the containing scope, but in order for the outer `#Script` to access its local bindings they need +to be exported as seen above which registers the value of its `local-arg` value into the `result` argument. + +#### Language Block Modifiers + +You can provide any additional modifiers to language blocks by specifying them after them after the `|` operator, languages +can use these modifiers to change how it evaluates the script. By default the only modifiers the built-in languages support are `|quiet` +and its shorter `|mute` and `|q` aliases which you can use to discard any output from being rendered within the page. + +If you use Lisp's [setq special form](http://www.lispworks.com/documentation/HyperSpec/Body/s_setq.htm) to assign a variable, +that value is also returned which would be rendered in the page, you can ignore this output by using one of the above modifiers, e.g: + {{/markdown}} -{{ 'gfm/syntax/06.md' | githubMarkdown | convertScriptToCodeBlocks }} +{{ 'gfm/syntax/09.md' | githubMarkdown | convertScriptToLispBlocks }} + +{{#markdown}} +### Language Expressions + +If you wanted to instead embed an expression in a different language instead of executing an entire statement block, +you can embed them within Language Expressions `{| ... |}`, e.g: +{{/markdown}} + +{{ 'gfm/syntax/10.md' | githubMarkdown }} + +{{#markdown}} +You could also use them to evaluate `code` expressions, e.g. `{|code 1 + 2 |}`, but that's also how `#Script` +more concise Template Expressions are evaluated `{{ 1 + 2 }}`. +{{/markdown}} {{ "doc-links" | partial({ order }) }} diff --git a/src/wwwroot/docs/test.html b/src/wwwroot/docs/test.html new file mode 100644 index 0000000..d130c24 --- /dev/null +++ b/src/wwwroot/docs/test.html @@ -0,0 +1,9 @@ +{{ "live-pages" | partial({ + page: 'page', + files: + { + '_layout.html': 'I am the Layout: {{ page }}', + 'page.html' : 'I am the Page' + } + }) +}} diff --git a/src/wwwroot/docs/transformers.html b/src/wwwroot/docs/transformers.html index 508e6cc..a4e0e87 100644 --- a/src/wwwroot/docs/transformers.html +++ b/src/wwwroot/docs/transformers.html @@ -67,4 +67,37 @@

htmlencode

{{ pass: "page.txt" | includeFile | htmlencode }}
+

Preprocessor Code Transformations

+ +{{#markdown}} +Preprocessors allows you to perform source-code transformations before `#Script` evaluates it which is useful when you want to +convert any placeholder markers into HTML or `#Script` code at runtime, e.g. this feature could be used to replace translated text with the +language that the App's configured with to use on Startup. + +With this feature we can also change how [#Script's Language Block Feature](/docs/syntax#language-blocks-and-expressions) is implemented where +we could instead use a [simple TransformCodeBlocks PreProcessor](https://github.com/ServiceStack/ServiceStack/blob/master/src/ServiceStack.Common/Script/ScriptPreprocessors.cs) +to process any `code` bocks by registering in the `Preprocessors` list: + + var context = new ScriptContext { + Preprocessors = { ScriptPreprocessors.TransformCodeBlocks } + }.Init(); + +There by overriding the existing implementation with the code transformation below: + +#### Code Transformation + +This preprocessor performs a basic transformation that assumes every statement is an expression and wraps them in an `{{...}}` +expression block, the exception are expressions which are already within an expression block which are ignored and you still +need to use to wrap multi-line expressions in `code` blocks. + +The other transformation `code` blocks perform is collapsing new lines and trimming each line, this is so scripts which are primarily +structured and indented for readability aren't reflected in its text output. + +We can see an example of how `code` blocks work from the example below: +{{/markdown}} + +{{ 'gfm/syntax/06.md' | githubMarkdown | convertScriptToCodeBlocks }} + + + {{ "doc-links" | partial({ order }) }} diff --git a/src/wwwroot/examples/monthly-budget.txt b/src/wwwroot/examples/monthly-budget.txt index e60a136..f940c0b 100644 --- a/src/wwwroot/examples/monthly-budget.txt +++ b/src/wwwroot/examples/monthly-budget.txt @@ -1,36 +1,36 @@ ```code -11200 | to => balance -3 | to => projectedMonths +11200 |> to => balance +3 |> to => projectedMonths #keyvalues monthlyRevenues ':' -* Salary: 4000 -* App Royalties: 200 + Salary: 4000 + App Royalties: 200 /keyvalues #keyvalues monthlyExpenses -* Rent 1000 -* Internet 50 -* Mobile 50 -* Food 400 -* Misc 200 + Rent 1000 + Internet 50 + Mobile 50 + Food 400 + Misc 200 /keyvalues -monthlyRevenues | sum => it.Value | to => totalRevenues -monthlyExpenses | sum => it.Value | to => totalExpenses -(totalRevenues - totalExpenses) | to => totalSavings +monthlyRevenues |> sum => it.Value |> to => totalRevenues +monthlyExpenses |> sum => it.Value |> to => totalExpenses +(totalRevenues - totalExpenses) |> to => totalSavings ``` -Current Balance: {{ balance | currency }} +Current Balance: {{ balance |> currency }} Monthly Revenues: -{{monthlyRevenues | select: {it.Key.padRight(16)} {it.Value.currency()}\n }} -Total {{ totalRevenues | currency }} +{{monthlyRevenues |> select: {it.Key.padRight(16)} {it.Value.currency()}\n }} +Total {{ totalRevenues |> currency }} Monthly Expenses: -{{monthlyExpenses | select: {it.Key.padRight(16)} {it.Value.currency()}\n }} -Total {{ totalExpenses | currency }} +{{monthlyExpenses |> select: {it.Key.padRight(16)} {it.Value.currency()}\n }} +Total {{ totalExpenses |> currency }} -Monthly Savings: {{ totalSavings | currency }} +Monthly Savings: {{ totalSavings |> currency }} Projected Cash Position: -{{projectedMonths.times() | map => index + 1 | map => -`${now.addMonths(it).dateFormat()} ${(it * totalSavings + balance).currency()}`| joinln}} \ No newline at end of file +{{projectedMonths.times() |> map => index + 1 |> map => +`${now.addMonths(it).dateFormat()} ${(it * totalSavings + balance).currency()}`|> joinln}} \ No newline at end of file diff --git a/src/wwwroot/examples/query-github.txt b/src/wwwroot/examples/query-github.txt index ca2078e..e45c905 100644 --- a/src/wwwroot/examples/query-github.txt +++ b/src/wwwroot/examples/query-github.txt @@ -3,11 +3,11 @@ {{/markdown}} ```code -"https://api.github.com/orgs/ServiceStack/repos" | urlContentsWithCache({userAgent:'Script'}) | to => json +"https://api.github.com/orgs/ServiceStack/repos"|> urlContentsWithCache({userAgent:'Script'})|> to => json -json | parseJson | orderByDesc => it.watchers | to => repos +json |> parseJson |> orderByDesc => it.watchers |> to => repos -repos | take(5) | map => { it.name, it.watchers, it.forks } | htmlDump({caption:'Top 5 Repos'}) +repos |> take(5) |> map => { it.name, it.watchers, it.forks } |> htmlDump({caption:'Top 5 Repos'}) ``` -Total Repos: {{ repos.count() }}, Watchers: {{ repos | map => it.watchers | sum }} \ No newline at end of file +Total Repos: {{ repos.count() }}, Watchers: {{ repos |> map => it.watchers |> sum }} \ No newline at end of file diff --git a/src/wwwroot/gfm/blocks/00.html b/src/wwwroot/gfm/blocks/00.html new file mode 100644 index 0000000..5064c13 --- /dev/null +++ b/src/wwwroot/gfm/blocks/00.html @@ -0,0 +1,168 @@ +

+Default Blocks

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
namebody
noopverbatim
withdefault
ifdefault
whiledefault
rawverbatim
functioncode
defnlisp
capturetemplate
markdownverbatim
csvverbatim
partialtemplate
htmltemplate
+

+ServiceStack Blocks

+ + + + + + + + + + + + + + + + + + + + + + + + + +
namebody
minifyjsverbatim
minifycssverbatim
minifyhtmlverbatim
svgtemplate
+

+Script Block Body

+

The Body of the Script Block specifies how the body is evaluated. Script Blocks can be used in both #Script Template and +Code Statement Blocks.

+

Below are examples of using a script block of each body type in both #Script template and code statement blocks:

+

+default

+

If unspecified Script Blocks are evaluated within the language they're used within. +By default #Script pages use template expression handlebars syntax:

+
{{#if test.isEven() }}
+    {{test}} is even
+{{else}}
+    {{test}} is odd
+{{/if}}
+

Whilst in Code Statement Blocks the body is executed as JS Expressions, which requires using +quoted strings or template literals for any text you want to emit, e.g:

+
<script>
+#if test.isEven()
+    `${test} is even`
+else
+    `${test} is odd`
+/if
+</script>
+

+verbatim

+

The contents of verbatim script blocks are unprocessed and evaluated as raw text by the script block:

+
{{#csv cars}}
+Tesla,Model S,79990
+Tesla,Model 3,38990
+Tesla,Model X,84990
+{{/csv}}
+
<script>
+#csv cars
+Tesla,Model S,79990
+Tesla,Model 3,38990
+Tesla,Model X,84990
+/csv
+</script>
+

+template

+

The contents of template Script Blocks are processed using Template Expression syntax:

+
{{#capture out}}
+    {{#each range(3)}}
+    - {{it + 1}}
+    {{/each}}
+{{/capture}}
+
<script>
+#capture out
+    {{#each range(3)}}
+    - {{it + 1}}
+    {{/each}}
+/capture
+</script>
+

+code

+

The contents of code Script Blocks are processed as JS Expression statements:

+
{{#function calc(a, b) }}
+    a * b | to => c
+    a + b + c | return
+{{/function}}
+
<script>
+#function calc(a, b)
+    a * b | to => c
+    a + b + c | return
+/function 
+</script>
+

+lisp

+

Finally the contents of lisp Script Blocks is processed by #Script Lisp:

+
{{#defn calc [a, b] }}
+    (def c (* a b))
+    (+ a b c)
+{{/defn}}
+
<script>
+#defn calc [a, b]
+    (def c (* a b))
+    (+ a b c)
+/defn
+</script>
+
\ No newline at end of file diff --git a/src/wwwroot/gfm/blocks/00.md b/src/wwwroot/gfm/blocks/00.md new file mode 100644 index 0000000..30207f7 --- /dev/null +++ b/src/wwwroot/gfm/blocks/00.md @@ -0,0 +1,143 @@ +## Default Blocks + +| name | body | +|---------------------------|----------| +| [noop](#noop) | verbatim | +| [with](#with) | default | +| [if](#if) | default | +| [while](#while) | default | +| [raw](#raw) | verbatim | +| [function](#function) | code | +| [defn](#defn) | lisp | +| [capture](#capture) | template | +| [markdown](#markdown) | verbatim | +| [csv](#csv) | verbatim | +| [partial](#partial) | template | +| [html](#html) | template | + +## ServiceStack Blocks + +| name | body | +|---------------------------|----------| +| [minifyjs](#minifyjs) | verbatim | +| [minifycss](#minifycss) | verbatim | +| [minifyhtml](#minifyhtml) | verbatim | +| [svg](#svg) | template | + +### Script Block Body + +The `Body` of the Script Block specifies how the body is evaluated. Script Blocks can be used in both `#Script` Template and +[Code Statement Blocks](/docs/syntax#language-blocks-and-expressions). + +Below are examples of using a script block of each body type in both #Script template and code statement blocks: + +### default + +If unspecified Script Blocks are evaluated within the language they're used within. +By default `#Script` pages use **template** expression handlebars syntax: + +```hbs +{{#if test.isEven() }} + {{test}} is even +{{else}} + {{test}} is odd +{{/if}} +``` + +Whilst in Code Statement Blocks the body is executed as JS Expressions, which requires using +[quoted strings or template literals](/docs/syntax#quotes) for any text you want to emit, e.g: + +```html + +``` + +### verbatim + +The contents of **verbatim** script blocks are unprocessed and evaluated as raw text by the script block: + +```hbs +{{#csv cars}} +Tesla,Model S,79990 +Tesla,Model 3,38990 +Tesla,Model X,84990 +{{/csv}} +``` + +```html + +``` + +### template + +The contents of **template** Script Blocks are processed using Template Expression syntax: + +```hbs +{{#capture out}} + {{#each range(3)}} + - {{it + 1}} + {{/each}} +{{/capture}} +``` + +```html + +``` + +### code + +The contents of **code** Script Blocks are processed as JS Expression statements: + +```hbs +{{#function calc(a, b) }} + a * b | to => c + a + b + c | return +{{/function}} +``` + +```html + +``` + +### lisp + +Finally the contents of **lisp** Script Blocks is processed by [#Script Lisp](/lisp/): + +```hbs +{{#defn calc [a, b] }} + (def c (* a b)) + (+ a b c) +{{/defn}} +``` + +```html + +``` + diff --git a/src/wwwroot/gfm/blocks/13.html b/src/wwwroot/gfm/blocks/13.html index a78ef7d..c1ba9f5 100644 --- a/src/wwwroot/gfm/blocks/13.html +++ b/src/wwwroot/gfm/blocks/13.html @@ -9,6 +9,8 @@ { public override string Name => "raw"; + public override ScriptLanguage Body => ScriptVerbatim.Language; + public override async Task WriteAsync( ScriptScopeContext scope, PageBlockFragment block, CancellationToken token) { diff --git a/src/wwwroot/gfm/blocks/13.md b/src/wwwroot/gfm/blocks/13.md index 598a2dc..68212b2 100644 --- a/src/wwwroot/gfm/blocks/13.md +++ b/src/wwwroot/gfm/blocks/13.md @@ -10,6 +10,8 @@ public class RawScriptBlock : ScriptBlock { public override string Name => "raw"; + public override ScriptLanguage Body => ScriptVerbatim.Language; + public override async Task WriteAsync( ScriptScopeContext scope, PageBlockFragment block, CancellationToken token) { diff --git a/src/wwwroot/gfm/blocks/14.html b/src/wwwroot/gfm/blocks/14.html index dce23de..2b6ed6e 100644 --- a/src/wwwroot/gfm/blocks/14.html +++ b/src/wwwroot/gfm/blocks/14.html @@ -1,7 +1,6 @@
/// <summary>
 /// Captures the output and assigns it to the specified variable.
 /// Accepts an optional Object Dictionary as scope arguments when evaluating body.
-/// Effectively is similar 
 ///
 /// Usages: {{#capture output}} {{#each args}} - [{{it}}](/path?arg={{it}}) {{/each}} {{/capture}}
 ///         {{#capture output {nums:[1,2,3]} }} {{#each nums}} {{it}} {{/each}} {{/capture}}
@@ -11,20 +10,36 @@
 {
     public override string Name => "capture";
 
+    public override ScriptLanguage Body => ScriptTemplate.Language;
+
+    internal struct Tuple
+    {
+        internal string name;
+        internal Dictionary<string, object> scopeArgs;
+        internal bool appendTo;
+        internal Tuple(string name, Dictionary<string, object> scopeArgs, bool appendTo)
+        {
+            this.name = name;
+            this.scopeArgs = scopeArgs;
+            this.appendTo = appendTo;
+        }
+    }
+
     public override async Task WriteAsync(
         ScriptScopeContext scope, PageBlockFragment block, CancellationToken token)
     {
-        var (name, scopeArgs, appendTo) = Parse(scope, block);
+        var tuple = Parse(scope, block);
+        var name = tuple.name;
 
         using (var ms = MemoryStreamFactory.GetStream())
         {
-            var useScope = scope.ScopeWith(scopeArgs, ms);
+            var useScope = scope.ScopeWith(tuple.scopeArgs, ms);
 
             await WriteBodyAsync(useScope, block, token);
 
             var capturedOutput = ms.ReadToEnd();
 
-            if (appendTo && scope.PageResult.Args.TryGetValue(name, out var oVar)
+            if (tuple.appendTo && scope.PageResult.Args.TryGetValue(name, out var oVar)
                          && oVar is string existingString)
             {
                 scope.PageResult.Args[name] = existingString + capturedOutput;
@@ -36,34 +51,33 @@
     }
 
     //Extract usages of Span outside of async method 
-    private (string name, Dictionary<string, object> scopeArgs, bool appendTo) 
-        Parse(ScriptScopeContext scope, PageBlockFragment block)
+    private Tuple Parse(ScriptScopeContext scope, PageBlockFragment block)
     {
-        if (block.Argument.IsNullOrWhiteSpace())
-            throw new NotSupportedException("'capture' block is missing variable name to assign output to");
+        if (block.Argument.IsNullOrWhiteSpace())
+            throw new NotSupportedException("'capture' block is missing variable name to assign output to");
         
-        var literal = block.Argument.AdvancePastWhitespace();
+        var literal = block.Argument.AdvancePastWhitespace();
         bool appendTo = false;
-        if (literal.StartsWith("appendTo "))
+        if (literal.StartsWith("appendTo "))
         {
             appendTo = true;
             literal = literal.Advance("appendTo ".Length);
         }
             
-        literal = literal.ParseVarName(out var name);
-        if (name.IsNullOrEmpty())
-            throw new NotSupportedException("'capture' block is missing variable name to assign output to");
+        literal = literal.ParseVarName(out var name);
+        if (name.IsNullOrEmpty())
+            throw new NotSupportedException("'capture' block is missing variable name to assign output to");
 
-        literal = literal.AdvancePastWhitespace();
+        literal = literal.AdvancePastWhitespace();
 
-        var argValue = literal.GetJsExpressionAndEvaluate(scope);
+        var argValue = literal.GetJsExpressionAndEvaluate(scope);
 
-        var scopeArgs = argValue as Dictionary<string, object>;
+        var scopeArgs = argValue as Dictionary<string, object>;
 
-        if (argValue != null && scopeArgs == null)
-            throw new NotSupportedException("Any 'capture' argument must be an Object Dictionary");
+        if (argValue != null && scopeArgs == null)
+            throw new NotSupportedException("Any 'capture' argument must be an Object Dictionary");
 
-        return (name.ToString(), scopeArgs, appendTo);
+        return new Tuple(name.ToString(), scopeArgs, appendTo);
     }
 }
\ No newline at end of file diff --git a/src/wwwroot/gfm/blocks/14.md b/src/wwwroot/gfm/blocks/14.md index 3b5f393..552e02b 100644 --- a/src/wwwroot/gfm/blocks/14.md +++ b/src/wwwroot/gfm/blocks/14.md @@ -2,7 +2,6 @@ /// /// Captures the output and assigns it to the specified variable. /// Accepts an optional Object Dictionary as scope arguments when evaluating body. -/// Effectively is similar /// /// Usages: {{#capture output}} {{#each args}} - [{{it}}](/path?arg={{it}}) {{/each}} {{/capture}} /// {{#capture output {nums:[1,2,3]} }} {{#each nums}} {{it}} {{/each}} {{/capture}} @@ -12,20 +11,36 @@ public class CaptureScriptBlock : ScriptBlock { public override string Name => "capture"; + public override ScriptLanguage Body => ScriptTemplate.Language; + + internal struct Tuple + { + internal string name; + internal Dictionary scopeArgs; + internal bool appendTo; + internal Tuple(string name, Dictionary scopeArgs, bool appendTo) + { + this.name = name; + this.scopeArgs = scopeArgs; + this.appendTo = appendTo; + } + } + public override async Task WriteAsync( ScriptScopeContext scope, PageBlockFragment block, CancellationToken token) { - var (name, scopeArgs, appendTo) = Parse(scope, block); + var tuple = Parse(scope, block); + var name = tuple.name; using (var ms = MemoryStreamFactory.GetStream()) { - var useScope = scope.ScopeWith(scopeArgs, ms); + var useScope = scope.ScopeWith(tuple.scopeArgs, ms); await WriteBodyAsync(useScope, block, token); var capturedOutput = ms.ReadToEnd(); - if (appendTo && scope.PageResult.Args.TryGetValue(name, out var oVar) + if (tuple.appendTo && scope.PageResult.Args.TryGetValue(name, out var oVar) && oVar is string existingString) { scope.PageResult.Args[name] = existingString + capturedOutput; @@ -37,8 +52,7 @@ public class CaptureScriptBlock : ScriptBlock } //Extract usages of Span outside of async method - private (string name, Dictionary scopeArgs, bool appendTo) - Parse(ScriptScopeContext scope, PageBlockFragment block) + private Tuple Parse(ScriptScopeContext scope, PageBlockFragment block) { if (block.Argument.IsNullOrWhiteSpace()) throw new NotSupportedException("'capture' block is missing variable name to assign output to"); @@ -64,7 +78,7 @@ public class CaptureScriptBlock : ScriptBlock if (argValue != null && scopeArgs == null) throw new NotSupportedException("Any 'capture' argument must be an Object Dictionary"); - return (name.ToString(), scopeArgs, appendTo); + return new Tuple(name.ToString(), scopeArgs, appendTo); } } ``` diff --git a/src/wwwroot/gfm/blocks/26.html b/src/wwwroot/gfm/blocks/26.html new file mode 100644 index 0000000..68987e9 --- /dev/null +++ b/src/wwwroot/gfm/blocks/26.html @@ -0,0 +1,16 @@ +
{{#defn calc [a, b] }}
+    (def c (* a b))
+    (+ a b c)
+{{/defn}}
+
+calc: {{ calc(1,2) }}
+
+{{#defn fib [n] }}
+    (if (<= n 1)
+        n
+        (+ (fib (- n 1))
+           (fib (- n 2)) ))
+{{/defn}}
+
+fib: {{ 10.fib() }} 
+
\ No newline at end of file diff --git a/src/wwwroot/gfm/blocks/26.md b/src/wwwroot/gfm/blocks/26.md new file mode 100644 index 0000000..b936350 --- /dev/null +++ b/src/wwwroot/gfm/blocks/26.md @@ -0,0 +1,17 @@ +```hbs +{{#defn calc [a, b] }} + (def c (* a b)) + (+ a b c) +{{/defn}} + +calc: {{ calc(1,2) }} + +{{#defn fib [n] }} + (if (<= n 1) + n + (+ (fib (- n 1)) + (fib (- n 2)) )) +{{/defn}} + +fib: {{ 10.fib() }} +``` \ No newline at end of file diff --git a/src/wwwroot/gfm/blocks/27.html b/src/wwwroot/gfm/blocks/27.html new file mode 100644 index 0000000..a1950c7 --- /dev/null +++ b/src/wwwroot/gfm/blocks/27.html @@ -0,0 +1,39 @@ +
/// <summary>
+/// while block
+/// Usages: {{#while times > 0}} {{times}}. {{times - 1 | to => times}} {{/while}}
+///         {{#while b}} {{ false | to => b }} {{else}} {{b}} was false {{/while}}
+/// 
+/// Max Iterations = Context.Args[ScriptConstants.MaxQuota]
+/// </summary>
+public class WhileScriptBlock : ScriptBlock
+{
+    public override string Name => "while";
+    
+    public override async Task WriteAsync(
+        ScriptScopeContext scope, PageBlockFragment block, CancellationToken ct)
+    {
+        var result = await block.Argument.GetJsExpressionAndEvaluateToBoolAsync(scope,
+            ifNone: () => throw new NotSupportedException("'while' block is not valid"));
+
+        var iterations = 0;
+        
+        if (result)
+        {
+            do
+            {
+                await WriteBodyAsync(scope, block, ct);
+                
+                result = await block.Argument.GetJsExpressionAndEvaluateToBoolAsync(scope,
+                    ifNone: () => throw new NotSupportedException("'while' block is not valid"));
+
+                Context.DefaultMethods.AssertWithinMaxQuota(iterations++);
+                
+            } while (result);
+        }
+        else
+        {
+            await WriteElseAsync(scope, block.ElseBlocks, ct);
+        }
+    }
+}
+
\ No newline at end of file diff --git a/src/wwwroot/gfm/blocks/27.md b/src/wwwroot/gfm/blocks/27.md new file mode 100644 index 0000000..2ff247e --- /dev/null +++ b/src/wwwroot/gfm/blocks/27.md @@ -0,0 +1,40 @@ +```csharp +/// +/// while block +/// Usages: {{#while times > 0}} {{times}}. {{times - 1 | to => times}} {{/while}} +/// {{#while b}} {{ false | to => b }} {{else}} {{b}} was false {{/while}} +/// +/// Max Iterations = Context.Args[ScriptConstants.MaxQuota] +/// +public class WhileScriptBlock : ScriptBlock +{ + public override string Name => "while"; + + public override async Task WriteAsync( + ScriptScopeContext scope, PageBlockFragment block, CancellationToken ct) + { + var result = await block.Argument.GetJsExpressionAndEvaluateToBoolAsync(scope, + ifNone: () => throw new NotSupportedException("'while' block is not valid")); + + var iterations = 0; + + if (result) + { + do + { + await WriteBodyAsync(scope, block, ct); + + result = await block.Argument.GetJsExpressionAndEvaluateToBoolAsync(scope, + ifNone: () => throw new NotSupportedException("'while' block is not valid")); + + Context.DefaultMethods.AssertWithinMaxQuota(iterations++); + + } while (result); + } + else + { + await WriteElseAsync(scope, block.ElseBlocks, ct); + } + } +} +``` \ No newline at end of file diff --git a/src/wwwroot/gfm/installation/01.html b/src/wwwroot/gfm/installation/01.html index 9614f4d..be1cd8a 100644 --- a/src/wwwroot/gfm/installation/01.html +++ b/src/wwwroot/gfm/installation/01.html @@ -1,3 +1,3 @@
var context = new ScriptContext().Init();
-var output = context.EvaluateScript("The time is now: {{ now | dateFormat('HH:mm:ss') }}");
+var output = context.RenderScript("The time is now: {{ now | dateFormat('HH:mm:ss') }}");
\ No newline at end of file diff --git a/src/wwwroot/gfm/installation/01.md b/src/wwwroot/gfm/installation/01.md index 4ee4c97..463dda4 100644 --- a/src/wwwroot/gfm/installation/01.md +++ b/src/wwwroot/gfm/installation/01.md @@ -1,4 +1,4 @@ ```ts var context = new ScriptContext().Init(); -var output = context.EvaluateScript("The time is now: {{ now | dateFormat('HH:mm:ss') }}"); +var output = context.RenderScript("The time is now: {{ now | dateFormat('HH:mm:ss') }}"); ``` diff --git a/src/wwwroot/gfm/introduction/02.html b/src/wwwroot/gfm/introduction/02.html index 0ec40fc..04f4f0e 100644 --- a/src/wwwroot/gfm/introduction/02.html +++ b/src/wwwroot/gfm/introduction/02.html @@ -1,4 +1,4 @@ -
var output = context.EvaluateScript("Time is now: {{ now | dateFormat('HH:mm:ss') }}");
+
var output = context.RenderScript("Time is now: {{ now | dateFormat('HH:mm:ss') }}");

Or if your script performs any async I/O, it can be evaluated asynchronously with:

-
var output = await context.EvaluateScriptAsync("Time is now: {{ now | dateFormat('HH:mm:ss') }}");
+
var output = await context.RenderScriptAsync("Time is now: {{ now | dateFormat('HH:mm:ss') }}");
\ No newline at end of file diff --git a/src/wwwroot/gfm/introduction/02.md b/src/wwwroot/gfm/introduction/02.md index 3763118..d6f9fc0 100644 --- a/src/wwwroot/gfm/introduction/02.md +++ b/src/wwwroot/gfm/introduction/02.md @@ -1,9 +1,9 @@ ```csharp -var output = context.EvaluateScript("Time is now: {{ now | dateFormat('HH:mm:ss') }}"); +var output = context.RenderScript("Time is now: {{ now | dateFormat('HH:mm:ss') }}"); ``` Or if your script performs any async I/O, it can be evaluated asynchronously with: ```csharp -var output = await context.EvaluateScriptAsync("Time is now: {{ now | dateFormat('HH:mm:ss') }}"); +var output = await context.RenderScriptAsync("Time is now: {{ now | dateFormat('HH:mm:ss') }}"); ``` diff --git a/src/wwwroot/gfm/linq/01.html b/src/wwwroot/gfm/linq/01.html new file mode 100644 index 0000000..15cf395 --- /dev/null +++ b/src/wwwroot/gfm/linq/01.html @@ -0,0 +1,19 @@ +
LinqContext = new ScriptContext {
+    ScriptLanguages = { ScriptLisp.Language },
+    Args = {
+        [ScriptConstants.DefaultDateFormat] = "yyyy/MM/dd",
+        ["products"] = TemplateQueryData.Products,
+        ["products-list"] = Lisp.ToCons(TemplateQueryData.Products),
+        ["customers"] = TemplateQueryData.Customers,
+        ["customers-list"] = Lisp.ToCons(TemplateQueryData.Customers),
+        ["comparer"] = new CaseInsensitiveComparer(),
+        ["anagramComparer"] = new AnagramEqualityComparer(),
+    },
+    // Enable access to .NET Types to Lisp
+    ScriptTypes = {
+        typeof(DateTime),
+        typeof(CaseInsensitiveComparer),
+        typeof(AnagramEqualityComparer),
+    },
+};
+
\ No newline at end of file diff --git a/src/wwwroot/gfm/linq/01.md b/src/wwwroot/gfm/linq/01.md new file mode 100644 index 0000000..4af4257 --- /dev/null +++ b/src/wwwroot/gfm/linq/01.md @@ -0,0 +1,20 @@ +```csharp +LinqContext = new ScriptContext { + ScriptLanguages = { ScriptLisp.Language }, + Args = { + [ScriptConstants.DefaultDateFormat] = "yyyy/MM/dd", + ["products"] = TemplateQueryData.Products, + ["products-list"] = Lisp.ToCons(TemplateQueryData.Products), + ["customers"] = TemplateQueryData.Customers, + ["customers-list"] = Lisp.ToCons(TemplateQueryData.Customers), + ["comparer"] = new CaseInsensitiveComparer(), + ["anagramComparer"] = new AnagramEqualityComparer(), + }, + // Enable access to .NET Types to Lisp + ScriptTypes = { + typeof(DateTime), + typeof(CaseInsensitiveComparer), + typeof(AnagramEqualityComparer), + }, +}; +``` \ No newline at end of file diff --git a/src/wwwroot/gfm/lisp/01.html b/src/wwwroot/gfm/lisp/01.html new file mode 100644 index 0000000..6cdb5b6 --- /dev/null +++ b/src/wwwroot/gfm/lisp/01.html @@ -0,0 +1,4 @@ +
var context = new ScriptContext {
+    ScriptLanguages = { ScriptLisp.Language },
+}.Init();
+
\ No newline at end of file diff --git a/src/wwwroot/gfm/lisp/01.md b/src/wwwroot/gfm/lisp/01.md new file mode 100644 index 0000000..824c818 --- /dev/null +++ b/src/wwwroot/gfm/lisp/01.md @@ -0,0 +1,5 @@ +```csharp +var context = new ScriptContext { + ScriptLanguages = { ScriptLisp.Language }, +}.Init(); +``` \ No newline at end of file diff --git a/src/wwwroot/gfm/lisp/02.html b/src/wwwroot/gfm/lisp/02.html new file mode 100644 index 0000000..99f876e --- /dev/null +++ b/src/wwwroot/gfm/lisp/02.html @@ -0,0 +1,28 @@ +
;<!--
+; db sqlite
+; db.connection northwind.sqlite
+; files s3
+; files.config {AccessKey:$AWS_S3_ACCESS_KEY,SecretKey:$AWS_S3_SECRET_KEY,Region:us-east-1,Bucket:rockwind}
+;-->
+
+; delete remove.txt file
+(sh (str (if isWin "del" "rm") " remove.txt"))
+
+; View all `northwind.sqlite` RDBMS Tables
+(textDump (dbTableNames) { :caption "Northwind" } )
+
+; Display first `customer` row in Single Row View showing all Table Columns
+(textDump (dbSelect "select * from customer limit 1"))
+
+; Display all Customers in London
+(def city "London")
+(textDump (dbSelect "select Id, CompanyName, ContactName from customer where city = @city" { :city city } ))
+
+; View all root files and folders in configured S3 Virtual File Provider
+(joinln (map #(str (.Name %) "/") (allRootDirectories vfsContent)))
+(joinln (map .Name (allRootFiles vfsContent)))
+
+; Show first 10 *.png files in S3 VFS Provider
+(def pattern (or (first ARGV) "*.png"))
+(joinln (map .VirtualPath (take 10 (findFiles vfsContent pattern))))
+
\ No newline at end of file diff --git a/src/wwwroot/gfm/lisp/02.md b/src/wwwroot/gfm/lisp/02.md new file mode 100644 index 0000000..66dc93e --- /dev/null +++ b/src/wwwroot/gfm/lisp/02.md @@ -0,0 +1,28 @@ +```lisp +; + +; delete remove.txt file +(sh (str (if isWin "del" "rm") " remove.txt")) + +; View all `northwind.sqlite` RDBMS Tables +(textDump (dbTableNames) { :caption "Northwind" } ) + +; Display first `customer` row in Single Row View showing all Table Columns +(textDump (dbSelect "select * from customer limit 1")) + +; Display all Customers in London +(def city "London") +(textDump (dbSelect "select Id, CompanyName, ContactName from customer where city = @city" { :city city } )) + +; View all root files and folders in configured S3 Virtual File Provider +(joinln (map #(str (.Name %) "/") (allRootDirectories vfsContent))) +(joinln (map .Name (allRootFiles vfsContent))) + +; Show first 10 *.png files in S3 VFS Provider +(def pattern (or (first ARGV) "*.png")) +(joinln (map .VirtualPath (take 10 (findFiles vfsContent pattern)))) diff --git a/src/wwwroot/gfm/lisp/03.html b/src/wwwroot/gfm/lisp/03.html new file mode 100644 index 0000000..2a1960a --- /dev/null +++ b/src/wwwroot/gfm/lisp/03.html @@ -0,0 +1,110 @@ +
; quick lisp test!
+(+ 1 2 3)
+
+; List of ScriptMethodInfo that the ScriptContext running this Lisp Interpreter has access to
+scriptMethods
+
+; first script method
+(:0 scriptMethods)
+
+; show public properties of ScriptMethodInfo 
+(props (:0 scriptMethods))
+
+; show 1 property per line
+(joinln (props (:0 scriptMethods)))
+
+; show both Property Type and Name
+(joinln (propTypes (:0 scriptMethods)))
+
+; view the Names of all avaialble script methods
+(joinln (map .Name scriptMethods))
+
+; view all script methods starting with 'a'
+(globln "a*" (map .Name scriptMethods))
+
+; view all script methods starting with 'env'
+(globln "env*" (map .Name scriptMethods))
+
+; print environment info about this machine seperated by spaces
+(printlns envOSVersion envMachineName envFrameworkDescription envLogicalDrives)
+
+; expand logical drives
+(printlns envOSVersion envMachineName envFrameworkDescription "- drives:" (join envLogicalDrives " "))
+
+; view all current global symbols defined in this Lisp interpreter
+symbols
+
+; view all symbols starting with 'c'
+(globln "c*" symbols)
+
+; see how many symbols are defined in this interpreter
+(count symbols)
+
+; see how many script methods there are available
+(count scriptMethods)
+
+; view the method signature for all script methods starting with 'all'
+(globln "all*" (map .Signature scriptMethods))
+
+; count all files accessible from the configured ScriptContext
+(count allFiles)
+
+; view the public properties of the first IVirtualFile
+(props (:0 allFiles))
+
+; display the VirtualPath of all available files
+(joinln (map .VirtualPath allFiles))
+
+; display the method signature for all script methods starting with 'findFiles'
+(globln "findFiles*" (map .Signature scriptMethods))
+
+; see how many .html files are available to this App
+(count (findFiles "*.html"))
+
+; see how many .js files are available to this App
+(count (findFiles "*.js"))
+
+; show the VirtualPath of all .html files
+(joinln (map .VirtualPath (findFiles "*.html")))
+
+; view the VirtualPath's of the 1st and 2nd .html files
+(:0 (map .VirtualPath (findFiles "*.html")))
+(:1 (map .VirtualPath (findFiles "*.html")))
+
+; view the text file contents of the 1st and 2nd .html files
+(fileTextContents (:0 (map .VirtualPath (findFiles "*.html"))))
+(fileTextContents (:1 (map .VirtualPath (findFiles "*.html"))))
+
+; display the method signatures of all script methods starting with 'redis'
+(globln "redis*" (map .Signature scriptMethods))
+
+; search for all Redis Keys starting with 'urn:' in the redis-server instances this App is configured with
+(redisSearchKeys "urn:*")
+
+; display the first redis search entry
+(:0 (redisSearchKeys "urn:*"))
+
+; display the key names of all redis keys starting with 'urn:'
+(joinln (map :id (redisSearchKeys "urn:*")))
+
+; find out the redis-server data type of the 'urn:tags' key
+(redisCall "TYPE urn:tags")
+
+; view all tags in the 'urn:tags' sorted set
+(redisCall "ZRANGE urn:tags 0 -1")
+
+; view the string contents of the 'urn:question:1' key
+(redisCall "GET urn:question:1")
+
+; parse the json contents of question 1 and display its tag names
+(:Tags (parseJson (redisCall "GET urn:question:1")))
+
+; extract the 2nd tag of question 1
+(:1 (:Tags (parseJson (redisCall "GET urn:question:1"))))
+
+; clear the Console screen
+clear
+
+; exit the Lisp REPL
+quit
+
\ No newline at end of file diff --git a/src/wwwroot/gfm/lisp/03.md b/src/wwwroot/gfm/lisp/03.md new file mode 100644 index 0000000..7f63ed1 --- /dev/null +++ b/src/wwwroot/gfm/lisp/03.md @@ -0,0 +1,111 @@ +```lisp +; quick lisp test! +(+ 1 2 3) + +; List of ScriptMethodInfo that the ScriptContext running this Lisp Interpreter has access to +scriptMethods + +; first script method +(:0 scriptMethods) + +; show public properties of ScriptMethodInfo +(props (:0 scriptMethods)) + +; show 1 property per line +(joinln (props (:0 scriptMethods))) + +; show both Property Type and Name +(joinln (propTypes (:0 scriptMethods))) + +; view the Names of all avaialble script methods +(joinln (map .Name scriptMethods)) + +; view all script methods starting with 'a' +(globln "a*" (map .Name scriptMethods)) + +; view all script methods starting with 'env' +(globln "env*" (map .Name scriptMethods)) + +; print environment info about this machine seperated by spaces +(printlns envOSVersion envMachineName envFrameworkDescription envLogicalDrives) + +; expand logical drives +(printlns envOSVersion envMachineName envFrameworkDescription "- drives:" (join envLogicalDrives " ")) + +; view all current global symbols defined in this Lisp interpreter +symbols + +; view all symbols starting with 'c' +(globln "c*" symbols) + +; see how many symbols are defined in this interpreter +(count symbols) + +; see how many script methods there are available +(count scriptMethods) + +; view the method signature for all script methods starting with 'all' +(globln "all*" (map .Signature scriptMethods)) + +; count all files accessible from the configured ScriptContext +(count allFiles) + +; view the public properties of the first IVirtualFile +(props (:0 allFiles)) + +; display the VirtualPath of all available files +(joinln (map .VirtualPath allFiles)) + +; display the method signature for all script methods starting with 'findFiles' +(globln "findFiles*" (map .Signature scriptMethods)) + +; see how many .html files are available to this App +(count (findFiles "*.html")) + +; see how many .js files are available to this App +(count (findFiles "*.js")) + +; show the VirtualPath of all .html files +(joinln (map .VirtualPath (findFiles "*.html"))) + +; view the VirtualPath's of the 1st and 2nd .html files +(:0 (map .VirtualPath (findFiles "*.html"))) +(:1 (map .VirtualPath (findFiles "*.html"))) + +; view the text file contents of the 1st and 2nd .html files +(fileTextContents (:0 (map .VirtualPath (findFiles "*.html")))) +(fileTextContents (:1 (map .VirtualPath (findFiles "*.html")))) + +; display the method signatures of all script methods starting with 'redis' +(globln "redis*" (map .Signature scriptMethods)) + +; search for all Redis Keys starting with 'urn:' in the redis-server instances this App is configured with +(redisSearchKeys "urn:*") + +; display the first redis search entry +(:0 (redisSearchKeys "urn:*")) + +; display the key names of all redis keys starting with 'urn:' +(joinln (map :id (redisSearchKeys "urn:*"))) + +; find out the redis-server data type of the 'urn:tags' key +(redisCall "TYPE urn:tags") + +; view all tags in the 'urn:tags' sorted set +(redisCall "ZRANGE urn:tags 0 -1") + +; view the string contents of the 'urn:question:1' key +(redisCall "GET urn:question:1") + +; parse the json contents of question 1 and display its tag names +(:Tags (parseJson (redisCall "GET urn:question:1"))) + +; extract the 2nd tag of question 1 +(:1 (:Tags (parseJson (redisCall "GET urn:question:1")))) + +; clear the Console screen +clear + +; exit the Lisp REPL +quit +``` \ No newline at end of file diff --git a/src/wwwroot/gfm/lisp/04.html b/src/wwwroot/gfm/lisp/04.html new file mode 100644 index 0000000..5adc12c --- /dev/null +++ b/src/wwwroot/gfm/lisp/04.html @@ -0,0 +1,9 @@ +
if (Config.DebugMode)
+{
+    Plugins.Add(new LispReplTcpServer {
+        ScriptMethods = {
+            new DbScriptsAsync()
+        }
+    });
+}
+
\ No newline at end of file diff --git a/src/wwwroot/gfm/lisp/04.md b/src/wwwroot/gfm/lisp/04.md new file mode 100644 index 0000000..964f80e --- /dev/null +++ b/src/wwwroot/gfm/lisp/04.md @@ -0,0 +1,10 @@ +```csharp +if (Config.DebugMode) +{ + Plugins.Add(new LispReplTcpServer { + ScriptMethods = { + new DbScripts() + } + }); +} +``` diff --git a/src/wwwroot/gfm/lisp/05.html b/src/wwwroot/gfm/lisp/05.html new file mode 100644 index 0000000..ae1bd64 --- /dev/null +++ b/src/wwwroot/gfm/lisp/05.html @@ -0,0 +1,14 @@ +
if (Config.DebugMode)
+{
+    Plugins.Add(new LispReplTcpServer {
+        ScriptMethods = {
+            new DbScripts()
+        },
+        ScriptNamespaces = {
+            nameof(TechStacks),
+            $"{nameof(TechStacks)}.{nameof(ServiceInterface)}",
+            $"{nameof(TechStacks)}.{nameof(ServiceModel)}",
+        },
+    });
+}
+
\ No newline at end of file diff --git a/src/wwwroot/gfm/lisp/05.md b/src/wwwroot/gfm/lisp/05.md new file mode 100644 index 0000000..274d924 --- /dev/null +++ b/src/wwwroot/gfm/lisp/05.md @@ -0,0 +1,15 @@ +```csharp +if (Config.DebugMode) +{ + Plugins.Add(new LispReplTcpServer { + ScriptMethods = { + new DbScripts() + }, + ScriptNamespaces = { + nameof(TechStacks), + $"{nameof(TechStacks)}.{nameof(ServiceInterface)}", + $"{nameof(TechStacks)}.{nameof(ServiceModel)}", + }, + }); +} +``` diff --git a/src/wwwroot/gfm/lisp/06.html b/src/wwwroot/gfm/lisp/06.html new file mode 100644 index 0000000..a7b22f1 --- /dev/null +++ b/src/wwwroot/gfm/lisp/06.html @@ -0,0 +1,80 @@ +
; resolve `ITwitterUpdates` IOC dependency and assign it to `twitter`
+(def twitter (resolve "ITwitterUpdates"))
+
+; view its concrete Type Name
+(typeName twitter)
+
+; view its method names 
+(joinln (methods twitter))
+
+; view its method signatures 
+(joinln (methodTypes twitter))
+
+; use it to send tweet from its @webstacks account
+(.Tweet twitter "Who's using #Script Lisp? https://sharpscript.net/lisp")
+
+; view all available scripts in #Script Lisp Library Index gist.github.com/3624b0373904cfb2fc7bb3c2cb9dc1a3
+(gistindex)
+
+; view the source code of the `parse-rss` library
+(load-src "index:parse-rss")
+
+; assign the XML contents of HN's RSS feed to `xml`
+(def xml (urlContents "https://news.ycombinator.com/rss"))
+
+; preview its first 1000 chars
+(subString xml 0 1000)
+
+; use `parse-rss` to parse the RSS feed into a .NET Collection and assign it to `rss`
+(def rss (parse-rss xml))
+
+; view the `title`, `description` and the first `item` in the RSS feed:
+(:title rss)
+(:description rss)
+(:0 (:items rss))
+
+; view the links of all RSS feed items
+(joinln (map :link (:items rss)))
+
+; view the links and titles of the top 5 news items
+(joinln (map :link (take 5 (:items rss))))
+(joinln (map :title (take 5 (:items rss))))
+
+; construct a plain-text numbered list of the top 5 HN Links and assign it to `body`
+(joinln (map-index #(str %2 (:title %1)) (take 5 (:items rss))))
+(joinln (map-index #(str (padLeft (1+ %2) 2) ". " (:title %1)) (take 5 (:items rss))))
+(def body (joinln 
+    (map-index #(str (padLeft (1+ %2) 2) ". " (:title %1) "\n" (:link %1) "\n") (take 5 (:items rss)))))
+
+; view all TechStacks PostgreSQL AWS RDS tables
+(dbTableNames)
+(joinln dbTableNames)
+
+; view the column names and definitions of the `technology` table
+(joinln (dbColumnNames "technology"))
+(joinln (dbColumns "technology"))
+
+; search for all `user` tables
+(globln "*user*" (dbTableNames))
+
+; view how many Admin Users with Emails there are
+(dbScalar "select count(email) from custom_user_auth where roles like '%Admin%'")
+
+; assign the Admin Users email to the `emails` list
+(def emails (map :email (dbSelect "select email from custom_user_auth where roles like '%Admin%'")))
+
+; search for all `operation` script methods
+(globln "*operation*" scriptMethods)
+
+; search for all `email` Request DTOs
+(globln "*email*" metaAllOperationNames)
+
+; view the properties available on the `SendEmail` Request DTO
+(props (SendEmail.))
+
+; search for all `publish` script methods that can publish messages
+(globln "publish*" scriptMethods)
+
+; create and publish 5x `SendEmail` Request DTOs for processing by TechStacks configured MQ Server
+(doseq (to emails) (publishMessage "SendEmail" { :To to :Subject "Top 5 HN Links" :Body body }))
+
\ No newline at end of file diff --git a/src/wwwroot/gfm/lisp/06.md b/src/wwwroot/gfm/lisp/06.md new file mode 100644 index 0000000..5d6c687 --- /dev/null +++ b/src/wwwroot/gfm/lisp/06.md @@ -0,0 +1,81 @@ +```lisp +; resolve `ITwitterUpdates` IOC dependency and assign it to `twitter` +(def twitter (resolve "ITwitterUpdates")) + +; view its concrete Type Name +(typeName twitter) + +; view its method names +(joinln (methods twitter)) + +; view its method signatures +(joinln (methodTypes twitter)) + +; use it to send tweet from its @webstacks account +(.Tweet twitter "Who's using #Script Lisp? https://sharpscript.net/lisp") + +; view all available scripts in #Script Lisp Library Index gist.github.com/3624b0373904cfb2fc7bb3c2cb9dc1a3 +(gistindex) + +; view the source code of the `parse-rss` library +(load-src "index:parse-rss") + +; assign the XML contents of HN's RSS feed to `xml` +(def xml (urlContents "https://news.ycombinator.com/rss")) + +; preview its first 1000 chars +(subString xml 0 1000) + +; use `parse-rss` to parse the RSS feed into a .NET Collection and assign it to `rss` +(def rss (parse-rss xml)) + +; view the `title`, `description` and the first `item` in the RSS feed: +(:title rss) +(:description rss) +(:0 (:items rss)) + +; view the links of all RSS feed items +(joinln (map :link (:items rss))) + +; view the links and titles of the top 5 news items +(joinln (map :link (take 5 (:items rss)))) +(joinln (map :title (take 5 (:items rss)))) + +; construct a plain-text numbered list of the top 5 HN Links and assign it to `body` +(joinln (map-index #(str %2 (:title %1)) (take 5 (:items rss)))) +(joinln (map-index #(str (padLeft (1+ %2) 2) ". " (:title %1)) (take 5 (:items rss)))) +(def body (joinln + (map-index #(str (padLeft (1+ %2) 2) ". " (:title %1) "\n" (:link %1) "\n") (take 5 (:items rss))))) + +; view all TechStacks PostgreSQL AWS RDS tables +(dbTableNames) +(joinln dbTableNames) + +; view the column names and definitions of the `technology` table +(joinln (dbColumnNames "technology")) +(joinln (dbColumns "technology")) + +; search for all `user` tables +(globln "*user*" (dbTableNames)) + +; view how many Admin Users with Emails there are +(dbScalar "select count(email) from custom_user_auth where roles like '%Admin%'") + +; assign the Admin Users email to the `emails` list +(def emails (map :email (dbSelect "select email from custom_user_auth where roles like '%Admin%'"))) + +; search for all `operation` script methods +(globln "*operation*" scriptMethods) + +; search for all `email` Request DTOs +(globln "*email*" metaAllOperationNames) + +; view the properties available on the `SendEmail` Request DTO +(props (SendEmail.)) + +; search for all `publish` script methods that can publish messages +(globln "publish*" scriptMethods) + +; create and publish 5x `SendEmail` Request DTOs for processing by TechStacks configured MQ Server +(doseq (to emails) (publishMessage "SendEmail" { :To to :Subject "Top 5 HN Links" :Body body })) +``` \ No newline at end of file diff --git a/src/wwwroot/gfm/lisp/07.html b/src/wwwroot/gfm/lisp/07.html new file mode 100644 index 0000000..34c4e72 --- /dev/null +++ b/src/wwwroot/gfm/lisp/07.html @@ -0,0 +1,27 @@ +

To render lisp you'll first need to register the Lisp Language with the ScriptContext you're using:

+
var context = new ScriptContext {
+    ScriptLanguages = { ScriptLisp.Language }
+}.Init();
+

Then use RenderLisp (i.e. instead of RenderScript) to render Lisp code, e.g:

+
// render lisp
+var output = context.RenderLisp("(dateFormat now \"HH:mm:ss\")"); 
+
+// async
+var output = await context.RenderLispAsync("(dateFormat now \"HH:mm:ss\")"); 
+

These APIs match the high-level APIs for rendering normal #Script:

+
var output = context.RenderScript("{{ now | dateFormat('HH:mm:ss') }}"); 
+var output = await context.RenderScriptAsync("{{ now | dateFormat('HH:mm:ss') }}"); 
+

+Finer grained control

+

The high-level APIs above wraps the finer-grained functionality below which works by rendering a SharpPage configured with the lisp +language in a PageResult that all languages use:

+
var context = new ScriptContext {
+    ScriptLanguages = { ScriptLisp.Language }
+}.Init();
+var dynamicPage = context.LispSharpPage("(dateFormat now \"HH:mm:ss\")");          // render lisp
+//var dynamicPage = context.SharpScriptPage("{{ now | dateFormat('HH:mm:ss') }}"); // render #Script
+var output = new PageResult(dynamicPage).RenderScript();
+
+//async
+var output = await new PageResult(dynamicPage).RenderScriptAsync();
+
\ No newline at end of file diff --git a/src/wwwroot/gfm/lisp/07.md b/src/wwwroot/gfm/lisp/07.md new file mode 100644 index 0000000..0651fa2 --- /dev/null +++ b/src/wwwroot/gfm/lisp/07.md @@ -0,0 +1,41 @@ +To render `lisp` you'll first need to register the Lisp Language with the `ScriptContext` you're using: + +```csharp +var context = new ScriptContext { + ScriptLanguages = { ScriptLisp.Language } +}.Init(); +``` + +Then use `RenderLisp` (i.e. instead of `RenderScript`) to render Lisp code, e.g: + +```csharp +// render lisp +var output = context.RenderLisp("(dateFormat now \"HH:mm:ss\")"); + +// async +var output = await context.RenderLispAsync("(dateFormat now \"HH:mm:ss\")"); +``` + +These APIs match the high-level APIs for rendering normal `#Script`: + +```csharp +var output = context.RenderScript("{{ now | dateFormat('HH:mm:ss') }}"); +var output = await context.RenderScriptAsync("{{ now | dateFormat('HH:mm:ss') }}"); +``` + +### Finer grained control + +The high-level APIs above wraps the finer-grained functionality below which works by rendering a `SharpPage` configured with the `lisp` +language in a `PageResult` that all languages use: + +```csharp +var context = new ScriptContext { + ScriptLanguages = { ScriptLisp.Language } +}.Init(); +var dynamicPage = context.LispSharpPage("(dateFormat now \"HH:mm:ss\")"); // render lisp +//var dynamicPage = context.SharpScriptPage("{{ now | dateFormat('HH:mm:ss') }}"); // render #Script +var output = new PageResult(dynamicPage).RenderScript(); + +//async +var output = await new PageResult(dynamicPage).RenderScriptAsync(); +``` diff --git a/src/wwwroot/gfm/lisp/08.html b/src/wwwroot/gfm/lisp/08.html new file mode 100644 index 0000000..4b688ea --- /dev/null +++ b/src/wwwroot/gfm/lisp/08.html @@ -0,0 +1,12 @@ +
var result = context.EvaluateLisp("(return (+ 1 1))"); //= 2
+

The generic overloads below utilizes ServiceStack's Auto Mapping utils +to convert the return value into your preferred type, e.g:

+
double result = context.EvaluateLisp<double>("(return (+ 1 1))"); //= 2.0
+string result = context.EvaluateLisp<string>("(return (+ 1 1))"); //= "2"
+

Which can also be used for more powerful conversions like converting an Object Dictionary into your preferred POCO:

+
var result = context.EvaluateLisp<Customer>(
+    "(return (dbSingle \"select * from customer where id=@id\" { :id id }))",
+    new ObjectDictionary {
+        ["id"] = 1
+    });
+
\ No newline at end of file diff --git a/src/wwwroot/gfm/lisp/08.md b/src/wwwroot/gfm/lisp/08.md new file mode 100644 index 0000000..c71b8ae --- /dev/null +++ b/src/wwwroot/gfm/lisp/08.md @@ -0,0 +1,21 @@ +```csharp +var result = context.EvaluateLisp("(return (+ 1 1))"); //= 2 +``` + +The generic overloads below utilizes ServiceStack's [Auto Mapping utils](https://docs.servicestack.net/auto-mapping) +to convert the return value into your preferred type, e.g: + +```csharp +double result = context.EvaluateLisp("(return (+ 1 1))"); //= 2.0 +string result = context.EvaluateLisp("(return (+ 1 1))"); //= "2" +``` + +Which can also be used for more powerful conversions like converting an Object Dictionary into your preferred POCO: + +```csharp +var result = context.EvaluateLisp( + "(return (dbSingle \"select * from customer where id=@id\" { :id id }))", + new ObjectDictionary { + ["id"] = 1 + }); +``` \ No newline at end of file diff --git a/src/wwwroot/gfm/lisp/09.html b/src/wwwroot/gfm/lisp/09.html new file mode 100644 index 0000000..5f24c00 --- /dev/null +++ b/src/wwwroot/gfm/lisp/09.html @@ -0,0 +1,79 @@ +

To see what this looks like in action here's an annotated simple real-world example that heavily utilizes .NET interop:

+
; define function and assign to `parse-rss` value in Lisp interpreters symbols table
+(defn parse-rss [xml]
+    ; define local variables used within this scope
+    (let ( (to) (doc) (channel) (items) (el) )
+        ; use XDocument.Parse() to parse xml string argument containing xml and assign to `doc`
+        (def doc (System.Xml.Linq.XDocument/Parse xml))
+        ; create empty ObjectDictionary (wrapper for Dictionary<string,object>) and assign to `to`
+        (def to  (ObjectDictionary.))
+        ; create empty List of ObjectDictionary and assign to `items`
+        (def items (List<ObjectDictionary>.))
+        ; descend into first <channel> XML element and assign to `channel`
+        (def channel (first (.Descendants doc "channel")))
+        ; use `XLinqExtensions.FirstElement()` extension method to assign channels first XML element to `el`
+        (def el  (XLinqExtensions/FirstElement channel))
+
+        ; iterate through all elements up to the first <item> and add them as top-level entries in `to`
+        (while (not= (.LocalName (.Name el)) "item")
+            ; add current XML element name and value entry to `to`
+            (.Add to (.LocalName (.Name el)) (.Value el))
+            ; move to next element using `XLinqExtensions.NextElement()` extension method
+            (def el (XLinqExtensions/NextElement el)))
+
+        ; add all rss <item>'s to `items` list
+        ; iterate through all `channel` child <item> XML elements
+        (doseq (elItem (.Descendants channel "item"))
+            ; create empty ObjectDictionary and assign to `item`
+            (def item (ObjectDictionary.))
+            
+            ; use `XLinqExtensions.FirstElement()` to assign <item> first XML element to `el`
+            (def el (XLinqExtensions/FirstElement elItem))
+            (while el
+                ; add current XML element name and value entry to `item`
+                (.Add item (.LocalName (.Name el)) (.Value el))
+                ; move to next element using `XLinqExtensions.NextElement()` extension method
+                (def el (XLinqExtensions/NextElement el)))
+
+            ; add `item` ObjectDictionary to `items` List
+            (.Add items item))
+
+        ; add `items` ObjectDictionary List to `to` at key `items`
+        (.Add to "items" items)
+        ; return `to` ObjectDictionary
+        to
+    )
+)
+

For comparison, this would be the equivalent implementation in C#:

+
public static ObjectDictionary ParseRss(string xml)
+{
+    var to = new ObjectDictionary();
+    var items = new List<ObjectDictionary>();
+
+    var doc = XDocument.Parse(xml);
+    var channel = doc.Descendants("channel").First();
+    var el = channel.FirstElement();
+    while (el.Name != "item")
+    {
+        to[el.Name.LocalName] = el.Value;
+        el = el.NextElement();
+    }
+
+    var elItems = channel.Descendants("item");
+    foreach (var elItem in elItems)
+    {
+        var item = new ObjectDictionary();
+        el = elItem.FirstElement();
+        while (el != null)
+        {
+            item[el.Name.LocalName] = el.Value;
+            el = el.NextElement();
+        }
+
+        items.Add(item);
+    }
+
+    to["items"] = items;
+    return to;
+}
+
\ No newline at end of file diff --git a/src/wwwroot/gfm/lisp/09.md b/src/wwwroot/gfm/lisp/09.md new file mode 100644 index 0000000..d192802 --- /dev/null +++ b/src/wwwroot/gfm/lisp/09.md @@ -0,0 +1,85 @@ +To see what this looks like in action here's an annotated simple real-world example that heavily utilizes .NET interop: + +```lisp +; define function and assign to `parse-rss` value in Lisp interpreters symbols table +(defn parse-rss [xml] + ; define local variables used within this scope + (let ( (to) (doc) (channel) (items) (el) ) + ; use XDocument.Parse() to parse xml string argument containing xml and assign to `doc` + (def doc (System.Xml.Linq.XDocument/Parse xml)) + ; create empty ObjectDictionary (wrapper for Dictionary) and assign to `to` + (def to (ObjectDictionary.)) + ; create empty List of ObjectDictionary and assign to `items` + (def items (List.)) + ; descend into first XML element and assign to `channel` + (def channel (first (.Descendants doc "channel"))) + ; use `XLinqExtensions.FirstElement()` extension method to assign channels first XML element to `el` + (def el (XLinqExtensions/FirstElement channel)) + + ; iterate through all elements up to the first and add them as top-level entries in `to` + (while (not= (.LocalName (.Name el)) "item") + ; add current XML element name and value entry to `to` + (.Add to (.LocalName (.Name el)) (.Value el)) + ; move to next element using `XLinqExtensions.NextElement()` extension method + (def el (XLinqExtensions/NextElement el))) + + ; add all rss 's to `items` list + ; iterate through all `channel` child XML elements + (doseq (elItem (.Descendants channel "item")) + ; create empty ObjectDictionary and assign to `item` + (def item (ObjectDictionary.)) + + ; use `XLinqExtensions.FirstElement()` to assign first XML element to `el` + (def el (XLinqExtensions/FirstElement elItem)) + (while el + ; add current XML element name and value entry to `item` + (.Add item (.LocalName (.Name el)) (.Value el)) + ; move to next element using `XLinqExtensions.NextElement()` extension method + (def el (XLinqExtensions/NextElement el))) + + ; add `item` ObjectDictionary to `items` List + (.Add items item)) + + ; add `items` ObjectDictionary List to `to` at key `items` + (.Add to "items" items) + ; return `to` ObjectDictionary + to + ) +) +``` + +For comparison, this would be the equivalent implementation in C#: + +```csharp +public static ObjectDictionary ParseRss(string xml) +{ + var to = new ObjectDictionary(); + var items = new List(); + + var doc = XDocument.Parse(xml); + var channel = doc.Descendants("channel").First(); + var el = channel.FirstElement(); + while (el.Name != "item") + { + to[el.Name.LocalName] = el.Value; + el = el.NextElement(); + } + + var elItems = channel.Descendants("item"); + foreach (var elItem in elItems) + { + var item = new ObjectDictionary(); + el = elItem.FirstElement(); + while (el != null) + { + item[el.Name.LocalName] = el.Value; + el = el.NextElement(); + } + + items.Add(item); + } + + to["items"] = items; + return to; +} +``` \ No newline at end of file diff --git a/src/wwwroot/gfm/lisp/10.html b/src/wwwroot/gfm/lisp/10.html new file mode 100644 index 0000000..03cdca8 --- /dev/null +++ b/src/wwwroot/gfm/lisp/10.html @@ -0,0 +1,7 @@ +
Lisp.Import(@"
+(defun fib (n)
+    (if (< n 2)
+        1
+        (+ (fib (- n 1))
+        (fib (- n 2)) )))");
+
\ No newline at end of file diff --git a/src/wwwroot/gfm/lisp/10.md b/src/wwwroot/gfm/lisp/10.md new file mode 100644 index 0000000..6c6d173 --- /dev/null +++ b/src/wwwroot/gfm/lisp/10.md @@ -0,0 +1,9 @@ + +```csharp +Lisp.Import(@" +(defun fib (n) + (if (< n 2) + 1 + (+ (fib (- n 1)) + (fib (- n 2)) )))"); +``` \ No newline at end of file diff --git a/src/wwwroot/gfm/lisp/11.html b/src/wwwroot/gfm/lisp/11.html new file mode 100644 index 0000000..e853e78 --- /dev/null +++ b/src/wwwroot/gfm/lisp/11.html @@ -0,0 +1,64 @@ +
var context = new ScriptContext {
+    ScriptLanguages = { ScriptLisp.Language },
+    ScriptMethods = { new ProtectedScripts() },
+};
+context.VirtualFiles.WriteFile("lib1.l", "(defn lib-calc [a b] (+ a b))");
+context.VirtualFiles.WriteFile("/dir/lib2.l", "(defn lib-calc [a b] (* a b))");
+context.Init();
+

You can load these scripts by symbol name where it assumes a .l extension, by quoting the argument so +Lisp doesn't try to evaluate it as an argument, e.g:

+
(load 'lib1)
+
+(lib-calc 4 5) ;= 9
+

Alternatively you can specify the virtual path to the script. You can load multiple scripts with the same definitions, +in Lisp this just updates the value assigned to the symbol name with the latest definition, e.g:

+
(load "lib1.l")
+
+(lib-calc 4 5) ;= 9
+
+(load "/dir/lib2.l")
+
+(lib-calc 4 5) ;= 20
+

+Import Scripts from URLs

+

Inspired by Deno you can also import remote scripts from URLs, e.g:

+
(load "https://example.org/lib.l")
+

+Locally Cached

+

Like Deno all remote resources are cached after first use so after it's loaded once it only loads the locally cached +version of the script (where it will still work in an airplane without an internet connection). This cache is maintained +under a .lisp folder at your configured Virtual Files provider (that can be deleted to clear any caches).

+

For Sharp Scripts or Apps using the web or app dotnet tools it's stored in its own cache folder that can be cleared with:

+
$ web --clean
+
+

+Import Scripts from Gists

+

There's also first-class support for gists which you can reference with gist:<gist-id>, e.g:

+
(load "gist:2f14d629ba1852ee55865607f1fa2c3e")
+

This will load all gist files in gist order, if you only to load a single file from a gist you can specify it with:

+
(load "gist:2f14d629ba1852ee55865607f1fa2c3e/lib1.l")
+

+Script Lisp Library Index

+

To provide human readable names to remote Lisp Scripts and a discoverable catalog where anyone can share their own scripts, +you reference gists by name listed in the #Script Lisp Library Index +which is itself a self-documenting machine and human readable gist of named links to external gists maintained by their +respective authors.

+

Index library references can be loaded using the format index:<name>, e.g:

+
(load "index:lib-calc")
+

Which also support being able to reference individual gist files:

+
(load "index:lib-calc/lib1.l")
+

If you'd like to share your own Lisp Scripts with everyone and publish your library to the index, just add a link +to your gist with your preferred name in the Gist Index Comments.

+

+Viewing Script Source Code

+

You can view the source code of any load script references with load-src, e.g:

+
(load-src 'lib)
+(load-src "/dir/lib2.l")
+(load-src "https://example.org/lib.l")
+(load-src "gist:2f14d629ba1852ee55865607f1fa2c3e/lib1.l")
+(load-src "index:lib-calc")
+

+Disable Remote Imports

+

Should you wish, you can prevent anyone from loading remote scripts with:

+
Lisp.AllowLoadingRemoteScripts = false;
+
\ No newline at end of file diff --git a/src/wwwroot/gfm/lisp/11.md b/src/wwwroot/gfm/lisp/11.md new file mode 100644 index 0000000..4986ea3 --- /dev/null +++ b/src/wwwroot/gfm/lisp/11.md @@ -0,0 +1,105 @@ +```csharp +var context = new ScriptContext { + ScriptLanguages = { ScriptLisp.Language }, + ScriptMethods = { new ProtectedScripts() }, +}; +context.VirtualFiles.WriteFile("lib1.l", "(defn lib-calc [a b] (+ a b))"); +context.VirtualFiles.WriteFile("/dir/lib2.l", "(defn lib-calc [a b] (* a b))"); +context.Init(); +``` + +You can load these scripts by symbol name where it assumes a `.l` extension, by quoting the argument so +Lisp doesn't try to evaluate it as an argument, e.g: + +```lisp +(load 'lib1) + +(lib-calc 4 5) ;= 9 +``` + +Alternatively you can specify the virtual path to the script. You can load multiple scripts with the same definitions, +in Lisp this just updates the value assigned to the symbol name with the latest definition, e.g: + +```lisp +(load "lib1.l") + +(lib-calc 4 5) ;= 9 + +(load "/dir/lib2.l") + +(lib-calc 4 5) ;= 20 +``` + +### Import Scripts from URLs + +Inspired by [Deno](https://deno.land) you can also import remote scripts from URLs, e.g: + +```lisp +(load "https://example.org/lib.l") +``` + +### Locally Cached + +Like [Deno](https://deno.land/manual.html) all remote resources are cached after first use so after it's loaded once it only loads the locally cached +version of the script (where it will still work in an airplane without an internet connection). This cache is maintained +under a `.lisp` folder at your configured Virtual Files provider (that can be deleted to clear any caches). + +For Sharp Scripts or Apps using the `web` or `app` dotnet tools it's stored in its own cache folder that can be cleared with: + + $ web --clean + +### Import Scripts from Gists + +There's also first-class support for gists which you can reference with `gist:`, e.g: + +```lisp +(load "gist:2f14d629ba1852ee55865607f1fa2c3e") +``` + +This will load all gist files in gist order, if you only to load a single file from a gist you can specify it with: + +```lisp +(load "gist:2f14d629ba1852ee55865607f1fa2c3e/lib1.l") +``` + +### Script Lisp Library Index + +To provide human readable names to remote Lisp Scripts and a discoverable catalog where anyone can share their own scripts, +you reference gists by name listed in the [#Script Lisp Library Index](https://gist.github.com/gistlyn/3624b0373904cfb2fc7bb3c2cb9dc1a3) +which is itself a self-documenting machine and human readable gist of named links to external gists maintained by their +respective authors. + +Index library references can be loaded using the format `index:`, e.g: + +```lisp +(load "index:lib-calc") +``` + +Which also support being able to reference individual gist files: + +```lisp +(load "index:lib-calc/lib1.l") +``` + +If you'd like to share your own Lisp Scripts with everyone and publish your library to the index, just add a link +to your gist with your preferred name in the [Gist Index Comments](https://gist.github.com/gistlyn/3624b0373904cfb2fc7bb3c2cb9dc1a3). + +### Viewing Script Source Code + +You can view the source code of any load script references with `load-src`, e.g: + +```lisp +(load-src 'lib) +(load-src "/dir/lib2.l") +(load-src "https://example.org/lib.l") +(load-src "gist:2f14d629ba1852ee55865607f1fa2c3e/lib1.l") +(load-src "index:lib-calc") +``` + +### Disable Remote Imports + +Should you wish, you can prevent anyone from loading remote scripts with: + +```csharp +Lisp.AllowLoadingRemoteScripts = false; +``` diff --git a/src/wwwroot/gfm/methods/05.html b/src/wwwroot/gfm/methods/05.html index 3285363..5e7b459 100644 --- a/src/wwwroot/gfm/methods/05.html +++ b/src/wwwroot/gfm/methods/05.html @@ -1,2 +1,2 @@ -
var output = context.EvaluateScript("<p>{{ 'contextArg' | greetArg }}</p>");
+
var output = context.RenderScript("<p>{{ 'contextArg' | greetArg }}</p>");
\ No newline at end of file diff --git a/src/wwwroot/gfm/methods/05.md b/src/wwwroot/gfm/methods/05.md index 1bb8001..5cb7ad0 100644 --- a/src/wwwroot/gfm/methods/05.md +++ b/src/wwwroot/gfm/methods/05.md @@ -1,3 +1,3 @@ ```csharp -var output = context.EvaluateScript("

{{ 'contextArg' | greetArg }}

"); +var output = context.RenderScript("

{{ 'contextArg' | greetArg }}

"); ``` diff --git a/src/wwwroot/gfm/methods/07.html b/src/wwwroot/gfm/methods/07.html index 9a07d82..e60f2eb 100644 --- a/src/wwwroot/gfm/methods/07.html +++ b/src/wwwroot/gfm/methods/07.html @@ -12,5 +12,5 @@ context.Container.Resolve<ICacheClient>().Set("key", "foo"); context.Init(); -var output = context.EvaluateScript("<p>{{ 'key' | fromCache }}</p>");
+var output = context.RenderScript("<p>{{ 'key' | fromCache }}</p>");
\ No newline at end of file diff --git a/src/wwwroot/gfm/methods/07.md b/src/wwwroot/gfm/methods/07.md index b1b285f..9497e7c 100644 --- a/src/wwwroot/gfm/methods/07.md +++ b/src/wwwroot/gfm/methods/07.md @@ -13,5 +13,5 @@ context.Container.AddSingleton(() => new MemoryCacheClient()); context.Container.Resolve().Set("key", "foo"); context.Init(); -var output = context.EvaluateScript("

{{ 'key' | fromCache }}

"); +var output = context.RenderScript("

{{ 'key' | fromCache }}

"); ``` diff --git a/src/wwwroot/gfm/scode/01.html b/src/wwwroot/gfm/scode/01.html new file mode 100644 index 0000000..cf5a4b6 --- /dev/null +++ b/src/wwwroot/gfm/scode/01.html @@ -0,0 +1,5 @@ +
{{ products
+    | groupBy => it.Category
+    | map => { Category: it.Key, MostExpensivePrice: it.max(p => p.UnitPrice) }
+    | htmlDump }}
+
\ No newline at end of file diff --git a/src/wwwroot/gfm/scode/01.md b/src/wwwroot/gfm/scode/01.md new file mode 100644 index 0000000..ef33901 --- /dev/null +++ b/src/wwwroot/gfm/scode/01.md @@ -0,0 +1,6 @@ +```hbs +{{ products + | groupBy => it.Category + | map => { Category: it.Key, MostExpensivePrice: it.max(p => p.UnitPrice) } + | htmlDump }} +``` diff --git a/src/wwwroot/gfm/scode/02.html b/src/wwwroot/gfm/scode/02.html new file mode 100644 index 0000000..7b043aa --- /dev/null +++ b/src/wwwroot/gfm/scode/02.html @@ -0,0 +1,13 @@ +
{{#if test.isEven() }}
+{{test}} is even
+{{else}}
+{{test}} is odd
+{{/if}}
+
<script>
+#if test.isEven()
+    `${test} is even`
+else
+    `${test} is odd`
+/if
+</script>
+
\ No newline at end of file diff --git a/src/wwwroot/gfm/scode/02.md b/src/wwwroot/gfm/scode/02.md new file mode 100644 index 0000000..5dc81bb --- /dev/null +++ b/src/wwwroot/gfm/scode/02.md @@ -0,0 +1,19 @@ +```hbs +{{#if test.isEven() }} +{{test}} is even +{{else}} +{{test}} is odd +{{/if}} +``` + + + +```html + +``` diff --git a/src/wwwroot/gfm/scode/03.html b/src/wwwroot/gfm/scode/03.html new file mode 100644 index 0000000..1eae115 --- /dev/null +++ b/src/wwwroot/gfm/scode/03.html @@ -0,0 +1,20 @@ +
// render code statements
+var output = context.RenderCode("now | dateFormat('HH:mm:ss')"); 
+
+// async
+var output = await context.RenderCodeAsync("now | dateFormat('HH:mm:ss')"); 
+

These APIs match the high-level APIs for rendering normal #Script:

+
var output = context.RenderScript("{{ now | dateFormat('HH:mm:ss') }}"); 
+var output = await context.RenderScriptAsync("{{ now | dateFormat('HH:mm:ss') }}"); 
+

+Finer grained control

+

The high-level APIs above wraps the finer-grained functionality below which works by rendering a SharpPage configured with the code +language in a PageResult that all languages use:

+
var context = new ScriptContext().Init();
+var dynamicPage = context.CodeSharpPage("now | dateFormat('HH:mm:ss')");           // render code
+//var dynamicPage = context.SharpScriptPage("{{ now | dateFormat('HH:mm:ss') }}"); // render #Script
+var output = new PageResult(dynamicPage).RenderScript();
+
+//async
+var output = await new PageResult(dynamicPage).RenderScriptAsync();
+
\ No newline at end of file diff --git a/src/wwwroot/gfm/scode/03.md b/src/wwwroot/gfm/scode/03.md new file mode 100644 index 0000000..e6743df --- /dev/null +++ b/src/wwwroot/gfm/scode/03.md @@ -0,0 +1,29 @@ +```csharp +// render code statements +var output = context.RenderCode("now | dateFormat('HH:mm:ss')"); + +// async +var output = await context.RenderCodeAsync("now | dateFormat('HH:mm:ss')"); +``` + +These APIs match the high-level APIs for rendering normal `#Script`: + +```csharp +var output = context.RenderScript("{{ now | dateFormat('HH:mm:ss') }}"); +var output = await context.RenderScriptAsync("{{ now | dateFormat('HH:mm:ss') }}"); +``` + +### Finer grained control + +The high-level APIs above wraps the finer-grained functionality below which works by rendering a `SharpPage` configured with the `code` +language in a `PageResult` that all languages use: + +```csharp +var context = new ScriptContext().Init(); +var dynamicPage = context.CodeSharpPage("now | dateFormat('HH:mm:ss')"); // render code +//var dynamicPage = context.SharpScriptPage("{{ now | dateFormat('HH:mm:ss') }}"); // render #Script +var output = new PageResult(dynamicPage).RenderScript(); + +//async +var output = await new PageResult(dynamicPage).RenderScriptAsync(); +``` diff --git a/src/wwwroot/gfm/scode/04.html b/src/wwwroot/gfm/scode/04.html new file mode 100644 index 0000000..5abf9ed --- /dev/null +++ b/src/wwwroot/gfm/scode/04.html @@ -0,0 +1,11 @@ +
var result = context.EvaluateCode("return ( 1 + 1 )"); //= 2
+

The generic overloads below utilizes ServiceStack's Auto Mapping utils +to convert the return value into your preferred type, e.g:

+
double result = context.EvaluateCode<double>("return (1 + 1)"); //= 2.0
+string result = context.EvaluateCode<string>("return (1 + 1)"); //= "2"
+

Which can also be used for more powerful conversions like converting an Object Dictionary into your preferred POCO:

+
var result = context.EvaluateCode<Customer>("`select * from customer where id=@id` | dbSingle({id}) | return",
+    new ObjectDictionary {
+        ["id"] = 1
+    });
+
\ No newline at end of file diff --git a/src/wwwroot/gfm/scode/04.md b/src/wwwroot/gfm/scode/04.md new file mode 100644 index 0000000..82401ac --- /dev/null +++ b/src/wwwroot/gfm/scode/04.md @@ -0,0 +1,20 @@ +```csharp +var result = context.EvaluateCode("return ( 1 + 1 )"); //= 2 +``` + +The generic overloads below utilizes ServiceStack's [Auto Mapping utils](https://docs.servicestack.net/auto-mapping) +to convert the return value into your preferred type, e.g: + +```csharp +double result = context.EvaluateCode("return (1 + 1)"); //= 2.0 +string result = context.EvaluateCode("return (1 + 1)"); //= "2" +``` + +Which can also be used for more powerful conversions like converting an Object Dictionary into your preferred POCO: + +```csharp +var result = context.EvaluateCode("`select * from customer where id=@id` | dbSingle({id}) | return", + new ObjectDictionary { + ["id"] = 1 + }); +``` \ No newline at end of file diff --git a/src/wwwroot/gfm/servicestack-scripts/09.html b/src/wwwroot/gfm/servicestack-scripts/09.html new file mode 100644 index 0000000..b14e033 --- /dev/null +++ b/src/wwwroot/gfm/servicestack-scripts/09.html @@ -0,0 +1,2 @@ +
{{ 'ProcessOrder'.publishMessage({ orderId }) }}
+
\ No newline at end of file diff --git a/src/wwwroot/gfm/servicestack-scripts/09.md b/src/wwwroot/gfm/servicestack-scripts/09.md new file mode 100644 index 0000000..a5ee8ae --- /dev/null +++ b/src/wwwroot/gfm/servicestack-scripts/09.md @@ -0,0 +1,3 @@ +```hbs +{{ 'ProcessOrder'.publishMessage({ orderId }) }} +``` \ No newline at end of file diff --git a/src/wwwroot/gfm/syntax/05.html b/src/wwwroot/gfm/syntax/05.html index 0532baf..2516383 100644 --- a/src/wwwroot/gfm/syntax/05.html +++ b/src/wwwroot/gfm/syntax/05.html @@ -4,7 +4,7 @@ } Basic Calc a + b = @(a + b) -

The equivalent in #Script using code blocks:

+

The equivalent in #Script using code language blocks:

<script>
 1 | to => a
 2 | to => b
diff --git a/src/wwwroot/gfm/syntax/05.md b/src/wwwroot/gfm/syntax/05.md
index cee41c0..c986a59 100644
--- a/src/wwwroot/gfm/syntax/05.md
+++ b/src/wwwroot/gfm/syntax/05.md
@@ -7,7 +7,7 @@ Basic Calc
 a + b = @(a + b)
 ```
 
-The equivalent in `#Script` using code blocks:
+The equivalent in `#Script` using `code` language blocks:
 
 ```html
 
+
+Code result: {{ result }}
+```
diff --git a/src/wwwroot/gfm/syntax/08.html b/src/wwwroot/gfm/syntax/08.html
new file mode 100644
index 0000000..379ce31
--- /dev/null
+++ b/src/wwwroot/gfm/syntax/08.html
@@ -0,0 +1,9 @@
+
{{ 1 | to => a}}
+
+<script>
+(def local-arg (+ a 2))
+(export result local-arg)
+</script>
+
+Lisp result: {{ result }}
+
\ No newline at end of file diff --git a/src/wwwroot/gfm/syntax/08.md b/src/wwwroot/gfm/syntax/08.md new file mode 100644 index 0000000..bfe90ac --- /dev/null +++ b/src/wwwroot/gfm/syntax/08.md @@ -0,0 +1,10 @@ +```html +{{ 1 | to => a}} + + + +Lisp result: {{ result }} +``` diff --git a/src/wwwroot/gfm/syntax/09.html b/src/wwwroot/gfm/syntax/09.html new file mode 100644 index 0000000..985edeb --- /dev/null +++ b/src/wwwroot/gfm/syntax/09.html @@ -0,0 +1,9 @@ +
{{ 1 | to => a}}
+
+<script>|q
+(setq local-arg (+ a 2))
+(export result local-arg)
+</script>
+
+Lisp result: {{ result }}
+
\ No newline at end of file diff --git a/src/wwwroot/gfm/syntax/09.md b/src/wwwroot/gfm/syntax/09.md new file mode 100644 index 0000000..d6d42e1 --- /dev/null +++ b/src/wwwroot/gfm/syntax/09.md @@ -0,0 +1,10 @@ +```html +{{ 1 | to => a}} + + + +Lisp result: {{ result }} +``` diff --git a/src/wwwroot/gfm/syntax/10.html b/src/wwwroot/gfm/syntax/10.html new file mode 100644 index 0000000..286cd84 --- /dev/null +++ b/src/wwwroot/gfm/syntax/10.html @@ -0,0 +1,4 @@ +
{{ 1 | to => a }}
+
+Lisp result: {|lisp (+ a 2) |}
+
\ No newline at end of file diff --git a/src/wwwroot/gfm/syntax/10.md b/src/wwwroot/gfm/syntax/10.md new file mode 100644 index 0000000..9292dc0 --- /dev/null +++ b/src/wwwroot/gfm/syntax/10.md @@ -0,0 +1,5 @@ +```hbs +{{ 1 | to => a }} + +Lisp result: {|lisp (+ a 2) |} +``` diff --git a/src/wwwroot/index.html b/src/wwwroot/index.html index 00ff63f..1122f9b 100644 --- a/src/wwwroot/index.html +++ b/src/wwwroot/index.html @@ -234,10 +234,11 @@

Learn #Script

{{ "linq-preview" | partial({ rows: 7, example: "linq01" }) }}

- Alternative, rewritten using Code Blocks: + Or can be written using different languages within the same #Script using + Language Blocks and Expressions:

-{{ "linq-preview" | partial({ rows: 7, example: "linq01-code" }) }} +{{ "linq-preview" | partial({ rows: 11, example: "linq01-langs", lang:"template" }) }}
Language
diff --git a/src/wwwroot/lang-select.html b/src/wwwroot/lang-select.html new file mode 100644 index 0000000..bc9c1cc --- /dev/null +++ b/src/wwwroot/lang-select.html @@ -0,0 +1,19 @@ +
+
+
+
languages
+
+
+ + + +
+
+
+
diff --git a/src/wwwroot/linq-links.html b/src/wwwroot/linq-links.html index 3c158aa..001bfdd 100644 --- a/src/wwwroot/linq-links.html +++ b/src/wwwroot/linq-links.html @@ -1,7 +1,13 @@ \ No newline at end of file diff --git a/src/wwwroot/linq-preview.html b/src/wwwroot/linq-preview.html index 0677549..348fe19 100644 --- a/src/wwwroot/linq-preview.html +++ b/src/wwwroot/linq-preview.html @@ -1,7 +1,10 @@ -
+{{ (lang ?? qs.lang) | to => lang }} +{{ (lang == 'code' ? 'sc' : lang == 'lisp' ? 'l' : 'ss') | to => ext }} + +
- +
diff --git a/src/wwwroot/linq/Miscellaneous-operators.html b/src/wwwroot/linq/Miscellaneous-operators.html index e1d6fb6..665b3f6 100644 --- a/src/wwwroot/linq/Miscellaneous-operators.html +++ b/src/wwwroot/linq/Miscellaneous-operators.html @@ -3,6 +3,8 @@ order: 12 --> +{{ "lang-select" | partial }} +

linq94: Concat - 1

//c#
diff --git a/src/wwwroot/linq/aggregate-operators.html b/src/wwwroot/linq/aggregate-operators.html
index 285c317..4152189 100644
--- a/src/wwwroot/linq/aggregate-operators.html
+++ b/src/wwwroot/linq/aggregate-operators.html
@@ -3,6 +3,8 @@
 order: 11
 -->
 
+{{ "lang-select" | partial }}
+
 

linq73: Count - Simple

//c#
diff --git a/src/wwwroot/linq/conversion-operators.html b/src/wwwroot/linq/conversion-operators.html
index 68c89d7..ec1f3bd 100644
--- a/src/wwwroot/linq/conversion-operators.html
+++ b/src/wwwroot/linq/conversion-operators.html
@@ -3,6 +3,8 @@
 order: 7
 -->
 
+{{ "lang-select" | partial }}
+
 

linq54: ToArray

//c#
diff --git a/src/wwwroot/linq/element-operators.html b/src/wwwroot/linq/element-operators.html
index ad143bf..57c2f0e 100644
--- a/src/wwwroot/linq/element-operators.html
+++ b/src/wwwroot/linq/element-operators.html
@@ -3,6 +3,8 @@
 order: 8
 -->
 
+{{ "lang-select" | partial }}
+
 

linq58: First - Simple

//c#
diff --git a/src/wwwroot/linq/generation-operators.html b/src/wwwroot/linq/generation-operators.html
index 5fddf33..8f8629e 100644
--- a/src/wwwroot/linq/generation-operators.html
+++ b/src/wwwroot/linq/generation-operators.html
@@ -3,6 +3,8 @@
 order: 9
 -->
 
+{{ "lang-select" | partial }}
+
 

linq65: Range

//c#
diff --git a/src/wwwroot/linq/grouping-operators.html b/src/wwwroot/linq/grouping-operators.html
index 4113d16..8cafe0c 100644
--- a/src/wwwroot/linq/grouping-operators.html
+++ b/src/wwwroot/linq/grouping-operators.html
@@ -3,6 +3,8 @@
 order: 5
 -->
 
+{{ "lang-select" | partial }}
+
 

linq40: GroupBy - Simple 1

//c#
diff --git a/src/wwwroot/linq/index.html b/src/wwwroot/linq/index.html
index 3058723..f11cd8c 100644
--- a/src/wwwroot/linq/index.html
+++ b/src/wwwroot/linq/index.html
@@ -7,17 +7,7 @@
     All LINQ Examples are executed using the same ScriptContext instance below:
 

-

-    LinqContext = new ScriptContext {
-        Args = {
-            [ScriptConstants.DefaultDateFormat] = "yyyy/MM/dd",
-            ["products"] = TemplateQueryData.Products,
-            ["customers"] = TemplateQueryData.Customers,
-            ["comparer"] = new CaseInsensitiveComparer(),
-            ["anagramComparer"] = new AnagramEqualityComparer(),
-        }
-    }.Init();
-
+{{ 'gfm/linq/01.md' | githubMarkdown }}

Which makes available the products and customer data sources to each template, all other data @@ -33,5 +23,12 @@ The Args[DefaultDateFormat] changes the default format the dateFormat filter uses if none is provided.

-{{ "linq-links" | partial({ order }) }} +

+ View the LINQ Examples in all of #Script Languages: +

+
+ template + code + lisp +
diff --git a/src/wwwroot/linq/ordering-operators.html b/src/wwwroot/linq/ordering-operators.html index bc7a228..afef86f 100644 --- a/src/wwwroot/linq/ordering-operators.html +++ b/src/wwwroot/linq/ordering-operators.html @@ -3,6 +3,8 @@ order: 4 --> +{{ "lang-select" | partial }} +

linq28: OrderBy - Simple 1

//c#
diff --git a/src/wwwroot/linq/partitioning-operators.html b/src/wwwroot/linq/partitioning-operators.html
index 766b05a..fe9c672 100644
--- a/src/wwwroot/linq/partitioning-operators.html
+++ b/src/wwwroot/linq/partitioning-operators.html
@@ -3,6 +3,8 @@
 order: 3
 -->
 
+{{ "lang-select" | partial }}
+
 

linq20: Take - Simple

//c#
@@ -45,7 +47,7 @@ 

linq21: Take - Nested

}
-{{ "linq-preview" | partial({ rows: 7, output: "html-lg", example: "linq21" }) }} +{{ "linq-preview" | partial({ rows: 12, output: "html-lg", example: "linq21" }) }}

linq22: Skip - Simple

@@ -89,7 +91,7 @@

linq23: Skip - Nested

}
-{{ "linq-preview" | partial({ rows: 7, output: "html-lg", example: "linq23" }) }} +{{ "linq-preview" | partial({ rows: 11, output: "html-lg", example: "linq23" }) }}

linq24: TakeWhile - Simple

diff --git a/src/wwwroot/linq/projection-operators.html b/src/wwwroot/linq/projection-operators.html index 0c48d77..507acd4 100644 --- a/src/wwwroot/linq/projection-operators.html +++ b/src/wwwroot/linq/projection-operators.html @@ -3,6 +3,8 @@ order: 2 --> +{{ "lang-select" | partial }} +

linq6: Select - Simple 1

//c#
@@ -106,7 +108,7 @@ 

linq10: Select - Anonymous Types 2

}
-{{ "linq-preview" | partial({ example: "linq10" }) }} +{{ "linq-preview" | partial({ rows:7, example: "linq10" }) }}

linq11: Select - Anonymous Types 3

@@ -128,7 +130,7 @@

linq11: Select - Anonymous Types 3

}
-{{ "linq-preview" | partial({ example: "linq11" }) }} +{{ "linq-preview" | partial({ rows:8, example: "linq11" }) }}

linq12: Select - Indexed

@@ -194,7 +196,7 @@

linq14: SelectMany - Compound from 1

}
-{{ "linq-preview" | partial({ example: "linq14", rows: 8 }) }} +{{ "linq-preview" | partial({ example: "linq14", rows:8 }) }}

linq15: SelectMany - Compound from 2

@@ -213,7 +215,7 @@

linq15: SelectMany - Compound from 2

}
-{{ "linq-preview" | partial({ output: "html-lg", example: "linq15" }) }} +{{ "linq-preview" | partial({ rows:8, output: "html-lg", example: "linq15" }) }}

linq16: SelectMany - Compound from 3

@@ -232,7 +234,7 @@

linq16: SelectMany - Compound from 3

}
-{{ "linq-preview" | partial({ output: "html-lg", example: "linq16" }) }} +{{ "linq-preview" | partial({ rows:9, output: "html-lg", example: "linq16" }) }}

linq17: SelectMany - from Assignment

@@ -251,7 +253,7 @@

linq17: SelectMany - from Assignment

}
-{{ "linq-preview" | partial({ output: "html-lg", example: "linq17" }) }} +{{ "linq-preview" | partial({ rows:9, output: "html-lg", example: "linq17" }) }}

linq18: SelectMany - Multiple from

@@ -273,7 +275,7 @@

linq18: SelectMany - Multiple from

}
-{{ "linq-preview" | partial({ output: "html-lg", example: "linq18", rows: 8 }) }} +{{ "linq-preview" | partial({ output: "html-lg", example: "linq18", rows: 10 }) }}

linq19: SelectMany - Indexed

@@ -292,6 +294,6 @@

linq19: SelectMany - Indexed

}
-{{ "linq-preview" | partial({ example: "linq19" }) }} +{{ "linq-preview" | partial({ rows:7, example: "linq19" }) }} {{ "linq-links" | partial({ order }) }} diff --git a/src/wwwroot/linq/quantifiers.html b/src/wwwroot/linq/quantifiers.html index 599396a..f1e0a0e 100644 --- a/src/wwwroot/linq/quantifiers.html +++ b/src/wwwroot/linq/quantifiers.html @@ -3,6 +3,8 @@ order: 10 --> +{{ "lang-select" | partial }} +

linq67: Any - Simple

//c#
diff --git a/src/wwwroot/linq/query-execution.html b/src/wwwroot/linq/query-execution.html
index b8a57ae..e41081e 100644
--- a/src/wwwroot/linq/query-execution.html
+++ b/src/wwwroot/linq/query-execution.html
@@ -3,6 +3,8 @@
 order: 13
 -->
 
+{{ "lang-select" | partial }}
+
 

linq99: Deferred Execution

//c#
@@ -98,11 +100,6 @@ 

linq101: Query Reuse

}
-

- There is no query reuse in Templates, lowNumbers is assigned the result of the query instead of a reusable query. - But you can modify the contents of numbers as seen below: -

- {{ "linq-preview" | partial({ example: "linq101", rows: 12 }) }} diff --git a/src/wwwroot/linq/restriction-operators.html b/src/wwwroot/linq/restriction-operators.html index 94cf80e..747b1bf 100644 --- a/src/wwwroot/linq/restriction-operators.html +++ b/src/wwwroot/linq/restriction-operators.html @@ -3,6 +3,8 @@ order: 1 --> +{{ "lang-select" | partial }} +

linq1: Where - Simple 1

//c#
@@ -26,10 +28,11 @@ 

linq1: Where - Simple 1

{{ "linq-preview" | partial({ example: "linq01" }) }}

- Or can be alternatively written using Code Blocks: + Or can be written using different languages within the same #Script using + Language Blocks and Expressions:

-{{ "linq-preview" | partial({ rows:7, example: "linq01-code" }) }} +{{ "linq-preview" | partial({ rows:11, example: "linq01-langs", lang: 'template' }) }}

linq2: Where - Simple 2

@@ -73,7 +76,7 @@

linq3: Where - Simple 3

}
-{{ "linq-preview" | partial({ rows: 4, example: "linq03" }) }} +{{ "linq-preview" | partial({ rows: 5, example: "linq03" }) }}

linq4: Where - Drilldown

diff --git a/src/wwwroot/linq/set-operators.html b/src/wwwroot/linq/set-operators.html index ae2cdc2..9225d61 100644 --- a/src/wwwroot/linq/set-operators.html +++ b/src/wwwroot/linq/set-operators.html @@ -3,6 +3,8 @@ order: 6 --> +{{ "lang-select" | partial }} +

linq46: Distinct - 1

//c#
diff --git a/src/wwwroot/lisp/index.html b/src/wwwroot/lisp/index.html
new file mode 100644
index 0000000..d3e5545
--- /dev/null
+++ b/src/wwwroot/lisp/index.html
@@ -0,0 +1,638 @@
+
+
+{{#markdown}}
+
+`#Script` is designed as a small, expressive and wrist-friendly dynamic scripting language that for maximimum familiarity
+is modelled after the world's most popular and ubiquitos scripting Language, JavaScript. Its minimal syntax was inspired 
+by other small but powerful languages which heavily utilizes functions instead of adopting a larger language grammar 
+defining different bespoke syntax for language constructs.
+
+Small Languages like Smalltalk, despite being one of the most influential languages in history, is famous for its minimal
+[syntax that fits on a post card](https://en.wikipedia.org/wiki/Smalltalk#Syntax). A language with arguably the
+best power to size ratio is [Lisp](https://en.wikipedia.org/wiki/Lisp_%28programming_language%29) which the inventor of
+Smalltalk, Alan Kay has credited it as being
+the [greatest single programming language ever designed](https://en.wikiquote.org/wiki/Lisp_%28programming_language%29)
+after realizing: 
+
+> “the half page of code on the bottom of page 13… was Lisp in itself. These were 
+[“Maxwell’s Equations of Software!”](http://www.michaelnielsen.org/ddi/lisp-as-the-maxwells-equations-of-software/)
+
+[Lisp's unprecedented elegance and simplicity](http://languagelog.ldc.upenn.edu/myl/llog/jmc.pdf) spawned a [myraid of dialects](https://en.wikipedia.org/wiki/List_of_Lisp-family_programming_languages),
+some noteworthy implementations illustrating the beauty of its small size and expressive power is [lispy](http://norvig.com/lispy.html) by
+by [Peter Norvig](http://norvig.com) (Director of Google Research) that implements a Lisp interpreter in just **117 lines of Python 3 code** (inc. a REPL).
+
+Another compact dialect is [Zick Standard Lisp](https://github.com/zick/ZickStandardLisp) which [@zick](https://github.com/zick) 
+has implemented in **42 different languages** including a [recursive Lisp evaluator in Lisp](https://github.com/zick/ZickStandardLisp/blob/master/lisp.lsp)
+implemented in only **66 lines of code**.
+
+A more complete Lisp implementation in C# is the elegant [Nukata Lisp](https://github.com/nukata/lisp-in-cs) 
+by [SUZUKI Hisao](https://github.com/nukata) which is a 
+Common Lisp-like [Lisp-1](https://stackoverflow.com/q/4578574/85785) dialect with tail call optimization and partially hygienic macros, although
+has some notable limitations including a [small standard library](https://github.com/nukata/lisp-in-cs/blob/d4258ec4b5681b0854a0d464b708df9ae26bbe3c/lisp.cs#L1216-L1391), 
+only uses the `double` numeric type and doesn't include any .NET Scripting.
+
+### Script Lisp Overview
+
+[ScriptLisp](https://github.com/ServiceStack/ServiceStack/blob/master/src/ServiceStack.Common/Script/ScriptLanguage.Lisp.cs) is an
+enhanced version of [Nukata Lisp](https://github.com/nukata/lisp-in-cs) with a number of new features
+that reuses `#Script` existing scripting capabilities to provide seamless integration with both the rest of `#Script`
+(see [Language Blocks an Expressions](http://localhost:5000/docs/syntax#language-blocks-and-expressions))
+and .NET including [Scripting of .NET Types](/docs/script-net), support for all .NET numeric types and access to its comprehensive 
+library of over **1000+** [Script Methods](/docs/methods) - optimally designed for accessing .NET functionality from a dynamic language.
+
+To improve compatibility with existing Common Lisp source code it also implements most of the [Simplified Common Lisp Reference](http://jtra.cz/stuff/lisp/sclr/)
+as well as all missing functions required to implement C# LINQ 101 Examples in Lisp:
+
+
+
+To improve readability and familiarity it also adopts a number of **Clojure** syntax for defining a 
+[data list and map](https://clojure.org/guides/learn/syntax#_literal_collections) literals, 
+[anonymous functions](https://clojure.org/guides/learn/functions#_anonymous_function_syntax), 
+syntax in [Java Interop](https://clojure.org/guides/learn/functions#_java_interop) for .NET Interop,
+[keyword syntax for indexing collections](https://clojure.org/guides/learn/hashed_colls#_field_accessors) and accessing index accessors
+and Clojure's popular shorter aliases for `fn`, `def`, `defn` - improving source-code compatibility with Clojure.
+
+### Lisp REPL
+
+In addition to being a 1st class language option in `#Script`, Lisp's dynamism and extensibility makes it particularly 
+well suited for explanatory programming and usage within a REPL which is now built into the latest 
+[web](https://docs.servicestack.net/web-tool) and [app](https://docs.servicestack.net/netcore-windows-desktop) dotnet tools 
+that you can update to the latest version with:
+
+    $ dotnet tool update -g web
+
+this will let you start an instant Lisp REPL with:  
+
+    $ web lisp
+
+The quick demo below shows the kind of exploratory programming available where you can query the `scriptMethods` available, 
+query an objects `props`, query the Lisp interpreter's global `symbols` table containing all its global state including all 
+defined lisp functions, macros and variables:
+
+> YouTube: [youtu.be/RR7yk6ReNnQ](https://youtu.be/RR7yk6ReNnQ)
+
+[![](https://raw.githubusercontent.com/ServiceStack/docs/master/docs/images/sharpscript/web-lisp.gif)](https://youtu.be/RR7yk6ReNnQ)
+
+### Annotated REPL Walk through
+
+Here's an annotated version of the demo below which explains what each of the different expressions is doing.
+
+Just like [Sharp Scripts](/docs/sharp-scripts) and [Sharp Apps](/docs/sharp-apps) the Lisp REPL runs within the 
+[#Script Pages](/docs/sharp-pages) ScriptContext [sandbox](/docs/sandbox) that when run from a Sharp App folder, 
+starts a .NET Core App Server that simulates a fully configured .NET Core App. 
+In this case it's running in the [redis Sharp App](https://github.com/sharp-apps/redis) directory where it was able to access its static web assets
+as well as its redis-server connection configured in its [app.settings](https://github.com/sharp-apps/redis/blob/master/app.settings).
+
+{{/markdown}}
+
+{{ 'gfm/lisp/03.md' | githubMarkdown }}
+
+{{#markdown}}
+
+#### Enable features and access resources with app.settings
+
+You can configure the Lisp REPL with any of the resources and features that [Sharp Apps](/docs/sharp-apps) and 
+[Gist Desktop Apps](/docs/gist-desktop-apps) have access to, by creating a plain text `app.settings` file with all the 
+features and resources you want the Lisp REPL to have access to, e.g. this [Pure Cloud App app.settings](/docs/sharp-apps#pure-cloud-apps)
+allows the Lisp REPL to use [Database Scripts](/docs/db-scripts) against a AWS PostgreSQL RDS server and query remote 
+[S3 Virtual Files](https://docs.servicestack.net/virtual-file-system) using [Virtual File System APIs](/docs/protected-scripts#virtual-file-system-apis):
+
+    # Note: values prefixed with '$' are resolved from Environment Variables
+    name AWS S3 PostgreSQL Web App
+    db postgres
+    db.connection $AWS_RDS_POSTGRES
+    files s3
+    files.config {AccessKey:$AWS_S3_ACCESS_KEY,SecretKey:$AWS_S3_SECRET_KEY,Region:us-east-1,Bucket:rockwind}
+
+See the [plugins app.settings](/docs/sharp-apps#registering-servicestack-plugins) for examples of how to load and configure 
+[ServiceStack Plugins](https://docs.servicestack.net/plugins).
+
+### Lisp REPL TCP Server
+
+In addition to launching a Lisp REPL from the Console above, you can also open a Lisp REPL into any ServiceStack App 
+configured with the `LispReplTcpServer` ServiceStack plugin. This effectively opens a **"programmable gateway"** into any 
+ServiceStack App where it's able to perform live queries, access IOC dependencies, invoke internal Server functions and query
+the state of a running Server which like the [Debug Inspector](https://docs.servicestack.net/debugging#debug-inspector) 
+can provide invaluable insight when diagnosing issues on a remote server.
+
+To see it in action we'll enable it one of our production Apps [techstacks.io](https://techstacks.io) which as it's a 
+Vuetify SPA App is only configured with an empty `SharpPagesFeature` as it doesn't use any server-side scripting features.
+
+We'll enable it in `DebugMode` where we can enable by setting `DebugMode` in our App's `appsettings.Production.json` 
+which will launch a TCP Socket Server which by default is configured to listen to the **loopback** IP on port `5005`.
+
+{{/markdown}}
+
+{{ 'gfm/lisp/05.md' | githubMarkdown }}
+
+{{#markdown}}
+
+> [ScriptNamespaces](/docs/script-net#type-resolution) behaves like C#'s `using Namespace;` statement letting you reference Types 
+by `Name` instead of its fully-qualified Namespace.
+
+Whilst you can now connect to it with basic `telnet`, it's a much nicer experience to use it with the [rlwrap](https://linux.die.net/man/1/rlwrap)
+readline wrap utility which provides an enhanced experience with line editing, persistent history and completion.
+
+    $ sudo apt-get install rlwrap
+
+Then you can open a TCP Connection to connect to a new Lisp REPL with:
+
+    $ rlwrap telnet localhost 5005
+
+Where you now have full scriptability of the running server as allowed by [#Script Pages](/docs/sharp-pages) `SharpPagesFeature` which
+allows [scripting of all .NET Types](/docs/script-net#allowscriptingofalltypes) by default. 
+
+#### TechStacks TCP Lisp REPL Demo
+
+In this demo we'll explore some of the possibilities of scripting the live [techstacks.io](https://techstacks.io) Server where we can 
+`resolve` IOC dependencies to send out tweets using its registered `ITwitterUpdates` dependency, view the source and load a remote 
+[parse-rss](https://gist.github.com/gistlyn/3624b0373904cfb2fc7bb3c2cb9dc1a3) lisp function into the new Lisp interpreter attached to the TCP connection, 
+use it to parse [Hacker News RSS Feed](https://news.ycombinator.com/rss) into a .NET Collection where it can be more easily queried using its built-in functions
+which is used to construct an email body with **HN's current Top 5 links**. 
+
+It then uses [DB Scripts](/docs/db-scripts) to explore its configured AWS RDS PostgreSQL RDBMS, listing its DB tables and viewing its 
+column names and definitions before retrieving the Email addresses of all **Admin** users, sending them each an email with HN's Top 5 Links by 
+publishing **5x** `SendEmail` Request DTOs using the [publishMessage](/docs/servicestack-scripts#publishmessage) ServiceStack Script to where 
+they're processed in the background by its configured [MQ Server](https://docs.servicestack.net/messaging) that uses it to execute the 
+`SendEmail` ServiceStack Service where it uses its configured AWS SES SMTP Server to finally send out the Emails:
+
+> YouTube: [youtu.be/HO523cFkDfk](https://youtu.be/HO523cFkDfk)
+
+[![](https://raw.githubusercontent.com/ServiceStack/docs/master/docs/images/sharpscript/lisp-tcp-repl.gif)](https://youtu.be/HO523cFkDfk)
+
+#### Annotated Lisp TCP REPL Transcript
+
+{{/markdown}}
+
+{{ 'gfm/lisp/06.md' | githubMarkdown }}
+
+
+{{#markdown}}
+
+### Run and watch Lisp Scripts
+
+The same [Sharp Scripts](/docs/sharp-scripts) functionality for `#Script` is also available to Lisp scripts where you can use the `web` and `app`
+dotnet tools to **run** and **watch** stand-alone Lisp scripts with the `.l` file extension, e.g: 
+
+    $ web run lisp.l
+    $ web watch lisp.l
+
+To clarify the behavioural differences between the Lisp REPL's above which uses the **same Lisp interpreter** to maintain state changes across each command, 
+the `watch` Script is run with a **new Lisp Interpreter** which starts with a **fresh** copy of the Global symbols table so any state changes after each 
+`Ctrl+S` save point is discarded.
+
+### Watch `lisp` scripts
+
+This quick demo illustrates the same functionality in [Sharp Scripts](/docs/sharp-scripts) is also available in `lisp` scripts 
+where it provides instant feedback whilst you develop in real-time:
+
+> YouTube: [youtu.be/rIgEP8ssikY](https://youtu.be/rIgEP8ssikY)
+
+[![](https://raw.githubusercontent.com/ServiceStack/docs/master/docs/images/sharpscript/lisp-l.gif)](https://youtu.be/rIgEP8ssikY)
+
+### Annotated Lisp watch script
+{{/markdown}}
+
+{{ 'gfm/lisp/02.md' | githubMarkdown }}
+
+{{#markdown}}
+
+#### Page Arguments
+
+You can also use the same syntax for declaring any `app.settings` page arguments used in `#Script` and `code` Scripts:
+
+    
+
+But for compatibility with any Lisp syntax highlighters and code editors they can also be prefixed with a `;` line comment as seen above.
+
+## Executing Lisp in .NET
+
+Lisp like all `#Script` languages are executed within a `ScriptContext` that defines all functionality available to them, i.e:
+
+{{/markdown}}
+
+{{ "net-usage" | partial }}
+
+{{ 'gfm/lisp/07.md' | githubMarkdown }}
+
+{{#markdown}}
+
+### Evaluating Lisp Script Results
+
+If you instead wanted to access return values instead of its rendered output, use the `EvaluateLisp()` APIs:
+
+{{/markdown}}
+
+{{ 'gfm/lisp/08.md' | githubMarkdown }}
+
+{{#markdown}}
+
+## .NET Interop
+
+The syntax for .NET Interop is inspired directly from Clojure's syntax used for [Java Interop](https://clojure.org/reference/java_interop).
+See [Scripting .NET Type Resolution](/docs/script-net#type-resolution) for how to configure Types and imported Namespaces you want your 
+Lisp scripts to have access to.
+
+### Member Access
+
+The `'.'` prefix if for accessing an instance members which can be used for retrieving a properties public properties, fields and
+invoking instance methods, e.g:
+
+- **(.Property instance)**
+- **(.Field    instance)**
+- **(.Method   instance ...args)**
+
+### Indexer Access
+
+Use `':'` prefix for accessing a Types indexer or for indexing collections, e.g:
+
+- **(:key indexer)**
+- **(:"string key" dictionary)**
+- **(:n list)**
+- **(:n array)**
+- **(:n enumerable)**
+- **(:n indexer)**
+
+It can also be used to access an instance public Properties and Fields:
+
+- **(:Property instance)**
+- **(:Field    instance)**
+
+However for readability we recommend using `'.'` prefix above to convey instance member access.
+
+### Constructor Access
+
+Use `'.'` **suffix** for creating instances of Types:
+
+- **(Type. ...args)**
+- **(Namespace.Type. ...args)**
+
+You can also create instances using the [new script method](/docs/script-net#creating-instances), which as it accepts a 
+`string` Type Name can be used to create generic classes with multiple generic args, e.g:
+
+- **(new "Type" ...args)**
+- **(new "Type<T1,T2,T3>" ...args)**
+
+### Static Member Access
+
+Use the `'/'` separator to access a Type's static members or to invoke its static methods, e.g:
+
+- **(StaticType/Property)**
+- **(StaticType/Field)**
+- **(StaticType/Const)**
+- **(StaticType/Method ...args)**
+
+Use `'.'` dot notation for specifying the fully-qualified Type name or to reference its Inner classes, e.g:
+
+- **(Namespace.StaticType/Member)**
+- **(Namespace.StaticType.InnerType/Member)**
+
+### Script Methods
+
+Use `'/'` prefix to reference any Script Method registered in your `ScriptContext`:
+
+- **(/scriptMethod ...args)**
+
+Script Methods without arguments can be referenced as an [argument binding](/docs/default-scripts#as-bindings) 
+that when referenced as an argument (i.e. without brackets) are implicitly evaluated, in-effect making them a calculated property:
+
+- **/methodAsBinding**
+
+While a `'/'` prefix indicates a reference to a [script method](/docs/methods), for readability it can be excluded as when 
+there's no existing symbol defined in the Lisp interpreter's symbol table it will fallback to referencing a script method:
+
+- **(scriptMethod ...args)**
+- **methodAsBinding**
+
+This does mean that when there exists a `symbol` of the same name defined you will need to use the `'/'` prefix to reference a script method.
+
+### Generic Types
+
+All references above support referencing generic types and methods with a single generic Type argument, e.g:
+
+- **(StaticType/Method<T>)**
+- **(GenericStaticType<T>/Member)**
+- **(GenericStaticType<T>/Method<T>)**
+- **(GenericType<T>.)**
+
+As `','` is one of Lisp's few syntax tokens ([unquoting](http://www.lispworks.com/documentation/HyperSpec/Body/02_df.htm)) it prevents them
+from being able to use them to specify multiple generic arguments.
+
+Instead you'll need to use the [Constructor function](/docs/script-net#creating-instances) for referencing constructors with multiple generic
+arguments where you'll also need to specify the types of the exact constructor you want to call, e.g:
+
+- **(/C "Tuple<String,int>(String,int)")**
+
+The difference between the `/C` script method constructor function and Lisp's `C` function is that the script method only returns a reference
+to the constructor which you'll need to invoke with arguments to create an instance:
+
+- **((/C "Tuple<String,int>(String,int)") "A" 1)**
+
+Whilst Lisp's `C` function will auto-invoke the constructor function with the supplied arguments in a single expression:
+
+- **(C "Tuple<String,int>(String,int)" "A" 1)**
+
+Likewise when needing to invoke generic methods with multiple generic args you'll need to use [Function](/docs/script-net#function):
+
+- **((/F "Tuple.Create<String,int>(String,int)") "A" 1)**
+
+Or Script Lisp's `F` function for invoking a function reference in a single expression:
+
+- **(F "Tuple.Create<String,int>(String,int)" "A" 1)**
+
+For more examples and information see [Scripting .NET Types](/docs/script-net).
+
+### Property Setters
+
+You can populate multiple properties on an instance using the [set script method](/docs/script-net#user-content-set), e.g:
+
+- **(set instance { :Prop arg ... })**
+
+Alternatively properties can be set individually with:
+
+- **(.Prop instance arg)**
+
+### Lisp Lists vs .NET Collections
+
+A potential source of friction when interoperating with .NET is that Lisp Lists are [Cons Cells](https://cs.gmu.edu/~sean/lisp/cons/)
+so that a code or data list in Lisp, i.e:
+
+    '(1 2 3)
+    [1 2 3]
+    
+Is implemented as a Linked List of Cons cells:
+
+    (1 . (2 . (3 . null)))
+
+Which is what Lisp's core functions expect to operate on, namely:
+
+    car cdr caar cadr cdar cddr caaar caadr cadar caddr cdaar cdadr cddar cdddr append mapcar consp cons?
+    listp list? memq member assq assoc nreverse last nconc dolist dotimes mapcan mapc nthcdr nbutlast 
+
+These core Lisp functions can't be used against .NET collections directly, instead you can use 
+`(to-cons collection)` to convert a .NET `IEnumerable` collection into a cons list, e.g:
+
+    (cdr (to-cons netEnumerable))
+
+Should you need to do the inverse you can use `(to-list cons-list)` to convert a cons list to a .NET List, e.g:
+
+    (to-list (range 10))
+
+We've made Script Lisp's cons `Cell` an `IEnumerable` so that **all other built-in Lisp functions** can operate on both 
+cons cells and .NET Collections where instead of iterating a list with `(do-list)` you can use `(do-seq)` to iterate
+both .NET Collections and cons cells, e.g:
+
+    (do-seq (x collection) (println x) )
+
+### Annotated .NET Interop Example
+
+{{/markdown}}
+
+{{ 'gfm/lisp/09.md' | githubMarkdown }}
+
+{{#markdown}}
+
+### Importing Global Scripts
+
+Importing scripts in Lisp is essentially a 2-stage process of parsing Lisp source code into an [SExpression](https://en.wikipedia.org/wiki/S-expression), 
+(basically Lisp's AST of tokenized elements captured in a [2-field Cons Cell](https://cs.gmu.edu/~sean/lisp/cons/)) then evaluating it in a 
+Lisp interpreter where any defined symbols are captured in its Symbols table.
+
+Lisp Script captures its "standard library" in a Global Interpreter which serves as the starting template for all other Lisp Interpreters 
+which starts with a copy of the Global symbols table which you can further populate with your own common functions using `Lisp.Import()`, e.g:
+
+{{/markdown}}
+
+{{ 'gfm/lisp/10.md' | githubMarkdown }}
+
+{{#markdown}}
+
+### Loading Scripts
+
+Loading scripts within a Lisp script works similarly except they're only loaded into that Lisp interpreters symbol table, a new one
+of which is created in each new `PageResult`.
+
+Scripts loaded locally are loaded from the `ScriptContext` configured [Virtual Files Provider](https://docs.servicestack.net/virtual-file-system) 
+which for [#Script Pages](/docs/sharp-pages) `SharpPagesFeature` is configured to use the App's cascading virtual file sources. 
+
+A new `ScriptContext` starts with an empty [MemoryVirtualFiles](https://docs.servicestack.net/virtual-file-system#virtual-file-systems-available)
+which you can write files to with:
+
+{{/markdown}}
+
+{{ 'gfm/lisp/11.md' | githubMarkdown }}
+
+{{#markdown}}
+
+### #Script Pages Integration
+
+Whilst Lisp is a small, powerfully expressive functional dynamic language it's not great for use as a templating language. 
+Whilst there have been [several attempts to create a HTML DSL in Lisp](https://www.cliki.net/HTML%20generator), nothing is better
+than having **no syntax** which is the default **Template mode** for `#Script` where it will emit everything that's not in a 
+Template or Language Expression as verbatim text.
+
+A nice USP of Script Lisp is that you're never forced into going "full Lisp", you can utilize `#Script` template expressions and
+[Script Blocks](/docs/blocks) handlebars syntax that provides the ideal DSL for usage in a Template Language for generating HTML
+and utilize your preferred `Lisp` or [Code](/scode/) Script Languages for any computational logic you want included in your page
+using [Language Blocks and Expressions](/docs/syntax#language-blocks-and-expressions).
+
+#### Implementation
+
+Despite being implemented in different languages a `#Script` page containing multiple languages, e.g:
+
+{{/markdown}}
+
+{{ "linq-preview" | partial({ rows:11, example: "linq01-langs", lang: 'template' }) }}
+
+{{#markdown}}
+
+Still only produces a **single page AST**, where when first loaded `#Script` parses the page contents as a contiguous
+`ReadOnlyMemory` where page slices of any [Language Blocks and Expressions](/docs/syntax#language-blocks-and-expressions) 
+on the page are delegated to the `ScriptContext` registered `ScriptLanguages` for parsing which returns a fragment which is
+added to the pages AST:
+
+[![](/assets/img/multi-langs.svg)](/assets/img/multi-langs.svg)
+
+When executing the page, each language is responsible for rendering its own fragments which all write directly to the pages `OutputStream`
+to generate the pages output.
+
+The multi-languages support in `#Script` is designed to be extensible where everything about the language is encapsulated within its
+`ScriptLanguage` implementation so that if you omit its registration:
+
+    var context = new ScriptContext {
+    //    ScriptLanguages = { ScriptLisp.Language }
+    }.Init();
+
+Any language expressions and language blocks referencing it become inert and its source code emitted as plain-text.
+
+### Lisp Argument Scopes
+
+One differentiator between Lisp and [Code](/scode/) languages is that `code` utilizes the containing page current scope
+for all its argument references where as Lisp stores all its definitions within the Lisp interpreter symbol table
+attached to the `PageResult`, so whilst Lisp scripts can access arguments within the pages scope, in order for the 
+outer page to access any Lisp symbols they need to be exported, e.g:
+
+{{/markdown}}
+
+{{ "live-template" | partial({ output:'no-scroll', rows:7, template: "{{ 2 |> toGlobal => pageResultArg }} 
+{{ 3 |> to => scopeArg }}
+...lisp
+(export retVal (+ pageResultArg scopeArg) 
+        newVal 2)
+...
+Result: {{ retVal + newVal }}".replace('...','```') }) }}
+
+{{#markdown}}
+
+### Exporting Lisp Functions
+
+Lisp functions can also be exported for usage in the rest of the page by calling `(to-delegate lispFn)` to convert it 
+into a .NET Delegate, e.g:
+
+{{/markdown}}
+
+{{ "live-template" |> partial({ output:'no-scroll', rows:7, template: "
+{{ 1 |> to => scopeArg }}
+...lisp
+(def localArg 2)
+(def lispAdd  #(+ %1 %2 localArg))
+(export lispAdd (to-delegate lispAdd))
+...
+Result: {{ lispAdd(scopeArg, 3) }} or {{ scopeArg.lispAdd(3) }}".replace('...','```') }) }}
+
+{{#markdown}}
+
+Although an easier way to define functions in Lisp is to use the [defn Script Block](/docs/blocks#defn) which wraps
+this in a convenient syntax:
+
+{{/markdown}}
+
+{{ "live-template" |> partial({ output:'no-scroll', rows:12, template: "{{#defn calc [a, b] }}
+    (def c (* a b))
+    (+ a b c)
+{{/defn}}
+
+{{#defn fib [n] }}
+    (if (<= n 1)
+        n
+        (+ (fib (- n 1))
+           (fib (- n 2)) ))
+{{/defn}}
+calc: {{ calc(1,2) }}, fib: {{ 10.fib() }} ".replace('...','```') }) }}
+
+{{#markdown}}
+
+### Controlling Lisp output
+
+One of Lisp's famous traits is **everything is an expression** which is typically desired within a language, but may not 
+be what you want when generating output. E.g traditionally Lisp uses [setq](http://www.lispworks.com/documentation/HyperSpec/Body/s_setq.htm)
+to set a variable which also returns its value that `#Script` will emit as it automatically emits all statement return values. 
+
+You could use `def` which is an alias for `setq` which returns `null`, other options include wrapping all statements within an empty `let`
+expression where only the last expression is returned, or you could use a [Language Block Modifier](/docs/syntax#language-block-modifiers)
+to ignore the entire `lisp` block output and only export the result you want to be able to control precisely where to emit it:
+
+{{/markdown}}
+
+{{ "live-template" |> partial({ output:'no-scroll', rows:18, template: `
+default:
+...lisp
+(setq result (+ 1 1))
+(str "1 + 1 = " result)
+...
+let:
+...lisp
+(let ()
+    (setq result (+ 1 1))
+    (str "1 + 1 = " result))
+...
+
quiet block: +...lisp|q +(setq result (+ 1 1)) +(export ret (str "1 + 1 = " result)) +... +{{ ret }} +`.replace('...','```') }) }} + +{{#markdown}} + +> can use either 'q', 'quiet' or 'mute' block modifier to ignore output + +Another way to generate output from Lisp is to use its built-in print functions below: + + - **(print ...args)** - write all arguments to the `OutputStream` + - **(println ...args)** - write all arguments to the `OutputStream` followed by a **new line** + - **(printlns ...args)** - write all arguments to the `OutputStream` with a `' '` **space delimiter** followed by a **new line** + - **(pr ...args)** - same as `print` but **HTML encode** all arguments + - **(prn ...args)** - same as `println` but **HTML encode** all arguments + +## Learn #Script Lisp + +A great resource for learning Script Lisp is seeing it in action by seeing how to implement **C#'s 101 LINQ Examples in Lisp**: + + + +### Explore APIs in real-time + +We can also take advantage of Lisp's dynamism and interactivity to explore APIs in real-time, a great way to do this is via +a [watched Lisp script](#run-and-watch-lisp-scripts) on the side where it provides instant feedback after each `Ctrl+S` save point +or a active [Lisp REPL](#lisp-repl). + +- **symbols** - List all symbols in Lisp interpreter - most symbols are named after [standard Lisp](http://www.lispworks.com/documentation/HyperSpec/Front/X_AllSym.htm) +or [clojure functions](https://clojure.org/api/cheatsheet) +- **(symbol-type symbol)** - Display the Symbol's Value Type +- **scriptMethods** - List all available Script Method Names registered in `ScriptContext` +- **scriptMethodTypes** - List all available Script Method Type information +- **(joinln collection)** - Display the string output of each item in the collection on a separate line +- **(globln pattern collection)** - Only display lines matching the glob pattern +- **(typeName instance)** - View the instance Type Name +- **(props instance)** - Display the Property names of an Instance public properties +- **(fields instance)** - Display the Field names of an Instance public fields +- **(methods instance)** - Display the Method names of an Instance public methods +- **(propTypes instance)** - Get the PropertyInfo[] of an Instance public properties +- **(fieldTypes instance)** - Get the FieldInfo[] of an Instance public fields +- **(methodTypes instance)** - Get the Script Method Infos of an Instance public methods +- **(staticProps instance)** - Display the Property names of an Instance public static properties +- **(staticFields instance)** - Display the Field names of an Instance public static fields +- **(staticMethods instance)** - Display the Method names of an Instance public static methods +- **(staticPropTypes instance)** - Get the PropertyInfo[] of an Instance public static properties +- **(staticFieldTypes instance)** - Get the FieldInfo[] of an Instance public static fields +- **(staticMethodTypes instance)** - Get the Script Method Infos of an Instance public static methods + +You can view the [Scripts API Reference](/docs/scripts-reference) and [Scripts Documentation](/docs/default-scripts) +on this website to interactively explore the available APIs, we'll work on providing further interactive documentation +for the built-in Lisp functions, in the mean-time the best resource are +[their implementation](https://github.com/ServiceStack/ServiceStack/blob/32185f14aaf93d768dda5673381aa0d2fdbe34b4/src/ServiceStack.Common/Script/ScriptLanguage.Lisp.cs#L3106-L3508). + +For reference, here's are a quick list of all built-in Lisp symbols: + + - % * *gensym-counter* *version* / /= _append _nreverse + < <= = > >= 1- 1+ 1st 2nd 3rd abs all? and any? + append apply assoc assoc-key assoc-value assq atom atom? average butlast C caaar caadr caar cadar caddr + cadr car cdaar cdadr cdar cddar cdddr cddr cdr ceiling cons cons? consp cos count debug dec decf def + defmacro defn defun dispose dolist doseq doseq-while dotimes dump dump-inline elt empty? end? endp + enumerator enumeratorCurrent enumeratorNext eq eql equal error even? every every? exit exp expt F f++ + false filter filter-index first flatmap flatten floor gensym glob globln group-by htmldump identity if + inc incf instance? intern isqrt it last length let letrec list list? listp load load-src logand logior + logxor lower-case make-symbol map mapc mapcan mapcar map-index map-where max member memq min mod nbutlast + nconc new new-map next not not= nreverse nth nthcdr null number? odd? or order-by pop pr prin1 princ + print println printlns prn prs push push-end random range reduce remove remove-if rest return reverse + round rplaca rplacd second seq? setcar setcdr set-difference sets sin skip skip-while some some? sort + sort-by sqrt str string string? string-downcase string-upcase subseq sum symbol-name symbol-type t take + take-while tan terpri textdump third to-array to-cons to-delegate to-dictionary to-list true truncate + union unless upper-case when where where-index while zero? zerop zip zip-where + +Common Lisp by convention uses a `*p` suffix for **predicate** functions but we prefer Clojure's (and Ruby's) +more readable `*?` suffix convention, for source-code compatibility we include both for +[core Lisp predicts](http://jtra.cz/stuff/lisp/sclr/index.html) and just `*?` for others. + +{{/markdown}} diff --git a/src/wwwroot/scode/index.html b/src/wwwroot/scode/index.html new file mode 100644 index 0000000..71e1258 --- /dev/null +++ b/src/wwwroot/scode/index.html @@ -0,0 +1,109 @@ + + +{{#markdown}} + +Code statements is an alternative language dialect which allows us to invert `#Script` from **"Template Mode"** +where all text is emitted as-is with only Template Expressions `{{ ... }}` being evaluated and changed it to **"Code Mode"** where all code +statements are evaluated as JS Expression statements. + +This is akin to Razor's statement blocks which inverts Razor's **mode** of emitting text to treating text inside statement blocks as code, e.g: + +{{/markdown}} + +{{ 'gfm/syntax/05.md' | githubMarkdown | convertScriptToCodeBlocks }} + +{{#markdown}} + +Which is useful in reducing boilerplate when you need to evaluate code blocks with 2+ or more lines without the distracting boilerplate of wrapping +each expression within a `{{ ... }}` Template Expression. + +### Syntax + +Effectively you can think of **Code** Statements as `#Script` without template expressions since the code within a Template Expressions `{{ ... }}` +are both evaluated as JS Expressions. + +One difference between them is that each line in a code statement block must be a complete expression, any expressions that spans multiple +lines need to be wrapped withing Template Expressions where-by the source code for both **Template** and **Code** blocks remain exactly the same, e.g: + +{{/markdown}} + +{{ 'gfm/scode/01.md' | githubMarkdown }} + +{{#markdown}} + +The difference between them is that whitespace and indentation in code statement blocks are inert whereas they're emitted in Templates +so these examples are equivalent: + +{{/markdown}} + +{{ 'gfm/scode/02.md' | githubMarkdown | convertScriptToCodeBlocks }} + +

+ Whilst code statements can effectively "#Script without Template Expressions", you can view the LINQ examples in each + to see how they compare: +

+ + + +{{#markdown}} + +## Executing #Script Code in .NET + +All `#Script` languages are executed within a `ScriptContext` that defines all functionality available to them, i.e: + +{{/markdown}} + +{{ "net-usage" | partial }} + +{{#markdown}} + +But to render `code` statements you'd use `RenderCode` instead of `RenderScript`, e.g: + +{{/markdown}} + +{{ 'gfm/scode/03.md' | githubMarkdown }} + +{{#markdown}} + +### Evaluating Script Results + +If you instead wanted to access Script return values instead of the code rendered output you would use the `EvaluateCode()` APIs: + +{{/markdown}} + +{{ 'gfm/scode/04.md' | githubMarkdown }} + +{{#markdown}} + +## Scripting .NET Types + +All `#Script` languages have the same access to [Scripting .NET Types](/docs/script-net). + +## Code Scripts + +The same functionality in [Sharp Scripts](/docs/sharp-scripts) is also available in `#Script` Code, except instead of using the `*.ss` file +extension for executing `#Script` you'd use the `*.sc` file extension which will allow you to use the +[web](https://docs.servicestack.net/web-tool) and [app](/netcore-windows-desktop) dotnet tools to watch or run `code` scripts: + + $ web run code.sc + $ web watch code.sc + +### Watch `code` scripts + +The same functionality in [Sharp Scripts](/docs/sharp-scripts) is also available in `code` scripts which provides instant feedback +whilst you develop in real-time: + + $ web watch code.sc + +[![](https://raw.githubusercontent.com/ServiceStack/docs/master/docs/images/sharpscript/code-sc.gif)](https://youtu.be/TQPOZ0kVpw4) + +> YouTube: [youtu.be/TQPOZ0kVpw4](https://youtu.be/TQPOZ0kVpw4) +{{/markdown}} + diff --git a/src/wwwroot/sidebar.html b/src/wwwroot/sidebar.html index ca80101..9db85a7 100644 --- a/src/wwwroot/sidebar.html +++ b/src/wwwroot/sidebar.html @@ -11,6 +11,24 @@
docs
+
  • +
    code
    +
      + {{#each codeLinks}} +
    • {{Value}}
    • + {{/each}} +
    +
  • + +
  • +
    lisp
    +
      + {{#each lispLinks}} +
    • {{Value}}
    • + {{/each}} +
    +
  • +
  • use cases
      @@ -24,7 +42,7 @@
      use cases
      linq examples
      diff --git a/src/wwwroot/usecases/live-documents.html b/src/wwwroot/usecases/live-documents.html index b1ec264..2406057 100644 --- a/src/wwwroot/usecases/live-documents.html +++ b/src/wwwroot/usecases/live-documents.html @@ -31,8 +31,8 @@

      csv

      Tesla,Model 3,38990 Tesla,Model X,84990 {{/csv}} -{{ cars | map => { Make: it[0], Model: it[1], Cost: it[2] } | htmldump }} -Total Cost: {{ cars | sum => it[2] | currency }}{{/raw}} +{{ cars |> map => { Make: it[0], Model: it[1], Cost: it[2] } |> htmldump }} +Total Cost: {{ cars |> sum => it[2] |> currency }}{{/raw}}
  • diff --git a/wip/script-net.ss b/wip/script-net.ss new file mode 100644 index 0000000..684ee6e --- /dev/null +++ b/wip/script-net.ss @@ -0,0 +1,19 @@ +Type Examples + +{{#function info(o) }} + `${o.getType().typeQualifiedName()} '${o}'` | raw | return +{{/function}} + +```code +'DateTime'.new() | info +'int'.new() | info +'Int32'.new() | info +'System.Text.StringBuilder'.new() | info +'Dictionary'.new() | info + +'Ints'.new([1,2]) +'Ints'.new([1.0,2.0]) +'KeyValuePair'.new(['A',1]) +'KeyValuePair'.new(['A',1]) + +``` From a6b917580d88a2eb18e7dfae167a469ecc7b91a5 Mon Sep 17 00:00:00 2001 From: Demis Bellot Date: Sat, 28 Sep 2019 01:54:00 -0400 Subject: [PATCH 096/206] update linq examples to use JS pipeline operator --- src/wwwroot/code/linq01-langs.ss | 2 +- src/wwwroot/code/linq04.ss | 4 +- src/wwwroot/code/linq08.sc | 4 +- src/wwwroot/code/linq08.ss | 4 +- src/wwwroot/code/linq09.sc | 4 +- src/wwwroot/code/linq09.ss | 4 +- src/wwwroot/code/linq10.sc | 6 +- src/wwwroot/code/linq10.ss | 6 +- src/wwwroot/code/linq100.sc | 6 +- src/wwwroot/code/linq100.ss | 6 +- src/wwwroot/code/linq101.sc | 10 +-- src/wwwroot/code/linq101.ss | 14 ++-- src/wwwroot/code/linq11.sc | 2 +- src/wwwroot/code/linq11.ss | 4 +- src/wwwroot/code/linq12.sc | 2 +- src/wwwroot/code/linq12.ss | 2 +- src/wwwroot/code/linq13.sc | 4 +- src/wwwroot/code/linq13.ss | 4 +- src/wwwroot/code/linq14.sc | 10 +-- src/wwwroot/code/linq14.ss | 10 +-- src/wwwroot/code/linq15.sc | 10 +-- src/wwwroot/code/linq15.ss | 10 +-- src/wwwroot/code/linq16.sc | 10 +-- src/wwwroot/code/linq16.ss | 10 +-- src/wwwroot/code/linq17.sc | 10 +-- src/wwwroot/code/linq17.ss | 10 +-- src/wwwroot/code/linq18.sc | 14 ++-- src/wwwroot/code/linq18.ss | 14 ++-- src/wwwroot/code/linq19.sc | 8 +-- src/wwwroot/code/linq19.ss | 8 +-- src/wwwroot/code/linq20.sc | 2 +- src/wwwroot/code/linq20.ss | 2 +- src/wwwroot/code/linq21.sc | 12 ++-- src/wwwroot/code/linq21.ss | 12 ++-- src/wwwroot/code/linq22.sc | 2 +- src/wwwroot/code/linq22.ss | 2 +- src/wwwroot/code/linq23.sc | 12 ++-- src/wwwroot/code/linq23.ss | 12 ++-- src/wwwroot/code/linq24.sc | 4 +- src/wwwroot/code/linq24.ss | 6 +- src/wwwroot/code/linq25.sc | 4 +- src/wwwroot/code/linq25.ss | 6 +- src/wwwroot/code/linq26.sc | 4 +- src/wwwroot/code/linq26.ss | 6 +- src/wwwroot/code/linq27.sc | 4 +- src/wwwroot/code/linq27.ss | 6 +- src/wwwroot/code/linq28.sc | 2 +- src/wwwroot/code/linq28.ss | 2 +- src/wwwroot/code/linq29.sc | 2 +- src/wwwroot/code/linq29.ss | 2 +- src/wwwroot/code/linq30.sc | 2 +- src/wwwroot/code/linq30.ss | 4 +- src/wwwroot/code/linq31.sc | 4 +- src/wwwroot/code/linq31.ss | 6 +- src/wwwroot/code/linq32.sc | 2 +- src/wwwroot/code/linq32.ss | 2 +- src/wwwroot/code/linq33.sc | 2 +- src/wwwroot/code/linq33.ss | 4 +- src/wwwroot/code/linq34.sc | 4 +- src/wwwroot/code/linq34.ss | 6 +- src/wwwroot/code/linq35.sc | 4 +- src/wwwroot/code/linq35.ss | 8 +-- src/wwwroot/code/linq36.sc | 4 +- src/wwwroot/code/linq36.ss | 8 +-- src/wwwroot/code/linq37.sc | 2 +- src/wwwroot/code/linq37.ss | 6 +- src/wwwroot/code/linq38.sc | 4 +- src/wwwroot/code/linq38.ss | 8 +-- src/wwwroot/code/linq39.sc | 4 +- src/wwwroot/code/linq39.ss | 8 +-- src/wwwroot/code/linq40.sc | 10 +-- src/wwwroot/code/linq40.ss | 10 +-- src/wwwroot/code/linq41.sc | 6 +- src/wwwroot/code/linq41.ss | 6 +- src/wwwroot/code/linq42.sc | 2 +- src/wwwroot/code/linq42.ss | 6 +- src/wwwroot/code/linq43.sc | 4 +- src/wwwroot/code/linq43.ss | 4 +- src/wwwroot/code/linq44.sc | 4 +- src/wwwroot/code/linq44.ss | 4 +- src/wwwroot/code/linq45.sc | 4 +- src/wwwroot/code/linq45.ss | 4 +- src/wwwroot/code/linq46.sc | 4 +- src/wwwroot/code/linq46.ss | 4 +- src/wwwroot/code/linq47.sc | 2 +- src/wwwroot/code/linq47.ss | 6 +- src/wwwroot/code/linq48.sc | 4 +- src/wwwroot/code/linq48.ss | 4 +- src/wwwroot/code/linq49.sc | 4 +- src/wwwroot/code/linq49.ss | 8 +-- src/wwwroot/code/linq50.sc | 6 +- src/wwwroot/code/linq50.ss | 6 +- src/wwwroot/code/linq51.sc | 4 +- src/wwwroot/code/linq51.ss | 8 +-- src/wwwroot/code/linq52.sc | 6 +- src/wwwroot/code/linq52.ss | 6 +- src/wwwroot/code/linq53.sc | 4 +- src/wwwroot/code/linq53.ss | 8 +-- src/wwwroot/code/linq54.sc | 4 +- src/wwwroot/code/linq54.ss | 8 +-- src/wwwroot/code/linq55.sc | 4 +- src/wwwroot/code/linq55.ss | 8 +-- src/wwwroot/code/linq56.sc | 4 +- src/wwwroot/code/linq56.ss | 4 +- src/wwwroot/code/linq57.sc | 4 +- src/wwwroot/code/linq57.ss | 6 +- src/wwwroot/code/linq58.sc | 2 +- src/wwwroot/code/linq58.ss | 6 +- src/wwwroot/code/linq59.sc | 2 +- src/wwwroot/code/linq59.ss | 2 +- src/wwwroot/code/linq61.ss | 2 +- src/wwwroot/code/linq62.sc | 2 +- src/wwwroot/code/linq62.ss | 2 +- src/wwwroot/code/linq64.sc | 4 +- src/wwwroot/code/linq64.ss | 4 +- src/wwwroot/code/linq66.sc | 2 +- src/wwwroot/code/linq66.ss | 2 +- src/wwwroot/code/linq67.sc | 4 +- src/wwwroot/code/linq67.ss | 4 +- src/wwwroot/code/linq69.sc | 8 +-- src/wwwroot/code/linq69.ss | 8 +-- src/wwwroot/code/linq70.sc | 4 +- src/wwwroot/code/linq70.ss | 4 +- src/wwwroot/code/linq72.sc | 8 +-- src/wwwroot/code/linq72.ss | 8 +-- src/wwwroot/code/linq73.sc | 4 +- src/wwwroot/code/linq73.ss | 4 +- src/wwwroot/code/linq74.sc | 4 +- src/wwwroot/code/linq74.ss | 4 +- src/wwwroot/code/linq76.sc | 4 +- src/wwwroot/code/linq76.ss | 4 +- src/wwwroot/code/linq77.sc | 6 +- src/wwwroot/code/linq77.ss | 6 +- src/wwwroot/code/linq78.sc | 4 +- src/wwwroot/code/linq78.ss | 4 +- src/wwwroot/code/linq79.sc | 4 +- src/wwwroot/code/linq79.ss | 4 +- src/wwwroot/code/linq80.sc | 6 +- src/wwwroot/code/linq80.ss | 6 +- src/wwwroot/code/linq81.sc | 4 +- src/wwwroot/code/linq81.ss | 4 +- src/wwwroot/code/linq82.sc | 4 +- src/wwwroot/code/linq82.ss | 4 +- src/wwwroot/code/linq83.sc | 6 +- src/wwwroot/code/linq83.ss | 6 +- src/wwwroot/code/linq84.sc | 8 +-- src/wwwroot/code/linq84.ss | 8 +-- src/wwwroot/code/linq85.sc | 4 +- src/wwwroot/code/linq85.ss | 4 +- src/wwwroot/code/linq86.sc | 4 +- src/wwwroot/code/linq86.ss | 4 +- src/wwwroot/code/linq87.sc | 6 +- src/wwwroot/code/linq87.ss | 6 +- src/wwwroot/code/linq88.sc | 8 +-- src/wwwroot/code/linq88.ss | 8 +-- src/wwwroot/code/linq89.sc | 4 +- src/wwwroot/code/linq89.ss | 4 +- src/wwwroot/code/linq90.sc | 4 +- src/wwwroot/code/linq90.ss | 4 +- src/wwwroot/code/linq91.sc | 6 +- src/wwwroot/code/linq91.ss | 6 +- src/wwwroot/code/linq92.sc | 4 +- src/wwwroot/code/linq92.ss | 4 +- src/wwwroot/code/linq93.sc | 4 +- src/wwwroot/code/linq93.ss | 4 +- src/wwwroot/code/linq94.sc | 6 +- src/wwwroot/code/linq94.ss | 6 +- src/wwwroot/code/linq95.sc | 6 +- src/wwwroot/code/linq95.ss | 10 +-- src/wwwroot/code/linq96.sc | 6 +- src/wwwroot/code/linq96.ss | 8 +-- src/wwwroot/code/linq97.sc | 6 +- src/wwwroot/code/linq97.ss | 8 +-- src/wwwroot/code/linq99.sc | 6 +- src/wwwroot/code/linq99.ss | 6 +- src/wwwroot/code/mv.sc | 2 +- src/wwwroot/docs/arguments.html | 2 +- src/wwwroot/docs/blocks.html | 4 +- src/wwwroot/docs/default-scripts.html | 68 +++++++++---------- src/wwwroot/docs/error-handling.html | 12 ++-- src/wwwroot/docs/gist-desktop-apps.html | 8 +-- src/wwwroot/docs/html-scripts.html | 14 ++-- src/wwwroot/docs/introduction.html | 2 +- src/wwwroot/docs/methods.html | 2 +- src/wwwroot/docs/partials.html | 4 +- src/wwwroot/docs/protected-scripts.html | 2 +- src/wwwroot/docs/scripts-reference.html | 44 ++++++------ src/wwwroot/docs/servicestack-scripts.html | 6 +- src/wwwroot/docs/syntax.html | 10 +-- src/wwwroot/examples/adhoc-query-db.html | 6 +- src/wwwroot/examples/introspect-process.html | 2 +- src/wwwroot/examples/introspect.html | 4 +- src/wwwroot/examples/quote.html | 2 +- .../examples/sendToAutoQuery-data.html | 2 +- .../examples/sendToAutoQuery-rdms.html | 2 +- .../examples/sendtogateway-customers.html | 6 +- src/wwwroot/examples/webapps-menu.html | 2 +- src/wwwroot/gfm/adhoc-querying/04.html | 4 +- src/wwwroot/gfm/adhoc-querying/04.md | 14 ++-- src/wwwroot/gfm/blocks/00.html | 4 +- src/wwwroot/gfm/blocks/00.md | 4 +- src/wwwroot/gfm/blocks/19.html | 2 +- src/wwwroot/gfm/blocks/19.md | 2 +- src/wwwroot/gfm/blocks/24.html | 2 +- src/wwwroot/gfm/blocks/24.md | 2 +- src/wwwroot/gfm/blocks/27.html | 4 +- src/wwwroot/gfm/blocks/27.md | 4 +- src/wwwroot/gfm/introduction/08.html | 2 +- src/wwwroot/gfm/introduction/08.md | 2 +- src/wwwroot/gfm/protected-scripts/02.html | 14 ++-- src/wwwroot/gfm/protected-scripts/02.md | 14 ++-- src/wwwroot/gfm/scode/01.md | 6 +- src/wwwroot/gfm/script-net/Function.html | 32 ++++----- src/wwwroot/gfm/script-net/Function.md | 32 ++++----- src/wwwroot/gfm/script-net/call-methods.html | 4 +- src/wwwroot/gfm/script-net/call-methods.md | 4 +- .../gfm/script-net/create-instances.html | 8 +-- .../gfm/script-net/create-instances.md | 8 +-- src/wwwroot/gfm/script-plugins/01.html | 6 +- src/wwwroot/gfm/script-plugins/01.md | 6 +- src/wwwroot/gfm/servicestack-scripts/02.html | 2 +- src/wwwroot/gfm/servicestack-scripts/02.md | 2 +- src/wwwroot/gfm/sharp-apis/02.html | 8 +-- src/wwwroot/gfm/sharp-apis/02.md | 8 +-- src/wwwroot/gfm/sharp-apis/03.html | 8 +-- src/wwwroot/gfm/sharp-apis/03.md | 8 +-- src/wwwroot/gfm/sharp-apis/04.html | 2 +- src/wwwroot/gfm/sharp-apis/04.md | 4 +- src/wwwroot/gfm/sharp-apps/01.html | 2 +- src/wwwroot/gfm/sharp-apps/01.md | 2 +- src/wwwroot/gfm/sharp-apps/10.html | 2 +- src/wwwroot/gfm/sharp-apps/10.md | 2 +- src/wwwroot/gfm/sharp-apps/11.html | 4 +- src/wwwroot/gfm/sharp-apps/11.md | 4 +- src/wwwroot/gfm/sharp-apps/14.html | 2 +- src/wwwroot/gfm/sharp-apps/14.md | 8 +-- src/wwwroot/gfm/sharp-pages/10.html | 2 +- src/wwwroot/gfm/sharp-pages/10.md | 2 +- src/wwwroot/gfm/sharp-scripts/01.html | 4 +- src/wwwroot/gfm/sharp-scripts/01.md | 12 ++-- src/wwwroot/gfm/sharp-scripts/02.html | 2 +- src/wwwroot/gfm/sharp-scripts/02.md | 2 +- src/wwwroot/gfm/sharp-scripts/03.html | 4 +- src/wwwroot/gfm/sharp-scripts/03.md | 8 +-- src/wwwroot/gfm/sharp-scripts/04.html | 4 +- src/wwwroot/gfm/sharp-scripts/04.md | 8 +-- src/wwwroot/gfm/sharp-scripts/10.html | 6 +- src/wwwroot/gfm/sharp-scripts/10.md | 8 +-- src/wwwroot/gfm/sharp-scripts/12.html | 12 ++-- src/wwwroot/gfm/sharp-scripts/12.md | 12 ++-- src/wwwroot/gfm/syntax/03.html | 26 +++---- src/wwwroot/gfm/syntax/03.md | 26 +++---- src/wwwroot/gfm/syntax/04.md | 8 +-- src/wwwroot/gfm/syntax/05.html | 4 +- src/wwwroot/gfm/syntax/05.md | 4 +- src/wwwroot/gfm/syntax/06.md | 8 +-- src/wwwroot/gfm/syntax/07.html | 4 +- src/wwwroot/gfm/syntax/07.md | 4 +- src/wwwroot/gfm/syntax/08.html | 2 +- src/wwwroot/gfm/syntax/08.md | 2 +- src/wwwroot/gfm/syntax/09.html | 2 +- src/wwwroot/gfm/syntax/09.md | 2 +- src/wwwroot/gfm/syntax/10.html | 2 +- src/wwwroot/gfm/syntax/10.md | 2 +- src/wwwroot/linq-preview.html | 4 +- src/wwwroot/usecases/adhoc-querying.html | 2 +- src/wwwroot/usecases/email-templates.html | 2 +- src/wwwroot/usecases/introspect-state.html | 2 +- src/wwwroot/usecases/live-documents.html | 4 +- .../usecases/rockstar-files/menu-alive.html | 2 +- .../usecases/rockstar-files/menu-dead.html | 2 +- 271 files changed, 797 insertions(+), 797 deletions(-) diff --git a/src/wwwroot/code/linq01-langs.ss b/src/wwwroot/code/linq01-langs.ss index 346bdfa..0d42289 100644 --- a/src/wwwroot/code/linq01-langs.ss +++ b/src/wwwroot/code/linq01-langs.ss @@ -1,4 +1,4 @@ -{{ [5, 4, 1, 3, 9, 8, 6, 7, 2, 0] | to => numbers }} +{{ [5, 4, 1, 3, 9, 8, 6, 7, 2, 0] |> to => numbers }} code: ```code diff --git a/src/wwwroot/code/linq04.ss b/src/wwwroot/code/linq04.ss index 7805f73..799b905 100644 --- a/src/wwwroot/code/linq04.ss +++ b/src/wwwroot/code/linq04.ss @@ -1,7 +1,7 @@ Customers from Washington and their orders: {{#each c in customers where c.Region == 'WA'}} -Customer {{ c.CustomerId }} {{ c.CompanyName | raw }} +Customer {{ c.CustomerId }} {{ c.CompanyName |> raw }} {{#each c.Orders}} - Order {{ OrderId }}: {{ OrderDate | dateFormat }} + Order {{ OrderId }}: {{ OrderDate |> dateFormat }} {{/each}} {{/each}} \ No newline at end of file diff --git a/src/wwwroot/code/linq08.sc b/src/wwwroot/code/linq08.sc index 09c4baa..15b7ef3 100644 --- a/src/wwwroot/code/linq08.sc +++ b/src/wwwroot/code/linq08.sc @@ -1,5 +1,5 @@ -[5, 4, 1, 3, 9, 8, 6, 7, 2, 0] | to => numbers -['zero','one','two','three','four','five','six','seven','eight','nine'] | to => strings +[5, 4, 1, 3, 9, 8, 6, 7, 2, 0] |> to => numbers +['zero','one','two','three','four','five','six','seven','eight','nine'] |> to => strings `Number strings:` #each n in numbers strings[n] diff --git a/src/wwwroot/code/linq08.ss b/src/wwwroot/code/linq08.ss index 8f3c50c..290dbe0 100644 --- a/src/wwwroot/code/linq08.ss +++ b/src/wwwroot/code/linq08.ss @@ -1,5 +1,5 @@ -{{ [5, 4, 1, 3, 9, 8, 6, 7, 2, 0] | to => numbers }} -{{ ['zero','one','two','three','four','five','six','seven','eight','nine'] | to => strings }} +{{ [5, 4, 1, 3, 9, 8, 6, 7, 2, 0] |> to => numbers }} +{{ ['zero','one','two','three','four','five','six','seven','eight','nine'] |> to => strings }} Number strings: {{#each n in numbers}} {{strings[n]}} diff --git a/src/wwwroot/code/linq09.sc b/src/wwwroot/code/linq09.sc index bb21f2f..f1956d2 100644 --- a/src/wwwroot/code/linq09.sc +++ b/src/wwwroot/code/linq09.sc @@ -1,5 +1,5 @@ -['aPPLE', 'BlUeBeRrY', 'cHeRry'] | to => words -words | map => { Uppercase: it.upper(), Lowercase: it.lower() } | to => upperLowerWords +['aPPLE', 'BlUeBeRrY', 'cHeRry'] |> to => words +words |> map => { Uppercase: it.upper(), Lowercase: it.lower() } |> to => upperLowerWords #each ul in upperLowerWords `Uppercase: ${ul.Uppercase}, Lowercase: ${ul.Lowercase}` /each \ No newline at end of file diff --git a/src/wwwroot/code/linq09.ss b/src/wwwroot/code/linq09.ss index ae3a461..9d59608 100644 --- a/src/wwwroot/code/linq09.ss +++ b/src/wwwroot/code/linq09.ss @@ -1,5 +1,5 @@ -{{ ['aPPLE', 'BlUeBeRrY', 'cHeRry'] | to => words }} -{{ words | map => {Uppercase: it.upper(), Lowercase: it.lower()} | to => upperLowerWords }} +{{ ['aPPLE', 'BlUeBeRrY', 'cHeRry'] |> to => words }} +{{ words |> map => {Uppercase: it.upper(), Lowercase: it.lower()} |> to => upperLowerWords }} {{#each ul in upperLowerWords}} {{ `Uppercase: ${ul.Uppercase}, Lowercase: ${ul.Lowercase}` }} {{/each}} \ No newline at end of file diff --git a/src/wwwroot/code/linq10.sc b/src/wwwroot/code/linq10.sc index e9d4812..4990c86 100644 --- a/src/wwwroot/code/linq10.sc +++ b/src/wwwroot/code/linq10.sc @@ -1,6 +1,6 @@ -[5, 4, 1, 3, 9, 8, 6, 7, 2, 0] | to => numbers -['zero','one','two','three','four','five','six','seven','eight','nine'] | to => strings -numbers | map => { Digit: strings[it], Even: it % 2 == 0 } | to => digitOddEvens +[5, 4, 1, 3, 9, 8, 6, 7, 2, 0] |> to => numbers +['zero','one','two','three','four','five','six','seven','eight','nine'] |> to => strings +numbers |> map => { Digit: strings[it], Even: it % 2 == 0 } |> to => digitOddEvens #each digitOddEvens `The digit ${Digit} is ${Even ? "even" : "odd"}.` /each \ No newline at end of file diff --git a/src/wwwroot/code/linq10.ss b/src/wwwroot/code/linq10.ss index 3fc77ce..187eef8 100644 --- a/src/wwwroot/code/linq10.ss +++ b/src/wwwroot/code/linq10.ss @@ -1,6 +1,6 @@ -{{ [5, 4, 1, 3, 9, 8, 6, 7, 2, 0] | to => numbers }} -{{ ['zero','one','two','three','four','five','six','seven','eight','nine'] | to => strings}} -{{ numbers | map => { Digit: strings[it], Even: it % 2 == 0 } | to => digitOddEvens }} +{{ [5, 4, 1, 3, 9, 8, 6, 7, 2, 0] |> to => numbers }} +{{ ['zero','one','two','three','four','five','six','seven','eight','nine'] |> to => strings}} +{{ numbers |> map => { Digit: strings[it], Even: it % 2 == 0 } |> to => digitOddEvens }} {{#each digitOddEvens}} The digit {{Digit}} is {{Even ? "even" : "odd"}}. {{/each}} \ No newline at end of file diff --git a/src/wwwroot/code/linq100.sc b/src/wwwroot/code/linq100.sc index 8f6ca69..8ed6800 100644 --- a/src/wwwroot/code/linq100.sc +++ b/src/wwwroot/code/linq100.sc @@ -1,3 +1,3 @@ -[5, 4, 1, 3, 9, 8, 6, 7, 2, 0] | to => numbers -0 | to => i -numbers | let => { i: i + 1 } | toList | map => `v = ${index + 1}, i = ${i}` | joinln \ No newline at end of file +[5, 4, 1, 3, 9, 8, 6, 7, 2, 0] |> to => numbers +0 |> to => i +numbers |> let => { i: i + 1 } |> toList |> map => `v = ${index + 1}, i = ${i}` |> joinln \ No newline at end of file diff --git a/src/wwwroot/code/linq100.ss b/src/wwwroot/code/linq100.ss index c946516..0819dda 100644 --- a/src/wwwroot/code/linq100.ss +++ b/src/wwwroot/code/linq100.ss @@ -1,3 +1,3 @@ -{{ [5, 4, 1, 3, 9, 8, 6, 7, 2, 0] | to => numbers }} -{{ 0 | to => i }} -{{ numbers | let => { i: i + 1 } | toList | select: v = {index + 1}, i = {i}\n }} \ No newline at end of file +{{ [5, 4, 1, 3, 9, 8, 6, 7, 2, 0] |> to => numbers }} +{{ 0 |> to => i }} +{{ numbers |> let => { i: i + 1 } |> toList |> select: v = {index + 1}, i = {i}\n }} \ No newline at end of file diff --git a/src/wwwroot/code/linq101.sc b/src/wwwroot/code/linq101.sc index db21959..78f9be5 100644 --- a/src/wwwroot/code/linq101.sc +++ b/src/wwwroot/code/linq101.sc @@ -1,12 +1,12 @@ -[5, 4, 1, 3, 9, 8, 6, 7, 2, 0] | to => numbers -numbers | where => it <= 3 | to => lowNumbers +[5, 4, 1, 3, 9, 8, 6, 7, 2, 0] |> to => numbers +numbers |> where => it <= 3 |> to => lowNumbers `First run numbers <= 3:` -lowNumbers | joinln +lowNumbers |> joinln 10 | times | do => assign('numbers[index]', -numbers[index]) `Second run numbers <= 3:` -lowNumbers | joinln +lowNumbers |> joinln `Contents of numbers:` -numbers | joinln \ No newline at end of file +numbers |> joinln \ No newline at end of file diff --git a/src/wwwroot/code/linq101.ss b/src/wwwroot/code/linq101.ss index b97f13a..f4c5f40 100644 --- a/src/wwwroot/code/linq101.ss +++ b/src/wwwroot/code/linq101.ss @@ -1,11 +1,11 @@ -{{ [5, 4, 1, 3, 9, 8, 6, 7, 2, 0] | to => numbers }} +{{ [5, 4, 1, 3, 9, 8, 6, 7, 2, 0] |> to => numbers }} {{ numbers - | where => it <= 3 - | to => lowNumbers }} + |> where => it <= 3 + |> to => lowNumbers }} First run numbers <= 3: -{{ lowNumbers | joinln }} -{{ 10 | times | do => assign('numbers[index]', -numbers[index]) }} +{{ lowNumbers |> joinln }} +{{ 10 |> times |> do => assign('numbers[index]', -numbers[index]) }} Second run numbers <= 3: -{{ lowNumbers | joinln }} +{{ lowNumbers |> joinln }} Contents of numbers: -{{ numbers | joinln }} \ No newline at end of file +{{ numbers |> joinln }} \ No newline at end of file diff --git a/src/wwwroot/code/linq11.sc b/src/wwwroot/code/linq11.sc index 54650d1..7fd5bf5 100644 --- a/src/wwwroot/code/linq11.sc +++ b/src/wwwroot/code/linq11.sc @@ -1,5 +1,5 @@ `Product Info:` -products | map => { it.ProductName, it.Category, Price: it.UnitPrice } | to => productInfos +products |> map => { it.ProductName, it.Category, Price: it.UnitPrice } |> to => productInfos #each productInfos `${ProductName} is in the Category ${Category} and costs ${Price.currency()} per unit.` /each \ No newline at end of file diff --git a/src/wwwroot/code/linq11.ss b/src/wwwroot/code/linq11.ss index e0c105e..f9a1629 100644 --- a/src/wwwroot/code/linq11.ss +++ b/src/wwwroot/code/linq11.ss @@ -1,6 +1,6 @@ Product Info: -{{ products | map => { it.ProductName, it.Category, Price: it.UnitPrice } - | to => productInfos }} +{{ products |> map => { it.ProductName, it.Category, Price: it.UnitPrice } + |> to => productInfos }} {{#each productInfos}} {{ProductName}} is in the Category {{Category}} and costs {{Price | currency}} per unit. {{/each}} \ No newline at end of file diff --git a/src/wwwroot/code/linq12.sc b/src/wwwroot/code/linq12.sc index 8993d82..2d23251 100644 --- a/src/wwwroot/code/linq12.sc +++ b/src/wwwroot/code/linq12.sc @@ -1,4 +1,4 @@ -[5, 4, 1, 3, 9, 8, 6, 7, 2, 0] | to => numbers +[5, 4, 1, 3, 9, 8, 6, 7, 2, 0] |> to => numbers `Number: In-place?` #each n in numbers `${n}: ${ n == index }` diff --git a/src/wwwroot/code/linq12.ss b/src/wwwroot/code/linq12.ss index 20581b5..2790b32 100644 --- a/src/wwwroot/code/linq12.ss +++ b/src/wwwroot/code/linq12.ss @@ -1,4 +1,4 @@ -{{ [5, 4, 1, 3, 9, 8, 6, 7, 2, 0] | to => numbers }} +{{ [5, 4, 1, 3, 9, 8, 6, 7, 2, 0] |> to => numbers }} Number: In-place? {{#each n in numbers}} {{n}}: {{ n == index }} diff --git a/src/wwwroot/code/linq13.sc b/src/wwwroot/code/linq13.sc index 4904a24..fc8574b 100644 --- a/src/wwwroot/code/linq13.sc +++ b/src/wwwroot/code/linq13.sc @@ -1,5 +1,5 @@ -[5, 4, 1, 3, 9, 8, 6, 7, 2, 0] | to => numbers -['zero','one','two','three','four','five','six','seven','eight','nine'] | to => digits +[5, 4, 1, 3, 9, 8, 6, 7, 2, 0] |> to => numbers +['zero','one','two','three','four','five','six','seven','eight','nine'] |> to => digits `Numbers < 5:` #each numbers where it < 5 digits[it] diff --git a/src/wwwroot/code/linq13.ss b/src/wwwroot/code/linq13.ss index 05a4271..95e9414 100644 --- a/src/wwwroot/code/linq13.ss +++ b/src/wwwroot/code/linq13.ss @@ -1,5 +1,5 @@ -{{ [5, 4, 1, 3, 9, 8, 6, 7, 2, 0] | to => numbers }} -{{ ['zero','one','two','three','four','five','six','seven','eight','nine'] | to =>digits }} +{{ [5, 4, 1, 3, 9, 8, 6, 7, 2, 0] |> to => numbers }} +{{ ['zero','one','two','three','four','five','six','seven','eight','nine'] |> to =>digits }} Numbers < 5: {{#each numbers where it < 5}} {{ digits[it] }} diff --git a/src/wwwroot/code/linq14.sc b/src/wwwroot/code/linq14.sc index 81b92f2..a70357d 100644 --- a/src/wwwroot/code/linq14.sc +++ b/src/wwwroot/code/linq14.sc @@ -1,8 +1,8 @@ -[0, 2, 4, 5, 6, 8, 9] | to => numbersA -[1, 3, 5, 7, 8] | to => numbersB +[0, 2, 4, 5, 6, 8, 9] |> to => numbersA +[1, 3, 5, 7, 8] |> to => numbersB `Pairs where a < b:` {{ numbersA.zip(numbersB) - | let => { a: it[0], b: it[1] } - | where => a < b - | map => `${a} is less than ${b}` | joinln + |> let => { a: it[0], b: it[1] } + |> where => a < b + |> map => `${a} is less than ${b}` |> joinln }} \ No newline at end of file diff --git a/src/wwwroot/code/linq14.ss b/src/wwwroot/code/linq14.ss index 4c57a0d..b33cdd0 100644 --- a/src/wwwroot/code/linq14.ss +++ b/src/wwwroot/code/linq14.ss @@ -1,8 +1,8 @@ -{{ [0, 2, 4, 5, 6, 8, 9] | to => numbersA }} -{{ [1, 3, 5, 7, 8] | to => numbersB }} +{{ [0, 2, 4, 5, 6, 8, 9] |> to => numbersA }} +{{ [1, 3, 5, 7, 8] |> to => numbersB }} Pairs where a < b: {{ numbersA.zip(numbersB) - | let => { a: it[0], b: it[1] } - | where => a < b - | map => `${a} is less than ${b}` | joinln + |> let => { a: it[0], b: it[1] } + |> where => a < b + |> map => `${a} is less than ${b}` |> joinln }} \ No newline at end of file diff --git a/src/wwwroot/code/linq15.sc b/src/wwwroot/code/linq15.sc index 5f77aa3..c7272f0 100644 --- a/src/wwwroot/code/linq15.sc +++ b/src/wwwroot/code/linq15.sc @@ -1,5 +1,5 @@ -{{ customers | zip => it.Orders - | let => { c: it[0], o: it[1] } - | where => o.Total < 500 - | map => o - | htmlDump }} \ No newline at end of file +{{ customers |> zip => it.Orders + |> let => { c: it[0], o: it[1] } + |> where => o.Total < 500 + |> map => o + |> htmlDump }} \ No newline at end of file diff --git a/src/wwwroot/code/linq15.ss b/src/wwwroot/code/linq15.ss index 5f77aa3..c7272f0 100644 --- a/src/wwwroot/code/linq15.ss +++ b/src/wwwroot/code/linq15.ss @@ -1,5 +1,5 @@ -{{ customers | zip => it.Orders - | let => { c: it[0], o: it[1] } - | where => o.Total < 500 - | map => o - | htmlDump }} \ No newline at end of file +{{ customers |> zip => it.Orders + |> let => { c: it[0], o: it[1] } + |> where => o.Total < 500 + |> map => o + |> htmlDump }} \ No newline at end of file diff --git a/src/wwwroot/code/linq16.sc b/src/wwwroot/code/linq16.sc index 674e807..c8b1c74 100644 --- a/src/wwwroot/code/linq16.sc +++ b/src/wwwroot/code/linq16.sc @@ -1,5 +1,5 @@ -{{ customers | zip => it.Orders - | let => { c: it[0], o: it[1] } - | where => o.OrderDate >= '1998-01-01' - | map => o - | htmlDump }} \ No newline at end of file +{{ customers |> zip => it.Orders + |> let => { c: it[0], o: it[1] } + |> where => o.OrderDate >= '1998-01-01' + |> map => o + |> htmlDump }} \ No newline at end of file diff --git a/src/wwwroot/code/linq16.ss b/src/wwwroot/code/linq16.ss index 674e807..c8b1c74 100644 --- a/src/wwwroot/code/linq16.ss +++ b/src/wwwroot/code/linq16.ss @@ -1,5 +1,5 @@ -{{ customers | zip => it.Orders - | let => { c: it[0], o: it[1] } - | where => o.OrderDate >= '1998-01-01' - | map => o - | htmlDump }} \ No newline at end of file +{{ customers |> zip => it.Orders + |> let => { c: it[0], o: it[1] } + |> where => o.OrderDate >= '1998-01-01' + |> map => o + |> htmlDump }} \ No newline at end of file diff --git a/src/wwwroot/code/linq17.sc b/src/wwwroot/code/linq17.sc index 6ee81ae..e0543af 100644 --- a/src/wwwroot/code/linq17.sc +++ b/src/wwwroot/code/linq17.sc @@ -1,5 +1,5 @@ -{{ customers | zip => it.Orders - | let => { c: it[0], o: it[1] } - | where => o.Total >= 2000 - | map => o - | htmlDump }} \ No newline at end of file +{{ customers |> zip => it.Orders + |> let => { c: it[0], o: it[1] } + |> where => o.Total >= 2000 + |> map => o + |> htmlDump }} \ No newline at end of file diff --git a/src/wwwroot/code/linq17.ss b/src/wwwroot/code/linq17.ss index 6ee81ae..e0543af 100644 --- a/src/wwwroot/code/linq17.ss +++ b/src/wwwroot/code/linq17.ss @@ -1,5 +1,5 @@ -{{ customers | zip => it.Orders - | let => { c: it[0], o: it[1] } - | where => o.Total >= 2000 - | map => o - | htmlDump }} \ No newline at end of file +{{ customers |> zip => it.Orders + |> let => { c: it[0], o: it[1] } + |> where => o.Total >= 2000 + |> map => o + |> htmlDump }} \ No newline at end of file diff --git a/src/wwwroot/code/linq18.sc b/src/wwwroot/code/linq18.sc index dae7bda..989d4ee 100644 --- a/src/wwwroot/code/linq18.sc +++ b/src/wwwroot/code/linq18.sc @@ -1,8 +1,8 @@ -'1997-01-01' | to => cutoffDate +'1997-01-01' |> to => cutoffDate {{ customers - | where => it.Region = 'WA' - | zip => it.Orders - | let => { c: it[0], o: it[1] } - | where => o.OrderDate >= cutoffDate - | map => o - | htmlDump }} \ No newline at end of file + |> where => it.Region = 'WA' + |> zip => it.Orders + |> let => { c: it[0], o: it[1] } + |> where => o.OrderDate >= cutoffDate + |> map => o + |> htmlDump }} \ No newline at end of file diff --git a/src/wwwroot/code/linq18.ss b/src/wwwroot/code/linq18.ss index 35c7542..a9e4380 100644 --- a/src/wwwroot/code/linq18.ss +++ b/src/wwwroot/code/linq18.ss @@ -1,8 +1,8 @@ -{{ '1997-01-01' | to => cutoffDate }} +{{ '1997-01-01' |> to => cutoffDate }} {{ customers - | where => it.Region = 'WA' - | zip => it.Orders - | let => { c: it[0], o: it[1] } - | where => o.OrderDate >= cutoffDate - | map => o - | htmlDump }} \ No newline at end of file + |> where => it.Region = 'WA' + |> zip => it.Orders + |> let => { c: it[0], o: it[1] } + |> where => o.OrderDate >= cutoffDate + |> map => o + |> htmlDump }} \ No newline at end of file diff --git a/src/wwwroot/code/linq19.sc b/src/wwwroot/code/linq19.sc index 6216d01..d5bfc6b 100644 --- a/src/wwwroot/code/linq19.sc +++ b/src/wwwroot/code/linq19.sc @@ -1,5 +1,5 @@ {{ customers - | let => { cust: it, custIndex: index } - | zip => cust.Orders - | let => { o: it[1] } - | map => `Customer #${custIndex + 1} has an order with OrderID ${o.OrderId}` | joinln }} \ No newline at end of file + |> let => { cust: it, custIndex: index } + |> zip => cust.Orders + |> let => { o: it[1] } + |> map => `Customer #${custIndex + 1} has an order with OrderID ${o.OrderId}` |> joinln }} \ No newline at end of file diff --git a/src/wwwroot/code/linq19.ss b/src/wwwroot/code/linq19.ss index 6216d01..d5bfc6b 100644 --- a/src/wwwroot/code/linq19.ss +++ b/src/wwwroot/code/linq19.ss @@ -1,5 +1,5 @@ {{ customers - | let => { cust: it, custIndex: index } - | zip => cust.Orders - | let => { o: it[1] } - | map => `Customer #${custIndex + 1} has an order with OrderID ${o.OrderId}` | joinln }} \ No newline at end of file + |> let => { cust: it, custIndex: index } + |> zip => cust.Orders + |> let => { o: it[1] } + |> map => `Customer #${custIndex + 1} has an order with OrderID ${o.OrderId}` |> joinln }} \ No newline at end of file diff --git a/src/wwwroot/code/linq20.sc b/src/wwwroot/code/linq20.sc index c64848c..82fd2fb 100644 --- a/src/wwwroot/code/linq20.sc +++ b/src/wwwroot/code/linq20.sc @@ -1,4 +1,4 @@ -[5, 4, 1, 3, 9, 8, 6, 7, 2, 0] | to => numbers +[5, 4, 1, 3, 9, 8, 6, 7, 2, 0] |> to => numbers `First 3 numbers:` #each numbers take 3 it diff --git a/src/wwwroot/code/linq20.ss b/src/wwwroot/code/linq20.ss index 0ada0e1..ab2c926 100644 --- a/src/wwwroot/code/linq20.ss +++ b/src/wwwroot/code/linq20.ss @@ -1,4 +1,4 @@ -{{ [5, 4, 1, 3, 9, 8, 6, 7, 2, 0] | to => numbers }} +{{ [5, 4, 1, 3, 9, 8, 6, 7, 2, 0] |> to => numbers }} First 3 numbers: {{#each numbers take 3}} {{it}} diff --git a/src/wwwroot/code/linq21.sc b/src/wwwroot/code/linq21.sc index 9ee1bf2..fb034a9 100644 --- a/src/wwwroot/code/linq21.sc +++ b/src/wwwroot/code/linq21.sc @@ -1,7 +1,7 @@ ` First 3 orders in WA:` -{{ customers | zip => it.Orders - | let => { c: it[0], o: it[1] } - | where => c.Region = 'WA' - | take(3) - | map => o - | htmlDump }} \ No newline at end of file +{{ customers |> zip => it.Orders + |> let => { c: it[0], o: it[1] } + |> where => c.Region = 'WA' + |> take(3) + |> map => o + |> htmlDump }} \ No newline at end of file diff --git a/src/wwwroot/code/linq21.ss b/src/wwwroot/code/linq21.ss index 67db5c9..ed19801 100644 --- a/src/wwwroot/code/linq21.ss +++ b/src/wwwroot/code/linq21.ss @@ -1,7 +1,7 @@ First 3 orders in WA: -{{ customers | zip => it.Orders - | let => { c: it[0], o: it[1] } - | where => c.Region = 'WA' - | take(3) - | map => o - | htmlDump }} \ No newline at end of file +{{ customers |> zip => it.Orders + |> let => { c: it[0], o: it[1] } + |> where => c.Region = 'WA' + |> take(3) + |> map => o + |> htmlDump }} \ No newline at end of file diff --git a/src/wwwroot/code/linq22.sc b/src/wwwroot/code/linq22.sc index 64fc9cb..6073d27 100644 --- a/src/wwwroot/code/linq22.sc +++ b/src/wwwroot/code/linq22.sc @@ -1,4 +1,4 @@ -[5, 4, 1, 3, 9, 8, 6, 7, 2, 0] | to => numbers +[5, 4, 1, 3, 9, 8, 6, 7, 2, 0] |> to => numbers `All but first 4 numbers:` #each numbers skip 4 it diff --git a/src/wwwroot/code/linq22.ss b/src/wwwroot/code/linq22.ss index 7d6b0df..76d215e 100644 --- a/src/wwwroot/code/linq22.ss +++ b/src/wwwroot/code/linq22.ss @@ -1,4 +1,4 @@ -{{ [5, 4, 1, 3, 9, 8, 6, 7, 2, 0] | to => numbers }} +{{ [5, 4, 1, 3, 9, 8, 6, 7, 2, 0] |> to => numbers }} All but first 4 numbers: {{#each numbers skip 4}} {{it}} diff --git a/src/wwwroot/code/linq23.sc b/src/wwwroot/code/linq23.sc index 93f104d..646ebf9 100644 --- a/src/wwwroot/code/linq23.sc +++ b/src/wwwroot/code/linq23.sc @@ -1,7 +1,7 @@ ` All but first 2 orders in WA:` -{{ customers | zip => it.Orders - | let => { c: it[0], o: it[1] } - | where => c.Region = 'WA' - | skip(2) - | map => { c.CustomerId, o.OrderId, o.OrderDate } - | htmlDump }} \ No newline at end of file +{{ customers |> zip => it.Orders + |> let => { c: it[0], o: it[1] } + |> where => c.Region = 'WA' + |> skip(2) + |> map => { c.CustomerId, o.OrderId, o.OrderDate } + |> htmlDump }} \ No newline at end of file diff --git a/src/wwwroot/code/linq23.ss b/src/wwwroot/code/linq23.ss index b51fc8f..58cdbaf 100644 --- a/src/wwwroot/code/linq23.ss +++ b/src/wwwroot/code/linq23.ss @@ -1,7 +1,7 @@ All but first 2 orders in WA: -{{ customers | zip => it.Orders - | let => { c: it[0], o: it[1] } - | where => c.Region = 'WA' - | skip(2) - | map => { c.CustomerId, o.OrderId, o.OrderDate } - | htmlDump }} \ No newline at end of file +{{ customers |> zip => it.Orders + |> let => { c: it[0], o: it[1] } + |> where => c.Region = 'WA' + |> skip(2) + |> map => { c.CustomerId, o.OrderId, o.OrderDate } + |> htmlDump }} \ No newline at end of file diff --git a/src/wwwroot/code/linq24.sc b/src/wwwroot/code/linq24.sc index 216ea92..0684405 100644 --- a/src/wwwroot/code/linq24.sc +++ b/src/wwwroot/code/linq24.sc @@ -1,3 +1,3 @@ -[5, 4, 1, 3, 9, 8, 6, 7, 2, 0] | to => numbers +[5, 4, 1, 3, 9, 8, 6, 7, 2, 0] |> to => numbers `First numbers less than 6:` -numbers | takeWhile => it < 6 | joinln \ No newline at end of file +numbers |> takeWhile => it < 6 |> joinln \ No newline at end of file diff --git a/src/wwwroot/code/linq24.ss b/src/wwwroot/code/linq24.ss index c8257fc..9714909 100644 --- a/src/wwwroot/code/linq24.ss +++ b/src/wwwroot/code/linq24.ss @@ -1,5 +1,5 @@ -{{ [5, 4, 1, 3, 9, 8, 6, 7, 2, 0] | to => numbers }} +{{ [5, 4, 1, 3, 9, 8, 6, 7, 2, 0] |> to => numbers }} First numbers less than 6: {{ numbers - | takeWhile => it < 6 - | joinln }} \ No newline at end of file + |> takeWhile => it < 6 + |> joinln }} \ No newline at end of file diff --git a/src/wwwroot/code/linq25.sc b/src/wwwroot/code/linq25.sc index a5a1dcf..93357ed 100644 --- a/src/wwwroot/code/linq25.sc +++ b/src/wwwroot/code/linq25.sc @@ -1,3 +1,3 @@ -[5, 4, 1, 3, 9, 8, 6, 7, 2, 0] | to => numbers +[5, 4, 1, 3, 9, 8, 6, 7, 2, 0] |> to => numbers `First numbers not less than their position:` -numbers | takeWhile => it >= index | joinln \ No newline at end of file +numbers |> takeWhile => it >= index |> joinln \ No newline at end of file diff --git a/src/wwwroot/code/linq25.ss b/src/wwwroot/code/linq25.ss index dcdb3a8..ac90d5e 100644 --- a/src/wwwroot/code/linq25.ss +++ b/src/wwwroot/code/linq25.ss @@ -1,5 +1,5 @@ -{{ [5, 4, 1, 3, 9, 8, 6, 7, 2, 0] | to => numbers }} +{{ [5, 4, 1, 3, 9, 8, 6, 7, 2, 0] |> to => numbers }} First numbers not less than their position: {{ numbers - | takeWhile => it >= index - | joinln }} \ No newline at end of file + |> takeWhile => it >= index + |> joinln }} \ No newline at end of file diff --git a/src/wwwroot/code/linq26.sc b/src/wwwroot/code/linq26.sc index 1ab00b1..74771af 100644 --- a/src/wwwroot/code/linq26.sc +++ b/src/wwwroot/code/linq26.sc @@ -1,3 +1,3 @@ -[5, 4, 1, 3, 9, 8, 6, 7, 2, 0] | to => numbers +[5, 4, 1, 3, 9, 8, 6, 7, 2, 0] |> to => numbers `All elements starting from first element divisible by 3:` -numbers | skipWhile => it % 3 != 0 | joinln \ No newline at end of file +numbers |> skipWhile => it % 3 != 0 |> joinln \ No newline at end of file diff --git a/src/wwwroot/code/linq26.ss b/src/wwwroot/code/linq26.ss index ab12f35..ed93b85 100644 --- a/src/wwwroot/code/linq26.ss +++ b/src/wwwroot/code/linq26.ss @@ -1,5 +1,5 @@ -{{ [5, 4, 1, 3, 9, 8, 6, 7, 2, 0] | to => numbers }} +{{ [5, 4, 1, 3, 9, 8, 6, 7, 2, 0] |> to => numbers }} All elements starting from first element divisible by 3: {{ numbers - | skipWhile => it % 3 != 0 - | joinln }} \ No newline at end of file + |> skipWhile => it % 3 != 0 + |> joinln }} \ No newline at end of file diff --git a/src/wwwroot/code/linq27.sc b/src/wwwroot/code/linq27.sc index 48255f1..5d558be 100644 --- a/src/wwwroot/code/linq27.sc +++ b/src/wwwroot/code/linq27.sc @@ -1,3 +1,3 @@ -[5, 4, 1, 3, 9, 8, 6, 7, 2, 0] | to => numbers +[5, 4, 1, 3, 9, 8, 6, 7, 2, 0] |> to => numbers `All elements starting from first element less than its position:` -numbers | skipWhile => it >= index | joinln \ No newline at end of file +numbers |> skipWhile => it >= index |> joinln \ No newline at end of file diff --git a/src/wwwroot/code/linq27.ss b/src/wwwroot/code/linq27.ss index 5d33123..45ecf10 100644 --- a/src/wwwroot/code/linq27.ss +++ b/src/wwwroot/code/linq27.ss @@ -1,5 +1,5 @@ -{{ [5, 4, 1, 3, 9, 8, 6, 7, 2, 0] | to => numbers }} +{{ [5, 4, 1, 3, 9, 8, 6, 7, 2, 0] |> to => numbers }} All elements starting from first element less than its position: {{ numbers - | skipWhile => it >= index - | joinln }} \ No newline at end of file + |> skipWhile => it >= index + |> joinln }} \ No newline at end of file diff --git a/src/wwwroot/code/linq28.sc b/src/wwwroot/code/linq28.sc index c6ebb39..065d764 100644 --- a/src/wwwroot/code/linq28.sc +++ b/src/wwwroot/code/linq28.sc @@ -1,4 +1,4 @@ -['cherry', 'apple', 'blueberry'] | to => words +['cherry', 'apple', 'blueberry'] |> to => words `The sorted list of words:` #each words orderby it it diff --git a/src/wwwroot/code/linq28.ss b/src/wwwroot/code/linq28.ss index 1c1b3b3..799c19d 100644 --- a/src/wwwroot/code/linq28.ss +++ b/src/wwwroot/code/linq28.ss @@ -1,4 +1,4 @@ -{{ ['cherry', 'apple', 'blueberry'] | to => words }} +{{ ['cherry', 'apple', 'blueberry'] |> to => words }} The sorted list of words: {{#each words orderby it}} {{it}} diff --git a/src/wwwroot/code/linq29.sc b/src/wwwroot/code/linq29.sc index ed9c33b..2557cd1 100644 --- a/src/wwwroot/code/linq29.sc +++ b/src/wwwroot/code/linq29.sc @@ -1,4 +1,4 @@ -['cherry', 'apple', 'blueberry'] | to => words +['cherry', 'apple', 'blueberry'] |> to => words `The sorted list of words (by length):` #each words orderby it.Length it diff --git a/src/wwwroot/code/linq29.ss b/src/wwwroot/code/linq29.ss index 9fdb3aa..09e312f 100644 --- a/src/wwwroot/code/linq29.ss +++ b/src/wwwroot/code/linq29.ss @@ -1,4 +1,4 @@ -{{ ['cherry', 'apple', 'blueberry'] | to => words }} +{{ ['cherry', 'apple', 'blueberry'] |> to => words }} The sorted list of words (by length): {{#each words orderby it.Length}} {{it}} diff --git a/src/wwwroot/code/linq30.sc b/src/wwwroot/code/linq30.sc index 106c156..3a5a6e7 100644 --- a/src/wwwroot/code/linq30.sc +++ b/src/wwwroot/code/linq30.sc @@ -1 +1 @@ -products | orderBy => it.ProductName | htmlDump \ No newline at end of file +products |> orderBy => it.ProductName |> htmlDump \ No newline at end of file diff --git a/src/wwwroot/code/linq30.ss b/src/wwwroot/code/linq30.ss index 513a4b6..2194e0c 100644 --- a/src/wwwroot/code/linq30.ss +++ b/src/wwwroot/code/linq30.ss @@ -1,3 +1,3 @@ {{ products - | orderBy => it.ProductName - | htmlDump }} \ No newline at end of file + |> orderBy => it.ProductName + |> htmlDump }} \ No newline at end of file diff --git a/src/wwwroot/code/linq31.sc b/src/wwwroot/code/linq31.sc index b4d47aa..d3dd15b 100644 --- a/src/wwwroot/code/linq31.sc +++ b/src/wwwroot/code/linq31.sc @@ -1,2 +1,2 @@ -['aPPLE', 'AbAcUs', 'bRaNcH', 'BlUeBeRrY', 'ClOvEr', 'cHeRry'] | to => words - words | orderBy(o => o, { comparer }) | joinln \ No newline at end of file +['aPPLE', 'AbAcUs', 'bRaNcH', 'BlUeBeRrY', 'ClOvEr', 'cHeRry'] |> to => words + words |> orderBy(o => o, { comparer }) |> joinln \ No newline at end of file diff --git a/src/wwwroot/code/linq31.ss b/src/wwwroot/code/linq31.ss index 25e3016..78e278f 100644 --- a/src/wwwroot/code/linq31.ss +++ b/src/wwwroot/code/linq31.ss @@ -1,4 +1,4 @@ -{{ ['aPPLE', 'AbAcUs', 'bRaNcH', 'BlUeBeRrY', 'ClOvEr', 'cHeRry'] | to => words }} +{{ ['aPPLE', 'AbAcUs', 'bRaNcH', 'BlUeBeRrY', 'ClOvEr', 'cHeRry'] |> to => words }} {{ words - | orderBy(o => o, { comparer }) - | joinln }} \ No newline at end of file + |> orderBy(o => o, { comparer }) + |> joinln }} \ No newline at end of file diff --git a/src/wwwroot/code/linq32.sc b/src/wwwroot/code/linq32.sc index e278f9f..42dd375 100644 --- a/src/wwwroot/code/linq32.sc +++ b/src/wwwroot/code/linq32.sc @@ -1,4 +1,4 @@ -[1.7, 2.3, 1.9, 4.1, 2.9] | to => doubles +[1.7, 2.3, 1.9, 4.1, 2.9] |> to => doubles `The doubles from highest to lowest:` #each doubles orderby it descending it diff --git a/src/wwwroot/code/linq32.ss b/src/wwwroot/code/linq32.ss index 09efda6..eebb6cc 100644 --- a/src/wwwroot/code/linq32.ss +++ b/src/wwwroot/code/linq32.ss @@ -1,4 +1,4 @@ -{{ [1.7, 2.3, 1.9, 4.1, 2.9] | to => doubles }} +{{ [1.7, 2.3, 1.9, 4.1, 2.9] |> to => doubles }} The doubles from highest to lowest: {{#each doubles orderby it descending}} {{it}} diff --git a/src/wwwroot/code/linq33.sc b/src/wwwroot/code/linq33.sc index e08fec9..c6100ab 100644 --- a/src/wwwroot/code/linq33.sc +++ b/src/wwwroot/code/linq33.sc @@ -1 +1 @@ -products | orderByDescending => it.UnitsInStock | htmlDump \ No newline at end of file +products |> orderByDescending => it.UnitsInStock |> htmlDump \ No newline at end of file diff --git a/src/wwwroot/code/linq33.ss b/src/wwwroot/code/linq33.ss index 0223eb4..23a3c71 100644 --- a/src/wwwroot/code/linq33.ss +++ b/src/wwwroot/code/linq33.ss @@ -1,3 +1,3 @@ {{ products - | orderByDescending => it.UnitsInStock - | htmlDump }} \ No newline at end of file + |> orderByDescending => it.UnitsInStock + |> htmlDump }} \ No newline at end of file diff --git a/src/wwwroot/code/linq34.sc b/src/wwwroot/code/linq34.sc index 580fbd0..018f0bd 100644 --- a/src/wwwroot/code/linq34.sc +++ b/src/wwwroot/code/linq34.sc @@ -1,2 +1,2 @@ -['aPPLE', 'AbAcUs', 'bRaNcH', 'BlUeBeRrY', 'ClOvEr', 'cHeRry'] | to => words -words | orderByDescending(o => o, { comparer }) | joinln \ No newline at end of file +['aPPLE', 'AbAcUs', 'bRaNcH', 'BlUeBeRrY', 'ClOvEr', 'cHeRry'] |> to => words +words |> orderByDescending(o => o, { comparer }) |> joinln \ No newline at end of file diff --git a/src/wwwroot/code/linq34.ss b/src/wwwroot/code/linq34.ss index b45997b..f3c2dc7 100644 --- a/src/wwwroot/code/linq34.ss +++ b/src/wwwroot/code/linq34.ss @@ -1,4 +1,4 @@ -{{ ['aPPLE', 'AbAcUs', 'bRaNcH', 'BlUeBeRrY', 'ClOvEr', 'cHeRry'] | to => words }} +{{ ['aPPLE', 'AbAcUs', 'bRaNcH', 'BlUeBeRrY', 'ClOvEr', 'cHeRry'] |> to => words }} {{ words - | orderByDescending(o => o, { comparer }) - | joinln }} \ No newline at end of file + |> orderByDescending(o => o, { comparer }) + |> joinln }} \ No newline at end of file diff --git a/src/wwwroot/code/linq35.sc b/src/wwwroot/code/linq35.sc index 1fd6ff4..d2a52cd 100644 --- a/src/wwwroot/code/linq35.sc +++ b/src/wwwroot/code/linq35.sc @@ -1,3 +1,3 @@ -['zero','one','two','three','four','five','six','seven','eight','nine'] | to => digits +['zero','one','two','three','four','five','six','seven','eight','nine'] |> to => digits `Sorted digits:` -digits | orderBy => it.length | thenBy => it | joinln \ No newline at end of file +digits |> orderBy => it.length |> thenBy => it |> joinln \ No newline at end of file diff --git a/src/wwwroot/code/linq35.ss b/src/wwwroot/code/linq35.ss index a87ce66..e36dc89 100644 --- a/src/wwwroot/code/linq35.ss +++ b/src/wwwroot/code/linq35.ss @@ -1,6 +1,6 @@ -{{ ['zero','one','two','three','four','five','six','seven','eight','nine'] | to =>digits }} +{{ ['zero','one','two','three','four','five','six','seven','eight','nine'] |> to =>digits }} Sorted digits: {{ digits - | orderBy => it.length - | thenBy => it - | joinln }} \ No newline at end of file + |> orderBy => it.length + |> thenBy => it + |> joinln }} \ No newline at end of file diff --git a/src/wwwroot/code/linq36.sc b/src/wwwroot/code/linq36.sc index 858315a..80b4e13 100644 --- a/src/wwwroot/code/linq36.sc +++ b/src/wwwroot/code/linq36.sc @@ -1,2 +1,2 @@ -['aPPLE', 'AbAcUs', 'bRaNcH', 'BlUeBeRrY', 'ClOvEr', 'cHeRry'] | to => words -words | orderBy => it.length | thenBy(w => w, { comparer }) | joinln \ No newline at end of file +['aPPLE', 'AbAcUs', 'bRaNcH', 'BlUeBeRrY', 'ClOvEr', 'cHeRry'] |> to => words +words |> orderBy => it.length |> thenBy(w => w, { comparer }) |> joinln \ No newline at end of file diff --git a/src/wwwroot/code/linq36.ss b/src/wwwroot/code/linq36.ss index c4025d0..27d6360 100644 --- a/src/wwwroot/code/linq36.ss +++ b/src/wwwroot/code/linq36.ss @@ -1,5 +1,5 @@ -{{ ['aPPLE', 'AbAcUs', 'bRaNcH', 'BlUeBeRrY', 'ClOvEr', 'cHeRry'] | to => words }} +{{ ['aPPLE', 'AbAcUs', 'bRaNcH', 'BlUeBeRrY', 'ClOvEr', 'cHeRry'] |> to => words }} {{ words - | orderBy => it.length - | thenBy(w => w, { comparer }) - | joinln }} \ No newline at end of file + |> orderBy => it.length + |> thenBy(w => w, { comparer }) + |> joinln }} \ No newline at end of file diff --git a/src/wwwroot/code/linq37.sc b/src/wwwroot/code/linq37.sc index 0d8194d..c36bc0f 100644 --- a/src/wwwroot/code/linq37.sc +++ b/src/wwwroot/code/linq37.sc @@ -1 +1 @@ -products | orderBy => it.Category | thenByDescending => it.UnitPrice | htmlDump \ No newline at end of file +products |> orderBy => it.Category |> thenByDescending => it.UnitPrice |> htmlDump \ No newline at end of file diff --git a/src/wwwroot/code/linq37.ss b/src/wwwroot/code/linq37.ss index 58df7b2..d09dc4e 100644 --- a/src/wwwroot/code/linq37.ss +++ b/src/wwwroot/code/linq37.ss @@ -1,4 +1,4 @@ {{ products - | orderBy => it.Category - | thenByDescending => it.UnitPrice - | htmlDump }} \ No newline at end of file + |> orderBy => it.Category + |> thenByDescending => it.UnitPrice + |> htmlDump }} \ No newline at end of file diff --git a/src/wwwroot/code/linq38.sc b/src/wwwroot/code/linq38.sc index 2011c9b..826e657 100644 --- a/src/wwwroot/code/linq38.sc +++ b/src/wwwroot/code/linq38.sc @@ -1,2 +1,2 @@ -['aPPLE', 'AbAcUs', 'bRaNcH', 'BlUeBeRrY', 'ClOvEr', 'cHeRry'] | to => words -words | orderBy => it.length | thenByDescending(w => w, { comparer }) | joinln \ No newline at end of file +['aPPLE', 'AbAcUs', 'bRaNcH', 'BlUeBeRrY', 'ClOvEr', 'cHeRry'] |> to => words +words |> orderBy => it.length |> thenByDescending(w => w, { comparer }) |> joinln \ No newline at end of file diff --git a/src/wwwroot/code/linq38.ss b/src/wwwroot/code/linq38.ss index 48d4e14..1878f46 100644 --- a/src/wwwroot/code/linq38.ss +++ b/src/wwwroot/code/linq38.ss @@ -1,5 +1,5 @@ -{{ ['aPPLE', 'AbAcUs', 'bRaNcH', 'BlUeBeRrY', 'ClOvEr', 'cHeRry'] | to => words }} +{{ ['aPPLE', 'AbAcUs', 'bRaNcH', 'BlUeBeRrY', 'ClOvEr', 'cHeRry'] |> to => words }} {{ words - | orderBy => it.length - | thenByDescending(w => w, { comparer }) - | joinln }} \ No newline at end of file + |> orderBy => it.length + |> thenByDescending(w => w, { comparer }) + |> joinln }} \ No newline at end of file diff --git a/src/wwwroot/code/linq39.sc b/src/wwwroot/code/linq39.sc index 188f8fb..22129e0 100644 --- a/src/wwwroot/code/linq39.sc +++ b/src/wwwroot/code/linq39.sc @@ -1,3 +1,3 @@ -['zero','one','two','three','four','five','six','seven','eight','nine'] | to => digits +['zero','one','two','three','four','five','six','seven','eight','nine'] |> to => digits `A backwards list of the digits with a second character of 'i':` -digits | where => it[1] = 'i' | reverse | joinln \ No newline at end of file +digits |> where => it[1] = 'i' |> reverse |> joinln \ No newline at end of file diff --git a/src/wwwroot/code/linq39.ss b/src/wwwroot/code/linq39.ss index cb9743b..d1bcafb 100644 --- a/src/wwwroot/code/linq39.ss +++ b/src/wwwroot/code/linq39.ss @@ -1,6 +1,6 @@ -{{ ['zero','one','two','three','four','five','six','seven','eight','nine'] | to =>digits }} +{{ ['zero','one','two','three','four','five','six','seven','eight','nine'] |> to =>digits }} A backwards list of the digits with a second character of 'i': {{ digits - | where => it[1] = 'i' - | reverse - | joinln }} \ No newline at end of file + |> where => it[1] = 'i' + |> reverse + |> joinln }} \ No newline at end of file diff --git a/src/wwwroot/code/linq40.sc b/src/wwwroot/code/linq40.sc index c7c7e21..1e293a2 100644 --- a/src/wwwroot/code/linq40.sc +++ b/src/wwwroot/code/linq40.sc @@ -1,7 +1,7 @@ -[5, 4, 1, 3, 9, 8, 6, 7, 2, 0] | to => nums +[5, 4, 1, 3, 9, 8, 6, 7, 2, 0] |> to => nums {{ nums - | groupBy => it % 5 - | let => { remainder: it.Key, nums: it } - | map => `Numbers with a remainder of ${remainder} when divided by 5:\n${nums.join('\n')}` - | joinln + |> groupBy => it % 5 + |> let => { remainder: it.Key, nums: it } + |> map => `Numbers with a remainder of ${remainder} when divided by 5:\n${nums.join('\n')}` + |> joinln }} \ No newline at end of file diff --git a/src/wwwroot/code/linq40.ss b/src/wwwroot/code/linq40.ss index 620199b..493c607 100644 --- a/src/wwwroot/code/linq40.ss +++ b/src/wwwroot/code/linq40.ss @@ -1,7 +1,7 @@ -{{ [5, 4, 1, 3, 9, 8, 6, 7, 2, 0] | to => nums }} +{{ [5, 4, 1, 3, 9, 8, 6, 7, 2, 0] |> to => nums }} {{ nums - | groupBy => it % 5 - | let => { remainder: it.Key, nums: it } - | map => `Numbers with a remainder of ${remainder} when divided by 5:\n${nums.join('\n')}` - | joinln + |> groupBy => it % 5 + |> let => { remainder: it.Key, nums: it } + |> map => `Numbers with a remainder of ${remainder} when divided by 5:\n${nums.join('\n')}` + |> joinln }} \ No newline at end of file diff --git a/src/wwwroot/code/linq41.sc b/src/wwwroot/code/linq41.sc index a6bb72e..c02387c 100644 --- a/src/wwwroot/code/linq41.sc +++ b/src/wwwroot/code/linq41.sc @@ -1,6 +1,6 @@ -['blueberry', 'chimpanzee', 'abacus', 'banana', 'apple', 'cheese'] | to => words -words | groupBy => it[0] | map => { firstLetter: it.Key, words: it } | to => groups +['blueberry', 'chimpanzee', 'abacus', 'banana', 'apple', 'cheese'] |> to => words +words |> groupBy => it[0] |> map => { firstLetter: it.Key, words: it } |> to => groups #each groups `Words that start with the letter '${firstLetter}':` - words | joinln + words |> joinln /each diff --git a/src/wwwroot/code/linq41.ss b/src/wwwroot/code/linq41.ss index 12b84d9..4b535c5 100644 --- a/src/wwwroot/code/linq41.ss +++ b/src/wwwroot/code/linq41.ss @@ -1,6 +1,6 @@ -{{ ['blueberry', 'chimpanzee', 'abacus', 'banana', 'apple', 'cheese'] | to => words }} -{{ words | groupBy => it[0] | map => { firstLetter: it.Key, words: it } | to => groups }} +{{ ['blueberry', 'chimpanzee', 'abacus', 'banana', 'apple', 'cheese'] |> to => words }} +{{ words |> groupBy => it[0] |> map => { firstLetter: it.Key, words: it } |> to => groups }} {{#each groups}} Words that start with the letter '{{firstLetter}}': -{{ words | joinln }} +{{ words |> joinln }} {{/each}} diff --git a/src/wwwroot/code/linq42.sc b/src/wwwroot/code/linq42.sc index ae5b35a..5fde557 100644 --- a/src/wwwroot/code/linq42.sc +++ b/src/wwwroot/code/linq42.sc @@ -1 +1 @@ -products | groupBy => it.Category | let => { Category: it.Key, Products: it } | htmlDump \ No newline at end of file +products |> groupBy => it.Category |> let => { Category: it.Key, Products: it } |> htmlDump \ No newline at end of file diff --git a/src/wwwroot/code/linq42.ss b/src/wwwroot/code/linq42.ss index 3755d2a..ddffe13 100644 --- a/src/wwwroot/code/linq42.ss +++ b/src/wwwroot/code/linq42.ss @@ -1,4 +1,4 @@ {{ products - | groupBy => it.Category - | let => { Category: it.Key, Products: it } - | htmlDump }} \ No newline at end of file + |> groupBy => it.Category + |> let => { Category: it.Key, Products: it } + |> htmlDump }} \ No newline at end of file diff --git a/src/wwwroot/code/linq43.sc b/src/wwwroot/code/linq43.sc index b4bcd41..06bef6e 100644 --- a/src/wwwroot/code/linq43.sc +++ b/src/wwwroot/code/linq43.sc @@ -1,4 +1,4 @@ -{{ customers | map => { +{{ customers |> map => { CompanyName: it.CompanyName, YearGroups: it.Orders.groupBy(it => it.OrderDate.Year).map(yg => { @@ -9,4 +9,4 @@ } ) } - | htmlDump }} \ No newline at end of file + |> htmlDump }} \ No newline at end of file diff --git a/src/wwwroot/code/linq43.ss b/src/wwwroot/code/linq43.ss index b4bcd41..06bef6e 100644 --- a/src/wwwroot/code/linq43.ss +++ b/src/wwwroot/code/linq43.ss @@ -1,4 +1,4 @@ -{{ customers | map => { +{{ customers |> map => { CompanyName: it.CompanyName, YearGroups: it.Orders.groupBy(it => it.OrderDate.Year).map(yg => { @@ -9,4 +9,4 @@ } ) } - | htmlDump }} \ No newline at end of file + |> htmlDump }} \ No newline at end of file diff --git a/src/wwwroot/code/linq44.sc b/src/wwwroot/code/linq44.sc index 3cceae1..cbdb9d4 100644 --- a/src/wwwroot/code/linq44.sc +++ b/src/wwwroot/code/linq44.sc @@ -1,4 +1,4 @@ -['from ', ' salt', ' earn ', ' last ', ' near ', ' form '] | to => anagrams +['from ', ' salt', ' earn ', ' last ', ' near ', ' form '] |> to => anagrams #each anagrams.groupBy(w => w.trim(), { comparer: anagramComparer }) - it | json + it |> json /each diff --git a/src/wwwroot/code/linq44.ss b/src/wwwroot/code/linq44.ss index 06940af..e3672f9 100644 --- a/src/wwwroot/code/linq44.ss +++ b/src/wwwroot/code/linq44.ss @@ -1,4 +1,4 @@ -{{ ['from ', ' salt', ' earn ', ' last ', ' near ', ' form '] | to => anagrams }} +{{ ['from ', ' salt', ' earn ', ' last ', ' near ', ' form '] |> to => anagrams }} {{#each anagrams.groupBy(w => w.trim(), { comparer: anagramComparer }) }} -{{it | json}} +{{it |> json}} {{/each}} diff --git a/src/wwwroot/code/linq45.sc b/src/wwwroot/code/linq45.sc index d8d5112..ac2991d 100644 --- a/src/wwwroot/code/linq45.sc +++ b/src/wwwroot/code/linq45.sc @@ -1,4 +1,4 @@ -['from ', ' salt', ' earn ', ' last ', ' near ', ' form '] | to => anagrams +['from ', ' salt', ' earn ', ' last ', ' near ', ' form '] |> to => anagrams #each anagrams.groupBy(w => w.trim(), { map: a => a.upper(), comparer: anagramComparer }) - it | json + it |> json /each \ No newline at end of file diff --git a/src/wwwroot/code/linq45.ss b/src/wwwroot/code/linq45.ss index ac6f0c6..934cef5 100644 --- a/src/wwwroot/code/linq45.ss +++ b/src/wwwroot/code/linq45.ss @@ -1,4 +1,4 @@ -{{ ['from ', ' salt', ' earn ', ' last ', ' near ', ' form '] | to => anagrams }} +{{ ['from ', ' salt', ' earn ', ' last ', ' near ', ' form '] |> to => anagrams }} {{#each anagrams.groupBy(w => w.trim(), { map: a => a.upper(), comparer: anagramComparer }) }} -{{it | json}} +{{it |> json}} {{/each}} \ No newline at end of file diff --git a/src/wwwroot/code/linq46.sc b/src/wwwroot/code/linq46.sc index eabb4f5..a550c1f 100644 --- a/src/wwwroot/code/linq46.sc +++ b/src/wwwroot/code/linq46.sc @@ -1,3 +1,3 @@ -[2, 2, 3, 5, 5] | to => factorsOf300 +[2, 2, 3, 5, 5] |> to => factorsOf300 `Prime factors of 300:` -factorsOf300.distinct() | joinln \ No newline at end of file +factorsOf300.distinct() |> joinln \ No newline at end of file diff --git a/src/wwwroot/code/linq46.ss b/src/wwwroot/code/linq46.ss index 5adffe6..ce2c16a 100644 --- a/src/wwwroot/code/linq46.ss +++ b/src/wwwroot/code/linq46.ss @@ -1,3 +1,3 @@ -{{ [2, 2, 3, 5, 5] | to => factorsOf300 }} +{{ [2, 2, 3, 5, 5] |> to => factorsOf300 }} Prime factors of 300: -{{ factorsOf300.distinct() | joinln }} \ No newline at end of file +{{ factorsOf300.distinct() |> joinln }} \ No newline at end of file diff --git a/src/wwwroot/code/linq47.sc b/src/wwwroot/code/linq47.sc index a647892..558b6d0 100644 --- a/src/wwwroot/code/linq47.sc +++ b/src/wwwroot/code/linq47.sc @@ -1,2 +1,2 @@ `Category names:` -products | map => it.Category | distinct | joinln \ No newline at end of file +products |> map => it.Category |> distinct |> joinln \ No newline at end of file diff --git a/src/wwwroot/code/linq47.ss b/src/wwwroot/code/linq47.ss index 0d28fc5..a77cc4f 100644 --- a/src/wwwroot/code/linq47.ss +++ b/src/wwwroot/code/linq47.ss @@ -1,5 +1,5 @@ Category names: {{ products - | map => it.Category - | distinct - | joinln }} \ No newline at end of file + |> map => it.Category + |> distinct + |> joinln }} \ No newline at end of file diff --git a/src/wwwroot/code/linq48.sc b/src/wwwroot/code/linq48.sc index 7216e4c..2e62400 100644 --- a/src/wwwroot/code/linq48.sc +++ b/src/wwwroot/code/linq48.sc @@ -1,5 +1,5 @@ -[ 0, 2, 4, 5, 6, 8, 9 ] | to => numbersA -[ 1, 3, 5, 7, 8 ] | to => numbersB +[ 0, 2, 4, 5, 6, 8, 9 ] |> to => numbersA +[ 1, 3, 5, 7, 8 ] |> to => numbersB `Unique numbers from both arrays:` #each numbersA.union(numbersB) it diff --git a/src/wwwroot/code/linq48.ss b/src/wwwroot/code/linq48.ss index f52fa8a..4cee036 100644 --- a/src/wwwroot/code/linq48.ss +++ b/src/wwwroot/code/linq48.ss @@ -1,5 +1,5 @@ -{{ [ 0, 2, 4, 5, 6, 8, 9 ] | to => numbersA }} -{{ [ 1, 3, 5, 7, 8 ] | to => numbersB }} +{{ [ 0, 2, 4, 5, 6, 8, 9 ] |> to => numbersA }} +{{ [ 1, 3, 5, 7, 8 ] |> to => numbersB }} Unique numbers from both arrays: {{#each numbersA.union(numbersB)}} {{it}} diff --git a/src/wwwroot/code/linq49.sc b/src/wwwroot/code/linq49.sc index c9af1c5..ed00797 100644 --- a/src/wwwroot/code/linq49.sc +++ b/src/wwwroot/code/linq49.sc @@ -1,5 +1,5 @@ -products | map => it.ProductName[0] | to => productFirstChars -customers | map => it.CompanyName[0] | to => customerFirstChars +products |> map => it.ProductName[0] |> to => productFirstChars +customers |> map => it.CompanyName[0] |> to => customerFirstChars `Unique first letters from Product names and Customer names:` #each productFirstChars.union(customerFirstChars) it diff --git a/src/wwwroot/code/linq49.ss b/src/wwwroot/code/linq49.ss index a7c44dd..1ea6653 100644 --- a/src/wwwroot/code/linq49.ss +++ b/src/wwwroot/code/linq49.ss @@ -1,9 +1,9 @@ {{ products - | map => it.ProductName[0] - | to => productFirstChars }} + |> map => it.ProductName[0] + |> to => productFirstChars }} {{ customers - | map => it.CompanyName[0] - | to => customerFirstChars }} + |> map => it.CompanyName[0] + |> to => customerFirstChars }} Unique first letters from Product names and Customer names: {{#each productFirstChars.union(customerFirstChars) }} {{it}} diff --git a/src/wwwroot/code/linq50.sc b/src/wwwroot/code/linq50.sc index c1d835f..dea7b32 100644 --- a/src/wwwroot/code/linq50.sc +++ b/src/wwwroot/code/linq50.sc @@ -1,4 +1,4 @@ -[ 0, 2, 4, 5, 6, 8, 9 ] | to => numbersA -[ 1, 3, 5, 7, 8 ] | to => numbersB +[ 0, 2, 4, 5, 6, 8, 9 ] |> to => numbersA +[ 1, 3, 5, 7, 8 ] |> to => numbersB `Common numbers shared by both arrays:` -numbersA.intersect(numbersB) | joinln \ No newline at end of file +numbersA.intersect(numbersB) |> joinln \ No newline at end of file diff --git a/src/wwwroot/code/linq50.ss b/src/wwwroot/code/linq50.ss index 385c485..0fee992 100644 --- a/src/wwwroot/code/linq50.ss +++ b/src/wwwroot/code/linq50.ss @@ -1,4 +1,4 @@ -{{ [ 0, 2, 4, 5, 6, 8, 9 ] | to => numbersA }} -{{ [ 1, 3, 5, 7, 8 ] | to => numbersB }} +{{ [ 0, 2, 4, 5, 6, 8, 9 ] |> to => numbersA }} +{{ [ 1, 3, 5, 7, 8 ] |> to => numbersB }} Common numbers shared by both arrays: -{{ numbersA.intersect(numbersB) | joinln }} \ No newline at end of file +{{ numbersA.intersect(numbersB) |> joinln }} \ No newline at end of file diff --git a/src/wwwroot/code/linq51.sc b/src/wwwroot/code/linq51.sc index 1c22ec4..f522831 100644 --- a/src/wwwroot/code/linq51.sc +++ b/src/wwwroot/code/linq51.sc @@ -1,5 +1,5 @@ -products | map => it.ProductName[0] | to => productFirstChars -customers | map => it.CompanyName[0] | to => customerFirstChars +products |> map => it.ProductName[0] |> to => productFirstChars +customers |> map => it.CompanyName[0] |> to => customerFirstChars `Common first letters from Product names and Customer names:` #each productFirstChars.intersect(customerFirstChars) it diff --git a/src/wwwroot/code/linq51.ss b/src/wwwroot/code/linq51.ss index 1648ae5..36a5359 100644 --- a/src/wwwroot/code/linq51.ss +++ b/src/wwwroot/code/linq51.ss @@ -1,9 +1,9 @@ {{ products - | map => it.ProductName[0] - | to => productFirstChars }} + |> map => it.ProductName[0] + |> to => productFirstChars }} {{ customers - | map => it.CompanyName[0] - | to => customerFirstChars }} + |> map => it.CompanyName[0] + |> to => customerFirstChars }} Common first letters from Product names and Customer names: {{#each productFirstChars.intersect(customerFirstChars) }} {{it}} diff --git a/src/wwwroot/code/linq52.sc b/src/wwwroot/code/linq52.sc index 4cb7116..f174ee6 100644 --- a/src/wwwroot/code/linq52.sc +++ b/src/wwwroot/code/linq52.sc @@ -1,4 +1,4 @@ -[ 0, 2, 4, 5, 6, 8, 9 ] | to => numbersA -[ 1, 3, 5, 7, 8 ] | to => numbersB +[ 0, 2, 4, 5, 6, 8, 9 ] |> to => numbersA +[ 1, 3, 5, 7, 8 ] |> to => numbersB `Numbers in first array but not second array:` -numbersA.except(numbersB) | joinln \ No newline at end of file +numbersA.except(numbersB) |> joinln \ No newline at end of file diff --git a/src/wwwroot/code/linq52.ss b/src/wwwroot/code/linq52.ss index 59a7bec..7f200b7 100644 --- a/src/wwwroot/code/linq52.ss +++ b/src/wwwroot/code/linq52.ss @@ -1,4 +1,4 @@ -{{ [ 0, 2, 4, 5, 6, 8, 9 ] | to => numbersA }} -{{ [ 1, 3, 5, 7, 8 ] | to => numbersB }} +{{ [ 0, 2, 4, 5, 6, 8, 9 ] |> to => numbersA }} +{{ [ 1, 3, 5, 7, 8 ] |> to => numbersB }} Numbers in first array but not second array: -{{ numbersA.except(numbersB) | joinln }} \ No newline at end of file +{{ numbersA.except(numbersB) |> joinln }} \ No newline at end of file diff --git a/src/wwwroot/code/linq53.sc b/src/wwwroot/code/linq53.sc index 323ff10..e877ac6 100644 --- a/src/wwwroot/code/linq53.sc +++ b/src/wwwroot/code/linq53.sc @@ -1,5 +1,5 @@ -products | map => it.ProductName[0] | to => productFirstChars -customers | map => it.CompanyName[0] | to => customerFirstChars +products |> map => it.ProductName[0] |> to => productFirstChars +customers |> map => it.CompanyName[0] |> to => customerFirstChars `First letters from Product names, but not from Customer names:` #each productFirstChars.except(customerFirstChars) it diff --git a/src/wwwroot/code/linq53.ss b/src/wwwroot/code/linq53.ss index 7450aa4..67899cd 100644 --- a/src/wwwroot/code/linq53.ss +++ b/src/wwwroot/code/linq53.ss @@ -1,9 +1,9 @@ {{ products - | map => it.ProductName[0] - | to => productFirstChars }} + |> map => it.ProductName[0] + |> to => productFirstChars }} {{ customers - | map => it.CompanyName[0] - | to => customerFirstChars }} + |> map => it.CompanyName[0] + |> to => customerFirstChars }} First letters from Product names, but not from Customer names: {{#each productFirstChars.except(customerFirstChars) }} {{it}} diff --git a/src/wwwroot/code/linq54.sc b/src/wwwroot/code/linq54.sc index b87ae9f..60b5fbc 100644 --- a/src/wwwroot/code/linq54.sc +++ b/src/wwwroot/code/linq54.sc @@ -1,3 +1,3 @@ -[ 1.7, 2.3, 1.9, 4.1, 2.9 ] | to => doubles +[ 1.7, 2.3, 1.9, 4.1, 2.9 ] |> to => doubles `Every other double from highest to lowest:` -doubles | orderByDescending => it | step({ by: 2 }) | joinln \ No newline at end of file +doubles |> orderByDescending => it |> step({ by: 2 }) |> joinln \ No newline at end of file diff --git a/src/wwwroot/code/linq54.ss b/src/wwwroot/code/linq54.ss index dcadf28..7394c88 100644 --- a/src/wwwroot/code/linq54.ss +++ b/src/wwwroot/code/linq54.ss @@ -1,6 +1,6 @@ -{{ [ 1.7, 2.3, 1.9, 4.1, 2.9 ] | to => doubles }} +{{ [ 1.7, 2.3, 1.9, 4.1, 2.9 ] |> to => doubles }} Every other double from highest to lowest: {{ doubles - | orderByDescending => it - | step({ by: 2 }) - | joinln }} \ No newline at end of file + |> orderByDescending => it + |> step({ by: 2 }) + |> joinln }} \ No newline at end of file diff --git a/src/wwwroot/code/linq55.sc b/src/wwwroot/code/linq55.sc index 1dc09b7..6674422 100644 --- a/src/wwwroot/code/linq55.sc +++ b/src/wwwroot/code/linq55.sc @@ -1,3 +1,3 @@ -[ 'cherry', 'apple', 'blueberry' ] | to => words +[ 'cherry', 'apple', 'blueberry' ] |> to => words `The sorted word list:` -words | orderBy => it | toList | joinln \ No newline at end of file +words |> orderBy => it |> toList |> joinln \ No newline at end of file diff --git a/src/wwwroot/code/linq55.ss b/src/wwwroot/code/linq55.ss index 2105590..640df80 100644 --- a/src/wwwroot/code/linq55.ss +++ b/src/wwwroot/code/linq55.ss @@ -1,6 +1,6 @@ -{{ [ 'cherry', 'apple', 'blueberry' ] | to => words }} +{{ [ 'cherry', 'apple', 'blueberry' ] |> to => words }} The sorted word list: {{ words - | orderBy => it - | toList - | joinln }} \ No newline at end of file + |> orderBy => it + |> toList + |> joinln }} \ No newline at end of file diff --git a/src/wwwroot/code/linq56.sc b/src/wwwroot/code/linq56.sc index b90395b..1067867 100644 --- a/src/wwwroot/code/linq56.sc +++ b/src/wwwroot/code/linq56.sc @@ -1,3 +1,3 @@ -[ {name:'Alice',score:50}, {name:'Bob',score:40}, {name:'Cathy',score:45} ] | to => records -records | toDictionary => it.name | to => scoreRecordsDict +[ {name:'Alice',score:50}, {name:'Bob',score:40}, {name:'Cathy',score:45} ] |> to => records +records |> toDictionary => it.name |> to => scoreRecordsDict `Bob's score: ${scoreRecordsDict.Bob.score}` \ No newline at end of file diff --git a/src/wwwroot/code/linq56.ss b/src/wwwroot/code/linq56.ss index b66c488..d4abbd6 100644 --- a/src/wwwroot/code/linq56.ss +++ b/src/wwwroot/code/linq56.ss @@ -1,3 +1,3 @@ -{{ [{name:'Alice',score:50},{name:'Bob',score:40},{name:'Cathy',score:45}] | to => records }} -{{ records | toDictionary => it.name | to => scoreRecordsDict }} +{{ [{name:'Alice',score:50},{name:'Bob',score:40},{name:'Cathy',score:45}] |> to => records }} +{{ records |> toDictionary => it.name |> to => scoreRecordsDict }} Bob's score: {{ scoreRecordsDict.Bob.score }} \ No newline at end of file diff --git a/src/wwwroot/code/linq57.sc b/src/wwwroot/code/linq57.sc index 5a4959a..e1580a3 100644 --- a/src/wwwroot/code/linq57.sc +++ b/src/wwwroot/code/linq57.sc @@ -1,3 +1,3 @@ -[null, 1.0, 'two', 3, 'four', 5, 'six', 7.0] | to => numbers +[null, 1.0, 'two', 3, 'four', 5, 'six', 7.0] |> to => numbers `Numbers stored as doubles:` -numbers | of({ type: 'Double' }) | map => `${it.format('#.0') }` | joinln \ No newline at end of file +numbers |> of({ type: 'Double' }) |> map => `${it.format('#.0') }` |> joinln \ No newline at end of file diff --git a/src/wwwroot/code/linq57.ss b/src/wwwroot/code/linq57.ss index 120c00a..a6e79ca 100644 --- a/src/wwwroot/code/linq57.ss +++ b/src/wwwroot/code/linq57.ss @@ -1,5 +1,5 @@ -{{ [null, 1.0, 'two', 3, 'four', 5, 'six', 7.0] | to => numbers }} +{{ [null, 1.0, 'two', 3, 'four', 5, 'six', 7.0] |> to => numbers }} Numbers stored as doubles: {{ numbers - | of({ type: 'Double' }) - | select: { it | format('#.0') }\n }} \ No newline at end of file + |> of({ type: 'Double' }) + |> select: { it |> format('#.0') }\n }} \ No newline at end of file diff --git a/src/wwwroot/code/linq58.sc b/src/wwwroot/code/linq58.sc index 4d02977..f275794 100644 --- a/src/wwwroot/code/linq58.sc +++ b/src/wwwroot/code/linq58.sc @@ -1 +1 @@ -products | where => it.ProductId = 12 | first | dump \ No newline at end of file +products |> where => it.ProductId = 12 |> first |> dump \ No newline at end of file diff --git a/src/wwwroot/code/linq58.ss b/src/wwwroot/code/linq58.ss index f83f04c..1338883 100644 --- a/src/wwwroot/code/linq58.ss +++ b/src/wwwroot/code/linq58.ss @@ -1,4 +1,4 @@ {{ products - | where => it.ProductId = 12 - | first - | dump }} \ No newline at end of file + |> where => it.ProductId = 12 + |> first + |> dump }} \ No newline at end of file diff --git a/src/wwwroot/code/linq59.sc b/src/wwwroot/code/linq59.sc index aad4847..cc7d280 100644 --- a/src/wwwroot/code/linq59.sc +++ b/src/wwwroot/code/linq59.sc @@ -1,4 +1,4 @@ -['zero','one','two','three','four','five','six','seven','eight','nine'] | to => strings +['zero','one','two','three','four','five','six','seven','eight','nine'] |> to => strings #each s in strings where s[0] == 'o' take 1 `A string starting with 'o': ${s}` /each \ No newline at end of file diff --git a/src/wwwroot/code/linq59.ss b/src/wwwroot/code/linq59.ss index 0fb2eea..f1d5b62 100644 --- a/src/wwwroot/code/linq59.ss +++ b/src/wwwroot/code/linq59.ss @@ -1,4 +1,4 @@ -{{ ['zero','one','two','three','four','five','six','seven','eight','nine'] | to =>strings }} +{{ ['zero','one','two','three','four','five','six','seven','eight','nine'] |> to =>strings }} {{#each s in strings where s[0] == 'o' take 1 }} A string starting with 'o': {{s}} {{/each}} diff --git a/src/wwwroot/code/linq61.ss b/src/wwwroot/code/linq61.ss index 36ca9b5..66a4391 100644 --- a/src/wwwroot/code/linq61.ss +++ b/src/wwwroot/code/linq61.ss @@ -1,2 +1,2 @@ -{{ [] | to => numbers }} +{{ [] |> to => numbers }} {{ numbers.first() ?? 'null' }} \ No newline at end of file diff --git a/src/wwwroot/code/linq62.sc b/src/wwwroot/code/linq62.sc index 231f760..546dc39 100644 --- a/src/wwwroot/code/linq62.sc +++ b/src/wwwroot/code/linq62.sc @@ -1,2 +1,2 @@ -products | first => it.ProductId = 789 | to => product789 +products |> first => it.ProductId = 789 |> to => product789 `Product 789 exists: ${ product789 != null }` \ No newline at end of file diff --git a/src/wwwroot/code/linq62.ss b/src/wwwroot/code/linq62.ss index 51f010a..c40248d 100644 --- a/src/wwwroot/code/linq62.ss +++ b/src/wwwroot/code/linq62.ss @@ -1,2 +1,2 @@ -{{ products | first => it.ProductId = 789 | to => product789 }} +{{ products |> first => it.ProductId = 789 |> to => product789 }} Product 789 exists: {{ product789 != null }} \ No newline at end of file diff --git a/src/wwwroot/code/linq64.sc b/src/wwwroot/code/linq64.sc index 70715b1..e752b6b 100644 --- a/src/wwwroot/code/linq64.sc +++ b/src/wwwroot/code/linq64.sc @@ -1,3 +1,3 @@ -[ 5, 4, 1, 3, 9, 8, 6, 7, 2, 0 ] | to => numbers -numbers | where => it > 5 | elementAt(1) | to => fourthLowNum +[ 5, 4, 1, 3, 9, 8, 6, 7, 2, 0 ] |> to => numbers +numbers |> where => it > 5 | elementAt(1) |> to => fourthLowNum `Second number > 5: ${fourthLowNum}` \ No newline at end of file diff --git a/src/wwwroot/code/linq64.ss b/src/wwwroot/code/linq64.ss index cda24d5..5dd94ee 100644 --- a/src/wwwroot/code/linq64.ss +++ b/src/wwwroot/code/linq64.ss @@ -1,3 +1,3 @@ -{{ [ 5, 4, 1, 3, 9, 8, 6, 7, 2, 0 ] | to => numbers }} -{{ numbers | where => it > 5 | elementAt(1) | to => fourthLowNum }} +{{ [ 5, 4, 1, 3, 9, 8, 6, 7, 2, 0 ] |> to => numbers }} +{{ numbers |> where => it > 5 | elementAt(1) |> to => fourthLowNum }} Second number > 5: {{ fourthLowNum }} \ No newline at end of file diff --git a/src/wwwroot/code/linq66.sc b/src/wwwroot/code/linq66.sc index 2be0a50..964d239 100644 --- a/src/wwwroot/code/linq66.sc +++ b/src/wwwroot/code/linq66.sc @@ -1 +1 @@ -10.itemsOf(7) | joinln \ No newline at end of file +10.itemsOf(7) |> joinln \ No newline at end of file diff --git a/src/wwwroot/code/linq66.ss b/src/wwwroot/code/linq66.ss index 071bab0..a39d69d 100644 --- a/src/wwwroot/code/linq66.ss +++ b/src/wwwroot/code/linq66.ss @@ -1 +1 @@ -{{ 10.itemsOf(7) | joinln }} \ No newline at end of file +{{ 10.itemsOf(7) |> joinln }} \ No newline at end of file diff --git a/src/wwwroot/code/linq67.sc b/src/wwwroot/code/linq67.sc index 89ebce0..6de8f10 100644 --- a/src/wwwroot/code/linq67.sc +++ b/src/wwwroot/code/linq67.sc @@ -1,3 +1,3 @@ -['believe', 'relief', 'receipt', 'field'] | to => words -words | any => it.contains('ei') | to => iAfterE +['believe', 'relief', 'receipt', 'field'] |> to => words +words |> any => it.contains('ei') |> to => iAfterE `There is a word that contains in the list that contains 'ei': ${iAfterE.lower()}` \ No newline at end of file diff --git a/src/wwwroot/code/linq67.ss b/src/wwwroot/code/linq67.ss index 5f6d12b..15c5ece 100644 --- a/src/wwwroot/code/linq67.ss +++ b/src/wwwroot/code/linq67.ss @@ -1,3 +1,3 @@ -{{ ['believe', 'relief', 'receipt', 'field'] | to => words }} -{{ words | any => it.contains('ei') | to => iAfterE }} +{{ ['believe', 'relief', 'receipt', 'field'] |> to => words }} +{{ words |> any => it.contains('ei') |> to => iAfterE }} There is a word that contains in the list that contains 'ei': {{ iAfterE | lower }} \ No newline at end of file diff --git a/src/wwwroot/code/linq69.sc b/src/wwwroot/code/linq69.sc index 77e6d53..fb6abc2 100644 --- a/src/wwwroot/code/linq69.sc +++ b/src/wwwroot/code/linq69.sc @@ -1,5 +1,5 @@ {{ products - | groupBy => it.Category - | where => it.any(it => it.UnitsInStock = 0) - | let => { Category: it.Key, Products: it } - | htmlDump }} \ No newline at end of file + |> groupBy => it.Category + |> where => it.any(it => it.UnitsInStock = 0) + |> let => { Category: it.Key, Products: it } + |> htmlDump }} \ No newline at end of file diff --git a/src/wwwroot/code/linq69.ss b/src/wwwroot/code/linq69.ss index 77e6d53..fb6abc2 100644 --- a/src/wwwroot/code/linq69.ss +++ b/src/wwwroot/code/linq69.ss @@ -1,5 +1,5 @@ {{ products - | groupBy => it.Category - | where => it.any(it => it.UnitsInStock = 0) - | let => { Category: it.Key, Products: it } - | htmlDump }} \ No newline at end of file + |> groupBy => it.Category + |> where => it.any(it => it.UnitsInStock = 0) + |> let => { Category: it.Key, Products: it } + |> htmlDump }} \ No newline at end of file diff --git a/src/wwwroot/code/linq70.sc b/src/wwwroot/code/linq70.sc index 3485fe8..f6bb7b0 100644 --- a/src/wwwroot/code/linq70.sc +++ b/src/wwwroot/code/linq70.sc @@ -1,3 +1,3 @@ -[1, 11, 3, 19, 41, 65, 19] | to => numbers -numbers | all => it.isOdd() | to => onlyOdd +[1, 11, 3, 19, 41, 65, 19] |> to => numbers +numbers | all => it.isOdd() |> to => onlyOdd `The list contains only odd numbers: ${ onlyOdd }` \ No newline at end of file diff --git a/src/wwwroot/code/linq70.ss b/src/wwwroot/code/linq70.ss index 07e7dbc..07c68e6 100644 --- a/src/wwwroot/code/linq70.ss +++ b/src/wwwroot/code/linq70.ss @@ -1,3 +1,3 @@ -{{ [1, 11, 3, 19, 41, 65, 19] | to => numbers }} -{{ numbers | all => it.isOdd() | to => onlyOdd }} +{{ [1, 11, 3, 19, 41, 65, 19] |> to => numbers }} +{{ numbers | all => it.isOdd() |> to => onlyOdd }} The list contains only odd numbers: {{ onlyOdd }} \ No newline at end of file diff --git a/src/wwwroot/code/linq72.sc b/src/wwwroot/code/linq72.sc index 1a738eb..cb48e00 100644 --- a/src/wwwroot/code/linq72.sc +++ b/src/wwwroot/code/linq72.sc @@ -1,5 +1,5 @@ {{ products - | groupBy => it.Category - | where => it.all(it => it.UnitsInStock > 0) - | let => { Category: it.Key, Products: it } - | htmlDump }} \ No newline at end of file + |> groupBy => it.Category + |> where => it.all(it => it.UnitsInStock > 0) + |> let => { Category: it.Key, Products: it } + |> htmlDump }} \ No newline at end of file diff --git a/src/wwwroot/code/linq72.ss b/src/wwwroot/code/linq72.ss index 1a738eb..cb48e00 100644 --- a/src/wwwroot/code/linq72.ss +++ b/src/wwwroot/code/linq72.ss @@ -1,5 +1,5 @@ {{ products - | groupBy => it.Category - | where => it.all(it => it.UnitsInStock > 0) - | let => { Category: it.Key, Products: it } - | htmlDump }} \ No newline at end of file + |> groupBy => it.Category + |> where => it.all(it => it.UnitsInStock > 0) + |> let => { Category: it.Key, Products: it } + |> htmlDump }} \ No newline at end of file diff --git a/src/wwwroot/code/linq73.sc b/src/wwwroot/code/linq73.sc index bcf76aa..3861165 100644 --- a/src/wwwroot/code/linq73.sc +++ b/src/wwwroot/code/linq73.sc @@ -1,3 +1,3 @@ -[2, 2, 3, 5, 5] | to => factorsOf300 -factorsOf300.distinct().count() | to => uniqueFactors +[2, 2, 3, 5, 5] |> to => factorsOf300 +factorsOf300.distinct().count() |> to => uniqueFactors `There are ${uniqueFactors} unique factors of 300.` \ No newline at end of file diff --git a/src/wwwroot/code/linq73.ss b/src/wwwroot/code/linq73.ss index 84d1085..086b704 100644 --- a/src/wwwroot/code/linq73.ss +++ b/src/wwwroot/code/linq73.ss @@ -1,3 +1,3 @@ -{{ [2, 2, 3, 5, 5] | to => factorsOf300 }} -{{ factorsOf300.distinct().count() | to => uniqueFactors }} +{{ [2, 2, 3, 5, 5] |> to => factorsOf300 }} +{{ factorsOf300.distinct().count() |> to => uniqueFactors }} There are {{uniqueFactors}} unique factors of 300. \ No newline at end of file diff --git a/src/wwwroot/code/linq74.sc b/src/wwwroot/code/linq74.sc index 0c06823..a79f801 100644 --- a/src/wwwroot/code/linq74.sc +++ b/src/wwwroot/code/linq74.sc @@ -1,3 +1,3 @@ -[5, 4, 1, 3, 9, 8, 6, 7, 2, 0] | to => numbers -numbers | count => it.isOdd() | to => oddNumbers +[5, 4, 1, 3, 9, 8, 6, 7, 2, 0] |> to => numbers +numbers |> count => it.isOdd() |> to => oddNumbers `There are ${oddNumbers} odd numbers in the list.` \ No newline at end of file diff --git a/src/wwwroot/code/linq74.ss b/src/wwwroot/code/linq74.ss index 1b25d0f..9fefd70 100644 --- a/src/wwwroot/code/linq74.ss +++ b/src/wwwroot/code/linq74.ss @@ -1,3 +1,3 @@ -{{ [5, 4, 1, 3, 9, 8, 6, 7, 2, 0] | to => numbers }} -{{ numbers | count => it.isOdd() | to => oddNumbers }} +{{ [5, 4, 1, 3, 9, 8, 6, 7, 2, 0] |> to => numbers }} +{{ numbers |> count => it.isOdd() |> to => oddNumbers }} There are {{oddNumbers}} odd numbers in the list. \ No newline at end of file diff --git a/src/wwwroot/code/linq76.sc b/src/wwwroot/code/linq76.sc index 69ab2db..5cdf23f 100644 --- a/src/wwwroot/code/linq76.sc +++ b/src/wwwroot/code/linq76.sc @@ -1,3 +1,3 @@ {{ customers - | let => { it.CustomerId, OrderCount: it.Orders.count() } - | map => `${CustomerId}, ${OrderCount}` | joinln }} \ No newline at end of file + |> let => { it.CustomerId, OrderCount: it.Orders.count() } + |> map => `${CustomerId}, ${OrderCount}` |> joinln }} \ No newline at end of file diff --git a/src/wwwroot/code/linq76.ss b/src/wwwroot/code/linq76.ss index 69ab2db..5cdf23f 100644 --- a/src/wwwroot/code/linq76.ss +++ b/src/wwwroot/code/linq76.ss @@ -1,3 +1,3 @@ {{ customers - | let => { it.CustomerId, OrderCount: it.Orders.count() } - | map => `${CustomerId}, ${OrderCount}` | joinln }} \ No newline at end of file + |> let => { it.CustomerId, OrderCount: it.Orders.count() } + |> map => `${CustomerId}, ${OrderCount}` |> joinln }} \ No newline at end of file diff --git a/src/wwwroot/code/linq77.sc b/src/wwwroot/code/linq77.sc index d51ea1b..bb54963 100644 --- a/src/wwwroot/code/linq77.sc +++ b/src/wwwroot/code/linq77.sc @@ -1,4 +1,4 @@ {{ products - | groupBy => it.Category - | let => { Category: it.Key, ProductCount: it.count() } - | htmlDump }} \ No newline at end of file + |> groupBy => it.Category + |> let => { Category: it.Key, ProductCount: it.count() } + |> htmlDump }} \ No newline at end of file diff --git a/src/wwwroot/code/linq77.ss b/src/wwwroot/code/linq77.ss index d51ea1b..bb54963 100644 --- a/src/wwwroot/code/linq77.ss +++ b/src/wwwroot/code/linq77.ss @@ -1,4 +1,4 @@ {{ products - | groupBy => it.Category - | let => { Category: it.Key, ProductCount: it.count() } - | htmlDump }} \ No newline at end of file + |> groupBy => it.Category + |> let => { Category: it.Key, ProductCount: it.count() } + |> htmlDump }} \ No newline at end of file diff --git a/src/wwwroot/code/linq78.sc b/src/wwwroot/code/linq78.sc index 0d17295..10a1e12 100644 --- a/src/wwwroot/code/linq78.sc +++ b/src/wwwroot/code/linq78.sc @@ -1,3 +1,3 @@ -[5, 4, 1, 3, 9, 8, 6, 7, 2, 0] | to => numbers -numbers.sum() | to => numSum +[5, 4, 1, 3, 9, 8, 6, 7, 2, 0] |> to => numbers +numbers.sum() |> to => numSum `The sum of the numbers is ${numSum}.` \ No newline at end of file diff --git a/src/wwwroot/code/linq78.ss b/src/wwwroot/code/linq78.ss index 66358e6..7770fbb 100644 --- a/src/wwwroot/code/linq78.ss +++ b/src/wwwroot/code/linq78.ss @@ -1,3 +1,3 @@ -{{ [5, 4, 1, 3, 9, 8, 6, 7, 2, 0] | to => numbers }} -{{ numbers.sum() | to => numSum }} +{{ [5, 4, 1, 3, 9, 8, 6, 7, 2, 0] |> to => numbers }} +{{ numbers.sum() |> to => numSum }} The sum of the numbers is {{numSum}}. \ No newline at end of file diff --git a/src/wwwroot/code/linq79.sc b/src/wwwroot/code/linq79.sc index e7ea9cc..e9c8df8 100644 --- a/src/wwwroot/code/linq79.sc +++ b/src/wwwroot/code/linq79.sc @@ -1,3 +1,3 @@ -[ 'cherry', 'apple', 'blueberry'] | to => words -words | sum => it.Length | to => totalChars +[ 'cherry', 'apple', 'blueberry'] |> to => words +words |> sum => it.Length |> to => totalChars `There are a total of ${totalChars} characters in these words.` \ No newline at end of file diff --git a/src/wwwroot/code/linq79.ss b/src/wwwroot/code/linq79.ss index a89aba7..68991da 100644 --- a/src/wwwroot/code/linq79.ss +++ b/src/wwwroot/code/linq79.ss @@ -1,3 +1,3 @@ -{{ [ 'cherry', 'apple', 'blueberry'] | to => words }} -{{ words | sum => it.Length | to => totalChars }} +{{ [ 'cherry', 'apple', 'blueberry'] |> to => words }} +{{ words |> sum => it.Length |> to => totalChars }} There are a total of {{totalChars}} characters in these words. \ No newline at end of file diff --git a/src/wwwroot/code/linq80.sc b/src/wwwroot/code/linq80.sc index 739813c..965a9f8 100644 --- a/src/wwwroot/code/linq80.sc +++ b/src/wwwroot/code/linq80.sc @@ -1,4 +1,4 @@ {{ products - | groupBy => it.Category - | map => { Category: it.Key, TotalUnitsInStock: it.sum(p => p.UnitsInStock) } - | htmlDump }} \ No newline at end of file + |> groupBy => it.Category + |> map => { Category: it.Key, TotalUnitsInStock: it.sum(p => p.UnitsInStock) } + |> htmlDump }} \ No newline at end of file diff --git a/src/wwwroot/code/linq80.ss b/src/wwwroot/code/linq80.ss index 739813c..965a9f8 100644 --- a/src/wwwroot/code/linq80.ss +++ b/src/wwwroot/code/linq80.ss @@ -1,4 +1,4 @@ {{ products - | groupBy => it.Category - | map => { Category: it.Key, TotalUnitsInStock: it.sum(p => p.UnitsInStock) } - | htmlDump }} \ No newline at end of file + |> groupBy => it.Category + |> map => { Category: it.Key, TotalUnitsInStock: it.sum(p => p.UnitsInStock) } + |> htmlDump }} \ No newline at end of file diff --git a/src/wwwroot/code/linq81.sc b/src/wwwroot/code/linq81.sc index 793a495..03bca62 100644 --- a/src/wwwroot/code/linq81.sc +++ b/src/wwwroot/code/linq81.sc @@ -1,3 +1,3 @@ -[5, 4, 1, 3, 9, 8, 6, 7, 2, 0] | to => numbers -numbers.min() | to => minNum +[5, 4, 1, 3, 9, 8, 6, 7, 2, 0] |> to => numbers +numbers.min() |> to => minNum `The minimum number is ${minNum}.` \ No newline at end of file diff --git a/src/wwwroot/code/linq81.ss b/src/wwwroot/code/linq81.ss index e895750..5ae9718 100644 --- a/src/wwwroot/code/linq81.ss +++ b/src/wwwroot/code/linq81.ss @@ -1,3 +1,3 @@ -{{ [5, 4, 1, 3, 9, 8, 6, 7, 2, 0] | to => numbers }} -{{ numbers.min() | to => minNum }} +{{ [5, 4, 1, 3, 9, 8, 6, 7, 2, 0] |> to => numbers }} +{{ numbers.min() |> to => minNum }} The minimum number is {{minNum}}. \ No newline at end of file diff --git a/src/wwwroot/code/linq82.sc b/src/wwwroot/code/linq82.sc index 076c09b..766ff6c 100644 --- a/src/wwwroot/code/linq82.sc +++ b/src/wwwroot/code/linq82.sc @@ -1,3 +1,3 @@ -[ 'cherry', 'apple', 'blueberry' ] | to => words -words | min => it.Length | to => shortestWord +[ 'cherry', 'apple', 'blueberry' ] |> to => words +words |> min => it.Length |> to => shortestWord `The shortest word is ${shortestWord} characters long.` \ No newline at end of file diff --git a/src/wwwroot/code/linq82.ss b/src/wwwroot/code/linq82.ss index 9fd6bf8..abbf86e 100644 --- a/src/wwwroot/code/linq82.ss +++ b/src/wwwroot/code/linq82.ss @@ -1,3 +1,3 @@ -{{ [ 'cherry', 'apple', 'blueberry' ] | to => words }} -{{ words | min => it.Length | to => shortestWord }} +{{ [ 'cherry', 'apple', 'blueberry' ] |> to => words }} +{{ words |> min => it.Length |> to => shortestWord }} The shortest word is {{shortestWord}} characters long. \ No newline at end of file diff --git a/src/wwwroot/code/linq83.sc b/src/wwwroot/code/linq83.sc index 1911948..a1c1cbf 100644 --- a/src/wwwroot/code/linq83.sc +++ b/src/wwwroot/code/linq83.sc @@ -1,4 +1,4 @@ {{ products - | groupBy => it.Category - | map => { Category: it.Key, CheapestPrice: it.min(p => p.UnitPrice) } - | htmlDump }} \ No newline at end of file + |> groupBy => it.Category + |> map => { Category: it.Key, CheapestPrice: it.min(p => p.UnitPrice) } + |> htmlDump }} \ No newline at end of file diff --git a/src/wwwroot/code/linq83.ss b/src/wwwroot/code/linq83.ss index 1911948..a1c1cbf 100644 --- a/src/wwwroot/code/linq83.ss +++ b/src/wwwroot/code/linq83.ss @@ -1,4 +1,4 @@ {{ products - | groupBy => it.Category - | map => { Category: it.Key, CheapestPrice: it.min(p => p.UnitPrice) } - | htmlDump }} \ No newline at end of file + |> groupBy => it.Category + |> map => { Category: it.Key, CheapestPrice: it.min(p => p.UnitPrice) } + |> htmlDump }} \ No newline at end of file diff --git a/src/wwwroot/code/linq84.sc b/src/wwwroot/code/linq84.sc index fe9b0cb..4058552 100644 --- a/src/wwwroot/code/linq84.sc +++ b/src/wwwroot/code/linq84.sc @@ -1,8 +1,8 @@ {{ products - | groupBy => it.Category - | let => { + |> groupBy => it.Category + |> let => { g: it, MinPrice: it.min(p => p.UnitPrice), } - | map => { Category: g.Key, CheapestProducts: g.where(p => p.UnitPrice == MinPrice) } - | htmlDump }} + |> map => { Category: g.Key, CheapestProducts: g.where(p => p.UnitPrice == MinPrice) } + |> htmlDump }} diff --git a/src/wwwroot/code/linq84.ss b/src/wwwroot/code/linq84.ss index fe9b0cb..4058552 100644 --- a/src/wwwroot/code/linq84.ss +++ b/src/wwwroot/code/linq84.ss @@ -1,8 +1,8 @@ {{ products - | groupBy => it.Category - | let => { + |> groupBy => it.Category + |> let => { g: it, MinPrice: it.min(p => p.UnitPrice), } - | map => { Category: g.Key, CheapestProducts: g.where(p => p.UnitPrice == MinPrice) } - | htmlDump }} + |> map => { Category: g.Key, CheapestProducts: g.where(p => p.UnitPrice == MinPrice) } + |> htmlDump }} diff --git a/src/wwwroot/code/linq85.sc b/src/wwwroot/code/linq85.sc index 74e82cf..df19371 100644 --- a/src/wwwroot/code/linq85.sc +++ b/src/wwwroot/code/linq85.sc @@ -1,3 +1,3 @@ -[5, 4, 1, 3, 9, 8, 6, 7, 2, 0] | to => numbers -numbers.max() | to => maxNum +[5, 4, 1, 3, 9, 8, 6, 7, 2, 0] |> to => numbers +numbers.max() |> to => maxNum `The maximum number is ${maxNum}.` \ No newline at end of file diff --git a/src/wwwroot/code/linq85.ss b/src/wwwroot/code/linq85.ss index 66204c4..56cfa1b 100644 --- a/src/wwwroot/code/linq85.ss +++ b/src/wwwroot/code/linq85.ss @@ -1,3 +1,3 @@ -{{ [5, 4, 1, 3, 9, 8, 6, 7, 2, 0] | to => numbers }} -{{ numbers.max() | to => maxNum }} +{{ [5, 4, 1, 3, 9, 8, 6, 7, 2, 0] |> to => numbers }} +{{ numbers.max() |> to => maxNum }} The maximum number is {{maxNum}}. \ No newline at end of file diff --git a/src/wwwroot/code/linq86.sc b/src/wwwroot/code/linq86.sc index 2273477..7777fbe 100644 --- a/src/wwwroot/code/linq86.sc +++ b/src/wwwroot/code/linq86.sc @@ -1,3 +1,3 @@ -[ 'cherry', 'apple', 'blueberry' ] | to => words -words | max => it.Length | to => longestLength +[ 'cherry', 'apple', 'blueberry' ] |> to => words +words |> max => it.Length |> to => longestLength `The longest word is ${longestLength} characters long.` \ No newline at end of file diff --git a/src/wwwroot/code/linq86.ss b/src/wwwroot/code/linq86.ss index 68cf0ee..5015fd3 100644 --- a/src/wwwroot/code/linq86.ss +++ b/src/wwwroot/code/linq86.ss @@ -1,3 +1,3 @@ -{{ [ 'cherry', 'apple', 'blueberry' ] | to => words }} -{{ words | max => it.Length | to => longestLength }} +{{ [ 'cherry', 'apple', 'blueberry' ] |> to => words }} +{{ words |> max => it.Length |> to => longestLength }} The longest word is {{longestLength}} characters long. \ No newline at end of file diff --git a/src/wwwroot/code/linq87.sc b/src/wwwroot/code/linq87.sc index 0f56a40..aded549 100644 --- a/src/wwwroot/code/linq87.sc +++ b/src/wwwroot/code/linq87.sc @@ -1,4 +1,4 @@ {{ products - | groupBy => it.Category - | map => { Category: it.Key, MostExpensivePrice: it.max(p => p.UnitPrice) } - | htmlDump }} \ No newline at end of file + |> groupBy => it.Category + |> map => { Category: it.Key, MostExpensivePrice: it.max(p => p.UnitPrice) } + |> htmlDump }} \ No newline at end of file diff --git a/src/wwwroot/code/linq87.ss b/src/wwwroot/code/linq87.ss index 0f56a40..aded549 100644 --- a/src/wwwroot/code/linq87.ss +++ b/src/wwwroot/code/linq87.ss @@ -1,4 +1,4 @@ {{ products - | groupBy => it.Category - | map => { Category: it.Key, MostExpensivePrice: it.max(p => p.UnitPrice) } - | htmlDump }} \ No newline at end of file + |> groupBy => it.Category + |> map => { Category: it.Key, MostExpensivePrice: it.max(p => p.UnitPrice) } + |> htmlDump }} \ No newline at end of file diff --git a/src/wwwroot/code/linq88.sc b/src/wwwroot/code/linq88.sc index b5c12b8..8feea00 100644 --- a/src/wwwroot/code/linq88.sc +++ b/src/wwwroot/code/linq88.sc @@ -1,8 +1,8 @@ {{ products - | groupBy => it.Category - | let => { + |> groupBy => it.Category + |> let => { g: it, MaxPrice: it.max(p => p.UnitPrice), } - | map => { Category: g.Key, MostExpensiveProducts: g.where(p => p.UnitPrice = MaxPrice) } - | htmlDump }} \ No newline at end of file + |> map => { Category: g.Key, MostExpensiveProducts: g.where(p => p.UnitPrice = MaxPrice) } + |> htmlDump }} \ No newline at end of file diff --git a/src/wwwroot/code/linq88.ss b/src/wwwroot/code/linq88.ss index b5c12b8..8feea00 100644 --- a/src/wwwroot/code/linq88.ss +++ b/src/wwwroot/code/linq88.ss @@ -1,8 +1,8 @@ {{ products - | groupBy => it.Category - | let => { + |> groupBy => it.Category + |> let => { g: it, MaxPrice: it.max(p => p.UnitPrice), } - | map => { Category: g.Key, MostExpensiveProducts: g.where(p => p.UnitPrice = MaxPrice) } - | htmlDump }} \ No newline at end of file + |> map => { Category: g.Key, MostExpensiveProducts: g.where(p => p.UnitPrice = MaxPrice) } + |> htmlDump }} \ No newline at end of file diff --git a/src/wwwroot/code/linq89.sc b/src/wwwroot/code/linq89.sc index e429303..fdf9a20 100644 --- a/src/wwwroot/code/linq89.sc +++ b/src/wwwroot/code/linq89.sc @@ -1,3 +1,3 @@ -[5, 4, 1, 3, 9, 8, 6, 7, 2, 0] | to => numbers -numbers.average() | to => averageNum +[5, 4, 1, 3, 9, 8, 6, 7, 2, 0] |> to => numbers +numbers.average() |> to => averageNum `The average number is ${averageNum}.` \ No newline at end of file diff --git a/src/wwwroot/code/linq89.ss b/src/wwwroot/code/linq89.ss index b30b6a1..9c0f183 100644 --- a/src/wwwroot/code/linq89.ss +++ b/src/wwwroot/code/linq89.ss @@ -1,3 +1,3 @@ -{{ [5, 4, 1, 3, 9, 8, 6, 7, 2, 0] | to => numbers }} -{{ numbers.average() | to => averageNum }} +{{ [5, 4, 1, 3, 9, 8, 6, 7, 2, 0] |> to => numbers }} +{{ numbers.average() |> to => averageNum }} The average number is {{averageNum}}. \ No newline at end of file diff --git a/src/wwwroot/code/linq90.sc b/src/wwwroot/code/linq90.sc index fc564c5..f33bb2b 100644 --- a/src/wwwroot/code/linq90.sc +++ b/src/wwwroot/code/linq90.sc @@ -1,3 +1,3 @@ -[ 'cherry', 'apple', 'blueberry' ] | to => words -words | average => it.Length | to => averageLength +[ 'cherry', 'apple', 'blueberry' ] |> to => words +words |> average => it.Length |> to => averageLength `The average word length is ${averageLength} characters.` \ No newline at end of file diff --git a/src/wwwroot/code/linq90.ss b/src/wwwroot/code/linq90.ss index 337b656..b969528 100644 --- a/src/wwwroot/code/linq90.ss +++ b/src/wwwroot/code/linq90.ss @@ -1,3 +1,3 @@ -{{ [ 'cherry', 'apple', 'blueberry' ] | to => words }} -{{ words | average => it.Length | to => averageLength }} +{{ [ 'cherry', 'apple', 'blueberry' ] |> to => words }} +{{ words |> average => it.Length |> to => averageLength }} The average word length is {{averageLength}} characters. \ No newline at end of file diff --git a/src/wwwroot/code/linq91.sc b/src/wwwroot/code/linq91.sc index a8b4962..879bd24 100644 --- a/src/wwwroot/code/linq91.sc +++ b/src/wwwroot/code/linq91.sc @@ -1,4 +1,4 @@ {{ products - | groupBy => it.Category - | map => { Category: it.Key, AveragePrice: it.average(p => p.UnitPrice) } - | htmlDump }} \ No newline at end of file + |> groupBy => it.Category + |> map => { Category: it.Key, AveragePrice: it.average(p => p.UnitPrice) } + |> htmlDump }} \ No newline at end of file diff --git a/src/wwwroot/code/linq91.ss b/src/wwwroot/code/linq91.ss index a8b4962..879bd24 100644 --- a/src/wwwroot/code/linq91.ss +++ b/src/wwwroot/code/linq91.ss @@ -1,4 +1,4 @@ {{ products - | groupBy => it.Category - | map => { Category: it.Key, AveragePrice: it.average(p => p.UnitPrice) } - | htmlDump }} \ No newline at end of file + |> groupBy => it.Category + |> map => { Category: it.Key, AveragePrice: it.average(p => p.UnitPrice) } + |> htmlDump }} \ No newline at end of file diff --git a/src/wwwroot/code/linq92.sc b/src/wwwroot/code/linq92.sc index 6120cc2..f5f070b 100644 --- a/src/wwwroot/code/linq92.sc +++ b/src/wwwroot/code/linq92.sc @@ -1,3 +1,3 @@ -[1.7, 2.3, 1.9, 4.1, 2.9] | to => doubles -doubles.reduce((runningProduct, nextFactor) => runningProduct * nextFactor, 1) | to => product +[1.7, 2.3, 1.9, 4.1, 2.9] |> to => doubles +doubles.reduce((runningProduct, nextFactor) => runningProduct * nextFactor, 1) |> to => product `Total product of all numbers: ${ product }.` \ No newline at end of file diff --git a/src/wwwroot/code/linq92.ss b/src/wwwroot/code/linq92.ss index e53ab7c..7774cdf 100644 --- a/src/wwwroot/code/linq92.ss +++ b/src/wwwroot/code/linq92.ss @@ -1,4 +1,4 @@ -{{ [1.7, 2.3, 1.9, 4.1, 2.9] | to => doubles }} +{{ [1.7, 2.3, 1.9, 4.1, 2.9] |> to => doubles }} {{ doubles.reduce((runningProduct, nextFactor) => runningProduct * nextFactor, 1) - | to => product }} + |> to => product }} Total product of all numbers: {{ product }}. \ No newline at end of file diff --git a/src/wwwroot/code/linq93.sc b/src/wwwroot/code/linq93.sc index 0858373..cd72b14 100644 --- a/src/wwwroot/code/linq93.sc +++ b/src/wwwroot/code/linq93.sc @@ -1,5 +1,5 @@ -[20, 10, 40, 50, 10, 70, 30] | to => attemptedWithdrawals +[20, 10, 40, 50, 10, 70, 30] |> to => attemptedWithdrawals {{ attemptedWithdrawals.reduce((balance, nextWithdrawal) => ((nextWithdrawal <= balance) ? (balance - nextWithdrawal) : balance), 100.0) - | to => endBalance }} + |> to => endBalance }} `Ending balance: ${endBalance}.` \ No newline at end of file diff --git a/src/wwwroot/code/linq93.ss b/src/wwwroot/code/linq93.ss index cc2bbdf..2ac63b0 100644 --- a/src/wwwroot/code/linq93.ss +++ b/src/wwwroot/code/linq93.ss @@ -1,5 +1,5 @@ -{{ [20, 10, 40, 50, 10, 70, 30] | to => attemptedWithdrawals }} +{{ [20, 10, 40, 50, 10, 70, 30] |> to => attemptedWithdrawals }} {{ attemptedWithdrawals.reduce((balance, nextWithdrawal) => ((nextWithdrawal <= balance) ? (balance - nextWithdrawal) : balance), 100.0) - | to => endBalance }} + |> to => endBalance }} Ending balance: {{endBalance}}. \ No newline at end of file diff --git a/src/wwwroot/code/linq94.sc b/src/wwwroot/code/linq94.sc index 17848d4..8931818 100644 --- a/src/wwwroot/code/linq94.sc +++ b/src/wwwroot/code/linq94.sc @@ -1,4 +1,4 @@ -[0, 2, 4, 5, 6, 8, 9] | to => numbersA -[1, 3, 5, 7, 8] | to => numbersB +[0, 2, 4, 5, 6, 8, 9] |> to => numbersA +[1, 3, 5, 7, 8] |> to => numbersB `All numbers from both arrays:` -numbersA.concat(numbersB) | joinln \ No newline at end of file +numbersA.concat(numbersB) |> joinln \ No newline at end of file diff --git a/src/wwwroot/code/linq94.ss b/src/wwwroot/code/linq94.ss index 55ff8e9..f90f638 100644 --- a/src/wwwroot/code/linq94.ss +++ b/src/wwwroot/code/linq94.ss @@ -1,4 +1,4 @@ -{{ [0, 2, 4, 5, 6, 8, 9] | to => numbersA }} -{{ [1, 3, 5, 7, 8] | to => numbersB }} +{{ [0, 2, 4, 5, 6, 8, 9] |> to => numbersA }} +{{ [1, 3, 5, 7, 8] |> to => numbersB }} All numbers from both arrays: -{{ numbersA.concat(numbersB) | joinln }} \ No newline at end of file +{{ numbersA.concat(numbersB) |> joinln }} \ No newline at end of file diff --git a/src/wwwroot/code/linq95.sc b/src/wwwroot/code/linq95.sc index 05d9b63..f524706 100644 --- a/src/wwwroot/code/linq95.sc +++ b/src/wwwroot/code/linq95.sc @@ -1,4 +1,4 @@ -customers | map => it.CompanyName | to => customerNames -products | map => it.ProductName | to => productNames +customers |> map => it.CompanyName |> to => customerNames +products |> map => it.ProductName |> to => productNames `Customer and product names:` -customerNames.concat(productNames) | joinln \ No newline at end of file +customerNames.concat(productNames) |> joinln \ No newline at end of file diff --git a/src/wwwroot/code/linq95.ss b/src/wwwroot/code/linq95.ss index 3fde78f..1c494be 100644 --- a/src/wwwroot/code/linq95.ss +++ b/src/wwwroot/code/linq95.ss @@ -1,8 +1,8 @@ {{ customers - | map => it.CompanyName - | to => customerNames }} + |> map => it.CompanyName + |> to => customerNames }} {{ products - | map => it.ProductName - | to => productNames }} + |> map => it.ProductName + |> to => productNames }} Customer and product names: -{{ customerNames.concat(productNames) | joinln }} \ No newline at end of file +{{ customerNames.concat(productNames) |> joinln }} \ No newline at end of file diff --git a/src/wwwroot/code/linq96.sc b/src/wwwroot/code/linq96.sc index 7f98bf3..4976626 100644 --- a/src/wwwroot/code/linq96.sc +++ b/src/wwwroot/code/linq96.sc @@ -1,4 +1,4 @@ -[ 'cherry', 'apple', 'blueberry' ] | to => wordsA -[ 'cherry', 'apple', 'blueberry' ] | to => wordsB -wordsA.equivalentTo(wordsB) | to => match +[ 'cherry', 'apple', 'blueberry' ] |> to => wordsA +[ 'cherry', 'apple', 'blueberry' ] |> to => wordsB +wordsA.equivalentTo(wordsB) |> to => match `The sequences match: ${match.lower()}` \ No newline at end of file diff --git a/src/wwwroot/code/linq96.ss b/src/wwwroot/code/linq96.ss index f6e3663..e834ed9 100644 --- a/src/wwwroot/code/linq96.ss +++ b/src/wwwroot/code/linq96.ss @@ -1,4 +1,4 @@ -{{ [ 'cherry', 'apple', 'blueberry' ] | to => wordsA }} -{{ [ 'cherry', 'apple', 'blueberry' ] | to => wordsB }} -{{ wordsA.equivalentTo(wordsB) | to => match }} -The sequences match: {{ match | lower }} \ No newline at end of file +{{ [ 'cherry', 'apple', 'blueberry' ] |> to => wordsA }} +{{ [ 'cherry', 'apple', 'blueberry' ] |> to => wordsB }} +{{ wordsA.equivalentTo(wordsB) |> to => match }} +The sequences match: {{ match |> lower }} \ No newline at end of file diff --git a/src/wwwroot/code/linq97.sc b/src/wwwroot/code/linq97.sc index 49a6010..28844dd 100644 --- a/src/wwwroot/code/linq97.sc +++ b/src/wwwroot/code/linq97.sc @@ -1,4 +1,4 @@ -[ 'cherry', 'apple', 'blueberry' ] | to => wordsA -[ 'apple', 'blueberry', 'cherry' ] | to => wordsB -wordsA.equivalentTo(wordsB) | to => match +[ 'cherry', 'apple', 'blueberry' ] |> to => wordsA +[ 'apple', 'blueberry', 'cherry' ] |> to => wordsB +wordsA.equivalentTo(wordsB) |> to => match `The sequences match: ${match.lower()}` \ No newline at end of file diff --git a/src/wwwroot/code/linq97.ss b/src/wwwroot/code/linq97.ss index 386de8e..8802bf3 100644 --- a/src/wwwroot/code/linq97.ss +++ b/src/wwwroot/code/linq97.ss @@ -1,4 +1,4 @@ -{{ [ 'cherry', 'apple', 'blueberry' ] | to => wordsA }} -{{ [ 'apple', 'blueberry', 'cherry' ] | to => wordsB }} -{{ wordsA.equivalentTo(wordsB) | to => match }} -The sequences match: {{ match | lower }} +{{ [ 'cherry', 'apple', 'blueberry' ] |> to => wordsA }} +{{ [ 'apple', 'blueberry', 'cherry' ] |> to => wordsB }} +{{ wordsA.equivalentTo(wordsB) |> to => match }} +The sequences match: {{ match |> lower }} diff --git a/src/wwwroot/code/linq99.sc b/src/wwwroot/code/linq99.sc index bee840e..ba2fd9f 100644 --- a/src/wwwroot/code/linq99.sc +++ b/src/wwwroot/code/linq99.sc @@ -1,3 +1,3 @@ -[5, 4, 1, 3, 9, 8, 6, 7, 2, 0] | to => numbers -0 | to => i -numbers | let => { i: i + 1 } | map => `v = ${index + 1}, i = ${i}` | joinln \ No newline at end of file +[5, 4, 1, 3, 9, 8, 6, 7, 2, 0] |> to => numbers +0 |> to => i +numbers |> let => { i: i + 1 } |> map => `v = ${index + 1}, i = ${i}` |> joinln \ No newline at end of file diff --git a/src/wwwroot/code/linq99.ss b/src/wwwroot/code/linq99.ss index ce87826..b2aa78c 100644 --- a/src/wwwroot/code/linq99.ss +++ b/src/wwwroot/code/linq99.ss @@ -1,3 +1,3 @@ -{{ [5, 4, 1, 3, 9, 8, 6, 7, 2, 0] | to => numbers }} -{{ 0 | to => i }} -{{ numbers | let => { i: i + 1 } | select: v = {index + 1}, i = {i}\n }} \ No newline at end of file +{{ [5, 4, 1, 3, 9, 8, 6, 7, 2, 0] |> to => numbers }} +{{ 0 |> to => i }} +{{ numbers |> let => { i: i + 1 } | select: v = {index + 1}, i = {i}\n }} \ No newline at end of file diff --git a/src/wwwroot/code/mv.sc b/src/wwwroot/code/mv.sc index ff6c781..e9548e0 100644 --- a/src/wwwroot/code/mv.sc +++ b/src/wwwroot/code/mv.sc @@ -1,4 +1,4 @@ -vfsFileSystem('.') | to => fs +vfsFileSystem('.') |> to => fs #each f in fs.findFiles('linq*.txt') `copy ${f.Name} ${f.Name.lastLeftPart('.')}.sc` | sh diff --git a/src/wwwroot/docs/arguments.html b/src/wwwroot/docs/arguments.html index 51256a0..4036127 100644 --- a/src/wwwroot/docs/arguments.html +++ b/src/wwwroot/docs/arguments.html @@ -81,7 +81,7 @@

    Scoped Arguments

    arg: 3 --> -{{ 6 | to => arg }} +{{ 6 |> to => arg }} page arg: {{ arg }}' } }) diff --git a/src/wwwroot/docs/blocks.html b/src/wwwroot/docs/blocks.html index fa2f078..9e08c5e 100644 --- a/src/wwwroot/docs/blocks.html +++ b/src/wwwroot/docs/blocks.html @@ -387,7 +387,7 @@
    Autowired using ScriptContext IOC
    Food 400 Misc 200 {{/keyvalues}} -{{ monthlyExpenses | values | sum | to => totalExpenses }} +{{ monthlyExpenses | values | sum |> to => totalExpenses }} By default it's delimited by the first space ' ', but if the first key column can contain spaces you can specify to use a different delimiter, e.g: @@ -420,7 +420,7 @@
    Autowired using ScriptContext IOC
    Tesla,Model 3,38990 Tesla,Model X,84990 {{/csv}} -{{ cars | map => { Make: it[0], Model: it[1], Cost: it[2] } | htmldump }} +{{ cars |> map => { Make: it[0], Model: it[1], Cost: it[2] } |> htmlDump }} Total Cost: {{ cars | sum => it[2] | currency }}" }) }} {{#markdown}} diff --git a/src/wwwroot/docs/default-scripts.html b/src/wwwroot/docs/default-scripts.html index 8705c88..061fb78 100644 --- a/src/wwwroot/docs/default-scripts.html +++ b/src/wwwroot/docs/default-scripts.html @@ -89,7 +89,7 @@

    Math

    {{ 'live-template' | partial({ template: "Circumference = {{ 2 * pi * 10 | round(5) }} √ log10 10000 = {{ 10000 | log10 | sqrt }} -Powers of 2 = {{ 10 | times | map => `${(it + 1).pow(2)}` | join }}" }) }} +Powers of 2 = {{ 10 | times |> map => `${(it + 1).pow(2)}` | join }}" }) }}

    Date Functions

    @@ -160,7 +160,7 @@

    Trimming and Padding

    URL handling

    -{{ 'live-template' | partial({ rows: 8, template: "{{ 'http://example.org' | to => baseUrl }} +{{ 'live-template' | partial({ rows: 8, template: "{{ 'http://example.org' |> to => baseUrl }} {{ baseUrl | addPath('path') }} {{ baseUrl | addPaths(['path1', 'path2', 'path3']) }} {{ baseUrl | addQueryString({ a: 1, b: 2 }) }} @@ -244,7 +244,7 @@

    Object Conversions

    {{ 'live-template' | partial({ rows: 5, template: `toInt: {{ '1' | typeName }}, {{ '1' | toInt | typeName }}, {{ 1 | toDouble | typeName }} toChar: {{ ',' | typeName }}, {{ ',' | toChar |typeName }}, {{'true' | toBool | typeName }} -{{ {a:1,b:2,c:'',d:null} | to => o }} +{{ {a:1,b:2,c:'',d:null} |> to => o }} toKeys: {{ o | toKeys | join }}, toValues: {{ o | toValues | join }} without: {{ o | withoutNullValues | toKeys|join }} / {{ o | withoutEmptyValues | toKeys| join }}` }) }} @@ -270,10 +270,10 @@

    Content Handling

    {{ 'live-template' | partial({ rows: 7, template: `default: {{ title | default('A Title') }} {{ title != null ? title : 'A Title' }} -{{ 'The Title' | to => title }}{{ title | default('A Title') }} : {{ 1 | ifExists(title) }} +{{ 'The Title' |> to => title }}{{ title | default('A Title') }} : {{ 1 | ifExists(title) }} {{ noArg }} : {{ noArg | ifExists }} : {{ 1 | ifNotExists(noArg) }} : {{ 1 | ifNo(noArg) }} {{ 'empty' | ifEmpty('') }} : {{ 'empty' | ifEmpty([]) }} : {{ 'empty' | ifEmpty([1]) }} -{{ [1,2,3] | to => nums }}{{ nums | join | to => list }} +{{ [1,2,3] |> to => nums }}{{ nums | join |> to => list }} {{ "nums {0}" | fmt(list) | ifNotEmpty(nums) }}{{ "nums {0}" | fmt(list) | ifNotEmpty([]) }}` }) }}

    Control Execution

    @@ -296,9 +296,9 @@

    Control Execution

    any {{ 5 | times | endIfAny('it = 4') | join }}/{{ 5 | times | endIfAny('it = 5') | join }} all {{ 5 | times | endIfAll('lt(it,4)') |join }}/{{ 5 | times | endIfAll('lt(it,5)') |join }} where {{ 1 | endWhere: isString(it) }}/{{ 'a' | endWhere: isString(it) }} -useFmt {{ arg | endIfExists | useFmt('{0} + {1}', 1, 2) | to => arg }}{{ arg }} -useFmt {{ arg | endIfExists | useFmt('{0} + {1}', 3, 4) | to => arg }}{{ arg }} -useFormat {{ arg2 | endIfExists | useFormat('value', 'key={0}') | to => arg2 }}{{ arg2 }} +useFmt {{ arg | endIfExists | useFmt('{0} + {1}', 1, 2) |> to => arg }}{{ arg }} +useFmt {{ arg | endIfExists | useFmt('{0} + {1}', 3, 4) |> to => arg }}{{ arg }} +useFormat {{ arg2 | endIfExists | useFormat('value', 'key={0}') |> to => arg2 }}{{ arg2 }} {{ noArg | end }} : {{ 1 | end }} : {{ 1 | incr | end }}` }) }}
    @@ -331,7 +331,7 @@

    Assignment

    You can create temporary arguments within a script scope or modify existing arguments with:

    -{{ 'live-template' | partial({ rows: 6, template: "{{ [1,2,3,4,5] | to => numbers }} +{{ 'live-template' | partial({ rows: 6, template: "{{ [1,2,3,4,5] |> to => numbers }} {{ numbers | join }} {{ numbers | do => assign('numbers[index]', it * it) }} {{ numbers | join }} @@ -344,9 +344,9 @@

    Let Bindings and Scope Vars

    Let bindings allow you to create scoped argument bindings from individual string expressions:

    -{{ 'live-template' | partial({ rows: 4, template: ′{{ [{name:'Alice',score:50},{name:'Bob',score:40}] | to =>scoreRecords }} +{{ 'live-template' | partial({ rows: 4, template: ′{{ [{name:'Alice',score:50},{name:'Bob',score:40}] |> to =>scoreRecords }} {{ scoreRecords - | let => { name:it.name, score:it.score, personNum:index + 1 } + |> let => { name:it.name, score:it.score, personNum:index + 1 } | select: {personNum}. {name} = {score}\n }}′ }) }}

    @@ -354,20 +354,20 @@

    Let Bindings and Scope Vars

    as the name of the argument binding and the value as its value:

    -{{ 'live-template' | partial({ template: "{{ [{name:'Alice',score:50},{name:'Bob',score:40}] | to =>scoreRecords }} +{{ 'live-template' | partial({ template: "{{ [{name:'Alice',score:50},{name:'Bob',score:40}] |> to =>scoreRecords }} {{ scoreRecords | scopeVars | select: {index + 1}. {name} = {score}\n }}" }) }}

    Querying Objects

    -{{ 'live-template' | partial({ rows: 5, template: `{{ [10,20,30,40,50] | to => numbers }} -{{ { a:1, b:2, c:3 } | to => letters }} +{{ 'live-template' | partial({ rows: 5, template: `{{ [10,20,30,40,50] |> to => numbers }} +{{ { a:1, b:2, c:3 } |> to => letters }} Number at [3]: {{ numbers[3] }}, {{ numbers | get(3) }} Value of 'c': {{ letters['c'] }}, {{ letters.c }}, {{ letters | get('c') }} Property Value: {{ 'A String'.Length }}, {{ 'A String'['Len' + 'gth'] }}` }) }}

    Member Expressions

    -{{ 'live-template' | partial({ rows: 4, template: `{{ [now] | to => dates }} +{{ 'live-template' | partial({ rows: 4, template: `{{ [now] |> to => dates }} {{ round(dates[0].TimeOfDay.TotalHours, 3) }} {{ dates | get(0) | select: { it.TimeOfDay.TotalHours | round(3) } }} {{ [now.TimeOfDay][0].TotalHours | round(3) }}` }) }} @@ -382,32 +382,32 @@

    Mapping and Conversions

    Use map when you want to transform each item into a different value:

    -{{ 'live-template' | partial({ rows: 4, template: "{{ ['zero','one','two','three','four','five','six','seven','eight','nine'] | to =>digits }} +{{ 'live-template' | partial({ rows: 4, template: "{{ ['zero','one','two','three','four','five','six','seven','eight','nine'] |> to =>digits }} {{ range(3) - | map => it + 5 - | map => digits[it] | join(`\\n`) }}" }) }} + |> map => it + 5 + |> map => digits[it] | join(`\\n`) }}" }) }}

    It's also useful for transforming raw data sources into more manageable ones:

    -{{ 'live-template' | partial({ rows: 5, template: "{{ [[1,-2],[3,-4],[5,-6]] | to =>coords }} +{{ 'live-template' | partial({ rows: 5, template: "{{ [[1,-2],[3,-4],[5,-6]] |> to =>coords }} {{ coords - | map => { x: it[0], y: it[1] } + |> map => { x: it[0], y: it[1] } | scopeVars - | map => `${index + 1}. (${x}, ${y})\n` | join('') }}" }) }} + |> map => `${index + 1}. (${x}, ${y})\n` | join('') }}" }) }}

    Whilst these methods let you perform some other popular conversions:

    {{ 'live-template' | partial({ rows: 8, template: "{{ 100 | toString | select: {it.Length} }} -{{ { x:1, y:2 } | toList | map => `${it.Key} = ${it.Value}` | join(', ') }} -{{ range(5) | toArray | to => numbers }} +{{ { x:1, y:2 } | toList |> map => `${it.Key} = ${it.Value}` | join(', ') }} +{{ range(5) | toArray |> to => numbers }} {{ numbers.Length | times | do => assign('numbers[index]', -numbers[index]) }} {{ numbers | join }} Bob's score: {{ [{name:'Alice',score:50},{name:'Bob',score:40}] - | toDictionary => it.name | map => it.Bob.score }}" }) }} + | toDictionary => it.name |> map => it.Bob.score }}" }) }}

    Use parseKeyValueText to convert a key/value string into a dictionary, you can then use @@ -420,7 +420,7 @@

    Mapping and Conversions

    Mobile: 50 Food: 400 Misc: 200 -'| trim | parseKeyValueText(':') | to => expenses }} +'| trim | parseKeyValueText(':') |> to => expenses }} Expenses: {{ expenses | values | sum | currency }}` }) }}

    Serialization

    @@ -429,7 +429,7 @@

    Serialization

    Use the json method when you want to make your C# objects available to the client JavaScript page:

    -{{ 'live-template' | partial({ template: `{{ [{x:1,y:2},{x:3,y:4}] | to =>model }} +{{ 'live-template' | partial({ template: `{{ [{x:1,y:2},{x:3,y:4}] |> to =>model }} var coords = {{ model | json }};` }) }}

    Embedding in JavaScript

    @@ -442,7 +442,7 @@

    Embedding in JavaScript

    {{ 'live-template' | partial({ rows: 9, template: ′{{ '[ {"name":"Mc Donald\'s"} -]' | to =>json }} +]' |> to =>json }} var obj = {{ json }}; var str = '{{ json | jsString }}'; var str = {{ json | jsQuotedString }}; @@ -455,7 +455,7 @@

    Embedding in JavaScript

    of an object graph, you can also use dump if you want the JSV output indented:

    -{{ 'live-template' | partial({ template: `{{ [{x:1,y:2},{x:3,y:4}] | to => model }} +{{ 'live-template' | partial({ template: `{{ [{x:1,y:2},{x:3,y:4}] |> to => model }} {{ model | jsv }} {{ model | dump }}` }) }} @@ -463,7 +463,7 @@

    Embedding in JavaScript

    If needed, the csv and xml serialization formats are also available:

    -{{ 'live-template' | partial({ template: `{{ [{Name:'Alice',Score:50},{Name:'Bob',Score:40}] | to =>scoreRecords }} +{{ 'live-template' | partial({ template: `{{ [{Name:'Alice',Score:50},{Name:'Bob',Score:40}] |> to =>scoreRecords }} {{ scoreRecords | csv }}` }) }}

    Eval

    @@ -493,9 +493,9 @@

    Iterating

    for each item in a collection:

    -{{ 'live-template' | partial({ rows: 7, template: "{{ [{name:'Alice',score:50},{name:'Bob',score:40}] | to => scoreRecords }} +{{ 'live-template' | partial({ rows: 7, template: "{{ [{name:'Alice',score:50},{name:'Bob',score:40}] |> to => scoreRecords }} map -
      {{ scoreRecords | map => `
    1. ${it.name} = ${it.score}
    2. ` | join('') | raw }}
    +
      {{ scoreRecords |> map => `
    1. ${it.name} = ${it.score}
    2. ` | join('') | raw }}
    select
      {{ scoreRecords | select:
    1. {it.name} = {it.score}
    2. }}
    selectEach @@ -505,15 +505,15 @@

    Iterating

    Although a lot of times it's easier to use the each Script Block to iterate over items:

    -{{ 'live-template' | partial({ rows: 4, template: "{{ [{name:'Alice',score:50},{name:'Bob',score:40}] | to => scoreRecords }} +{{ 'live-template' | partial({ rows: 4, template: "{{ [{name:'Alice',score:50},{name:'Bob',score:40}] |> to => scoreRecords }} {{#each scoreRecords}} {{index+1}}. {{it.name}} = {{it.score}} {{/each}}" }) }}

    If you instead need to iterate over a collection to perform side-effects without generating output you can use forEach:

    -{{ 'live-template' | partial({ rows: 4, template: "{{ [{name:'Alice',score:50},{name:'Bob',score:40}] | to => scoreRecords }} -{{ [] | to => scores }} +{{ 'live-template' | partial({ rows: 4, template: "{{ [{name:'Alice',score:50},{name:'Bob',score:40}] |> to => scoreRecords }} +{{ [] |> to => scores }} {{scoreRecords.forEach((record, index) => scores.push(record.score))}} {{ scores | join}}" }) }} diff --git a/src/wwwroot/docs/error-handling.html b/src/wwwroot/docs/error-handling.html index ca5656e..b121838 100644 --- a/src/wwwroot/docs/error-handling.html +++ b/src/wwwroot/docs/error-handling.html @@ -245,20 +245,20 @@

    Ensure Argument Helpers

    all the arguments meet the condition or return the Object Dictionary if all conditions are met:

    -{{ 'live-template' | partial({ rows:5, template: `{{ 1 | to => one }}{{ 'bar' | to => foo }}{{ '' | to => empty }} -{{ { one, foo, empty } | ensureAllArgsNotNull | htmlDump({ caption: 'ensureAllArgsNotNull' }) }} +{{ 'live-template' | partial({ rows:5, template: `{{ 1 |> to => one }}{{ 'bar' |> to => foo }}{{ '' |> to => empty }} +{{ { one, foo, empty } | ensureAllArgsNotNull |> htmlDump({ caption: 'ensureAllArgsNotNull' }) }} ensureAllArgsNotEmpty: -{{ { one, foo, empty } | ensureAllArgsNotEmpty({ assignError:'ex' }) | htmlDump }} +{{ { one, foo, empty } | ensureAllArgsNotEmpty({ assignError:'ex' }) |> htmlDump }} {{ ex | htmlErrorMessage }}` }) }}

    The ensureAny* methods only requires one of the arguments meet the condition to return the Object Dictionary:

    -{{ 'live-template' | partial({ rows:5, template: `{{ '' | to => empty }} -{{ { foo, empty } | ensureAnyArgsNotNull | htmlDump({ caption: 'ensureAnyArgsNotNull' }) }} +{{ 'live-template' | partial({ rows:5, template: `{{ '' |> to => empty }} +{{ { foo, empty } | ensureAnyArgsNotNull |> htmlDump({ caption: 'ensureAnyArgsNotNull' }) }} ensureAnyArgsNotEmpty: -{{ { foo, empty } | ensureAnyArgsNotEmpty({ assignError:'ex' }) | htmlDump }} +{{ { foo, empty } | ensureAnyArgsNotEmpty({ assignError:'ex' }) |> htmlDump }} {{ ex | htmlErrorMessage }}` }) }}

    Fatal Exceptions

    diff --git a/src/wwwroot/docs/gist-desktop-apps.html b/src/wwwroot/docs/gist-desktop-apps.html index 1d5945a..b5aa5ad 100644 --- a/src/wwwroot/docs/gist-desktop-apps.html +++ b/src/wwwroot/docs/gist-desktop-apps.html @@ -349,12 +349,12 @@ | dbExec }} - {{ dbScalar(`SELECT COUNT(*) FROM Post`) | to => postsCount }} + {{ dbScalar(`SELECT COUNT(*) FROM Post`) |> to => postsCount }} {{#if postsCount == 0 }} - {{ `datetime(CURRENT_TIMESTAMP,'localtime')` | to => sqlNow }} - {{ `ServiceStack` | to => user }} + {{ `datetime(CURRENT_TIMESTAMP,'localtime')` |> to => sqlNow }} + {{ `ServiceStack` |> to => user }} ======================== Create ServiceStack User - Contains same info as if was @ServiceStack authenticated via Twitter @@ -395,7 +395,7 @@ #### [_init.html](https://gist.github.com/gistlyn/0148c87e154fb4731c7fa6219375d989#file-_init-html) - vfsFileSystem('.') | to => fs + vfsFileSystem('.') |> to => fs #if !fs.fileExists('northwind.sqlite') || fs.file('northwind.sqlite').Length == 0 fs.writeFile('northwind.sqlite', file('northwind.readonly.sqlite')) diff --git a/src/wwwroot/docs/html-scripts.html b/src/wwwroot/docs/html-scripts.html index d711e94..3f03f4b 100644 --- a/src/wwwroot/docs/html-scripts.html +++ b/src/wwwroot/docs/html-scripts.html @@ -14,22 +14,22 @@

    htmlDump

    headerStyle can be any Text Style

    -{{ 'live-template' | partial({ template: `{{ [{FirstName: 'Kurt', Age: 27},{FirstName: 'Jimi', Age: 27}] | to => rockstars }} -{{ rockstars | htmlDump }} -Uses defaults: {{ rockstars | htmlDump({ headerTag: 'th', headerStyle: 'splitCase' }) }}` }) }} +{{ 'live-template' | partial({ template: `{{ [{FirstName: 'Kurt', Age: 27},{FirstName: 'Jimi', Age: 27}] |> to => rockstars }} +{{ rockstars |> htmlDump }} +Uses defaults: {{ rockstars |> htmlDump({ headerTag: 'th', headerStyle: 'splitCase' }) }}` }) }}
    htmlDump customizations
    -{{ 'live-template' | partial({ rows:3, template: `{{ [{FirstName: 'Kurt', Age: 27},{FirstName: 'Jimi', Age: 27}] | to => rockstars }} -{{ rockstars | htmlDump({ className: "table table-striped", caption: "Rockstars" }) }} -{{ [] | htmlDump({ captionIfEmpty: "No Rockstars"}) }}` }) }} +{{ 'live-template' | partial({ rows:3, template: `{{ [{FirstName: 'Kurt', Age: 27},{FirstName: 'Jimi', Age: 27}] |> to => rockstars }} +{{ rockstars |> htmlDump({ className: "table table-striped", caption: "Rockstars" }) }} +{{ [] |> htmlDump({ captionIfEmpty: "No Rockstars"}) }}` }) }}

    htmlClass

    Helper method to simplify rendering a class="..." list on HTML elements, it accepts Dictionary of boolean flags, List of strings or string, e.g:

    -{{ 'live-template' | partial({ rows:7, template: `{{ 1 | to => index }} +{{ 'live-template' | partial({ rows:7, template: `{{ 1 |> to => index }} Dictionary All: {{ {alt:isOdd(index), active:true} | htmlClass }} Dictionary One: {{ {alt:isEven(index), active:true} | htmlClass }} Dictionary None: {{ {alt:isEven(index), active:false} | htmlClass }} diff --git a/src/wwwroot/docs/introduction.html b/src/wwwroot/docs/introduction.html index 11d2d13..b65eb11 100644 --- a/src/wwwroot/docs/introduction.html +++ b/src/wwwroot/docs/introduction.html @@ -320,7 +320,7 @@
    Same intent, different implementations
    and sets a value in the current scope before returning an empty Stream so nothing is written to the Response.

    -
    {{ pass: url | urlContents | markdown | to => quote }}
    +
    {{ pass: url | urlContents | markdown |> to => quote }}

    Once the streamed output is captured and assigned it goes back into becoming a normal argument that opens it up to be diff --git a/src/wwwroot/docs/methods.html b/src/wwwroot/docs/methods.html index 31efc28..e572aee 100644 --- a/src/wwwroot/docs/methods.html +++ b/src/wwwroot/docs/methods.html @@ -168,6 +168,6 @@

    Capture Block Method Output

    You can also capture the output of a Block Method and assign it to a normal argument by using the assignTo Block Method:

    -
    {{ pass: 'doc.md' | includeFile | to => contents }}
    +
    {{ pass: 'doc.md' | includeFile |> to => contents }}
    {{ "doc-links" | partial({ order }) }} diff --git a/src/wwwroot/docs/partials.html b/src/wwwroot/docs/partials.html index 60a1f15..db3da8a 100644 --- a/src/wwwroot/docs/partials.html +++ b/src/wwwroot/docs/partials.html @@ -16,7 +16,7 @@ page: 'page', files: { - '_layout.html': `{{ 'from layout' | to => layoutArg }} + '_layout.html': `{{ 'from layout' |> to => layoutArg }} I am a Layout with page {{ page }}`, 'page.html' : `I am a Page with a partial @@ -67,7 +67,7 @@

    Inline Partials

    Customer {{ it.CustomerId }} {{ it.CompanyName | raw }} {{ it.Orders | selectPartial: order }}{{/partial}} -{{ customers | where => it.Region = 'WA' | to => waCustomers }} +{{ customers |> where => it.Region = 'WA' |> to => waCustomers }} Customers from Washington and their orders: {{ waCustomers | selectPartial: customer }}` }) }} diff --git a/src/wwwroot/docs/protected-scripts.html b/src/wwwroot/docs/protected-scripts.html index ea687a4..dc0af68 100644 --- a/src/wwwroot/docs/protected-scripts.html +++ b/src/wwwroot/docs/protected-scripts.html @@ -82,7 +82,7 @@

    includeUrl

    page: 'page', files: { - 'page.html' : '{{ "https://raw.githubusercontent.com/ServiceStack/sharpscript/master/src" | to => src }} + 'page.html' : '{{ "https://raw.githubusercontent.com/ServiceStack/sharpscript/master/src" |> to => src }} {{ `${src}/wwwroot/examples/email-template.txt` | includeUrl }}' } }) diff --git a/src/wwwroot/docs/scripts-reference.html b/src/wwwroot/docs/scripts-reference.html index 6d8b115..96320f4 100644 --- a/src/wwwroot/docs/scripts-reference.html +++ b/src/wwwroot/docs/scripts-reference.html @@ -79,60 +79,60 @@ } -{{ 6 | to => rows }} +{{ 6 |> to => rows }}
    -{{ "live-template" | partial({ rows, className, template:`{{ 'DefaultScripts' | to => method }} -{{ method | methodsAvailable | where => it.Name.lower().contains((nameContains ?? '').lower()) - | to => methods }} +{{ "live-template" | partial({ rows, className, template:`{{ 'DefaultScripts' |> to => method }} +{{ method | methodsAvailable |> where => it.Name.lower().contains((nameContains ?? '').lower()) + |> to => methods }} {{#each methods}}{{/each}}
    {{ method | methodLinkToSrc }}
    {{FirstParam}}{{Body}}{{Return}}
    ` }) }}
    -{{ "live-template" | partial({ rows, className, template:`{{ 'HtmlScripts' | to => method }} -{{ method | methodsAvailable | where => it.Name.lower().contains((nameContains ?? '').lower()) - | to => methods }} +{{ "live-template" | partial({ rows, className, template:`{{ 'HtmlScripts' |> to => method }} +{{ method | methodsAvailable |> where => it.Name.lower().contains((nameContains ?? '').lower()) + |> to => methods }} {{#each methods}}{{/each}}
    {{ method | methodLinkToSrc }}
    {{FirstParam}}{{Body}}{{Return}}
    ` }) }}
    -{{ "live-template" | partial({ rows, className, template:`{{ 'ProtectedScripts' | to => method }} -{{ method | methodsAvailable | where => it.Name.lower().contains((nameContains ?? '').lower()) - | to => methods }} +{{ "live-template" | partial({ rows, className, template:`{{ 'ProtectedScripts' |> to => method }} +{{ method | methodsAvailable |> where => it.Name.lower().contains((nameContains ?? '').lower()) + |> to => methods }} {{#each methods}}{{/each}}
    {{ method | methodLinkToSrc }}
    {{FirstParam}}{{Body}}{{Return}}
    ` }) }}
    -{{ "live-template" | partial({ rows, className, template:`{{ 'InfoScripts' | to => method }} -{{ method | methodsAvailable | where => it.Name.lower().contains((nameContains ?? '').lower()) - | to => methods }} +{{ "live-template" | partial({ rows, className, template:`{{ 'InfoScripts' |> to => method }} +{{ method | methodsAvailable |> where => it.Name.lower().contains((nameContains ?? '').lower()) + |> to => methods }} {{#each methods}}{{/each}}
    {{ method | methodLinkToSrc }}
    {{FirstParam}}{{Body}}{{Return}}
    ` }) }}
    -{{ "live-template" | partial({ rows, className, template:`{{ 'RedisScripts' | to => method }} -{{ method | methodsAvailable | where => it.Name.lower().contains((nameContains ?? '').lower()) - | to => methods }} +{{ "live-template" | partial({ rows, className, template:`{{ 'RedisScripts' |> to => method }} +{{ method | methodsAvailable |> where => it.Name.lower().contains((nameContains ?? '').lower()) + |> to => methods }} {{#each methods}}{{/each}}
    {{ method | methodLinkToSrc }}
    {{FirstParam}}{{Body}}{{Return}}
    ` }) }}
    -{{ "live-template" | partial({ rows, className, template:`{{ 'DbScriptsAsync' | to => method }} -{{ method | methodsAvailable | where => it.Name.lower().contains((nameContains ?? '').lower()) - | to => methods }} +{{ "live-template" | partial({ rows, className, template:`{{ 'DbScriptsAsync' |> to => method }} +{{ method | methodsAvailable |> where => it.Name.lower().contains((nameContains ?? '').lower()) + |> to => methods }} {{#each methods}}{{/each}}
    {{ method | methodLinkToSrc }}
    {{FirstParam}}{{Body}}{{Return}}
    ` }) }}
    -{{ "live-template" | partial({ rows, className, template:`{{ 'ServiceStackScripts' | to => method }} -{{ method | methodsAvailable | where => it.Name.lower().contains((nameContains ?? '').lower()) - | to => methods }} +{{ "live-template" | partial({ rows, className, template:`{{ 'ServiceStackScripts' |> to => method }} +{{ method | methodsAvailable |> where => it.Name.lower().contains((nameContains ?? '').lower()) + |> to => methods }} {{#each methods}}{{/each}}
    {{ method | methodLinkToSrc }}
    {{FirstParam}}{{Body}}{{Return}}
    ` }) }} diff --git a/src/wwwroot/docs/servicestack-scripts.html b/src/wwwroot/docs/servicestack-scripts.html index 5b8d870..0377e29 100644 --- a/src/wwwroot/docs/servicestack-scripts.html +++ b/src/wwwroot/docs/servicestack-scripts.html @@ -35,7 +35,7 @@

    sendToGateway

    {{ 'gfm/servicestack-scripts/01.md' | githubMarkdown }} -{{ 'examples/sendtogateway-customers.html' | includeFile | to => template }} +{{ 'examples/sendtogateway-customers.html' | includeFile |> to => template }} {{ "live-template" | partial({ rows:11, template }) }}

    execService

    @@ -113,7 +113,7 @@

    GitHub AutoQuery Data Example

    and other Querying related functionality:

    -{{ 'examples/sendToAutoQuery-data.html' | includeFile | to => template }} +{{ 'examples/sendToAutoQuery-data.html' | includeFile |> to => template }} {{ "live-template" | partial({ rows:6, template }) }}

    AutoQuery RDBMS

    @@ -163,7 +163,7 @@

    sendToAutoQuery

    implicit conventions as well:

    -{{ 'examples/sendToAutoQuery-rdms.html' | includeFile | to => template }} +{{ 'examples/sendToAutoQuery-rdms.html' | includeFile |> to => template }} {{ "live-template" | partial({ rows:6, template }) }}

    diff --git a/src/wwwroot/docs/syntax.html b/src/wwwroot/docs/syntax.html index 5731a8d..bcd2aff 100644 --- a/src/wwwroot/docs/syntax.html +++ b/src/wwwroot/docs/syntax.html @@ -83,8 +83,8 @@

    JavaScript literal notation

    ES6 Shorthand notation is also supported where you can use the argument name as its property name in a Dictionary:

    -{{ 'live-template' | partial({ template: "{{ 'foo' | to => bar }} -{{ { bar } | to => obj }} +{{ 'live-template' | partial({ template: "{{ 'foo' |> to => bar }} +{{ { bar } |> to => obj }} {{ obj['bar'] }}" }) }}

    Quotes

    @@ -123,8 +123,8 @@

    Shorthand arrow expression syntax

    {{ 'live-template' | partial({ template: ′{{ [0,1,2,3,4,5] - | where => it >= 3 - | map => it + 10 | join(`\n`) }}′ }) }} + |> where => it >= 3 + |> map => it + 10 | join(`\n`) }}′ }) }}

    This is a shorthand for declaring lambda expressions with normal arrow expression syntax: @@ -165,7 +165,7 @@

    SQL-like Boolean Expressions

    {{ 'live-template' | partial({ template: "{{ [0,1,2,3,4,5] - | where => (it = 2 or it = 3) and it.isOdd() + |> where => (it = 2 or it = 3) and it.isOdd() | join }}" }) }}

    Include Raw Content Verbatim

    diff --git a/src/wwwroot/examples/adhoc-query-db.html b/src/wwwroot/examples/adhoc-query-db.html index 01003e1..d964239 100644 --- a/src/wwwroot/examples/adhoc-query-db.html +++ b/src/wwwroot/examples/adhoc-query-db.html @@ -1,14 +1,14 @@
     {{ "select customerId,companyName,city,country from customer where country=@country"
    -   | to => selectSql }}
    +   |> to => selectSql }}
     {{ selectSql }}
     {{ selectSql | dbSelect({ country: 'UK' }) | textDump({ caption:"HTML Results (select):" }) }}
     {{ "select * from customer c join [order] o on c.customerId = o.customerId 
    -         order by total desc limit 1" | to => singleSql }}{{ singleSql }}:
    +         order by total desc limit 1" |> to => singleSql }}{{ singleSql }}:
     
     {{ singleSql | dbSingle | textDump({ caption:"Text Results (single):" }) }}
     
     Object Value (scalar):
    -{{ "select min(orderDate) from [order]" | to => scalarSql }}
    +{{ "select min(orderDate) from [order]" |> to => scalarSql }}
     {{ scalarSql }}: {{ scalarSql | dbScalar | dateFormat }}
     
    \ No newline at end of file diff --git a/src/wwwroot/examples/introspect-process.html b/src/wwwroot/examples/introspect-process.html index 44b1057..f00a9e9 100644 --- a/src/wwwroot/examples/introspect-process.html +++ b/src/wwwroot/examples/introspect-process.html @@ -1,4 +1,4 @@ -{{ "h':'mm':'ss'.'FFF" | to => fmtTime }} +{{ "h':'mm':'ss'.'FFF" |> to => fmtTime }} {{ it.Id }} {{ it.ProcessName | substringWithElipsis(15) }} diff --git a/src/wwwroot/examples/introspect.html b/src/wwwroot/examples/introspect.html index 38c2604..d991316 100644 --- a/src/wwwroot/examples/introspect.html +++ b/src/wwwroot/examples/introspect.html @@ -17,8 +17,8 @@ IdNameCPU TimeUser TimeMemory (current) Memory (peak)Active Threads {{#if currentProcess}} - {{ currentProcess | to => p }} - {{ "h':'mm':'ss'.'FFF" | to => fmtTime }} + {{ currentProcess |> to => p }} + {{ "h':'mm':'ss'.'FFF" |> to => fmtTime }} {{ Id }} {{ p.ProcessName | substringWithEllipsis(15) }} diff --git a/src/wwwroot/examples/quote.html b/src/wwwroot/examples/quote.html index 8e8018b..3aedd6a 100644 --- a/src/wwwroot/examples/quote.html +++ b/src/wwwroot/examples/quote.html @@ -1,3 +1,3 @@ -{{ 'select url from quote where id= @id' | dbScalar({ qs.id }) | urlContents | markdown | to =>quote}} +{{ 'select url from quote where id= @id' | dbScalar({ qs.id }) | urlContents | markdown |> to =>quote}} {{ quote | replace('Razor', 'Templates') | replace('2010', now.Year) | raw }} \ No newline at end of file diff --git a/src/wwwroot/examples/sendToAutoQuery-data.html b/src/wwwroot/examples/sendToAutoQuery-data.html index a754a2c..576de37 100644 --- a/src/wwwroot/examples/sendToAutoQuery-data.html +++ b/src/wwwroot/examples/sendToAutoQuery-data.html @@ -2,4 +2,4 @@ | sendToAutoQuery: QueryGitHubRepos | toResults | selectFields: Name, Homepage, Language, Watchers_Count - | htmlDump({ caption: "Most popular ServiceStack repos starting with 'ServiceStack'" }) }} \ No newline at end of file + |> htmlDump({ caption: "Most popular ServiceStack repos starting with 'ServiceStack'" }) }} \ No newline at end of file diff --git a/src/wwwroot/examples/sendToAutoQuery-rdms.html b/src/wwwroot/examples/sendToAutoQuery-rdms.html index e9d42a6..5d3d01f 100644 --- a/src/wwwroot/examples/sendToAutoQuery-rdms.html +++ b/src/wwwroot/examples/sendToAutoQuery-rdms.html @@ -2,4 +2,4 @@ | sendToAutoQuery: QueryCustomers | toResults | selectFields: CustomerId, CompanyName, City, Fax - | htmlDump({ caption: 'Implicit AutoQuery Conventions' }) }} + |> htmlDump({ caption: 'Implicit AutoQuery Conventions' }) }} diff --git a/src/wwwroot/examples/sendtogateway-customers.html b/src/wwwroot/examples/sendtogateway-customers.html index 7b51dff..7e7accb 100644 --- a/src/wwwroot/examples/sendtogateway-customers.html +++ b/src/wwwroot/examples/sendtogateway-customers.html @@ -1,10 +1,10 @@ {{ { customerId } | sendToGateway('QueryCustomers') | toResults | get(0) - | htmlDump({ caption: 'ALFKI' }) }} + |> htmlDump({ caption: 'ALFKI' }) }} {{ { countryIn:['UK','Germany'], orderBy:'customerId',take:5 } | sendToGateway: QueryCustomers | toResults | selectFields(['CustomerId', 'CompanyName', 'City']) - | htmlDump({ caption: 'Implicit AutoQuery Conventions' }) }} + |> htmlDump({ caption: 'Implicit AutoQuery Conventions' }) }} {{ { companyNameContains:'the',orderBy:'-Country,CustomerId' } | sendToGateway: QueryCustomers | toResults | selectFields: CustomerId, CompanyName, Country - | htmlDump }} + |> htmlDump }} diff --git a/src/wwwroot/examples/webapps-menu.html b/src/wwwroot/examples/webapps-menu.html index 51f9c43..557cd00 100644 --- a/src/wwwroot/examples/webapps-menu.html +++ b/src/wwwroot/examples/webapps-menu.html @@ -2,4 +2,4 @@ '/about': 'About', '/services': 'Services', '/contact': 'Contact', - } | toList | to => links }} \ No newline at end of file + } | toList |> to => links }} \ No newline at end of file diff --git a/src/wwwroot/gfm/adhoc-querying/04.html b/src/wwwroot/gfm/adhoc-querying/04.html index 7931598..625832d 100644 --- a/src/wwwroot/gfm/adhoc-querying/04.html +++ b/src/wwwroot/gfm/adhoc-querying/04.html @@ -18,10 +18,10 @@ INNER JOIN Shipper s ON o.ShipVia = s.Id WHERE o.Id = @id` - | dbSingle({ id }) | to => order }} + | dbSingle({ id }) |> to => order }} {{#with order}} - {{ "table table-striped" | to => className }} + {{ "table table-striped" |> to => className }} <style>table {border: 5px solid transparent} th {white-space: nowrap}</style> <div style="display:flex"> diff --git a/src/wwwroot/gfm/adhoc-querying/04.md b/src/wwwroot/gfm/adhoc-querying/04.md index 7392951..96e25df 100644 --- a/src/wwwroot/gfm/adhoc-querying/04.md +++ b/src/wwwroot/gfm/adhoc-querying/04.md @@ -20,18 +20,18 @@ title: Order Report INNER JOIN Shipper s ON o.ShipVia = s.Id WHERE o.Id = @id` - | dbSingle({ id }) | to => order }} + | dbSingle({ id }) |> to => order }} {{#with order}} - {{ "table table-striped" | to => className }} + {{ "table table-striped" |> to => className }}
    - {{ order | htmlDump({ caption: 'Order Details', className }) }} + {{ order |> htmlDump({ caption: 'Order Details', className }) }} {{ `SELECT * FROM Customer WHERE Id = @CustomerId` - | dbSingle({ CustomerId }) | htmlDump({ caption: `Customer Details`, className }) }} + | dbSingle({ CustomerId }) |> htmlDump({ caption: `Customer Details`, className }) }} {{ `SELECT Id,LastName,FirstName,Title,City,Country,Extension FROM Employee WHERE Id = @EmployeeId` - | dbSingle({ EmployeeId }) | htmlDump({ caption: `Employee Details`, className }) }} + | dbSingle({ EmployeeId }) |> htmlDump({ caption: `Employee Details`, className }) }}
    {{ `SELECT p.ProductName, ${sqlCurrency("od.UnitPrice")} UnitPrice, Quantity, Discount @@ -40,7 +40,7 @@ title: Order Report Product p ON od.ProductId = p.Id WHERE OrderId = @id` | dbSelect({ id }) - | htmlDump({ caption: "Line Items", className }) }} + |> htmlDump({ caption: "Line Items", className }) }} {{else}} {{ `There is no Order with id: ${id}` }} {{/with}} @@ -123,7 +123,7 @@ the partial HTML fragment with `return`: {{ `
    Potentially unsafe SQL detected
    ` | return }} {{/if}} -{{ sql | dbSelect | htmlDump | return }} +{{ sql | dbSelect |> htmlDump | return }} ``` ### Live Development Workflow diff --git a/src/wwwroot/gfm/blocks/00.html b/src/wwwroot/gfm/blocks/00.html index 5064c13..fa6cf91 100644 --- a/src/wwwroot/gfm/blocks/00.html +++ b/src/wwwroot/gfm/blocks/00.html @@ -143,12 +143,12 @@

    code

    The contents of code Script Blocks are processed as JS Expression statements:

    {{#function calc(a, b) }}
    -    a * b | to => c
    +    a * b |> to => c
         a + b + c | return
     {{/function}}
    <script>
     #function calc(a, b)
    -    a * b | to => c
    +    a * b |> to => c
         a + b + c | return
     /function 
     </script>
    diff --git a/src/wwwroot/gfm/blocks/00.md b/src/wwwroot/gfm/blocks/00.md index 30207f7..3ec394d 100644 --- a/src/wwwroot/gfm/blocks/00.md +++ b/src/wwwroot/gfm/blocks/00.md @@ -107,7 +107,7 @@ The contents of **code** Script Blocks are processed as JS Expression statements ```hbs {{#function calc(a, b) }} - a * b | to => c + a * b |> to => c a + b + c | return {{/function}} ``` @@ -115,7 +115,7 @@ The contents of **code** Script Blocks are processed as JS Expression statements ```html diff --git a/src/wwwroot/gfm/blocks/19.html b/src/wwwroot/gfm/blocks/19.html index 7f12390..e323a0d 100644 --- a/src/wwwroot/gfm/blocks/19.html +++ b/src/wwwroot/gfm/blocks/19.html @@ -1,5 +1,5 @@
    {{#if hasAccess}}
    -    {{ items | where => it.Age > 27 | to => items }}
    +    {{ items | where => it.Age > 27 |> to => items }}
         {{#if !isEmpty(items)}}
             <ul {{ ['nav', !disclaimerAccepted ? 'blur' : ''] | htmlClass }} id="menu-{{id}}">
             {{#each items}}
    diff --git a/src/wwwroot/gfm/blocks/19.md b/src/wwwroot/gfm/blocks/19.md
    index 788c370..d3b871d 100644
    --- a/src/wwwroot/gfm/blocks/19.md
    +++ b/src/wwwroot/gfm/blocks/19.md
    @@ -1,6 +1,6 @@
     ```hbs
     {{#if hasAccess}}
    -    {{ items | where => it.Age > 27 | to => items }}
    +    {{ items |> where => it.Age > 27 |> to => items }}
         {{#if !isEmpty(items)}}
             

    The /preview.html API page uses this to force a plain-text response with:

    -
    {{ content  | evalTemplate({use:{plugins:'MarkdownScriptPlugin'}}) | to =>response }}
    +
    {{ content  | evalTemplate({use:{plugins:'MarkdownScriptPlugin'}}) |> to =>response }}
     {{ response | return({ contentType:'text/plain' }) }}

    The preview API above is what provides the new Blog Web App's Live Preview feature where it will render any #Script provided in the content Query String or HTTP Post Form Data, e.g:

    diff --git a/src/wwwroot/gfm/sharp-apis/04.md b/src/wwwroot/gfm/sharp-apis/04.md index fbb7e9a..88acf88 100644 --- a/src/wwwroot/gfm/sharp-apis/04.md +++ b/src/wwwroot/gfm/sharp-apis/04.md @@ -54,7 +54,7 @@ Templates and Dynamic API Pages to implement all of its functionality. The [/preview.html](https://github.com/NetCoreWebApps/blog/blob/master/preview.html) API page uses this to force a plain-text response with: ```hbs -{{ content | evalTemplate({use:{plugins:'MarkdownScriptPlugin'}}) | to =>response }} +{{ content | evalTemplate({use:{plugins:'MarkdownScriptPlugin'}}) |> to =>response }} {{ response | return({ contentType:'text/plain' }) }} ``` @@ -110,7 +110,7 @@ The [/posts/_slug/api.html](https://github.com/NetCoreWebApps/blog/blob/master/p WHERE Slug = @slug ORDER BY p.Created DESC` | dbSingle({ slug }) - | to => post + |> to => post }} {{ post ?? httpResult({ status:404, statusDescription:'Post was not found' }) | return }} diff --git a/src/wwwroot/gfm/sharp-apps/01.html b/src/wwwroot/gfm/sharp-apps/01.html index 1281def..940c038 100644 --- a/src/wwwroot/gfm/sharp-apps/01.html +++ b/src/wwwroot/gfm/sharp-apps/01.html @@ -11,6 +11,6 @@ where CustomerId = @id group by o.Id, EmployeeId, FirstName, LastName, OrderDate, ShipCountry, ShippedDate` | dbSelect({ id }) - | to => orders }} + |> to => orders }} {{/if}}
    \ No newline at end of file diff --git a/src/wwwroot/gfm/sharp-apps/01.md b/src/wwwroot/gfm/sharp-apps/01.md index ec16f8f..7a5ff25 100644 --- a/src/wwwroot/gfm/sharp-apps/01.md +++ b/src/wwwroot/gfm/sharp-apps/01.md @@ -12,6 +12,6 @@ where CustomerId = @id group by o.Id, EmployeeId, FirstName, LastName, OrderDate, ShipCountry, ShippedDate` | dbSelect({ id }) - | to => orders }} + |> to => orders }} {{/if}} ``` \ No newline at end of file diff --git a/src/wwwroot/gfm/sharp-apps/10.html b/src/wwwroot/gfm/sharp-apps/10.html index 4df50d8..a1657a7 100644 --- a/src/wwwroot/gfm/sharp-apps/10.html +++ b/src/wwwroot/gfm/sharp-apps/10.html @@ -1,4 +1,4 @@ -
    {{ limit ?? 100  | to => limit }}
    +
    {{ limit ?? 100  |> to => limit }}
     
     {{ `${q ?? ''}*` | redisSearchKeys({ limit }) 
                      | return }}
    diff --git a/src/wwwroot/gfm/sharp-apps/10.md b/src/wwwroot/gfm/sharp-apps/10.md index 38931c5..4f3f3c9 100644 --- a/src/wwwroot/gfm/sharp-apps/10.md +++ b/src/wwwroot/gfm/sharp-apps/10.md @@ -1,5 +1,5 @@ ```hbs -{{ limit ?? 100 | to => limit }} +{{ limit ?? 100 |> to => limit }} {{ `${q ?? ''}*` | redisSearchKeys({ limit }) | return }} diff --git a/src/wwwroot/gfm/sharp-apps/11.html b/src/wwwroot/gfm/sharp-apps/11.html index 192021b..72ff474 100644 --- a/src/wwwroot/gfm/sharp-apps/11.html +++ b/src/wwwroot/gfm/sharp-apps/11.html @@ -1,11 +1,11 @@
    {{ { command } | ensureAllArgsNotEmpty }}
     
     {{ ['flush','monitor','brpop','blpop'] | any => contains(lower(command), it)
    -   | to => illegalCommand }}
    +   |> to => illegalCommand }}
     
     {{ illegalCommand ? throwArgumentException('Command is not allowed.') : null }}
     
    -{{ command  | redisCall | to => contents }}
    +{{ command  | redisCall |> to => contents }}
     
     {{ contents | return({ 'Content-Type': 'text/plain' }) }}
    \ No newline at end of file diff --git a/src/wwwroot/gfm/sharp-apps/11.md b/src/wwwroot/gfm/sharp-apps/11.md index 124fe1d..bcc7ff1 100644 --- a/src/wwwroot/gfm/sharp-apps/11.md +++ b/src/wwwroot/gfm/sharp-apps/11.md @@ -2,11 +2,11 @@ {{ { command } | ensureAllArgsNotEmpty }} {{ ['flush','monitor','brpop','blpop'] | any => contains(lower(command), it) - | to => illegalCommand }} + |> to => illegalCommand }} {{ illegalCommand ? throwArgumentException('Command is not allowed.') : null }} -{{ command | redisCall | to => contents }} +{{ command | redisCall |> to => contents }} {{ contents | return({ 'Content-Type': 'text/plain' }) }} ``` diff --git a/src/wwwroot/gfm/sharp-apps/14.html b/src/wwwroot/gfm/sharp-apps/14.html index 6bf38ac..e0d890d 100644 --- a/src/wwwroot/gfm/sharp-apps/14.html +++ b/src/wwwroot/gfm/sharp-apps/14.html @@ -89,7 +89,7 @@

    }

    To the /preview.html API Page which just renders and captures any Template Content its sent and returns the output:

    -
    {{ content  | evalTemplate({use:{plugins:'MarkdownScriptPlugin'}}) | to =>response }}
    +
    {{ content  | evalTemplate({use:{plugins:'MarkdownScriptPlugin'}}) |> to =>response }}
     {{ response | return({ contentType:'text/plain' }) }}

    By default the evalTemplate filter renders Templates in a new ScriptContext which can be customized to utilize any additional plugins, filters and blocks available in the configured SharpPagesFeature, diff --git a/src/wwwroot/gfm/sharp-apps/14.md b/src/wwwroot/gfm/sharp-apps/14.md index 1228997..8c2f003 100644 --- a/src/wwwroot/gfm/sharp-apps/14.md +++ b/src/wwwroot/gfm/sharp-apps/14.md @@ -62,11 +62,11 @@ The [new.html](https://github.com/NetCoreWebApps/Blog/blob/master/posts/new.html {{ assignErrorAndContinueExecuting: ex }} {{ 'Title must be between 5 and 200 characters' - | onlyIf(length(postTitle) < 5 || length(postTitle) > 200) | to => titleError }} + | onlyIf(length(postTitle) < 5 || length(postTitle) > 200) |> to => titleError }} {{ 'Content must be between 25 and 64000 characters' - | onlyIf(length(content) < 25 || length(content) > 64000) | to => contentError }} + | onlyIf(length(content) < 25 || length(content) > 64000) |> to => contentError }} {{ 'Potentially malicious characters detected' - | ifNotExists(contentError) | onlyIf(containsXss(content)) | to => contentError }} + | ifNotExists(contentError) | onlyIf(containsXss(content)) |> to => contentError }} ``` ![](https://raw.githubusercontent.com/ServiceStack/Assets/master/img/livedemos/blog/server-validation.png) @@ -120,7 +120,7 @@ To the [/preview.html](https://github.com/NetCoreWebApps/blog/blob/master/previe Template Content its sent and returns the output: ```hbs -{{ content | evalTemplate({use:{plugins:'MarkdownScriptPlugin'}}) | to =>response }} +{{ content | evalTemplate({use:{plugins:'MarkdownScriptPlugin'}}) |> to =>response }} {{ response | return({ contentType:'text/plain' }) }} ``` diff --git a/src/wwwroot/gfm/sharp-pages/10.html b/src/wwwroot/gfm/sharp-pages/10.html index df8848c..306e684 100644 --- a/src/wwwroot/gfm/sharp-pages/10.html +++ b/src/wwwroot/gfm/sharp-pages/10.html @@ -10,7 +10,7 @@ | dbExec }} -{{ dbScalar(`SELECT COUNT(*) FROM Post`) | to => postsCount }} +{{ dbScalar(`SELECT COUNT(*) FROM Post`) |> to => postsCount }} {{#if postsCount == 0 }} diff --git a/src/wwwroot/gfm/sharp-pages/10.md b/src/wwwroot/gfm/sharp-pages/10.md index 5fadc12..359056d 100644 --- a/src/wwwroot/gfm/sharp-pages/10.md +++ b/src/wwwroot/gfm/sharp-pages/10.md @@ -11,7 +11,7 @@ | dbExec }} -{{ dbScalar(`SELECT COUNT(*) FROM Post`) | to => postsCount }} +{{ dbScalar(`SELECT COUNT(*) FROM Post`) |> to => postsCount }} {{#if postsCount == 0 }} diff --git a/src/wwwroot/gfm/sharp-scripts/01.html b/src/wwwroot/gfm/sharp-scripts/01.html index 03baccf..e5e2a66 100644 --- a/src/wwwroot/gfm/sharp-scripts/01.html +++ b/src/wwwroot/gfm/sharp-scripts/01.html @@ -15,10 +15,10 @@ INNER JOIN Shipper s ON o.ShipVia = s.Id WHERE o.Id = @id` - | dbSingle({ id }) | to => order }} + | dbSingle({ id }) |> to => order }} {{#with order}} - {{ "table table-striped" | to => className }} + {{ "table table-striped" |> to => className }} <style>table {border: 5px solid transparent} th {white-space: nowrap}</style> <div style="display:flex"> diff --git a/src/wwwroot/gfm/sharp-scripts/01.md b/src/wwwroot/gfm/sharp-scripts/01.md index c70b366..df7c32b 100644 --- a/src/wwwroot/gfm/sharp-scripts/01.md +++ b/src/wwwroot/gfm/sharp-scripts/01.md @@ -16,18 +16,18 @@ db.connection ~/../apps/northwind.sqlite INNER JOIN Shipper s ON o.ShipVia = s.Id WHERE o.Id = @id` - | dbSingle({ id }) | to => order }} + | dbSingle({ id }) |> to => order }} {{#with order}} - {{ "table table-striped" | to => className }} + {{ "table table-striped" |> to => className }}

    - {{ order | htmlDump({ caption: 'Order Details', className }) }} + {{ order |> htmlDump({ caption: 'Order Details', className }) }} {{ `SELECT * FROM Customer WHERE Id = @CustomerId` - | dbSingle({ CustomerId }) | htmlDump({ caption: `Customer Details`, className }) }} + | dbSingle({ CustomerId }) |> htmlDump({ caption: `Customer Details`, className }) }} {{ `SELECT Id, LastName, FirstName, Title, City, Country, Extension FROM Employee WHERE Id=@EmployeeId` - | dbSingle({ EmployeeId }) | htmlDump({ caption: `Employee Details`, className }) }} + | dbSingle({ EmployeeId }) |> htmlDump({ caption: `Employee Details`, className }) }}
    {{ `SELECT p.ProductName, ${sqlCurrency("od.UnitPrice")} UnitPrice, Quantity, Discount @@ -36,7 +36,7 @@ db.connection ~/../apps/northwind.sqlite Product p ON od.ProductId = p.Id WHERE OrderId = @id` | dbSelect({ id }) - | htmlDump({ caption: "Line Items", className }) }} + |> htmlDump({ caption: "Line Items", className }) }} {{else}} {{ `There is no Order with id: ${id}` }} {{/with}} diff --git a/src/wwwroot/gfm/sharp-scripts/02.html b/src/wwwroot/gfm/sharp-scripts/02.html index 96156fb..8d3c2a0 100644 --- a/src/wwwroot/gfm/sharp-scripts/02.html +++ b/src/wwwroot/gfm/sharp-scripts/02.html @@ -11,7 +11,7 @@ INNER JOIN Shipper s ON o.ShipVia = s.Id WHERE o.Id = @id` - | dbSingle({ id }) | to => order }} + | dbSingle({ id }) |> to => order }} {{#with order}} diff --git a/src/wwwroot/gfm/sharp-scripts/02.md b/src/wwwroot/gfm/sharp-scripts/02.md index f84eb1f..6a00355 100644 --- a/src/wwwroot/gfm/sharp-scripts/02.md +++ b/src/wwwroot/gfm/sharp-scripts/02.md @@ -13,7 +13,7 @@ db.connection ~/../apps/northwind.sqlite INNER JOIN Shipper s ON o.ShipVia = s.Id WHERE o.Id = @id` - | dbSingle({ id }) | to => order }} + | dbSingle({ id }) |> to => order }} {{#with order}} diff --git a/src/wwwroot/gfm/sharp-scripts/03.html b/src/wwwroot/gfm/sharp-scripts/03.html index 6e993c0..801a987 100644 --- a/src/wwwroot/gfm/sharp-scripts/03.html +++ b/src/wwwroot/gfm/sharp-scripts/03.html @@ -10,7 +10,7 @@ <script> dbTableNamesWithRowCounts | textDump({ caption: 'Tables' }) -5 | to => limit +5 |> to => limit `Last ${limit} Orders:\n` {{ `SELECT * FROM "Order" ORDER BY "Id" DESC ${limit.sqlLimit()}` @@ -19,7 +19,7 @@ {{ vfsContent.allRootDirectories().map(dir => `${dir.Name}/`) .union(vfsContent.allRootFiles().map(file => file.Name)) | textDump({caption:'Root Files + Folders'}) }} -(ARGV.first() ?? '*.jpg') | to => pattern +(ARGV.first() ?? '*.jpg') |> to => pattern `\nFirst ${limit} ${pattern} files in S3:` vfsContent.findFiles(pattern) | take(limit) | map => it.VirtualPath | join('\n') </script>
    diff --git a/src/wwwroot/gfm/sharp-scripts/03.md b/src/wwwroot/gfm/sharp-scripts/03.md index a7cc028..d90e0d3 100644 --- a/src/wwwroot/gfm/sharp-scripts/03.md +++ b/src/wwwroot/gfm/sharp-scripts/03.md @@ -11,17 +11,17 @@ Querying AWS... ``` diff --git a/src/wwwroot/gfm/sharp-scripts/04.html b/src/wwwroot/gfm/sharp-scripts/04.html index f39cd51..4aeb414 100644 --- a/src/wwwroot/gfm/sharp-scripts/04.html +++ b/src/wwwroot/gfm/sharp-scripts/04.html @@ -10,7 +10,7 @@ <script> dbTableNamesWithRowCounts | textDump({ caption: 'Tables' }) -5 | to => limit +5 |> to => limit `Last ${limit} Orders:\n` {{ `SELECT * FROM "Order" ORDER BY "Id" DESC ${limit.sqlLimit()}` @@ -19,7 +19,7 @@ {{ vfsContent.allRootDirectories().map(dir => `${dir.Name}/`) .union(vfsContent.allRootFiles().map(file => file.Name)) | textDump({caption:'Root Files + Folders'}) }} -(ARGV.first() ?? '*.jpg') | to => pattern +(ARGV.first() ?? '*.jpg') |> to => pattern `\nFirst ${limit} ${pattern} files in Azure Blob Storage:` vfsContent.findFiles(pattern) | take(limit) | map => it.VirtualPath | join('\n') </script>
    diff --git a/src/wwwroot/gfm/sharp-scripts/04.md b/src/wwwroot/gfm/sharp-scripts/04.md index 3b2d67f..2e3a28a 100644 --- a/src/wwwroot/gfm/sharp-scripts/04.md +++ b/src/wwwroot/gfm/sharp-scripts/04.md @@ -11,17 +11,17 @@ Querying Azure... ``` diff --git a/src/wwwroot/gfm/sharp-scripts/10.html b/src/wwwroot/gfm/sharp-scripts/10.html index ea50037..b9fa40d 100644 --- a/src/wwwroot/gfm/sharp-scripts/10.html +++ b/src/wwwroot/gfm/sharp-scripts/10.html @@ -1,9 +1,9 @@
    <script>
     * run in host project directory with `web run wwwroot/_bundle.ss` *
     
    -false | to => debug
    -(debug ? '' : '.min')       | to => min
    -(debug ? '' : '[hash].min') | to => dist
    +false |> to => debug
    +(debug ? '' : '.min')       |> to => min
    +(debug ? '' : '[hash].min') |> to => dist
     
     {{ [`/css/lib.bundle${dist}.css`,`/js/lib.bundle${dist}.js`,`/js/bundle${dist}.js`] 
        | map => it.replace('[hash]','.*').findFiles()
    diff --git a/src/wwwroot/gfm/sharp-scripts/10.md b/src/wwwroot/gfm/sharp-scripts/10.md
    index a01f560..5f9f89c 100644
    --- a/src/wwwroot/gfm/sharp-scripts/10.md
    +++ b/src/wwwroot/gfm/sharp-scripts/10.md
    @@ -2,12 +2,12 @@
     
    diff --git a/src/wwwroot/gfm/syntax/03.html b/src/wwwroot/gfm/syntax/03.html
    index da7af68..29f3110 100644
    --- a/src/wwwroot/gfm/syntax/03.html
    +++ b/src/wwwroot/gfm/syntax/03.html
    @@ -1,10 +1,10 @@
     
    <script>
     `Create an Array`
    -['Apple', 'Banana'] | to => fruits
    +['Apple', 'Banana'] |> to => fruits
     fruits.Count
     
     `\nAccess (index into) an Array Item`
    -fruits[0] | to => first
    +fruits[0] |> to => first
     first
     
     `\nLoop over an Array`
    @@ -12,46 +12,46 @@
         `${item}, ${index}`
     /each
     
    -[] | to => sb
    +[] |> to => sb
     fruits.forEach((item,index,array) => sb.push(`${item}, ${index}`))
     sb.join(`\n`)
     
     `\nAdd to the end of an Array`
     
    -fruits.push('Orange') | to => newLength
    +fruits.push('Orange') |> to => newLength
     newLength
     
     `\nRemove from the end of an Array`
    -fruits.pop() | to => last
    +fruits.pop() |> to => last
     last
     
     `\nRemove from the front of an Array`
    -fruits.shift() | to => first
    +fruits.shift() |> to => first
     first
     
     `\nAdd to the front of an Array`
    -fruits.unshift('Strawberry') | to => newLength
    +fruits.unshift('Strawberry') |> to => newLength
     newLength
     
     `\nFind the index of an item in the Array`
     fruits.push('Mango') | end
     
    -fruits.indexOf('Banana') | to => pos
    +fruits.indexOf('Banana') |> to => pos
     pos
     
     `\nRemove an item by index position`
    -fruits.splice(pos, 1) | to => removedItem
    +fruits.splice(pos, 1) |> to => removedItem
     removedItem | join
     fruits | join
     
     `\nRemove items from an index position`
    -['Cabbage', 'Turnip', 'Radish', 'Carrot'] | to => vegetables 
    +['Cabbage', 'Turnip', 'Radish', 'Carrot'] |> to => vegetables 
     vegetables | join
     
    -1 | to => pos
    -2 | to => n
    +1 |> to => pos
    +2 |> to => n
     
    -vegetables.splice(pos, n) | to => removedItems 
    +vegetables.splice(pos, n) |> to => removedItems 
     vegetables | join
     removedItems | join
     
    diff --git a/src/wwwroot/gfm/syntax/03.md b/src/wwwroot/gfm/syntax/03.md
    index 58d5933..1697f60 100644
    --- a/src/wwwroot/gfm/syntax/03.md
    +++ b/src/wwwroot/gfm/syntax/03.md
    @@ -1,11 +1,11 @@
     ```html
     
     Basic Calc
     a + b = {{a + b}}
    diff --git a/src/wwwroot/gfm/syntax/06.md b/src/wwwroot/gfm/syntax/06.md
    index ff77188..cfd991b 100644
    --- a/src/wwwroot/gfm/syntax/06.md
    +++ b/src/wwwroot/gfm/syntax/06.md
    @@ -29,8 +29,8 @@ title: The title
     
         {{#if debug}}
             {{ range(1,5) 
    -        | where => it.isOdd() 
    -        | map => it * it   
    +        |> where => it.isOdd() 
    +        |> map => it * it   
             | join(',')
             }}
         {{/if}}
    @@ -65,8 +65,8 @@ title: The title
     {{ 1 + 1 }}
     {{#if debug}}
     {{ range(1,5)
    -| where => it.isOdd()
    -| map => it * it
    +|> where => it.isOdd()
    +|> map => it * it
     | join(',')
     }}
     {{/if}}
    diff --git a/src/wwwroot/gfm/syntax/07.html b/src/wwwroot/gfm/syntax/07.html
    index d8eee0f..3f99ad5 100644
    --- a/src/wwwroot/gfm/syntax/07.html
    +++ b/src/wwwroot/gfm/syntax/07.html
    @@ -1,7 +1,7 @@
    -
    {{ 1 | to => a}}
    +
    {{ 1 |> to => a}}
     
     <script>
    -(a + 2) | to => result
    +(a + 2) |> to => result
     </script>
     
     Code result: {{ result }}
    diff --git a/src/wwwroot/gfm/syntax/07.md b/src/wwwroot/gfm/syntax/07.md index 54f6d18..f2abdcf 100644 --- a/src/wwwroot/gfm/syntax/07.md +++ b/src/wwwroot/gfm/syntax/07.md @@ -1,8 +1,8 @@ ```html -{{ 1 | to => a}} +{{ 1 |> to => a}} Code result: {{ result }} diff --git a/src/wwwroot/gfm/syntax/08.html b/src/wwwroot/gfm/syntax/08.html index 379ce31..244759b 100644 --- a/src/wwwroot/gfm/syntax/08.html +++ b/src/wwwroot/gfm/syntax/08.html @@ -1,4 +1,4 @@ -
    {{ 1 | to => a}}
    +
    {{ 1 |> to => a}}
     
     <script>
     (def local-arg (+ a 2))
    diff --git a/src/wwwroot/gfm/syntax/08.md b/src/wwwroot/gfm/syntax/08.md
    index bfe90ac..0b09de8 100644
    --- a/src/wwwroot/gfm/syntax/08.md
    +++ b/src/wwwroot/gfm/syntax/08.md
    @@ -1,5 +1,5 @@
     ```html
    -{{ 1 | to => a}}
    +{{ 1 |> to => a}}
     
     ' | raw | to => scripts }}
    +' | raw |> to => scripts }}
     
     
    diff --git a/src/wwwroot/usecases/introspect-state.html b/src/wwwroot/usecases/introspect-state.html index c3c5c43..e769f23 100644 --- a/src/wwwroot/usecases/introspect-state.html +++ b/src/wwwroot/usecases/introspect-state.html @@ -43,7 +43,7 @@ $("#output").html(r); } }) -` | raw | to => scripts }} +` | raw |> to => scripts }}
    diff --git a/src/wwwroot/usecases/live-documents.html b/src/wwwroot/usecases/live-documents.html index 2406057..ab2fc9c 100644 --- a/src/wwwroot/usecases/live-documents.html +++ b/src/wwwroot/usecases/live-documents.html @@ -8,7 +8,7 @@ reactive live documents containing a mix of docs, processing and calculations like creating a personal budget:

    -{{ 'examples/monthly-budget.txt' | includeFile | to => template }} +{{ 'examples/monthly-budget.txt' | includeFile |> to => template }} {{ "live-template" | partial({ rows:36, template }) }} -{{ 'examples/adhoc-query-db.html' | includeFile |> to => template }} +{{ 'examples/adhoc-query-db.html' |> includeFile |> to => template }} -{{ "live-template" | partial({ rows:15, template }) }} +{{ "live-template" |> partial({ rows:15, template }) }}
    Tip: textDump generates GitHub Flavored Markdown tables @@ -69,4 +69,4 @@

    Order Report Example

    {{ 'gfm/adhoc-querying/04.md' | githubMarkdown }} -{{ "usecase-links" | partial({ order }) }} +{{ "usecase-links" |> partial({ order }) }} diff --git a/src/wwwroot/usecases/content-websites.html b/src/wwwroot/usecases/content-websites.html index b2bb7e6..7eb505f 100644 --- a/src/wwwroot/usecases/content-websites.html +++ b/src/wwwroot/usecases/content-websites.html @@ -56,10 +56,10 @@

    Porting an existing Razor Website

    The declarative {{ pass: page }} is used to embed a page in a layout instead of the imperative @RenderBody(). - Likewise the syntax for partials changes to {{ pass: "menu-alive" | partial }} from @Html.Partial("MenuAlive"). + Likewise the syntax for partials changes to {{ pass: "menu-alive" |> partial }} from @Html.Partial("MenuAlive"). #Script Pages also alleviates the need for bespoke partials like @Html.PartialMarkdown("Content") as it can instead leverage the flexibility of chaining existing filters to achieve the same result like - {{ pass: "content.md" | includeFile | markdown }}. + {{ pass: "content.md" |> includeFile |> markdown }}.

    @@ -109,7 +109,7 @@

    Embedding Remote Content

    performance where you can embed remote url content, cache it for 1 minute and convert from markdown with:

    -
    {{ pass: url | includeUrlWithCache | markdown }}
    +
    {{ pass: url |> includeUrlWithCache |> markdown }}

    As seen in /grohl-url/index.html @@ -118,4 +118,4 @@

    Embedding Remote Content

    /rockstar-files/alive/grohl-url/

    -{{ "usecase-links" | partial({ order }) }} +{{ "usecase-links" |> partial({ order }) }} diff --git a/src/wwwroot/usecases/email-templates.html b/src/wwwroot/usecases/email-templates.html index 1b484ba..f7a0209 100644 --- a/src/wwwroot/usecases/email-templates.html +++ b/src/wwwroot/usecases/email-templates.html @@ -41,11 +41,11 @@

    Order Confirmation Email Example

    + autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false">{{ 'examples/email-template.txt' |> includeFile }}
    + autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false">{{ 'examples/email-html-template.html' |> includeFile }}

    @@ -77,7 +77,7 @@

    Order Confirmation Email Example

    $("#text-preview").html(r.textEmail); } }) -' | raw |> to => scripts }} +' |> raw |> to => scripts }}
    @@ -106,4 +106,4 @@

    partial({ order }) }} \ No newline at end of file diff --git a/src/wwwroot/usecases/index.html b/src/wwwroot/usecases/index.html index cbe2af8..b2dcee5 100644 --- a/src/wwwroot/usecases/index.html +++ b/src/wwwroot/usecases/index.html @@ -39,5 +39,5 @@

    Adhoc Querying

    OrmLite.

    -{{ "usecase-links" | partial({ order }) }} +{{ "usecase-links" |> partial({ order }) }} diff --git a/src/wwwroot/usecases/introspect-state.html b/src/wwwroot/usecases/introspect-state.html index e769f23..716b472 100644 --- a/src/wwwroot/usecases/introspect-state.html +++ b/src/wwwroot/usecases/introspect-state.html @@ -27,7 +27,7 @@
    + autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false">{{ 'examples/introspect.html' |> includeFile }}
    @@ -43,7 +43,7 @@ $("#output").html(r); } }) -` | raw |> to => scripts }} +` |> raw |> to => scripts }}
    @@ -92,4 +92,4 @@

    Debug Inspector

    /metadata/debug

    -{{ "usecase-links" | partial({ order }) }} +{{ "usecase-links" |> partial({ order }) }} diff --git a/src/wwwroot/usecases/live-documents.html b/src/wwwroot/usecases/live-documents.html index ab2fc9c..70fd568 100644 --- a/src/wwwroot/usecases/live-documents.html +++ b/src/wwwroot/usecases/live-documents.html @@ -8,8 +8,8 @@ reactive live documents containing a mix of docs, processing and calculations like creating a personal budget:

    -{{ 'examples/monthly-budget.txt' | includeFile |> to => template }} -{{ "live-template" | partial({ rows:36, template }) }} +{{ 'examples/monthly-budget.txt' |> includeFile |> to => template }} +{{ "live-template" |> partial({ rows:36, template }) }} -{{ 6 |> to => rows }} +{{ var rows = 6 }}
    {{ "live-template" |> partial({ rows, className, template:`{{ 'DefaultScripts' |> to => method }} -{{ method | methodsAvailable |> where => it.Name.lower().contains((nameContains ?? '').lower()) +{{ method |> methodsAvailable |> where => it.Name.lower().contains((nameContains ?? '').lower()) |> to => methods }} - +
    {{ method | methodLinkToSrc }}
    {{#each methods}}{{/each}}
    {{ method |> methodLinkToSrc }}
    {{FirstParam}}{{Body}}{{Return}}
    ` }) }}
    {{ "live-template" |> partial({ rows, className, template:`{{ 'HtmlScripts' |> to => method }} -{{ method | methodsAvailable |> where => it.Name.lower().contains((nameContains ?? '').lower()) +{{ method |> methodsAvailable |> where => it.Name.lower().contains((nameContains ?? '').lower()) |> to => methods }} - +
    {{ method | methodLinkToSrc }}
    {{#each methods}}{{/each}}
    {{ method |> methodLinkToSrc }}
    {{FirstParam}}{{Body}}{{Return}}
    ` }) }}
    {{ "live-template" |> partial({ rows, className, template:`{{ 'ProtectedScripts' |> to => method }} -{{ method | methodsAvailable |> where => it.Name.lower().contains((nameContains ?? '').lower()) +{{ method |> methodsAvailable |> where => it.Name.lower().contains((nameContains ?? '').lower()) |> to => methods }} - +
    {{ method | methodLinkToSrc }}
    {{#each methods}}{{/each}}
    {{ method |> methodLinkToSrc }}
    {{FirstParam}}{{Body}}{{Return}}
    ` }) }}
    {{ "live-template" |> partial({ rows, className, template:`{{ 'InfoScripts' |> to => method }} -{{ method | methodsAvailable |> where => it.Name.lower().contains((nameContains ?? '').lower()) +{{ method |> methodsAvailable |> where => it.Name.lower().contains((nameContains ?? '').lower()) |> to => methods }} - +
    {{ method | methodLinkToSrc }}
    {{#each methods}}{{/each}}
    {{ method |> methodLinkToSrc }}
    {{FirstParam}}{{Body}}{{Return}}
    ` }) }}
    {{ "live-template" |> partial({ rows, className, template:`{{ 'RedisScripts' |> to => method }} -{{ method | methodsAvailable |> where => it.Name.lower().contains((nameContains ?? '').lower()) +{{ method |> methodsAvailable |> where => it.Name.lower().contains((nameContains ?? '').lower()) |> to => methods }} - +
    {{ method | methodLinkToSrc }}
    {{#each methods}}{{/each}}
    {{ method |> methodLinkToSrc }}
    {{FirstParam}}{{Body}}{{Return}}
    ` }) }}
    {{ "live-template" |> partial({ rows, className, template:`{{ 'DbScriptsAsync' |> to => method }} -{{ method | methodsAvailable |> where => it.Name.lower().contains((nameContains ?? '').lower()) +{{ method |> methodsAvailable |> where => it.Name.lower().contains((nameContains ?? '').lower()) + |> to => methods }} + +{{#each methods}}{{/each}} +
    {{ method |> methodLinkToSrc }}
    {{FirstParam}}{{Body}}{{Return}}
    ` }) }} +
    +
    +{{ "live-template" |> partial({ rows, className, template:`{{ 'ValidateScripts' |> to => method }} +{{ method |> methodsAvailable |> where => it.Name.lower().contains((nameContains ?? '').lower()) + |> to => methods }} + +{{#each methods}}{{/each}} +
    {{ method |> methodLinkToSrc }}
    {{FirstParam}}{{Body}}{{Return}}
    ` }) }} +
    +
    +{{ "live-template" |> partial({ rows, className, template:`{{ 'AutoQueryScripts' |> to => method }} +{{ method |> methodsAvailable |> where => it.Name.lower().contains((nameContains ?? '').lower()) |> to => methods }} - +
    {{ method | methodLinkToSrc }}
    {{#each methods}}{{/each}}
    {{ method |> methodLinkToSrc }}
    {{FirstParam}}{{Body}}{{Return}}
    ` }) }}
    {{ "live-template" |> partial({ rows, className, template:`{{ 'ServiceStackScripts' |> to => method }} -{{ method | methodsAvailable |> where => it.Name.lower().contains((nameContains ?? '').lower()) +{{ method |> methodsAvailable |> where => it.Name.lower().contains((nameContains ?? '').lower()) |> to => methods }} - +
    {{ method | methodLinkToSrc }}
    {{#each methods}}{{/each}}
    {{ method |> methodLinkToSrc }}
    {{FirstParam}}{{Body}}{{Return}}
    ` }) }}
    From 2b7299393588598094271f52f2a0aef54b87b197 Mon Sep 17 00:00:00 2001 From: Demis Bellot Date: Mon, 16 Mar 2020 17:17:17 +0800 Subject: [PATCH 143/206] update example code --- src/wwwroot/gfm/sharp-apis/04.html | 13 +++++++++++-- src/wwwroot/gfm/sharp-apis/04.md | 13 +++++++++++-- 2 files changed, 22 insertions(+), 4 deletions(-) diff --git a/src/wwwroot/gfm/sharp-apis/04.html b/src/wwwroot/gfm/sharp-apis/04.html index f3e764e..370e894 100644 --- a/src/wwwroot/gfm/sharp-apis/04.html +++ b/src/wwwroot/gfm/sharp-apis/04.html @@ -47,10 +47,19 @@

    The preview API above is what provides the new Blog Web App's Live Preview feature where it will render any #Script provided in the content Query String or HTTP Post Form Data, e.g:

    Which renders the plain text response:

    -
    0,1,4,9,16,25,36,49,64,81,
    +
    0
    +1
    +4
    +9
    +16
    +25
    +36
    +49
    +64
    +81
     

    /_user/api Page

    diff --git a/src/wwwroot/gfm/sharp-apis/04.md b/src/wwwroot/gfm/sharp-apis/04.md index 071a3d4..6c0d576 100644 --- a/src/wwwroot/gfm/sharp-apis/04.md +++ b/src/wwwroot/gfm/sharp-apis/04.md @@ -61,11 +61,20 @@ The [/preview.html](https://github.com/NetCoreWebApps/blog/blob/master/preview.h The preview API above is what provides the new [Blog Web App's](http://blog.web-app.io/) Live Preview feature where it will render any #Script provided in the **content** Query String or HTTP Post Form Data, e.g: -- [/preview?content={{10|times|select:{pow(index,2)},}}](http://blog.web-app.io/preview?content={{10|times|select:{pow(index,2)},}}) +- [/preview?content={{10|>times|>map=>it.pow(2)|>joinln}}](http://blog.web-app.io/preview?content={{10|%3Etimes|%3Emap=%3Eit.pow(2)|%3Ejoinln}}) Which renders the plain text response: - 0,1,4,9,16,25,36,49,64,81, + 0 + 1 + 4 + 9 + 16 + 25 + 36 + 49 + 64 + 81 ### /_user/api Page From 3ee6781812821a1a6ef9429418a8938a119ed67b Mon Sep 17 00:00:00 2001 From: Demis Bellot Date: Tue, 5 May 2020 10:57:57 +0800 Subject: [PATCH 144/206] Rename -webapp to -app --- src/wwwroot/docs/installation.html | 22 +++++++++--------- src/wwwroot/docs/sharp-apps.html | 36 +++++++++++++++--------------- src/wwwroot/gfm/sharp-apps/13.html | 12 +++++----- src/wwwroot/gfm/sharp-apps/13.md | 12 +++++----- 4 files changed, 41 insertions(+), 41 deletions(-) diff --git a/src/wwwroot/docs/installation.html b/src/wwwroot/docs/installation.html index aea7271..1da6d5c 100644 --- a/src/wwwroot/docs/installation.html +++ b/src/wwwroot/docs/installation.html @@ -129,36 +129,36 @@

    Init Sharp App

    -

    Bare SharpApp

    +

    Bare SharpApp

    - To start with a simple and minimal website, create a new bare-webapp project template: + To start with a simple and minimal website, create a new bare-app project template:

    - + -
    $ x new bare-webapp ProjectName
    +
    $ x new bare-app ProjectName

    This creates a multi-page Bootstrap Website with Menu navigation that's ideal for content-heavy Websites.

    -

    Parcel SharpApp

    +

    Parcel SharpApp

    For more sophisticated JavaScript Sharp Apps we recommended starting with the - parcel-webapp project template: + parcel-app project template:

    - + -
    $ x new parcel-webapp ProjectName
    +
    $ x new parcel-app ProjectName

    This provides a simple and powerful starting template for developing modern JavaScript .NET Core Sharp Apps utilizing the zero-configuration Parcel bundler to enable a rapid development workflow with access to best-of-class web technologies like TypeScript that's managed by pre-configured - npm scripts to handle its entire dev workflow. + npm scripts to handle its entire dev workflow.

    SharpApp Examples

    @@ -168,8 +168,8 @@

    SharpApp Examples

      -
    • About Bare SharpApp - Simple Content Website with Menu navigation
    • -
    • About Parcel SharpApp - Simple Integrated Parcel SPA Website utilizing TypeScript
    • +
    • About Bare SharpApp - Simple Content Website with Menu navigation
    • +
    • About Parcel SharpApp - Simple Integrated Parcel SPA Website utilizing TypeScript
    • Redis HTML - A Redis Admin Viewer developed as server-generated HTML Website
    • Redis Vue - A Redis Admin Viewer developed as Vue Client Single Page App
    • Rockwind - Combination of mult-layout Rockstars website + data-driven Nortwhind Browser
    • diff --git a/src/wwwroot/docs/sharp-apps.html b/src/wwwroot/docs/sharp-apps.html index aacfdeb..7bb212a 100644 --- a/src/wwwroot/docs/sharp-apps.html +++ b/src/wwwroot/docs/sharp-apps.html @@ -167,40 +167,40 @@

      SharpApp Project Templates

      $ dotnet tool install --global x
      -

      Bare SharpApp

      +

      Bare SharpApp

      - To start with a simple and minimal website, create a new bare-webapp project template: + To start with a simple and minimal website, create a new bare-app project template:

      - + -
      $ x new bare-webapp ProjectName
      +
      $ x new bare-app ProjectName

      This creates a multi-page Bootstrap Website with Menu navigation that's ideal for content-heavy Websites.

      -

      Parcel SharpApp

      +

      Parcel SharpApp

      For more sophisticated JavaScript Sharp Apps we recommended starting with the - parcel-webapp project template: + parcel-app project template:

      - + -
      $ x new parcel-webapp ProjectName
      +
      $ x new parcel-app ProjectName

      This provides a simple and powerful starting template for developing modern JavaScript .NET Core Sharp Apps utilizing the zero-configuration Parcel bundler to enable a rapid development workflow with access to best-of-class web technologies like TypeScript that's managed by pre-configured - npm scripts to handle its entire dev workflow. + npm scripts to handle its entire dev workflow.

      - If needed, it includes an optional server + If needed, it includes an optional server component containing any C# extensions needed that's automatically built and deployed to the app's /plugins folder when started. This enables an extensible .NET Core Web App with an integrated Parcel bundler to enable building sophisticated JavaScript Apps out-of-the-box within a live development environment. @@ -218,18 +218,18 @@

      Cloud Apps Starter Projects

    • rockwind-azure
    -

    About Bare WebApp

    +

    About Bare WebApp

    - The Getting Started project contains a copy of the bare-webapp.web-templates.io project below + The Getting Started project contains a copy of the bare-app.web-templates.io project below which is representative of a typical Company splash Website:

    -Bare WebApp screenshot +Bare WebApp screenshot

    The benefits over using a static website is improved maintenance @@ -280,10 +280,10 @@

    app.se debug true controls the level of internal diagnostics available and whether or not Hot Reloading is enabled.

    -

    About Parcel WebApp

    +

    About Parcel WebApp

    {{ 'gfm/sharp-apps/13.md' |> githubMarkdown }} diff --git a/src/wwwroot/gfm/sharp-apps/13.html b/src/wwwroot/gfm/sharp-apps/13.html index 5132736..4a58bdf 100644 --- a/src/wwwroot/gfm/sharp-apps/13.html +++ b/src/wwwroot/gfm/sharp-apps/13.html @@ -12,8 +12,8 @@

    Most development will happen within /client which is automatically published to /app using parcel's CLI that's invoked from the included npm scripts.

    client

    -

    The difference with templates-webapp is that the client source code is maintained in the /client folder and uses Parcel JS to automatically bundle and publish your App to /app/wwwroot which is updated with live changes during development.

    -

    The client folder also contains the npm package.json which contains all npm scripts required during development.

    +

    The difference with templates-app is that the client source code is maintained in the /client folder and uses Parcel JS to automatically bundle and publish your App to /app/wwwroot which is updated with live changes during development.

    +

    The client folder also contains the npm package.json which contains all npm scripts required during development.

    If this is the first time using Parcel, you will need to install its global CLI:

    $ npm install -g parcel-bundler
     
    @@ -29,7 +29,7 @@

    server

    To enable even richer functionality, this Sharp Apps template is also pre-configured with a custom Server project where you can extend your Web App with Plugins where all Plugins, Services, Filters, etc are automatically wired and made available to your Web App.

    -

    This template includes a simple ServerPlugin.cs which contains an Empty ServerPlugin and Hello Service:

    +

    This template includes a simple ServerPlugin.cs which contains an Empty ServerPlugin and Hello Service:

    public class ServerPlugin : IPlugin
     {
         public void Register(IAppHost appHost)
    @@ -62,11 +62,11 @@ 

    One benefit of creating your APIs with C# ServiceStack Services instead of API Pages is that you can generate TypeScript DTOs with:

    $ npm run dtos
     
    -

    Which saves generate DTOs for all your ServiceStack Services in dtos.ts which can then be accessed in your TypeScript source code.

    -

    If preferred you can instead develop Server APIs with API Pages, an example is included in /client/hello/_name.html

    +

    Which saves generate DTOs for all your ServiceStack Services in dtos.ts which can then be accessed in your TypeScript source code.

    +

    If preferred you can instead develop Server APIs with API Pages, an example is included in /client/hello/_name.html

    {{ { result: `Hi ${name} from /hello/_name.html` } |> return }}

    Which as it uses the same data structure as the Hello Service above, can be called with ServiceStack's JsonServiceClient and generated TypeScript DTOs.

    -

    The /client/index.ts shows an example of this where initially the App calls the C# Hello ServiceStack Service:

    +

    The /client/index.ts shows an example of this where initially the App calls the C# Hello ServiceStack Service:

    import { client } from "./shared";
     import { Hello, HelloResponse } from "./dtos";
     
    diff --git a/src/wwwroot/gfm/sharp-apps/13.md b/src/wwwroot/gfm/sharp-apps/13.md
    index e479dbc..8716787 100644
    --- a/src/wwwroot/gfm/sharp-apps/13.md
    +++ b/src/wwwroot/gfm/sharp-apps/13.md
    @@ -9,9 +9,9 @@ Most development will happen within `/client` which is automatically published t
     
     ### client
     
    -The difference with [templates-webapp](https://github.com/NetCoreTemplates/templates-webapp) is that the client source code is maintained in the `/client` folder and uses [Parcel JS](https://parceljs.org) to automatically bundle and publish your App to `/app/wwwroot` which is updated with live changes during development.
    +The difference with [templates-app](https://github.com/NetCoreTemplates/templates-app) is that the client source code is maintained in the `/client` folder and uses [Parcel JS](https://parceljs.org) to automatically bundle and publish your App to `/app/wwwroot` which is updated with live changes during development.
     
    -The client folder also contains the npm [package.json](https://github.com/NetCoreTemplates/parcel-webapp/blob/master/client/package.json) which contains all npm scripts required during development.
    +The client folder also contains the npm [package.json](https://github.com/NetCoreTemplates/parcel-app/blob/master/client/package.json) which contains all npm scripts required during development.
     
     If this is the first time using Parcel, you will need to install its global CLI:
     
    @@ -34,7 +34,7 @@ which will automatically reload your web page as it detects any file changes mad
     
     To enable even richer functionality, this Sharp Apps template is also pre-configured with a custom Server project where you can extend your Web App with [Plugins](http://sharpscript.net/docs/sharp-apps#plugins) where all `Plugins`, `Services`, `Filters`, etc are automatically wired and made available to your Web App. 
     
    -This template includes a simple [ServerPlugin.cs](https://github.com/NetCoreTemplates/parcel-webapp/blob/master/server/ServerPlugin.cs) which contains an Empty `ServerPlugin` and `Hello` Service:
    +This template includes a simple [ServerPlugin.cs](https://github.com/NetCoreTemplates/parcel-app/blob/master/server/ServerPlugin.cs) which contains an Empty `ServerPlugin` and `Hello` Service:
     
     ```csharp
     public class ServerPlugin : IPlugin
    @@ -74,9 +74,9 @@ One benefit of creating your APIs with C# ServiceStack Services instead of [API
     
         $ npm run dtos
     
    -Which saves generate DTOs for all your ServiceStack Services in [dtos.ts](https://github.com/NetCoreTemplates/parcel-webapp/blob/master/client/dtos.ts) which can then be accessed in your TypeScript source code.
    +Which saves generate DTOs for all your ServiceStack Services in [dtos.ts](https://github.com/NetCoreTemplates/parcel-app/blob/master/client/dtos.ts) which can then be accessed in your TypeScript source code.
     
    -If preferred you can instead develop Server APIs with API Pages, an example is included in [/client/hello/_name.html](https://github.com/NetCoreTemplates/parcel-webapp/blob/master/client/hello/_name.html)
    +If preferred you can instead develop Server APIs with API Pages, an example is included in [/client/hello/_name.html](https://github.com/NetCoreTemplates/parcel-app/blob/master/client/hello/_name.html)
     
     ```html
     {{ { result: `Hi ${name} from /hello/_name.html` } |> return }}
    @@ -84,7 +84,7 @@ If preferred you can instead develop Server APIs with API Pages, an example is i
     
     Which as it uses the same data structure as the `Hello` Service above, can be called with ServiceStack's `JsonServiceClient` and generated TypeScript DTOs.
     
    -The [/client/index.ts](https://github.com/NetCoreTemplates/parcel-webapp/blob/master/client/index.ts) shows an example of this where initially the App calls  the C# `Hello` ServiceStack Service:
    +The [/client/index.ts](https://github.com/NetCoreTemplates/parcel-app/blob/master/client/index.ts) shows an example of this where initially the App calls  the C# `Hello` ServiceStack Service:
     
     ```ts
     import { client } from "./shared";
    
    From 45c7ccdf626ed731367fa3ef7d402caa42dabbc5 Mon Sep 17 00:00:00 2001
    From: Demis Bellot 
    Date: Sun, 24 May 2020 03:43:53 +0800
    Subject: [PATCH 145/206] Use x dotnet tool
    
    ---
     src/wwwroot/docs/db-scripts.html           |  4 ++--
     src/wwwroot/docs/deploying-sharp-apps.html |  2 +-
     src/wwwroot/docs/sharp-apps.html           | 20 ++++++++++----------
     src/wwwroot/docs/sharp-scripts.html        |  4 ++--
     src/wwwroot/docs/syntax.html               |  2 +-
     src/wwwroot/gfm/sharp-scripts/10.html      |  2 +-
     src/wwwroot/gfm/sharp-scripts/10.md        |  2 +-
     src/wwwroot/index.html                     |  4 ++--
     8 files changed, 20 insertions(+), 20 deletions(-)
    
    diff --git a/src/wwwroot/docs/db-scripts.html b/src/wwwroot/docs/db-scripts.html
    index 4588eb1..fd54fab 100644
    --- a/src/wwwroot/docs/db-scripts.html
    +++ b/src/wwwroot/docs/db-scripts.html
    @@ -266,7 +266,7 @@ 

    Run Rockwind against your Local RDBMS

    against either an SQLite, SQL Server or MySql database by just changing which app.settings the App is run with, e.g:

    -
    web web.sqlserver.settings
    +
    x web.sqlserver.settings
    Populate local RDBMS with Northwind database
    @@ -297,7 +297,7 @@

    PostgreSQL Support

    can be run against either PostgreSQL, SQL Server or SQLite by changing the configuration it's run with, e.g:

    -
    web web.postgres.settings
    +
    x web.postgres.settings

    See the Scripts API Reference for the diff --git a/src/wwwroot/docs/deploying-sharp-apps.html b/src/wwwroot/docs/deploying-sharp-apps.html index dbba523..90d8eb9 100644 --- a/src/wwwroot/docs/deploying-sharp-apps.html +++ b/src/wwwroot/docs/deploying-sharp-apps.html @@ -14,7 +14,7 @@ App deployment where you can **completely skip all CI and build steps** as there's nothing to build or deploy with the built-in support for Gist publishing. -All that's required is to run the App on your server with `web open `, e.g. to run `redis` Gist App: +All that's required is to run the App on your server with `x open `, e.g. to run `redis` Gist App: $ x open redis diff --git a/src/wwwroot/docs/sharp-apps.html b/src/wwwroot/docs/sharp-apps.html index 7bb212a..452ab09 100644 --- a/src/wwwroot/docs/sharp-apps.html +++ b/src/wwwroot/docs/sharp-apps.html @@ -30,7 +30,7 @@

    Ultimate Simplicity

    All complexity with C#, .NET, namespaces, references, .dlls, strong naming, packages, MVC, Razor, build tools, IDE environments, etc has been eliminated leaving all Web Developers needing to do is run the cross-platform - web dotnet tool + x dotnet tool and configure a simple app.settings text file to specify which website folder to use, which ServiceStack features to enable, which db or redis providers to connect to, etc. @@ -88,7 +88,7 @@

    Getting Started

    Which will run the latest version of the spirals App each time. - After an App has been run once or installed, you can run its local version with web run: + After an App has been run once or installed, you can run its local version with x run:

    $ x run spirals
    @@ -109,7 +109,7 @@

    Getting Started

    Spirals

    - web install spirals - + x install spirals - spirals.web-app.io - mythz/spirals
    @@ -298,7 +298,7 @@

    Example Sharp Apps

    Redis HTML

    - web install redis-html - + x install redis-html - redis-html.web-app.io - sharp-apps/redis-html
    @@ -368,7 +368,7 @@

    Redis Vue

    @@ -487,7 +487,7 @@

    Deep linking and full page reloads

    Rockwind

    - web install rockwind - + x install rockwind - rockwind-sqlite.web-app.io - sharp-apps/Rockwind
    @@ -622,7 +622,7 @@

    app.sqlite.settings

    To run the Rockwind app using the northwind.sqlite database, run the command below on Windows, Linux or OSX:

    -
    web app.sqlite.settings
    +
    x app.sqlite.settings
    app.sqlserver.settings
    @@ -719,7 +719,7 @@

    Multi-RDBMS SQL

    Rockwind VFS

    - web install rockwind-aws - + x install rockwind-aws - rockwind-aws.web-app.io - sharp-apps/rockwind-aws
    @@ -834,7 +834,7 @@
    - web install plugins - + x install plugins - plugins.web-app.io - sharp-apps/Plugins
    @@ -962,7 +962,7 @@

    ServiceStack Ecosystem

    Chat

    - web install chat - + x install chat - chat.web-app.io - sharp-apps/Chat
    diff --git a/src/wwwroot/docs/sharp-scripts.html b/src/wwwroot/docs/sharp-scripts.html index 9b00f74..7f86c3c 100644 --- a/src/wwwroot/docs/sharp-scripts.html +++ b/src/wwwroot/docs/sharp-scripts.html @@ -8,7 +8,7 @@ literate programming where executable snippets can be embedded inside an executable document. [code blocks](/docs/syntax#code-blocks) are a convenient markup for embedding executable scripts within a document without the distracting boilerplate -of wrapping each statement in expression blocks. Together with `web watch`, `#Script` allows for an iterative, exploratory style of programming +of wrapping each statement in expression blocks. Together with `x watch`, `#Script` allows for an iterative, exploratory style of programming in a live programming environment that benefits many domains whose instant progressive feedback unleashes an unprecedented amount of productivity, one of those areas it benefits is shell scripting where the iterative feedback is invaluable in working towards a solution for automating the desired task. @@ -108,7 +108,7 @@ The Vue and React "lite" project templates take advantage of this in their [Pre-compiled minified production _bundle.ss script](https://docs.servicestack.net/templates-lite#pre-compiled-minified-production-bundles) -which is run with `web run {script}`: +which is run with `x run {script}`: {{/markdown}} {{ 'gfm/sharp-scripts/10.md' | githubMarkdown | convertScriptToCodeBlocks }} diff --git a/src/wwwroot/docs/syntax.html b/src/wwwroot/docs/syntax.html index 21b0c93..b1ca602 100644 --- a/src/wwwroot/docs/syntax.html +++ b/src/wwwroot/docs/syntax.html @@ -240,7 +240,7 @@

    Multi-line Comments

    {{#markdown}} -Which can be run with `web run {script}.ss` to view its expected output: +Which can be run with `x run {script}.ss` to view its expected output: Create an Array 2 diff --git a/src/wwwroot/gfm/sharp-scripts/10.html b/src/wwwroot/gfm/sharp-scripts/10.html index 9172d31..a700ff0 100644 --- a/src/wwwroot/gfm/sharp-scripts/10.html +++ b/src/wwwroot/gfm/sharp-scripts/10.html @@ -1,5 +1,5 @@
    <script>
    -* run in host project directory with `web run wwwroot/_bundle.ss` *
    +* run in host project directory with `x run wwwroot/_bundle.ss` *
     
     false |> to => debug
     (debug ? '' : '.min')       |> to => min
    diff --git a/src/wwwroot/gfm/sharp-scripts/10.md b/src/wwwroot/gfm/sharp-scripts/10.md
    index 8c9c628..d346c3d 100644
    --- a/src/wwwroot/gfm/sharp-scripts/10.md
    +++ b/src/wwwroot/gfm/sharp-scripts/10.md
    @@ -1,6 +1,6 @@
     ```html
     
    -

    - -

    - See the Making of Spirals for a walk through on how to create - the Spirals Web App from scratch. -

    - -

    Sharp App

    - -

    - The easiest way to create an empty Sharp App is to run app init from an Empty App Directory: -

    - -
    $ md Acme && cd Acme && web init
    - -

    - Which will create an empty Sharp App as seen in the Spirals video above: -

    - - - -

    SharpApp Project Templates

    - -

    - A more complete starting option is to start from one of the project templates below. All project templates can be installed using the - x new tool, which if not already can be installed with: -

    - -
    $ dotnet tool install --global x
    - -

    Bare SharpApp

    - -

    - To start with a simple and minimal website, create a new bare-app project template: -

    - - - -
    $ x new bare-app ProjectName
    - -

    - This creates a multi-page Bootstrap Website with Menu navigation that's ideal for content-heavy Websites. -

    - -

    Parcel SharpApp

    - -

    - For more sophisticated JavaScript Sharp Apps we recommended starting with the - parcel-app project template: -

    - - - -
    $ x new parcel-app ProjectName
    - -

    - This provides a simple and powerful starting template for developing modern JavaScript .NET Core Sharp Apps utilizing the - zero-configuration Parcel bundler to enable a rapid development workflow with access to - best-of-class web technologies like TypeScript that's managed by pre-configured - npm scripts to handle its entire dev workflow. -

    - -

    - If needed, it includes an optional server - component containing any C# extensions needed that's automatically built and deployed to the app's /plugins folder when started. - This enables an extensible .NET Core Web App with an integrated Parcel bundler to enable building sophisticated JavaScript - Apps out-of-the-box within a live development environment. -

    - -

    Cloud Apps Starter Projects

    - -

    - If you intend to deploy your Web App on AWS or Azure you may prefer to start with one of the example - Cloud Apps below which come pre-configured with deployment scripts for deploying with Travis CI and Docker: -

    - - - -

    About Bare WebApp

    - - -

    - The Getting Started project contains a copy of the bare-app.web-templates.io project below - which is representative of a typical Company splash Website: -

    - -Bare WebApp screenshot - -

    - The benefits over using a static website is improved maintenance - as you can extract and use its common _layout.html - instead of having it duplicated in each page. - The menu.html partial also makes menu items - easier to maintain by just adding an entry in the JavaScript object literal. The dynamic menu also takes care of highlighting the active menu item. -

    - -
    {{ 'examples/webapps-menu.html' |> includeFile }}
    - -

    Ideal for Web Designers and Content Authors

    - -

    - The other primary benefit is that this is an example of a website that can be maintained by employees who don't have any - programming experience as #Script in their basic form are intuitive and approachable to non-developers, e.g: - The title of each page is maintained as metadata HTML comments: -

    - -
    <!--
    -title: About Us
    --->
    -
    - -

    - #Script syntax is also the ideal way to convey variable substitution, e.g: <title>{{ pass: title }}</title> - and even embedding a partial reads like english {{ pass: 'menu' |> partial }} which is both intuitive and works well - with GUI HTML designers. -

    - -
    app.settings
    - -

    - Below is the app.settings for a Basic App, with contentRoot being the only setting required as the - rest can be inferred but including the other relevant settings is both more descriptive to other developers as well - making it easier to - use tools like sed or - powershell to replace them - during deployment. -

    - -
    debug true
    -name Bare Web App
    -contentRoot ~/../bare
    -webRoot ~/../bare
    - -
    - debug true controls the level of internal diagnostics available and whether or not Hot Reloading is enabled. -
    - -

    About Parcel WebApp

    - - -{{ 'gfm/sharp-apps/13.md' |> githubMarkdown }} - -

    Example Sharp Apps

    - -

    - In addition to the templates there's a number of Sharp Apps to illustrate the various features available and to showcase the different - kind of Web Apps that can easily be developed. The source code for each app is available either individually from - github.com/sharp-apps. -

    - -

    Redis HTML

    -
    - x install redis-html - - redis-html.web-app.io - - sharp-apps/redis-html -
    - -

    - For the Redis Browser Web App, we wanted to implement an App that was an ideal candidate for a Single Page App but constrain ourselves - to do all HTML rendering on the server and have each interaction request a full-page reload to see how a traditional server-generated - Web App feels like with the performance of .NET Core 3.1 and #Script. We're pleasantly surprised with the result as when - the App is run locally the responsiveness is effectively indistinguishable from an Ajax App. When hosted on the Internet - there is a sub-second delay which causes a noticeable flicker but it still retains a pleasant UX that's faster than most websites. -

    - -

    - The benefits of a traditional website is that it doesn't break the web where the back button and deep linking work without effort - and you get to avoid the complexity train of adopting a premier JavaScript SPA Framework's configuration, dependencies, workflow - and build system which has become overkill for small projects. -

    - -Redis HTML WebApp Screenshot - -

    - We've had a sordid history developing Redis UI's which we're built using the popular JavaScript frameworks that appeared - dominant at the time but have since seen their ecosystem decline, starting with the - Redis Admin UI (src) - built using - Google's Closure Library that as it works different to everything else - needed a complete rewrite when creating redisreact.servicestack.net - (src) using the hot new React framework, unfortunately it uses React's old - deprecated ES5 syntax and Reflux which is sufficiently different from our current recommended - TypeScript + React + Redux + WebPack JavaScript SPA Stack, - that is going to require a significant refactor to adopt our preferred SPA tech stack. -

    - -
    Beautiful, succinct, declarative code
    - -

    - The nice thing about generating HTML is that it's the one true constant in Web development that will always be there. - The entire functionality for the Redis Web App is contained in a single - /redis-html/app/index.html which includes - all Template and JavaScript Source Code in < 200 lines which also includes all as server logic as it doesn't rely on any - back-end Services and just uses the Redis Scripts to interface with Redis directly. - The source code also serves as a good - demonstration of the declarative coding style that #Script encourages that in addition to being highly-readable requires orders - of magnitude less code than our previous Redis JavaScript SPA's with a comparable feature-set. -

    - -

    - Having a much smaller code-base makes it much easier to maintain and enhance whilst being less susceptible to becoming obsolete - by the next new JavaScript framework as it would only require rewriting 75 lines of JavaScript instead of the complete rewrite - that would be required to convert the existing JavaScript Apps to a use different JavaScript fx. -

    - -
    app.settings
    - -

    - The app.settings for Redis is similar to Web App Starter above except it adds a redis.connection - to configure a RedisManagerPool at the - connection string provided - as well as Redis Scripts to give the scripts access to the Redis instance. -

    - -
    debug true
    -name Redis Web App
    -contentRoot ~/../redis-html
    -webRoot ~/../redis-html
    -redis.connection localhost:6379
    - -

    Redis Vue

    -
    - x install redis - - redis.web-app.io - - sharp-apps/Redis -
    - -

    - Whilst the above server-generated HTML Redis UI shows how you can easily develop traditional Web Apps using #Script, we've also - rewritten the Redis UI as a Single Page App which is the more suitable choice for an App like this as it provides a more - optimal and responsive UX by only loading the HTML page once on Startup then utilizes Ajax to only download and update - the incremental parts of the App's UI that needs changing. -

    - -

    - Instead of using jQuery and server-side HTML this version has been rewritten to use Vue - where the UI has been extracted into isolated Vue components utilizing - Vue X-Templates to render the App on the client where - all Redis Vue's functionality is contained within the - Redis/app/index.html page. -

    - -Redis Vue WebApp Screenshot - -

    Simple Vue App

    - -

    - #Script also provides a great development experience for Single Page Apps which for the most part gets out of your way letting you - develop the Single Page App as if it were a static .html file, but also benefits from the flexibility of a - dynamic web page when needed. -

    -

    - The containing _layout.html - page can be separated from the index.html page - that contains the App's functionality, where it's able to extract the title of the page and embed it in the - HTML <head/> as well as embed the page's <script /> in its optimal location at the bottom of the - HTML <body/>, after the page's blocking script dependencies: -

    - -{{ 'gfm/sharp-apps/07.md' |> githubMarkdown }} - -

    - Redis Vue avoids the complexity of adopting a npm build system by referencing Vue libraries as a simple script include: -

    - -
    <script src="../assets/js/vue{{ pass: '.min' |> if(!debug) }}.js">
    - -

    - Where it uses the more verbose and developer-friendly - vue.js - during development whilst using the production optimized - vue.min.js - for deployments. So despite avoiding the complexity tax of an npm-based build system it still gets some of its benefits - like conditional deployments and effortless hot reloading. -

    - -

    Server Pages

    - -

    - Whilst most of index.html is a static Vue - app, #Script is leveraged to generate the body of the <redis-info/> Component on the initial home page render: -

    - -{{ 'gfm/sharp-apps/08.md' |> githubMarkdown }} - -

    - This technique increases the time to first paint by being able to render the initial Vue page without waiting for an Ajax call response - whilst benefiting from improved SEO from server-generated HTML. -

    - -

    Server Handling

    - -

    - Another area #Script is used is to handle the HTTP POST where it calls the redisChangeConnection filter to change - the current Redis connection before rendering the - connection-info.html partial with the - current connection info: -

    - -{{ 'gfm/sharp-apps/09.md' |> githubMarkdown }} - -

    Vue Ajax Server APIs

    - -

    - All other Server functionality is invoked by Vue using Ajax to call one of the Ajax APIs below implemented as - API Pages: -

    - -
    search.html
    - -

    - Called when searching for Redis keys where the query is forwarded to the redisSearchKeys filter: -

    - -{{ 'gfm/sharp-apps/10.md' |> githubMarkdown }} - -
    call.html
    - -

    - Called to execute an arbitrary Redis command on the connected instance, with the response from Redis is returned as a - plain-text HTTP Response: -

    - -{{ 'gfm/sharp-apps/11.md' |> githubMarkdown }} - -

    - The benefits of using API Pages instead of a normal C# Service is being able to retain Web App's productive development workflow - where the entire Redis Vue App is built without requiring any compilation. -

    - -

    Deep linking and full page reloads

    - -

    - The Redis Vue Single Page App also takes advantage of HTML5's history.pushState API to enable deep-linking and back-button - support where most UI state changes is captured on the query string and used to initialize the Vue state on page navigation or - full-page reloads where it provides transparent navigation and back-button support that functions like a traditional Web App but with - the instant performance of a Single Page App. -

    - -

    Rockwind

    -
    - x install rockwind - - rockwind-sqlite.web-app.io - - sharp-apps/Rockwind -
    - -

    - The Rockwind website shows an example of combining multiple websites in a single Web App - a - Rockstars Content Website and a dynamic data-driven UI for the Northwind database which can - run against either SQL Server, MySql or SQLite database using just configuration. It also includes - API Pages examples for rapidly developing Web APIs. -

    - -

    Rockstars

    - -

    - /rockstars is an - example of a Content Website that itself maintains multiple sub sections with their own layouts - - /rockstars/alive - for living Rockstars and - /rockstars/dead - for the ones that have died. Each Rockstar maintains their own encapsulated - mix of HTML, markdown content and splash image - that intuitively uses the closest _layout.html, content.md and splash.jpg from the page they're - referenced from. This approach makes it easy to move entire sub sections over by just moving a folder and it will automatically - use the relevant layout and partials of its parent. -

    - -Rockwind WebApp screenshot - -

    Northwind

    - -

    - /northwind is an example of - a dynamic UI for a database containing a - form to filter results, multi-nested - detail pages and - deep-linking for quickly navigating between - referenced data. #Script is also a great solution for rapidly developing Web APIs where the - /api/customers.html - API Page below: -

    - -{{ 'gfm/sharp-apis/02.md' |> githubMarkdown }} - -

    - Is all the code needed to generate the following API endpoints: -

    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    /customers API
    All Customers - - - - -
    Accept HTTP Header also supported
    -
    Alfreds Futterkiste Details - - - -
    As List - - - -
    Customers in Germany - - - -
    Customers in London - - - -
    Combination Query - /api/customers?city=London&country=UK&limit=3 -
    - -

    Multi platform configurations

    - -

    - In addition to being a .NET Core 3.1 App that runs flawlessly cross-platform on Windows, Linux and OSX, Sharp Apps can also support - multiple RDBMS's and Virtual File Systems using just configuration. -

    - -

    app.sqlite.settings

    - -

    - SQLite uses a file system database letting you bundle your database with your App. So we can share the - northwind.sqlite database across multiple Apps, - the contentRoot is set to the /apps directory which can only be accessed by your App, whilst - the webRoot is configured to use the Sharp Apps folder that hosts all the publicly accessible files of your App. -

    - -
    debug true
    -name Rockwind SQLite Web App
    -contentRoot ..
    -webRoot .
    -db sqlite
    -db.connection ~/northwind.sqlite
    - -

    - To run the Rockwind app using the northwind.sqlite database, run the command below on Windows, Linux or OSX: -

    - -
    x app.sqlite.settings
    - -
    app.sqlserver.settings
    - -

    - To switch to use the Northwind database in SQL Server we just need to update the configuration to point to a SQL Server database - instance. Since the App no longer need access to the northwind.sqlite database, the contentRoot can be reverted - back to the Sharp Apps folder: -

    - -
    debug true
    -name Rockwind SQL Server Web App
    -port 5000
    -db sqlserver
    -db.connection Server=localhost;Database=northwind;User Id=test;Password=test;
    - -

    - The /support/northwind-data - project lets you quickly try out Rockwind against your local RDBMS by populating it with a copy of the Northwind database - using the same sqlserver identifier and connection string from the App, e.g: -

    - -
    dotnet run sqlserver "Server=localhost;Database=northwind;User Id=test;Password=test;"
    - -

    app.mysql.settings

    - -

    - You can run against a MySql database in the same way as SQL Server above but using a MySql db connection string: -

    - -
    debug true
    -name Rockwind MySql Web App
    -port 5000
    -db mysql
    -db.connection Server=localhost;Database=northwind;UID=root;Password=test;SslMode=none
    - -

    app.azure.settings

    - -

    - The example app.azure.settings - Azure configuration is also configured to use a different Virtual File System where instead of sourcing - Web App files from the filesystem they're sourced from an - Azure Blob Container. - In this case we're not using any files from the App so we don't need to set a contentRoot or webRoot path. - This also means that for deployment we're just deploying the WebApp binaries with just this app.settings since both the - Web App files and database are sourced remotely. -

    - -
    # Note: values prefixed with '$' are resolved from Environment Variables
    -debug false
    -name Azure Blob SQL Server Web App
    -bind *
    -port 5000
    -db sqlserver
    -db.connection $AZURE_SQL_CONNECTION_STRING
    -files azure
    -files.config {ConnectionString:$AZURE_BLOB_CONNECTION_STRING,ContainerName:rockwind-fs}
    -
    -# Reduces a Blob Storage API call, but takes longer for modified pages to appear
    -checkForModifiedPagesAfterSecs 60
    -defaultFileCacheExpirySecs     60
    - -

    - The /support/copy-files - project lets you run Rockwind against your own Azure Blob Container by populating it with a copy of the - /rockwind App's files using - the same configuration above: -

    - -
    dotnet run azure "{ConnectionString:$AZURE_BLOB_CONNECTION_STRING,ContainerName:rockwind}"
    - -

    Multi-RDBMS SQL

    - -

    - As #Script is unable to use a Typed ORM like OrmLite - to hide the nuances of each database, we need to be a bit more diligent in #Script to use parameterized SQL that works across - multiple databases by using the - sql* DB Filters to avoid using RDBMS-specific - SQL syntax. The - /northwind/customer.html - contains a good example containing a number of things to watch out for: -

    - -{{ 'gfm/sharp-apps/01.md' |> githubMarkdown }} - -

    - Use sqlConcat to concatenate strings using the RDBMS-specific SQL for the configured database. Likewise - sqlCurrency utilizes RDBMS-specific SQL functions to return monetary values in a currency format, whilst - sqlQuote is used for quoting tables named after a reserved word. -

    - -
    - Of course if you don't intend on supporting multiple RDBMS's, you can ignore this and use RDBMS-specific syntax. -
    - -

    Rockwind VFS

    -
    - x install rockwind-aws - - rockwind-aws.web-app.io - - sharp-apps/rockwind-aws -
    - -

    - /rockwind-vfs is a clone of - the Rockwind Web App with 3 differences: It uses the resolveAsset filter for each .js, .css and - image web asset so that it's able to generate external URLs directly to the S3 Bucket, Azure Blob Container or CDN - hosting a copy of your files to both reduce the load on your Web App and maximize the responsiveness to the end user. -

    - -

    - To maximize responsiveness when using remote storage, all - embedded files utilize caching: -

    - -
    {{ pass: "content.md" |> includeFileWithCache |> markdown }}
    - -

    - The other difference is that each table and column has been quoted in "double-quotes" so that it works in PostgreSQL which - otherwise treats unquoted symbols as lowercase. This version of Rockwind also works with SQL Server and SQLite as they also - support "Table" quotes but not MySql which uses `BackTicks` or [SquareBrackets]. It's therefore - infeasible to develop Apps that support both PostgreSQL and MySql unless you're willing to use all lowercase, - snake_case or the sqlQuote filter for every table and column. -

    - -Rockwind VFS WebApp screenshot - -

    resolveAsset

    - -

    - If using a remote file storage like AWS S3 or Azure Blob Storage it's a good idea to use the resolveAsset filter - for each external file reference. By default it returns the same path it was called with so it will continue to work locally - but then ServiceStack effectively becomes a proxy where it has to call the remote Storage Service for each requested download. -

    - -{{ 'gfm/sharp-apps/02.md' |> githubMarkdown }} - -

    - ServiceStack asynchronously writes each file to the Response Stream with the last Last-Modified HTTP Header to - enable browser caching so it's still a workable solution but for optimal performance you can specify an args.assetsBase - in your app.settings to populate the assetsBase ScriptContext Argument the resolveAsset filter uses to generate - an external URL reference to the file on the remote storage service, reducing the load and improving the performance of your App, - especially if it's configured to use a CDN. -

    - -

    Pure Cloud Apps

    - -
    rockwind-aws/app/app.settings
    - -

    - The AWS settings shows an example of this where every external resource - rockwind-aws.web-app.io has been replaced with a direct reference to the - asset on the S3 bucket: -

    - -
    # Note: values prefixed with '$' are resolved from Environment Variables
    -debug false
    -name AWS S3 PostgreSQL Web App
    -db postgres
    -db.connection $AWS_RDS_POSTGRES
    -files s3
    -files.config {AccessKey:$AWS_S3_ACCESS_KEY,SecretKey:$AWS_S3_SECRET_KEY,Region:us-east-1,Bucket:rockwind}
    -args.assetsBase http://s3-postgresql.s3-website-us-east-1.amazonaws.com/
    -
    -# Reduces an S3 API call, but takes longer for modified pages to appear
    -checkForModifiedPagesAfterSecs 60
    -defaultFileCacheExpirySecs     60
    - -

    - With all files being sourced from S3 and the App configured to use AWS RDS PostgreSQL, the AWS settings is an example of - a Pure Cloud App where the entire App is hosted on managed cloud services that's decoupled from the .NET Core 3.1 binary - that runs it that for the most part won't require redeploying the Web App binary unless making configuration changes or - upgrading the x dotnet tool - as any App changes can just be uploaded straight to S3 which changes reflected within the - checkForModifiedPagesAfterSecs setting, which tells the Web App how long to wait before checking for file changes - whilst defaultFileCacheExpirySecs specifies how long to cache files like content.md for. -

    - -
    DockerFile
    - -

    - Deployments are also greatly simplified as all that's needed is to deploy the WebApp binary and app.settings of your Cloud App, - e.g. here's the DockerFile for rockwind-aws.web-app.io - deployed to AWS ECS - using the deployment scripts in rockwind-aws and following our - .NET Core Docker Deployment Guideline: -

    - -{{ 'gfm/sharp-apps/03.md' |> githubMarkdown }} - -
    rockwind-azure/app/app.settings
    - -

    - We can also create Azure Cloud Apps in the same we've done for AWS above, which runs the same - /rockwind-vfs - Web App but using an Azure hosted SQL Server database and its files hosted on Azure Blob Storage: -

    - -
    # Note: values prefixed with '$' are resolved from Environment Variables
    -debug false
    -name Azure Blob SQL Server Web App
    -bind *
    -db sqlserver
    -db.connection $AZURE_SQL_CONNECTION_STRING
    -files azure
    -files.config {ConnectionString:$AZURE_BLOB_CONNECTION_STRING,ContainerName:rockwind}
    -args.assetsBase https://servicestack.blob.core.windows.net/rockwind/
    -
    -# Reduces an S3 API call, but takes longer for modified pages to appear
    -checkForModifiedPagesAfterSecs 60
    -defaultFileCacheExpirySecs     60
    - -

    Plugins

    -
    - x install plugins - - plugins.web-app.io - - sharp-apps/Plugins -
    - -

    - Up till now the Apps above only have only used functionality built into ServiceStack, to enable even greater functionality - but still retain all the benefits of developing Web Apps you can drop .dll with custom functionality into your - Web App's /plugins folder. The plugins support in Web Apps is as friction-less as we could make it, there's no - configuration to maintain or special interfaces to implement, you're able to drop your existing implementation .dll's - as-is into the App's `/plugins` folder. -

    - -

    - Plugins allow "no touch" sharing of - ServiceStack Plugins, - Services, - Script Methods - Sharp Code Pages, - Validators, etc. - contained within .dll's or .exe's dropped in a Sharp App's - /plugins folder which are auto-registered - on startup. The source code for all plugins used in this App were built from the .NET Core 3.1 projects in the - /example-plugins folder. The - plugins.web-app.io Sharp App below walks through examples of using Custom Filters, - Services and Validators: -

    - -Plugins WebApp screenshot - -

    Registering ServiceStack Plugins

    - -

    - ServiceStack Plugins can be added to your App by - listing it's Type Name in the features config entry in - app.settings: -

    - -
    debug true
    -name Web App Plugins
    -contentRoot ~/../plugins
    -webRoot ~/../plugins
    -features CustomPlugin, OpenApiFeature, PostmanFeature, CorsFeature, ValidationFeature
    -CustomPlugin { ShowProcessLinks: true }
    -ValidationFeature { ScanAppHostAssemblies: true }
    -
    - -

    - All plugins listed in features will be added to your Web App's AppHost in the order they're specified. - They can further customized by adding a separate config entry with the Plugin Name and a JavaScript Object literal to - populate the Plugin at registration, e.g the config above is equivalent to: -

    - -{{ 'gfm/sharp-apps/04.md' |> githubMarkdown }} - -

    Custom Plugin

    - -

    - In this case it tells our CustomPlugin - from /plugins/ServerInfo.dll to also show Process Links in its - /metadata Page: -

    - -{{ 'gfm/sharp-apps/05.md' |> githubMarkdown }} - -

    - Where as it was first registered in the list will appear before any links registered by other plugins: -

    - -Metadata screenshot - -

    Built-in Plugins

    - -

    - It also tells the ValidationFeature to scan all Service Assemblies for Validators and to automatically register them - which is how ServiceStack was able to find the - ContactValidator - used to validate the StoreContact request. -

    - -

    - Other optional plugins registered in this Web App is the metadata Services required for - Open API, - Postman as well as - support for CORS. - You can check the /metadata/debug Inspector for all Plugins loaded in your AppHost. -

    - -

    .NET Extensibility

    - -

    - Plugins can also implement .NET Core's IStartup to be able to register any required dependencies without any coupling to any Custom AppHost. -

    - -

    - To simplify configuration you can use the plugins/* wildcard in - app.settings - at the end of an ordered plugin list to register all remaining Plugins it finds in the apps `/plugins` folder: -

    - - -
    features OpenApiFeature, PostmanFeature, CorsFeature, ValidationFeature, plugins/*
    -CustomPlugin { ShowProcessLinks: true }
    -
    - -

    - Each plugin registered can continue to be furthered configured by specifying its name and a JavaScript object literal as seen above. -

    - -

    - The /plugins2 App shows an example of this with the - StartupPlugin - registering a StartupDep dependency which is used by its StartupServices at runtime: -

    - -{{ 'gfm/sharp-apps/12.md' |> githubMarkdown }} - -

    ServiceStack Ecosystem

    - -

    - All Services loaded by plugins continue to benefit from ServiceStack's rich metadata services, including being listed - in the /metadata page, being able to explore and interact with Services using - /swagger-ui/ as well as being able to generate Typed APIs for the most popular - Mobile, Web and Desktop platforms. -

    - -

    Chat

    -
    - x install chat - - chat.web-app.io - - sharp-apps/Chat -
    - -

    - /chat is an example of the ultimate form - of extensibility where instead of just being able to add Services, Filters and Plugins, etc. You can add your entire - AppHost which Sharp Apps will use instead of its own. This vastly expands the use-cases that can be built with - Sharp Apps as it gives you complete fine-grained control over how your App is configured. -

    - -Chat WebApp screenshot - -

    Develop back-end using .NET IDE's

    - -

    - For chat.web-app.io we've taken a copy of the existing .NET Core 3.1 - Chat App and moved its C# code to - /example-plugins/Chat - and its files to /apps/chat - where it can be developed like any other Web App except it utilizes the Chat AppHost and implementation in the - SelfHost Chat App. -

    - -

    - Customizations from the original - .NET Core Chat implementation - includes removing MVC and Razor dependencies and configuration, extracting its - _layout.html and - converting index.html - to use #Script from its original - default.cshtml. - It's also been enhanced with the ability to evaluate scripts from the Chat window, as seen in the screenshot above. -

    - -

    Chat AppHost

    - -{{ 'gfm/sharp-apps/06.md' |> githubMarkdown }} - -
    Reusing Web App's app.setting and files
    - -

    - One nice thing from being able to reuse existing AppHost's is being able to develop all back-end C# Services and Custom Filters - as a stand-alone .NET Core Project where it's more productive with access to .NET IDE tooling and debugging. -

    - -

    - To account for these 2 modes we use AddIfNotExists to only register the SharpPagesFeature plugin - when running as a stand-alone App and add an additional constructor so it reuses the existing app.settings as its - IAppSettings provider for is custom App configuration like OAuth App keys - required for enabling Sign-In's via with Twitter, Facebook and GitHub when running on http://localhost:5000: -

    - -
    debug true
    -name Chat Web App
    -contentRoot ~/../chat
    -webRoot ~/../chat
    -
    -oauth.RedirectUrl http://localhost:5000/
    -oauth.CallbackUrl http://localhost:5000/auth/{0}
    -oauth.twitter.ConsumerKey JvWZokH73rdghDdCFCFkJtCEU
    -oauth.twitter.ConsumerSecret WNeOT6YalxXDR4iWZjc4jVjFaydoDcY8jgRrGc5FVLjsVlY2Y8
    -oauth.facebook.Permissions email
    -oauth.facebook.AppId 447523305684110
    -oauth.facebook.AppSecret 7d8a16d5c7cbbfab4b49fd51183c93a0
    -oauth.github.Scopes user
    -oauth.github.ClientId dbe8c242e3d1099f4558
    -oauth.github.ClientSecret 42c8db8d0ca72a0ef202e0c197d1377670c990f4
    -
    - -

    - After the back-end has been implemented we can build and copy the compiled Chat.dll into the Chat's - /plugins folder where - we can take advantage of the improved development experience for rapidly developing its UI. -

    - -

    Blog

    -
    - web install blog - - blog.web-app.io - - sharp-apps/Blog -
    - -{{ 'gfm/sharp-apps/14.md' |> githubMarkdown }} - -

    Customizable Auth Providers

    - -

    - Authentication can now be configured using plain text config in your app.settings where initially you need register the AuthFeature - plugin as normal by specifying it in the - features list: -

    - -
    features AuthFeature
    - -

    - Then using AuthFeature.AuthProviders you can specify which Auth Providers you want to have registered, e.g: -

    - -
    AuthFeature.AuthProviders TwitterAuthProvider, GithubAuthProvider
    - -

    - Each Auth Provider checks the Sharp Apps app.settings for its Auth Provider specific configuration it needs, e.g. to configure both - Twitter and GitHub Auth Providers you would populate it with your - OAuth Apps details: -

    - -
    oauth.RedirectUrl http://127.0.0.1:5000/
    -oauth.CallbackUrl http://127.0.0.1:5000/auth/{0}
    -
    -oauth.twitter.ConsumerKey {Twitter App Consumer Key}
    -oauth.twitter.ConsumerSecret {Twitter App Consumer Secret Key}
    -
    -oauth.github.ClientId {GitHub Client Id}
    -oauth.github.ClientSecret {GitHub Client Secret}
    -oauth.github.Scopes {GitHub Auth Scopes}
    - -

    Customizable Markdown Providers

    - -

    - By default Sharp Apps now utilize Markdig implementation to render its Markdown. - You can also switch it back to the built-in Markdown provider that ServiceStack uses with: -

    - -
    markdownProvider MarkdownDeep
    - -

    Rich Config Arguments

    - -

    - Any app.settings configs that are prefixed with args. are made available to #Script Pages and any arguments starting with - a { or [ are automatically converted into a JS object: -

    - -
    args.blog { name:'blog.web-app.io', href:'/' }
    -args.tags ['technology','marketing']
    - -

    - Where they can be referenced as an object or an array directly: -

    - -{{#raw appendTo configExample}}{{blog.name}}{{/raw}} - -
    {{#raw}}{{#each tags}} {{it}} {{/each}}{{/raw}}
    {{configExample}}
    - -

    - The alternative approach is to give each argument value a different name: -

    - -
    args.blogName blog.web-app.io
    -args.blogHref /
    -
    - -{{ "doc-links" |> partial({ order }) }} +{{ httpResult({ status: 301, Location:'/sharp-apps/' }) |> return }} \ No newline at end of file diff --git a/src/wwwroot/docs/sharp-pages.html b/src/wwwroot/docs/sharp-pages.html index fe1d77f..528920a 100644 --- a/src/wwwroot/docs/sharp-pages.html +++ b/src/wwwroot/docs/sharp-pages.html @@ -1,5 +1,5 @@ diff --git a/src/wwwroot/gfm/apps/01.html b/src/wwwroot/gfm/apps/01.html new file mode 100644 index 0000000..c6e630f --- /dev/null +++ b/src/wwwroot/gfm/apps/01.html @@ -0,0 +1,298 @@ +

    +The Making of Spirals

    +

    Spirals is a good example showing how easy it is to create .NET Core Desktop Web Apps utilizing HTML5's familiar and simple development model to +leverage advanced Web Technologies like SVG in a fun, interactive and live development experience.

    +

    To start install the dotnet app global tool:

    +
    $ dotnet tool install -g app
    +
    +

    Then create a folder for our app called spirals and initialize and empty Web App with app init:

    +
    $ md spirals
    +$ cd spirals && app init
    +
    +

    This generates a minimal Web App but you could also start from any of the more complete +Web App Templates.

    +

    Now let's open the folder up for editing in our preferred text editor, VS Code:

    +
    $ code .
    +
    +

    +

    To start developing our App we just have to run app on the command-line which in VS Code we can open with Terminal > New Terminal or the Ctrl+Shift+` shortcut key. This will open our minimal App:

    +

    +

    The _layout.html shown above is currently where all +the action is which we'll quickly walk through:

    +
    <i hidden>{{ '/js/hot-fileloader.js' |> ifDebugIncludeScript }}</i>
    +

    This gives us a Live Development experience in debug mode where it injects a script that will detect file changes on Save and automatically reload the page at the current scroll offset.

    +
    {{ 'menu' |> partial({ 
    +        '/':           'Home',
    +        '/metadata':   '/metadata',
    +    }) 
    +}}
    +

    This evaluates the included _menu-partial.html with the links +to different routes we want in our Menu on top of the page.

    +
    <div id="body" class="container">
    +    <h2>{{title}}</h2>
    +    
    +    {{ page }}
    +</div>
    +

    The body of our App is used to render the title and contents of each page.

    +
    {{ scripts |> raw }}
    +

    If pages include any scripts they'll be rendered in the bottom of the page.

    +
    +

    The raw filter prevents the output from being HTML encoded.

    +
    +

    The other 2 files included is app.settings containing the +name of our App and debug true setting to run our App in Debug mode:

    +
    debug true
    +name My App
    +
    +

    The template only has one page index.html containing the title +of the page in a page argument which the _layout.html has access to without evaluating the page, anything after is the page contents:

    +
    <!-- 
    +title: Home Page
    +-->
    +
    +This is the home page.
    +

    We can now save changes to any of the pages and see our changes reflected instantly in the running App. But we also have access to an even better +live-development experience than preview as you save with preview as you type :)

    +

    +Live Previews

    +

    To take advantage of this we can exploit one of the features available in all ServiceStack Apps by clicking on /metadata Menu Item to view the +Metadata page containing links to our Apps Services, links to Metadata Services and any registered plugins:

    +

    +

    Then click on Debug Inspector to open a real-time REPL, which is normally used to get rich insights from a running App:

    +

    +

    But can also be used for executing ad hoc Template Scripts. Here we can drop in any mix of HTML and templates to view results in real-time.

    +

    In this case we want to generate SVG spirals by drawing a circle at each point along a +Archimedean spiral function which was initially used as a base and with the help of the live REPL was +quickly able to apply some constants to draw the tall & narrow spirals we want:

    +
    <svg height="640" width="240">
    +{{#each range(180) }}
    +  {{ var x = 120 + 100 * cos((1) * it * 0.02827) }}
    +  {{ var y = 320 + 300 * sin((1) * it * 0.02827) }}
    +  <circle cx="{{x}}" cy="{{y}}" r="10" fill="rgb(0,100,0)" stroke="black" stroke-width="1"/>
    +{{/each}} 
    +</svg>
    +

    We can further explore different spirals by modifying x and y cos/sin constants:

    +

    +

    Out of the spirals we've seen lets pick one of the interesting ones and add it to our index.html, let's also enhance them by modifying the fill and +radius properties with different weightings and compare them side-by-side:

    +
    <svg height="640" width="240">
    +{{#each range(180) }}
    +  {{ var x = 120 + 100 * cos((5) * it * 0.02827) }}
    +  {{ var y = 320 + 300 * sin((1) * it * 0.02827) }}
    +  <circle cx="{{x}}" cy="{{y}}" r="10" fill="rgb(0,100,0)" stroke="black" stroke-width="1"/>
    +{{/each}} 
    +</svg>
    +
    +<svg height="640" width="240">
    +{{#each range(180) }}
    +  {{ var x = 120 + 100 * cos((5) * it * 0.02827) }}
    +  {{ var y = 320 + 300 * sin((1) * it * 0.02827) }}
    +  <circle cx="{{x}}" cy="{{y}}" r="10" fill="rgb(0,{{it*1.4}},0)" stroke="black" stroke-width="1"/>
    +{{/each}} 
    +</svg>
    +
    +<svg height="640" width="240">
    +{{#each range(180) }}
    +  {{ var x = 120 + 100 * cos((5) * it * 0.02827) }}
    +  {{ var y = 320 + 300 * sin((1) * it * 0.02827) }}
    +  <circle cx="{{x}}" cy="{{y}}" r="{{it*0.1}}" fill="rgb(0,{{it*1.4}},0)" stroke="black" stroke-width="1"/>
    +{{/each}} 
    +</svg>
    +
    +

    You can use ALT+LEFT + ALT+RIGHT shortcut keys to navigate back and forward to the home page.

    +
    +

    Great, hitting save again will show us the effects of each change side-by-size:

    +

    +

    +Multiplying

    +

    Now that we have the effect that we want, let's go back to the debug inspector and see what a number of different spirals look side-by-side +by wrapping our last svg snippet in another each block:

    +
    <table>{{#each i in range(0, 4) }}
    +<svg height="640" width="240">
    +{{#each range(180) }}
    +  {{ var x = 120 + 100 * cos((1)   * it * 0.02827) }}
    +  {{ var y = 320 + 300 * sin((1+i) * it * 0.02827) }}
    +  <circle cx="{{x}}" cy="{{y}}" r="{{it*0.1}}" fill="rgb(0,{{it*1.4}},0)" stroke="black" stroke-width="1"/>
    +{{/each}} 
    +</svg>
    +{{/each}}
    +

    We can prefix our snippet with <table> as a temp workaround to force them to display side-by-side in Debug Inspector. In order to +for spirals to distort we'll only change 1 of the axis, as they're tall & narrow lets explore along the y-axis:

    +

    +

    We're all setup to begin our pattern explorer expedition where we can navigate across the range() index both incrementally and logarithmically across +to quickly discover new aesthetically pleasing patterns :)

    +

    +

    Let's go back to our App and embody our multi spiral viewer in a new multi.html page containing:

    +
    {{#each i in range(0, 4) }}
    +<svg height="640" width="240">
    +{{#each range(180) }}
    +  {{ var x = 120 + 100 * cos((5)   * it * 0.02827) }}
    +  {{ var y = 320 + 300 * sin((1+i) * it * 0.02827) }}
    +  <circle cx="{{x}}" cy="{{y}}" r="{{it*0.1}}" fill="rgb(0,{{it*1.4}},0)" stroke="black" stroke-width="1"/>
    +{{/each}} 
    +</svg>
    +{{/each}}
    +

    Then make it navigable by adding a link to our new page in the _layout.html menu:

    +
    {{ 'menu' |> partial({
    +     '/':           'Home',
    +     '/multi':      'Multi',
    +     '/metadata':   '/metadata',
    +   })
    +}}
    +

    Where upon save, our creation will reveal itself in the App's menu:

    +

    +

    +Animating

    +

    With the help of SVG's <animate> we can easily bring our spirals to life +by animating different properties on the generated SVG circles.

    +

    As we have to wait for the animation to complete before trying out different effects, we won't benefit from Debug Inspector's live REPL +so let's jump straight in and create a new animated.html and add a link to it in the menu:

    +
    {{ 'menu' |> partial({
    +     '/':           'Home',
    +     '/multi':      'Multi',
    +     '/animated':   'Animated',
    +     '/metadata':   '/metadata',
    +   })
    +}}
    +

    Then populate it with a copy of multi.html and sprinkle in some <animate> elements to cycle through different <circle> property values. +We're entering the "creative process" of our App where we can try out different values, hit Save and watch the effects of our tuning eventually +arriving at a combination we like:

    +
    {{#each i in range(0, 4) }}
    +<svg height="640" width="240">
    +{{#each range(180) }}
    +  {{ var x = 120 + 100 * cos((5)   * it * 0.02827) }}
    +  {{ var y = 320 + 300 * sin((1+i) * it * 0.02827) }}
    +  <circle cx="{{x}}" cy="{{y}}" r="{{it*0.1}}" fill="rgb(0,{{it*1.4}},0)" stroke="black" stroke-width="1">
    +    <animate attributeName="fill" values="green;yellow;red;green" dur="{{it%10}}s" repeatCount="indefinite" />
    +    <animate attributeName="opacity" values="1;.5;1" dur="5s" repeatCount="indefinite" />
    +    <animate attributeName="cx" values="{{x}};{{x*1.02}};{{x*0.98}};{{x}}" dur="3s" repeatCount="indefinite" />
    +    <animate attributeName="cy" values="{{y}};{{y*1.02}};{{y*0.98}};{{y}}" dur="3s" repeatCount="indefinite" />
    +  </circle>
    +{{/each}} 
    +</svg>
    +{{/each}}
    +

    Although hard to capture in a screenshot, we can sit back and watch our living, breathing Spirals :)

    +

    +
    +

    Checkout spirals.web-app.io for the animated version.

    +
    +

    +Navigating

    +

    Lets expand our App beyond these static Spirals by enabling some navigation, this is easily done by adding the snippet below on the top of the home page:

    +
    {{ from ?? 1 | toInt |> to => from }}
    +<div style="text-align:right;margin:-54px 0 30px 0">
    +  {{#if from > 1}} <a href="?from={{ max(from-1,0) }}" title="{{max(from-1,0)}}">previous</a> |{{/if}}
    +  {{from}} | <a href="?from={{ from+1 }}" title="{{max(from-1,0)}}">next</a>
    +</div>
    +

    Whilst the multi.html and animated.html pages can skip by 4:

    +
    {{ from ?? 1 | toInt |> to => from }}
    +<div style="text-align:right;margin:-54px 0 30px 0">
    +  {{#if from > 1}} <a href="?from={{ max(from-4,0) }}" title="{{max(from-1,0)}}">previous</a> |{{/if}}
    +  {{from}} | <a href="?from={{ from+4 }}" title="{{max(from-1,0)}}">next</a>
    +</div>
    +

    Then changing the index.html SVG fragment to use the from value on the y-axis:

    +
    <svg height="640" width="240">
    +{{#each range(180) }}
    +  {{ var x = 120 + 100 * cos((5)    * it * 0.02827) }}
    +  {{ var y = 320 + 300 * sin((from) * it * 0.02827) }}
    +  <circle cx="{{x}}" cy="{{y}}" r="10" fill="rgb(0,100,0)" stroke="black" stroke-width="1"/>
    +{{/each}} 
    +</svg>
    +

    Whilst the multi.html and animated.html pages can use it in its range(from, 4) function:

    +
    {{#each i in range(from, 4) }}
    +<svg height="640" width="240">
    +{{#each range(180) }}
    +  {{ var x = 120 + 100 * cos((5) * it * 0.02827) }}
    +  {{ var y = 320 + 300 * sin((1+i)  * it * 0.02827) }}
    +  <circle cx="{{x}}" cy="{{y}}" r="{{it*0.1}}" fill="rgb(0,{{it*1.4}},0)" stroke="black" stroke-width="1"/>
    +{{/each}} 
    +</svg>
    +{{/each}}
    +

    With navigation activated we can quickly scroll through and explore different spirals. To save ourselves the effort of finding them again lets +catalog our favorite picks and add them to a bookmarked list at the bottom of the page. Here are some interesting ones I've found for the home page:

    +
    <div>
    +  Jump to favorites: 
    +  {{#each [1,5,101,221,222,224,298,441,443,558,663,665,666,783,888] }}
    +    {{#if index > 0}} | {{/if}} {{#if from == it }} {{it}} {{else}} <a href="?from={{it}}">{{it}</a> {{/if}}
    +  {{/each}}
    +</div>
    +

    and my top picks for the multi.html and animated.html pages:

    +
    <div>
    +  Jump to favorites: 
    +  {{#each [1,217,225,229,441,449,661,669,673,885,1338,3326,3338,4330,8662,9330,11998] }}
    +    {{#if index > 0}} | {{/if}} {{#if from == it }} {{it}} {{else}} <a href="?from={{it}}">{{it}</a> {{/if}}
    +  {{/each}}
    +</div>
    +
    +

    If you've found more interesting ones, let me know!

    +
    +

    Now it's just a matter of signing off our digital piece by giving it a name in your app.settings:

    +
    name Spirals
    +
    +

    Which replaces the name in the menu and used in any shortcuts that are created, and with those finishing touches our App's journey +into the rich colorful world of SVG is complete:

    +

    +

    +Recap

    +

    If you've reached this far you've created a beautiful Desktop App without waiting for a single build or App restart, in a live and +interactive environment which let you rapidly prototype and get real-time feedback of any experimental changes, to produce a +rich graphical app with fast and fluid animations taking advantage of the impressive engineering invested in Chrome, implemented +declaratively by just generating SVG markup.

    +

    As Bret Victor shows us in his seminal Inventing on Principle presentation, being able to immediately visualize +changes gives us a deep connection and intuition with our creations that's especially important when creating visual software like UIs where +the rapid iterations lets us try out and perfect different ideas in the moment, as soon as we can think of them.

    +

    It's a good example of the type of App that would rarely be considered to be created with a native GUI toolkit and 2D drawing API, the amount +of effort and friction to achieve the same result would take the enjoyment out of the experience and the end result would have much less utility +where it wouldn't be able to be re-used on different OS's or other websites.

    +

    +Publishing your App

    +

    To share our digital masterpiece with the world we just need to publish it in a GitHub repo, which I've already done for my Spirals app at: +https://github.com/mythz/spirals.

    +

    Anyone will then be able to install your App by first downloading the app tool themselves (.NET Core 2.1 Required):

    +
    $ dotnet tool install -g app
    +
    +

    Then running install with the name of the repo and your GitHub User or Organization name in the --source argument:

    +
    $ app install spirals --source mythz
    +
    +

    Which installs instantly thanks to the 7kb .zip download that can then be opened by double-clicking on the generated Spirals Desktop Shortcut:

    +

    +

    +Publishing your App with binaries

    +

    The unique characteristics of Web Apps affords us different ways of publishing your App, e.g. to save users from needing to install the +app tool you can run publish in your App's directory:

    +
    $ app publish
    +
    +

    Which will copy your App to the publish/app folder and the app tool binaries in the publish/cef folder:

    +

    +

    A Desktop shortcut is also generated for convenience although this is dependent on where the App is installed on the end users computer. +If you know it will be in a fixed location you can update the Target and Start in properties to reference the cef\app.dll and +/app folder:

    +

    +

    This includes all app binaries needed to run Web Apps which compresses to 89 MB in a .zip or 61 MB in 7-zip .7z archive.

    +

    +Publishing a self-contained Windows 64 executable

    +

    But that's not all, we can even save end users who want to run your app the inconvenience of installing .NET Core :) by creating a self-contained +executable with:

    +
    $ app publish-exe
    +
    +

    +

    This downloads the WebWin self-contained .NET Core 2.1 binaries and copies then to publish/win folder with +the app copied to publish/app.

    +

    This publishing option includes a self-contained .NET Core with all app binaries which compresses to 121 MB in a .zip or 83 MB in 7-zip .7z archive.

    +

    +Publish to the world

    +

    To maximize reach and accessibility of your App leave a comment on the App Gallery +where after we link to it on NetCoreWebApps it will available to all users when they look for available apps in:

    +
    $ app list
    +
    +

    Which can then be installed with:

    +
    $ app install spirals
    +
    +

    +Learnable Living Apps

    +

    A beautiful feature of Web Apps is that they can be enhanced and customized in real-time whilst the App is running. No pre-installation of dev tools +or knowledge of .NET and C# is required, anyone can simply use any text editor to peek inside .html files to see how it works and see any of their +customizations visible in real-time.

    +

    Apps can be customized using both the JavaScript-like #Script language syntax for any "server" HTML generation +which can be further customized by any JavaScript on the client.

    +
    \ No newline at end of file diff --git a/src/wwwroot/gfm/apps/01.md b/src/wwwroot/gfm/apps/01.md new file mode 100644 index 0000000..23f2c35 --- /dev/null +++ b/src/wwwroot/gfm/apps/01.md @@ -0,0 +1,419 @@ +## The Making of Spirals + +Spirals is a good example showing how easy it is to create .NET Core Desktop Web Apps utilizing HTML5's familiar and simple development model to +leverage advanced Web Technologies like SVG in a fun, interactive and live development experience. + +To start install the dotnet `app` global tool: + + $ dotnet tool install -g app + +Then create a folder for our app called `spirals` and initialize and empty Web App with `app init`: + + $ md spirals + $ cd spirals && app init + +This generates a minimal Web App but you could also start from any of the more complete +[Web App Templates](http://sharpscript.net/docs/sharp-apps#getting-started). + +Now let's open the folder up for editing in our preferred text editor, VS Code: + + $ code . + +![](https://raw.githubusercontent.com/ServiceStack/docs/master/docs/images/app/app-init-code.png) + +To start developing our App we just have to run `app` on the command-line which in VS Code we can open with `Terminal > New Terminal` or the **Ctrl+Shift+`** shortcut key. This will open our minimal App: + +![](https://raw.githubusercontent.com/ServiceStack/docs/master/docs/images/app/app-init-run.png) + +The [_layout.html](https://gist.github.com/gistlyn/5c9ee9031e53cd8f85bd0e14881ddaa8#file-_layout-html) shown above is currently where all +the action is which we'll quickly walk through: + +```html + +``` + +This gives us a Live Development experience in `debug` mode where it injects a script that will detect file changes **on Save** and automatically reload the page at the current scroll offset. + +```html +{{ 'menu' |> partial({ + '/': 'Home', + '/metadata': '/metadata', + }) +}} +``` + +This evaluates the included [_menu-partial.html](https://gist.github.com/gistlyn/5c9ee9031e53cd8f85bd0e14881ddaa8#file-_menu-partial-html) with the links +to different routes we want in our Menu on top of the page. + +```html +
    +

    {{title}}

    + + {{ page }} +
    +``` + +The body of our App is used to render the title and contents of each page. + +```html +{{ scripts |> raw }} +``` + +If pages include any `scripts` they'll be rendered in the bottom of the page. + +> The `raw` filter prevents the output from being HTML encoded. + +The other 2 files included is [app.settings](https://gist.github.com/gistlyn/5c9ee9031e53cd8f85bd0e14881ddaa8#file-app-settings) containing the +name of our App and `debug true` setting to run our App in Debug mode: + + debug true + name My App + +The template only has one page [index.html](https://gist.github.com/gistlyn/5c9ee9031e53cd8f85bd0e14881ddaa8#file-index-html) containing the `title` +of the page in a page argument which the `_layout.html` has access to without evaluating the page, anything after is the page contents: + +```html + + +This is the home page. +``` + +We can now **save** changes to any of the pages and see our changes reflected instantly in the running App. But we also have access to an even better +live-development experience than **preview as you save** with **preview as you type** :) + +### Live Previews + +To take advantage of this we can exploit one of the features available in all ServiceStack Apps by clicking on `/metadata` Menu Item to view the +[Metadata page](/metadata-page) containing links to our Apps Services, links to Metadata Services and any registered plugins: + +![](https://raw.githubusercontent.com/ServiceStack/docs/master/docs/images/app/app-init-metadata.png) + +Then click on [Debug Inspector](/debugging#debug-inspector) to open a real-time REPL, which is normally used to get rich insights from a running App: + +![](https://raw.githubusercontent.com/ServiceStack/docs/master/docs/images/app/app-init-debug-inspector.png) + +But can also be used for executing ad hoc Template Scripts. Here we can drop in any mix of HTML and templates to view results in real-time. + +In this case we want to generate SVG spirals by drawing a `circle` at each point along a +[Archimedean spiral function](https://stackoverflow.com/a/6824451/85785) which was initially used as a base and with the help of the live REPL was +quickly able to apply some constants to draw the **tall & narrow** spirals we want: + +```hbs + +{{#each range(180) }} + {{ var x = 120 + 100 * cos((1) * it * 0.02827) }} + {{ var y = 320 + 300 * sin((1) * it * 0.02827) }} + +{{/each}} + +``` + +We can further explore different spirals by modifying `x` and `y` cos/sin constants: + +![](https://raw.githubusercontent.com/ServiceStack/docs/master/docs/images/app/spirals/single.gif) + +Out of the spirals we've seen lets pick one of the interesting ones and add it to our `index.html`, let's also enhance them by modifying the `fill` and +`radius` properties with different weightings and compare them side-by-side: + +```hbs + +{{#each range(180) }} + {{ var x = 120 + 100 * cos((5) * it * 0.02827) }} + {{ var y = 320 + 300 * sin((1) * it * 0.02827) }} + +{{/each}} + + + +{{#each range(180) }} + {{ var x = 120 + 100 * cos((5) * it * 0.02827) }} + {{ var y = 320 + 300 * sin((1) * it * 0.02827) }} + +{{/each}} + + + +{{#each range(180) }} + {{ var x = 120 + 100 * cos((5) * it * 0.02827) }} + {{ var y = 320 + 300 * sin((1) * it * 0.02827) }} + +{{/each}} + +``` + +> You can use `ALT+LEFT` + `ALT+RIGHT` shortcut keys to navigate back and forward to the home page. + +Great, hitting `save` again will show us the effects of each change side-by-size: + +[![](https://raw.githubusercontent.com/ServiceStack/docs/master/docs/images/app/spirals/single-fill-radius.png)](http://spirals.web-app.io) + +### Multiplying + +Now that we have the effect that we want, let's go back to the debug inspector and see what a number of different spirals look side-by-side +by wrapping our last svg snippet in another each block: + +```hbs +{{#each i in range(0, 4) }} + +{{#each range(180) }} + {{ var x = 120 + 100 * cos((1) * it * 0.02827) }} + {{ var y = 320 + 300 * sin((1+i) * it * 0.02827) }} + +{{/each}} + +{{/each}} +``` + +We can prefix our snippet with `
    ` as a temp workaround to force them to display side-by-side in Debug Inspector. In order to +for spirals to distort we'll only change 1 of the axis, as they're tall & narrow lets explore along the y-axis: + +![](https://raw.githubusercontent.com/ServiceStack/docs/master/docs/images/app/spirals/multi-01.png) + +We're all setup to begin our pattern explorer expedition where we can navigate across the `range()` index both incrementally and logarithmically across +to quickly discover new aesthetically pleasing patterns :) + +![](https://raw.githubusercontent.com/ServiceStack/docs/master/docs/images/app/spirals/multi.gif) + +Let's go back to our App and embody our multi spiral viewer in a new `multi.html` page containing: + +```hbs +{{#each i in range(0, 4) }} + +{{#each range(180) }} + {{ var x = 120 + 100 * cos((5) * it * 0.02827) }} + {{ var y = 320 + 300 * sin((1+i) * it * 0.02827) }} + +{{/each}} + +{{/each}} +``` + +Then make it navigable by adding a link to our new page in the `_layout.html` menu: + +```hbs +{{ 'menu' |> partial({ + '/': 'Home', + '/multi': 'Multi', + '/metadata': '/metadata', + }) +}} +``` + +Where upon save, our creation will reveal itself in the App's menu: + +[![](https://raw.githubusercontent.com/ServiceStack/docs/master/docs/images/app/spirals/multi.png)](http://spirals.web-app.io/multi) + +### Animating + +With the help of SVG's [``](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/animate) we can easily bring our spirals to life +by animating different properties on the generated SVG circles. + +As we have to wait for the animation to complete before trying out different effects, we won't benefit from Debug Inspector's live REPL +so let's jump straight in and create a new `animated.html` and add a link to it in the menu: + +```hbs +{{ 'menu' |> partial({ + '/': 'Home', + '/multi': 'Multi', + '/animated': 'Animated', + '/metadata': '/metadata', + }) +}} +``` + +Then populate it with a copy of `multi.html` and sprinkle in some `` elements to cycle through different `` property values. +We're entering the "creative process" of our App where we can try out different values, hit **Save** and watch the effects of our tuning eventually +arriving at a combination we like: + +```hbs +{{#each i in range(0, 4) }} + +{{#each range(180) }} + {{ var x = 120 + 100 * cos((5) * it * 0.02827) }} + {{ var y = 320 + 300 * sin((1+i) * it * 0.02827) }} + + + + + + +{{/each}} + +{{/each}} +``` + +Although hard to capture in a screenshot, we can sit back and watch our living, breathing Spirals :) + +[![](https://raw.githubusercontent.com/ServiceStack/docs/master/docs/images/app/spirals/animated.png)](http://spirals.web-app.io/animated?from=0) + +> Checkout [spirals.web-app.io](http://spirals.web-app.io/animated?from=0) for the animated version. + +### Navigating + +Lets expand our App beyond these static Spirals by enabling some navigation, this is easily done by adding the snippet below on the top of the home page: + +```hbs +{{ from ?? 1 | toInt |> to => from }} +
    + {{#if from > 1}} previous |{{/if}} + {{from}} | next +
    +``` + +Whilst the `multi.html` and `animated.html` pages can skip by 4: + +```hbs +{{ from ?? 1 | toInt |> to => from }} +
    + {{#if from > 1}} previous |{{/if}} + {{from}} | next +
    +``` + +Then changing the `index.html` SVG fragment to use the `from` value on the y-axis: + +```hbs + +{{#each range(180) }} + {{ var x = 120 + 100 * cos((5) * it * 0.02827) }} + {{ var y = 320 + 300 * sin((from) * it * 0.02827) }} + +{{/each}} + +``` + +Whilst the `multi.html` and `animated.html` pages can use it in its `range(from, 4)` function: + +```hbs +{{#each i in range(from, 4) }} + +{{#each range(180) }} + {{ var x = 120 + 100 * cos((5) * it * 0.02827) }} + {{ var y = 320 + 300 * sin((1+i) * it * 0.02827) }} + +{{/each}} + +{{/each}} +``` + +With navigation activated we can quickly scroll through and explore different spirals. To save ourselves the effort of finding them again lets +catalog our favorite picks and add them to a bookmarked list at the bottom of the page. Here are some interesting ones I've found for the home page: + +```hbs +
    + Jump to favorites: + {{#each [1,5,101,221,222,224,298,441,443,558,663,665,666,783,888] }} + {{#if index > 0}} | {{/if}} {{#if from == it }} {{it}} {{else}} {{it} {{/if}} + {{/each}} +
    +``` + +and my top picks for the `multi.html` and `animated.html` pages: + +```hbs +
    + Jump to favorites: + {{#each [1,217,225,229,441,449,661,669,673,885,1338,3326,3338,4330,8662,9330,11998] }} + {{#if index > 0}} | {{/if}} {{#if from == it }} {{it}} {{else}} {{it} {{/if}} + {{/each}} +
    +``` + +> If you've found more interesting ones, [let me know](https://github.com/mythz/spirals/issues)! + +Now it's just a matter of signing off our digital piece by giving it a name in your `app.settings`: + + name Spirals + +Which replaces the name in the `menu` and used in any shortcuts that are created, and with those finishing touches our App's journey +into the rich colorful world of SVG is complete: + +[![](https://raw.githubusercontent.com/ServiceStack/docs/master/docs/images/app/spirals/spiral-nav.png)](http://spirals.web-app.io/animated) + +### Recap + +If you've reached this far you've created a beautiful Desktop App without waiting for a single build or App restart, in a live and +interactive environment which let you rapidly prototype and get real-time feedback of any experimental changes, to produce a +rich graphical app with fast and fluid animations taking advantage of the impressive engineering invested in Chrome, implemented +declaratively by just generating SVG markup. + +As Bret Victor shows us in his seminal [Inventing on Principle](https://vimeo.com/36579366) presentation, being able to immediately visualize +changes gives us a deep connection and intuition with our creations that's especially important when creating visual software like UIs where +the rapid iterations lets us try out and perfect different ideas in the moment, as soon as we can think of them. + +It's a good example of the type of App that would rarely be considered to be created with a native GUI toolkit and 2D drawing API, the amount +of effort and friction to achieve the same result would take the enjoyment out of the experience and the end result would have much less utility +where it wouldn't be able to be re-used on different OS's or other websites. + +## Publishing your App + +To share our digital masterpiece with the world we just need to publish it in a GitHub repo, which I've already done for my Spirals app at: +[https://github.com/mythz/spirals](github.com/mythz/spirals). + +Anyone will then be able to install your App by first downloading the `app` tool themselves ([.NET Core 2.1 Required](https://www.microsoft.com/net/download/dotnet-core/2.1)): + + $ dotnet tool install -g app + +Then running `install` with the name of the **repo** and your GitHub **User** or **Organization** name in the `--source` argument: + + $ app install spirals --source mythz + +Which installs instantly thanks to the `7kb` .zip download that can then be opened by double-clicking on the generated **Spirals** Desktop Shortcut: + +![](https://raw.githubusercontent.com/ServiceStack/docs/master/docs/images/app/app-install-spirals.png) + +### Publishing your App with binaries + +The unique characteristics of Web Apps affords us different ways of publishing your App, e.g. to save users from needing to install the +`app` tool you can run `publish` in your App's directory: + + $ app publish + +Which will copy your App to the `publish/app` folder and the `app` tool binaries in the `publish/cef` folder: + +![](https://raw.githubusercontent.com/ServiceStack/docs/master/docs/images/app/app-publish.png) + +A Desktop shortcut is also generated for convenience although this is dependent on where the App is installed on the end users computer. +If you know it will be in a fixed location you can update the **Target** and **Start in** properties to reference the `cef\app.dll` and +`/app` folder: + +![](https://raw.githubusercontent.com/ServiceStack/docs/master/docs/images/app/app-shortcut-properties.png) + +This includes all `app` binaries needed to run Web Apps which compresses to **89 MB** in a `.zip` or **61 MB** in 7-zip `.7z` archive. + +### Publishing a self-contained Windows 64 executable + +But that's not all, we can even save end users who want to run your app the inconvenience of installing .NET Core :) by creating a self-contained +executable with: + + $ app publish-exe + +![](https://raw.githubusercontent.com/ServiceStack/docs/master/docs/images/app/app-publish-exe.png) + +This downloads the [WebWin](https://github.com/ServiceStack/WebWin) self-contained .NET Core 2.1 binaries and copies then to `publish/win` folder with +the app copied to `publish/app`. + +This publishing option includes a self-contained .NET Core with all `app` binaries which compresses to **121 MB** in a `.zip` or **83 MB** in 7-zip `.7z` archive. + +### Publish to the world + +To maximize reach and accessibility of your App leave a comment on the [App Gallery](https://gist.github.com/gistlyn/f555677c98fb235dccadcf6d87b9d098) +where after we link to it on [NetCoreWebApps](https://github.com/NetCoreWebApps) it will available to all users when they look for available apps in: + + $ app list + +Which can then be installed with: + + $ app install spirals + +### Learnable Living Apps + +A beautiful feature of Web Apps is that they can be enhanced and customized in real-time whilst the App is running. No pre-installation of dev tools +or knowledge of .NET and C# is required, anyone can simply use any text editor to peek inside `.html` files to see how it works and see any of their +customizations visible in real-time. + +Apps can be customized using both the JavaScript-like [#Script language syntax](/docs/syntax) for any "server" HTML generation +which can be further customized by any JavaScript on the client. + diff --git a/src/wwwroot/gfm/apps/02.html b/src/wwwroot/gfm/apps/02.html new file mode 100644 index 0000000..aa496af --- /dev/null +++ b/src/wwwroot/gfm/apps/02.html @@ -0,0 +1,422 @@ +

    The SharpData .NET Core project is a generic tool for providing an instant UI around multiple RDBMS's:

    +
    +

    YouTube: youtu.be/EJ-lDWshjcY

    +
    +

    +

    An impressively capable .NET Core App that fits into a tiny 20kb .zip footprint thanks to Gist Desktop App's Architecture. It's small dynamic #Script & Vue TypeScript code-base also makes it highly customizable to tailor & further extend with +App-specific requirements - suitable for offering advanced system users a quick, capable customized read-only UI of your DBs.

    +

    SharpData started as a demonstration showing how productive #Script can be in the number of areas where +dynamic languages offer far superior productivity then the typical .NET approach of using C# to type an entire code-base & models.

    +

    For example a single #Script page provides a lot of the functionality in AutoQuery where it provides an instant HTTP API +(in all registered ServiceStack formats) around all registered RDBMS tables, in all OrmLite supported RBDMS's, that includes support for custom fields, +multiple querying options, paging, multi OrderBy's in a parameterized SQL query executed with OrmLite's SQL async DB APIs:

    +

    +AutoQuery Script

    +

    +/db/_db/_table/index.html +

    +
    {{ {namedConnection:db} |> if (db && db != 'main') |> useDb }}
    +
    +```code|quiet
    +var ignore = ['db','fields','format','skip','take','orderBy']
    +var fields = qs.fields ? qs.fields.split(',').map(x => sqlQuote(x)).join(',') : '*'
    +var sql = `SELECT ${fields} FROM ${sqlQuote(table)}`
    +var filters = []
    +var queryMap = qs.toObjectDictionary().withoutKeys(ignore)
    +#each queryMap.Keys.toList()
    +    var search = queryMap[it.sqlVerifyFragment()].sqlVerifyFragment();
    +    #if search == '=null' || search == '!=null'
    +        `${sqlQuote(it)} ${search=='=null' ? 'IS' : 'IS NOT'} NULL` |> addTo => filters
    +        queryMap[it] = null
    +    else if search.startsWith('=')
    +        `${sqlQuote(it)} = @${it}` |> addTo => filters
    +        queryMap[it] = search.substring(1).coerce()
    +    else if search.startsWith('<=') || search.startsWith('>=') || search.startsWith('!=')
    +        `${sqlQuote(it)} ${search.substring(0,2)} @${it}` |> addTo => filters
    +        queryMap[it] = search.substring(2).coerce()
    +    else if search.startsWith('<') || search.startsWith('>')
    +        `${sqlQuote(it)} ${search.substring(0,1)} @${it}` |> addTo => filters
    +        queryMap[it] = search.substring(1).coerce()
    +    else if search.endsWith(',')
    +        `${sqlQuote(it)} IN (${search.trimEnd(',').split(',').map(i=>i.toLong()).join(',')})` |>addTo=>filters
    +        queryMap[it] = null
    +    else if search.startsWith('%') || search.endsWith('%')
    +        `${sqlQuote(it).sqlCast('varchar')} LIKE @${it}` |> addTo => filters
    +    else
    +        `${sqlQuote(it).sqlCast('varchar')} = @${it}` |> addTo => filters
    +    /if
    +/each
    +#if !filters.isEmpty()
    +    sql = `${sql} WHERE ${filters.join(' AND ')}`
    +/if
    +#if qs.orderBy
    +    sql = `${sql} ORDER BY ${sqlOrderByFields(qs.orderBy)}`
    +/if
    +#if qs.skip || qs.take
    +    sql = `${sql} ${sqlLimit(qs.skip,qs.take)}`
    +/if
    +sql |> dbSelect(queryMap) |> return
    +```
    +{{ ifError |> show(sql) }}
    +{{htmlError}}
    +
    +

    The _ prefixes in the path utilizes Page Based Routing allowing for +CoC based +Clean URL routes without needing to define & maintain separate routes where the +same script supports querying all registered multitenancy databases.

    +

    +Instant Customizable RDBMS UI

    +

    The SharpData project essentially provides a UI around this script, surfacing its features & give +it instant utility which ended up being so useful that it's become the quickest way to perform fast adhoc DB queries as it's easy to configure +which RDBMS's & tables to show in a simple text file, easy to customize its UI, enables 1-click export into Excel and its shortcut syntax +support in column filters is a fast way to perform quick adhoc queries.

    +

    +Quick Tour

    +

    We'll quickly go through some of its features to give you an idea of its capabilities, from the above screenshot we can some of its +filtering capabilities. All results displayed in the UI are queried using the above +sharpdata #Script HTTP API +which supports the following features:

    +

    +Filters

    +

    All query string parameter except for db,fields,format,skip,take,orderBy are treated as filters, where you can:

    +
      +
    • Use =null or !=null to search NULL columns
    • +
    • Use <=, <, >, >=, <>, != prefix to search with that operator
    • +
    • Use , trailing comma to perform an IN (values) search (integer columns only)
    • +
    • Use % suffix or prefix to perform a LIKE search
    • +
    • Use = prefix to perform a coerced "JS" search, for exact number, boolean, null and WCF date comparisons
    • +
    • Otherwise by default performs a "string equality" search where columns are casted and compared as strings
    • +
    +

    Here's the filtered list used in the above screenshot:

    +

    /db/main/Order?Id=>10200&CustomerId=V%&Freight=<=30&OrderDate=>1997-01-01

    +

    +Custom Field Selection

    +

    The column selection icon on the top left of the results lets you query custom select columns which is specified using ?fields:

    + +

    +Multiple OrderBy's

    +

    You can use AutoQuery Syntax to specify multiple Order By's:

    + +

    +Paging

    +

    Use ?skip and ?take to page through a result set

    +

    +Format

    +

    Use ?format to specify which Content-Type to return the results in, e.g:

    + +

    +Multitenancy

    +

    You can specify which registered DB to search using the path info, use main to query the default database:

    +
    /db/<named-db>/<table>
    +
    +

    +Launching SharpData

    +

    To run SharpData in a .NET Core Desktop App you'll need latest app dotnet tool:

    +
    $ dotnet tool update -g app
    +
    +
    +

    If on macOS/Linux you can use the x dotnet tool instead to view SharpData in your default browser

    +
    +

    +Configure RDBMS from command-line

    +

    You can override which database to connect to by specifying it on the command line, e.g. here's an example of connecting to https://techstacks.io RDBMS:

    +
    $ app open sharpdata -db postgres -db.connection $TECHSTACKS_DB
    +
    +

    Which will open SharpData listing all of TechStack's RDBMS tables. If you have a lot of tables the Sidebar filter provides a quick way to +find the table you want, e.g:

    +

    +

    +app URL Schemes

    +

    What can be done with the open command on the command-line can also be done from a custom URL Scheme, a feature that opens up a myriad of new +possibilities as app can open Gist Desktop Apps from Gists or in public & private GitHub repositories, +where it's able to download and launch Apps on the fly with custom arguments - allowing a single URL to run a never installed Desktop App stored in a +Gist & pass it custom params to enable deep linking.

    +

    With this organizations could maintain a dashboard of links to its different Desktop Apps that anyone can access, especially useful as the +only software that's needed to run any Sharp Apps is the app dotnet tool which thanks to all +ServiceStack .dll's & dependencies being bundled with the tool, (including Vue/React/Bootstrap fontawesome and Material SVG Icon assets), +the only files that need to be published are the App's specific resources, which is how Apps like SharpData can be compressed in a +20kb .zip - a tiny payload that's viable to download the latest app each on each run, removing the pain & friction to distribute updates as +everyone's already running the latest version every time it's run.

    +

    Should you need to (e.g. large Sharp App or github.com is down) you can run your previously locally cached App using run:

    +
    $ app run sharpdata
    +
    +

    With Custom URL Schemes everyone with app installed can view any database they have network access to from specifying the db type and connection string in the URL:

    +
    app://sharpdata?db=postgres&db.connection={CONNECTION_STRING}
    +
    +
    +

    CONNECTION_STRING needs to be URL Encoded, e.g. with JS's encodeURIComponent()

    +
    +

    or by specifying an Environment variable containing the connection string:

    +
    app://sharpdata?db=postgres&db.connection=$TECHSTACKS_DB
    +
    +

    In addition to Sharp Apps being downloaded and run on the fly, they're also able to take advantage of the dotnet tools mix support to +also download another Gist's content into the Sharp App's working directory.

    +

    With this you can publish a custom dataset in an SQLite database save it as a gist and generate a single URL that everyone can use to +download the database and open it in SharpData, e.g:

    +
      +
    • app://sharpdata?mix=northwind.sqlite&db=sqlite&db.connection=northwind.sqlite
    • +
    +

    It's possible to use the user-friendly northwind.sqlite alias here as it's published in the global mix.md directory where it links to the northwind.sqlite gist.

    +

    For your custom databases you use the Gist Id instead or if you plan to use this feature a lot you can override which mix.md document that +app should source its links from by specifying another Gist Id in the MIX_SOURCE Environment variable (or see below - to create a local alias).

    +

    But if you're already mixing in an external gist you may as well include a custom app.settings in the Gist so it's pre-configured with custom +RDBMS registrations and table lists, e.g:

    +
      +
    • app://sharpdata?mix=northwind.sharpdata
    • +
    +

    Which applies the northwind.sharpdata gist, which can also be referenced by Gist Id:

    +
      +
    • app://sharpdata?mix=0ce0d5b828303f1cb4637450b563adbd
    • +
    +

    Alternatively you may instead prefer to publish it to a private GitHub repo instead of a Gist which anyone can open up with:

    +
    app://user/sharpdata-private?token={TOKEN}
    +
    +

    The app dotnet tools will use the latest published GitHub release if there are any, otherwise will use the master.zip archive, +this feature can be used to maintain a working master repo and maintain control ver when to publish new versions of your custom SharpData App.

    +

    +app local aliases

    +

    Where ever you can use a Gist Id, you can assign a local user-friendly alias to use instead. So if you had a custom sqlite database and +sharpdata app.settings you could assign it to a local db alias with:

    +
    $ app alias db 0ce0d5b828303f1cb4637450b563adbd
    +
    +

    Which you'll be able to use in place of the Gist Id, e.g. via command-line:

    +
    $ app open sharpdata mix db
    +
    +

    or via URL Scheme:

    +
    app://sharpdata?mix=db
    +
    +

    Likewise the gist alias can also be used for referencing Gist Desktop Apps, e.g. we can assign the +redis gist app to use our preferred alias:

    +
    $ app alias local-redis 6de7993333b457445793f51f6f520ea8
    +
    +

    That we can open via command-line:

    +
    $ app open local-redis
    +
    +

    Or URL Scheme:

    +
    app://local-redis
    +
    +

    Or if we want to run our own modified copy of the Redis Desktop App, we can mix the Gist files to our +local directory:

    +
    $ app mix local-redis
    +
    +

    Make the changes we want, then run our local copy by running app (or x) without arguments:

    +
    $ app
    +
    +

    Other alias command include:

    +

    +View all aliases

    +
    $ app alias
    +
    +

    +View single alias

    +
    $ app alias db
    +
    +

    +Remove an alias

    +
    $ app unalias db
    +
    +

    +Custom App Settings

    +

    Each time a Gist Desktop App is opened it downloads and overrides the existing Gist with the latest version which it loads in a Gist VFS where any of its files can be overridden with a local copy.

    +

    As the App's working directory is preserved between restarts you can provide a custom app.settings at:

    +
    %USERPROFILE%\.sharp-apps\sharpdata\app.settings
    +
    +

    Where you can customize which RDBMS's and tables you want to be able to access, e.g:

    +
    debug false
    +name Northwind SharpData
    +appName sharpdata
    +
    +db sqlite
    +db.connection northwind.sqlite
    +db.connections[techstacks] { db:postgres, connection:$TECHSTACKS_DB }
    +
    +args.tables Customer,Order,OrderDetail,Category,Product,Employee,EmployeeTerritory,Shipper,Supplier,Region,Territory
    +args.tables_techstacks technology,technology_stack,technology_choice,organization,organization_member,post,post_comment,post_vote,custom_user_auth,user_auth_details,user_activity,page_stats
    +
    +

    Which will display both RDBMS Databases, showing only the user-specified tables in app.settings above:

    +

    +

    +Open in Excel

    +

    SharpData detects if Excel is installed and lets you open the un-paged filtered resultset directly by clicking the Excel button

    +

    +

    This works seamlessly as it's able to "by-pass" the browser download where the query is performed by the back-end .NET Core Server who streams the response directly to the Users Downloads folder and launches it in Excel as soon as it's finished.

    +

    +Custom Row Components

    +

    Whilst a tabular grid view might be a natural UI for browsing a database for devs, we can do better since we have the full UI source code of the Vue components. +A filtered tabular view makes it fast to find the record you're interested in, but it's not ideal for quickly finding related information about an Entity.

    +

    To provide a more customized UX for different App UIs, SharpData includes support for "Row Components" +(defined in /components/Custom) to be able to quickly drill down & view +richer info on any record.

    +

    For example when viewing an Order, it's natural to want to view the Order Details with it, enabled with the custom Vue component registration below:

    +
    @Component({ template:
    +`<div v-if="id">
    +    <jsonviewer :value="details" />
    +</div>
    +<div v-else class="alert alert-danger">Order Id needs to be selected</div>`
    +})
    +class Order extends Vue {
    +    @Prop() public db: string;
    +    @Prop() public table: string;
    +    @Prop() row: any;
    +    @Prop() columns: ColumnSchema[];
    +
    +    details:any[] = [];
    +
    +    get id() { return this.row.Id; }
    +
    +    async mounted() {
    +        this.details = await sharpData(this.db,'OrderDetail',{ OrderId: this.id });
    +    }
    +}
    +registerRowComponent('main','Order', Order, 'order');
    +

    All Row components are injected with the db, table properties, the entire row object that was selected as well as the Column Schema definition for that table. Inside the component you're free to display anything, in this case we're using the sharpData helper for calling the server #Script HTTP API to get it to fetch all OrderDetail entries for this order.

    +
    +

    If the resultset is filtered without the Order Id PK it can't fetch its referenced data, so displays an error instead

    +
    +

    The jsonviewer component used is similar to ServiceStack's +HTML5 auto pages to quickly view contents of any object.

    +

    The registerRowComponent(db,table,VueComponent,componentName) API is used to register this component with SharpData to make it available to render any order.

    +

    With the Order component registered we can now drill down into any Order to view its Order Details:

    +

    +

    You're free to render any kind of UI in the row component, e.g. here's the Customer.ts row component used to render a richer view for Customers:

    +
    @Component({ template:
    +`<div v-if="id" class="pl-2">
    +    <h3 class="text-success">{{customer.ContactName}}</h3>
    +    <table class="table table-bordered" style="width:auto">
    +        <tr>
    +            <th>Contact!</th>
    +            <td>{{ customer.ContactName }} ({{ customer.ContactTitle }})</td>
    +        </tr>
    +        <tr>
    +            <th>Address</th>
    +            <td>
    +                <div>{{ customer.Address }}</div>
    +                <div>{{ customer.City }}, {{ customer.PostalCode }}, {{ customer.Country }}</div>
    +            </td>
    +        </tr>
    +        <tr>
    +            <th>Phone</th>
    +            <td>{{ customer.Phone }}</td>
    +        </tr>
    +        <tr v-if="customer.Fax">
    +            <th>Fax</th>
    +            <td>{{ customer.Fax }}</td>
    +        </tr>
    +    </table>
    +    <jsonviewer :value="orders" />
    +</div>
    +<div v-else class="alert alert-danger">Customer Id needs to be selected</div>`})
    +class Customer extends Vue {
    +    @Prop() public db: string;
    +    @Prop() public table: string;
    +    @Prop() row: any;
    +    @Prop() columns: ColumnSchema[];
    +
    +    customer:any = null;
    +    orders:any[] = [];
    +
    +    get id() { return this.row.Id; }
    +
    +    async mounted() {
    +        this.customer = (await sharpData(this.db,this.table,{ Id: this.id }))[0];
    +        const fields = 'Id,EmployeeId,OrderDate,Freight,ShipVia,ShipCity,ShipCountry';
    +        this.orders = await sharpData(this.db,'Order',{ CustomerId: this.id, fields })
    +    }
    +}
    +registerRowComponent('main','customer', Customer, 'customer');
    +

    Which looks like:

    +

    +

    +SharpData .NET Core Project

    +

    Whilst NetCoreApps/SharpData can live a charmed life as a Desktop App, it's also just a regular ServiceStack .NET Core App with a Startup.cs and AppHost that can be developed, published and deployed as you're used to, here's an instance of it deployed as a .NET Core App on Linux:

    +

    +sharpdata.netcore.io +

    +
    +

    For best experience we recommend running locally to experience it without latency of our servers in Germany

    +
    +

    It's a unique ServiceStack App in that it doesn't contain any ServiceStack Services as it's only using pre-existing functionality already built into ServiceStack, +#Script for its HTTP APIs and a Vue SPA for its UI, so requires no .dll's need to be deployed with it.

    +

    It uses the same Vue SPA solution as vue-lite to avoid npm's size & complexity where you only need to run TypeScript's tsc -w to enable its live-reload dev UX which provides its instant feedback during development.

    +

    Some other of its unique traits is that instead of manually including all the Vue framework .js libraries, it instead references the new ServiceStack.Desktop.dll for its Vue framework libraries and its Material design SVG icons which are referenced as normal file references:

    +
    {{ [
    +    `/lib/js/vue/vue.min.js`,
    +    `/lib/js/vue-router/vue-router.min.js`,
    +    `/lib/js/vue-class-component/vue-class-component.min.js`,
    +    `/lib/js/vue-property-decorator/vue-property-decorator.min.js`,
    +    `/lib/js/@servicestack/desktop/servicestack-desktop.min.js`,
    +    `/lib/js/@servicestack/client/servicestack-client.min.js`,
    +    `/lib/js/@servicestack/vue/servicestack-vue.min.js`,
    +] |> map => `<script src="${it}"></script>` |> joinln |> raw }}
    +

    But instead of needing to exist on disk & deployed with your project it's referencing the embedded resources in ServiceStack.Desktop.dll and only the bundled assets need to be deployed with your project which is using the built-in NUglify support in the dotnet tools to produce its highly optimized/minified bundle without needing to rely on any npm tooling when publishing the .NET Core App:

    +
    <Target Name="Bundle" BeforeTargets="AfterPublish">
    +    <Exec Command="x run _bundle.ss -to /bin/Release/netcoreapp3.1/publish/wwwroot" />
    +</Target>
    +

    The included /typings are just the TypeScript definitions for each library which +TypeScript uses for its static analysis & its great dev UX in IDEs & VSCode, but are only needed during development and not deployed with the project.

    +

    +Publish to Gist Desktop App

    +

    The primary way SharpData is distributed is as a Gist Desktop App, where it's able to provide instant utility by running on a users local machine inside a native Chromium Desktop App making it suitable for a much broader use-case as a fast, lightweight, always up-to-date Desktop App with deeper Windows integration all packaged in a tiny 20kb .zip footprint. There's no need to provision servers, setup CI, manage cloud hosting resources, you can simply run a script to update a Gist where its latest features are immediately available to your end users the next time it's run.

    +

    To run, test & publish it as a Desktop App you can use the pre-made scripts in package.json. +Rider provides a nice UX here as it lets you run each individual script directly from their json editor:

    +

    +

    Essentially to package it into a Sharp App you just need to run the pack script which will bundle & copy all required assets into the /dist folder which you can then test locally in a .NET Core Desktop App by running app in that folder:

    +
    $ cd dist
    +$ app
    +
    +

    The init-test script just copies an example northwind.sqlite database and sample app.settings so you have something to test it with if you need it.

    +

    The publish-app script is if you want to publish it to a Gist, you will need it to provide the GitHub AccessToken with write access to the Gist User Account you want to publish it to. Adding an appName and description to app.settings will publish it to the Global App Registry, make it publicly discoverable and allow anyone to open your App using your user-friendly appName alias, otherwise they can run it using the Gist Id or Gist URL.

    +

    Alternatively the contents of the dist/ folder can be published to a GitHub repo (public or private) and run with:

    +
    $ app open <user>/<repo>
    +
    +

    Or link to it with its custom URL Scheme:

    +
    app://<user>/repo
    +
    +

    If it's in a private repo they'll need to either provide an AccessToken in the GITHUB_TOKEN Environment variable or using the -token argument:

    +
    $ app open <user>/<repo> -token <token>
    +
    +

    URL Scheme:

    +
    app://<user>/repo?token=<token>
    +
    +

    +RDBMS Configuration

    +

    When running as a .NET Core App you'd need to register which RDBMS's you want to use with OrmLite's configuration, e.g. the screenshot above registers an SQLite northwind.sqlite database and the https://techstacks.io PostgreSQL Database:

    +
    container.Register<IDbConnectionFactory>(c => new OrmLiteConnectionFactory(
    +    MapProjectPath("~/northwind.sqlite"), SqliteDialect.Provider));
    +
    +var dbFactory = container.Resolve<IDbConnectionFactory>();
    +dbFactory.RegisterConnection("techstacks",
    +     Environment.GetEnvironmentVariable("TECHSTACKS_DB"),
    +     PostgreSqlDialect.Provider);
    +

    By default it shows all Tables in each RDBMS, but you can limit it to only show a user-defined list of tables with #Script Arguments:

    +
    Plugins.Add(new SharpPagesFeature {
    +    //...
    +    Args = {
    +        //Only display user-defined list of tables:
    +        ["tables"] = "Customer,Order,OrderDetail,Category,Product,Employee,EmployeeTerritory,Shipper,Supplier,Region,Territory",
    +        ["tables_techstacks"] = "technology,technology_stack,technology_choice,organization,organization_member,post,post_comment,post_vote,custom_user_auth,user_auth_details,user_activity,page_stats",
    +    }
    +});
    +

    When running as a Sharp App it's instead configured in its +app.settings, here's equivalent settings for the above configuration:

    +
    # Configure below. Supported dialects: sqlite, mysql, postgres, sqlserver
    +db sqlite
    +db.connection northwind.sqlite
    +# db.connections[techstacks] { db:postgres, connection:$TECHSTACKS_DB }
    +
    +args.tables Customer,Order,OrderDetail,Category,Product,Employee,EmployeeTerritory,Shipper,Supplier,Region,Territory
    +args.tables_techstacks technology,technology_stack,technology_choice,organization,organization_member,post,post_comment,post_vote,custom_user_auth,user_auth_details,user_activity,page_stats
    +
    +

    +Feedback

    +

    We hope SharpData serves useful in some capacity, whether it's being able to quickly develop and Ship a UI to stakeholders or as a template to develop .NET Core Apps that you can distribute as Sharp Apps, as an example to explore the delivery and platform potential of URL schemes and install-less Desktop Apps or just as an inspiration for areas where #Script shines & the different kind of Apps you can create with it.

    +

    Whilst app is Windows 64 only, you can use the x cross-platform tool and its xapp:// URL scheme to run Sharp Apps on macOS/Linux, it just wont have access to any of its Window Integration features.

    +
    \ No newline at end of file diff --git a/src/wwwroot/gfm/apps/02.md b/src/wwwroot/gfm/apps/02.md new file mode 100644 index 0000000..a7a1cf0 --- /dev/null +++ b/src/wwwroot/gfm/apps/02.md @@ -0,0 +1,512 @@ +The [SharpData](https://github.com/NetCoreApps/SharpData) .NET Core project is a generic tool for providing an instant UI around multiple RDBMS's: + +> YouTube: [youtu.be/EJ-lDWshjcY](https://youtu.be/EJ-lDWshjcY) + +[![](https://raw.githubusercontent.com/ServiceStack/docs/master/docs/images/release-notes/v5.9/sharpdata-custom-appsettings.png)](https://youtu.be/EJ-lDWshjcY) + +An impressively capable .NET Core App that fits into a tiny **20kb .zip** footprint thanks to [Gist Desktop App's Architecture](/gist-desktop-apps). It's small dynamic `#Script` & Vue TypeScript code-base also makes it highly customizable to tailor & further extend with +App-specific requirements - suitable for offering advanced system users a quick, capable customized read-only UI of your DBs. + +**SharpData** started as a demonstration showing how productive [#Script](https://sharpscript.net) can be in the number of areas where +dynamic languages offer far superior productivity then the typical .NET approach of using C# to type an entire code-base & models. + +For example a single `#Script` page provides a lot of the functionality in [AutoQuery](https://docs.servicestack.net/autoquery-rdbms) where it provides an instant HTTP API +(in all registered ServiceStack formats) around all registered RDBMS tables, in all OrmLite supported RBDMS's, that includes support for custom fields, +multiple querying options, paging, multi OrderBy's in a parameterized SQL query executed with OrmLite's SQL async DB APIs: + +## AutoQuery Script + +### [/db/_db/_table/index.html](https://github.com/NetCoreApps/SharpData/blob/master/wwwroot/db/_db/_table/index.html) + + {{ {namedConnection:db} |> if (db && db != 'main') |> useDb }} + + ```code|quiet + var ignore = ['db','fields','format','skip','take','orderBy'] + var fields = qs.fields ? qs.fields.split(',').map(x => sqlQuote(x)).join(',') : '*' + var sql = `SELECT ${fields} FROM ${sqlQuote(table)}` + var filters = [] + var queryMap = qs.toObjectDictionary().withoutKeys(ignore) + #each queryMap.Keys.toList() + var search = queryMap[it.sqlVerifyFragment()].sqlVerifyFragment(); + #if search == '=null' || search == '!=null' + `${sqlQuote(it)} ${search=='=null' ? 'IS' : 'IS NOT'} NULL` |> addTo => filters + queryMap[it] = null + else if search.startsWith('=') + `${sqlQuote(it)} = @${it}` |> addTo => filters + queryMap[it] = search.substring(1).coerce() + else if search.startsWith('<=') || search.startsWith('>=') || search.startsWith('!=') + `${sqlQuote(it)} ${search.substring(0,2)} @${it}` |> addTo => filters + queryMap[it] = search.substring(2).coerce() + else if search.startsWith('<') || search.startsWith('>') + `${sqlQuote(it)} ${search.substring(0,1)} @${it}` |> addTo => filters + queryMap[it] = search.substring(1).coerce() + else if search.endsWith(',') + `${sqlQuote(it)} IN (${search.trimEnd(',').split(',').map(i=>i.toLong()).join(',')})` |>addTo=>filters + queryMap[it] = null + else if search.startsWith('%') || search.endsWith('%') + `${sqlQuote(it).sqlCast('varchar')} LIKE @${it}` |> addTo => filters + else + `${sqlQuote(it).sqlCast('varchar')} = @${it}` |> addTo => filters + /if + /each + #if !filters.isEmpty() + sql = `${sql} WHERE ${filters.join(' AND ')}` + /if + #if qs.orderBy + sql = `${sql} ORDER BY ${sqlOrderByFields(qs.orderBy)}` + /if + #if qs.skip || qs.take + sql = `${sql} ${sqlLimit(qs.skip,qs.take)}` + /if + sql |> dbSelect(queryMap) |> return + ``` + {{ ifError |> show(sql) }} + {{htmlError}} + + +The `_` prefixes in the path utilizes [Page Based Routing](https://sharpscript.net/docs/sharp-pages#page-based-routing) allowing for +[CoC](https://en.wikipedia.org/wiki/Convention_over_configuration) based +[Clean URL](https://en.wikipedia.org/wiki/Clean_URL) routes without needing to define & maintain separate routes where the +same script supports querying all [registered multitenancy databases](https://docs.servicestack.net/multitenancy#changedb-apphost-registration). + +### Instant Customizable RDBMS UI + +The [SharpData](https://github.com/NetCoreApps/SharpData) project essentially provides a UI around this script, surfacing its features & give +it instant utility which ended up being so useful that it's become the quickest way to perform fast adhoc DB queries as it's easy to configure +which RDBMS's & tables to show in a simple text file, easy to customize its UI, enables 1-click export into Excel and its shortcut syntax +support in column filters is a fast way to perform quick adhoc queries. + +### Quick Tour + +We'll quickly go through some of its features to give you an idea of its capabilities, from the above screenshot we can some of its +filtering capabilities. All results displayed in the UI are queried using the above +[sharpdata](https://github.com/NetCoreApps/SharpData/blob/master/wwwroot/db/_db/_table/index.html) `#Script` HTTP API +which supports the following features: + +### Filters + +All query string parameter except for `db,fields,format,skip,take,orderBy` are treated as filters, where you can: + + - Use `=null` or `!=null` to search `NULL` columns + - Use `<=`, `<`, `>`, `>=`, `<>`, `!=` prefix to search with that operator + - Use `,` trailing comma to perform an `IN (values)` search (integer columns only) + - Use `%` suffix or prefix to perform a `LIKE` search + - Use `=` prefix to perform a coerced "JS" search, for exact `number`, `boolean`, `null` and WCF date comparisons + - Otherwise by default performs a "string equality" search where columns are casted and compared as strings + +Here's the filtered list used in the above screenshot: + + [/db/main/Order?Id=>10200&CustomerId=V%&Freight=<=30&OrderDate=>1997-01-01](http://sharpdata.netcore.io/db/main/Order?format=json&Id=%3E10200&CustomerId=V%25&Freight=%3C%3D30&OrderDate=%3E1997-01-01&take=100) + +### Custom Field Selection + +The **column selection** icon on the top left of the results lets you query custom select columns which is specified using `?fields`: + + - [/db/main/Customer?fields=Id,CompanyName,ContactName,ContactTitle](https://sharpdata.netcore.io/db/main/Customer?format=json&fields=Id%2CCompanyName%2CContactName%2CContactTitle&take=100) + +### Multiple OrderBy's + +You can use [AutoQuery Syntax](https://docs.servicestack.net/autoquery-rdbms#multiple-orderbys) to specify multiple Order By's: + + - [/db/main/Customer?orderBy=-Id,CompanyName,-ContactName](https://sharpdata.netcore.io/db/main/Customer?format=json&orderBy=-Id,CompanyName,-ContactName) + +### Paging + +Use `?skip` and `?take` to page through a result set + +### Format + +Use `?format` to specify which **Content-Type** to return the results in, e.g: + + - [/db/main/Customer?format=html](https://sharpdata.netcore.io/db/main/Customer?format=html) + - [/db/main/Customer?format=json](https://sharpdata.netcore.io/db/main/Customer?format=json) + - [/db/main/Customer?format=csv](https://sharpdata.netcore.io/db/main/Customer?format=csv) + +### Multitenancy + +You can specify which registered DB to search using the path info, use `main` to query the default database: + + /db//
    + +### Launching SharpData + +To run SharpData in a .NET Core Desktop App you'll need latest `app` dotnet tool: + + $ dotnet tool update -g app + +> If on macOS/Linux you can use the [x dotnet tool](https://docs.servicestack.net/dotnet-tool) instead to view SharpData in your default browser + +### Configure RDBMS from command-line + +You can override which database to connect to by specifying it on the command line, e.g. here's an example of connecting to https://techstacks.io RDBMS: + + $ app open sharpdata -db postgres -db.connection $TECHSTACKS_DB + +Which will open SharpData listing all of TechStack's RDBMS tables. If you have a lot of tables the **Sidebar filter** provides a quick way to +find the table you want, e.g: + +![](https://raw.githubusercontent.com/ServiceStack/docs/master/docs/images/release-notes/v5.9/sharpdata-technology.png) + +### app URL Schemes + +What can be done with the `open` command on the command-line can also be done from a **custom URL Scheme**, a feature that opens up a myriad of new +possibilities as `app` can open [Gist Desktop Apps](https://sharpscript.net/docs/gist-desktop-apps) from Gists or in public & private GitHub repositories, +where it's able to download and launch Apps on the fly with custom arguments - allowing a single URL to run a **never installed** Desktop App stored in a +Gist & pass it custom params to enable **deep linking**. + +With this organizations could maintain a dashboard of links to its different Desktop Apps that anyone can access, especially useful as the +**only software** that's needed to run any [Sharp Apps](https://sharpscript.net/docs/sharp-apps) is the `app` dotnet tool which thanks to all +ServiceStack .dll's & dependencies being bundled with the tool, (including Vue/React/Bootstrap fontawesome and Material SVG Icon assets), +the only files that need to be published are the App's specific resources, which is how Apps like **SharpData** can be compressed in a +**20kb .zip** - a tiny payload that's viable to download the latest app each on each run, removing the pain & friction to distribute updates as +everyone's already running the latest version every time it's run. + +Should you need to (e.g. large Sharp App or github.com is down) you can run your previously locally cached App using `run`: + + $ app run sharpdata + +With Custom URL Schemes everyone with `app` installed can view any database they have network access to from specifying the db type and connection string in the URL: + + app://sharpdata?db=postgres&db.connection={CONNECTION_STRING} + +> CONNECTION_STRING needs to be URL Encoded, e.g. with JS's `encodeURIComponent()` + +or by specifying an Environment variable containing the connection string: + + app://sharpdata?db=postgres&db.connection=$TECHSTACKS_DB + +In addition to Sharp Apps being downloaded and run on the fly, they're also able to take advantage of the dotnet tools [mix support](https://docs.servicestack.net/mix-tool) to +also download another Gist's content into the Sharp App's working directory. + +With this you can publish a custom dataset in an SQLite database save it as a gist and **generate a single URL** that everyone can use to +download the database and open it in **SharpData**, e.g: + + - [app://sharpdata?mix=northwind.sqlite&db=sqlite&db.connection=northwind.sqlite](app://sharpdata?mix=northwind.sqlite&db=sqlite&db.connection=northwind.sqlite) + +It's possible to use the user-friendly `northwind.sqlite` alias here as it's published in the global [mix.md](https://gist.github.com/gistlyn/9b32b03f207a191099137429051ebde8) directory where it links to the [northwind.sqlite gist](https://gist.github.com/gistlyn/97d0bcd3ebd582e06c85f8400683e037). + +For your custom databases you use the **Gist Id** instead or if you plan to use this feature a lot you can override which `mix.md` document that +`app` should source its links from by specifying another **Gist Id** in the `MIX_SOURCE` Environment variable (or see below - to create a local alias). + +But if you're already mixing in an external gist you may as well include a custom `app.settings` in the Gist so it's pre-configured with custom +RDBMS registrations and table lists, e.g: + + - [app://sharpdata?mix=northwind.sharpdata](app://sharpdata?mix=northwind.sharpdata) + +Which applies the [northwind.sharpdata gist](https://gist.github.com/gistlyn/0ce0d5b828303f1cb4637450b563adbd), which can also be referenced by **Gist Id**: + + - [app://sharpdata?mix=0ce0d5b828303f1cb4637450b563adbd](app://sharpdata?mix=0ce0d5b828303f1cb4637450b563adbd) + +Alternatively you may instead prefer to publish it to a private GitHub repo instead of a Gist which anyone can open up with: + + app://user/sharpdata-private?token={TOKEN} + +The `app` dotnet tools will use the **latest published GitHub release** if there are any, otherwise will use the **master.zip** archive, +this feature can be used to maintain a working master repo and maintain control ver when to publish new versions of your custom SharpData App. + +### app local aliases + +Where ever you can use a Gist Id, you can assign a local user-friendly alias to use instead. So if you had a custom **sqlite** database and +sharpdata **app.settings** you could assign it to a local **db** alias with: + + $ app alias db 0ce0d5b828303f1cb4637450b563adbd + +Which you'll be able to use in place of the Gist Id, e.g. via command-line: + + $ app open sharpdata mix db + +or via URL Scheme: + + app://sharpdata?mix=db + +Likewise the gist alias can also be used for referencing [Gist Desktop Apps](https://sharpscript.net/docs/gist-desktop-apps), e.g. we can assign the +[redis gist app](https://gist.github.com/gistlyn/6de7993333b457445793f51f6f520ea8) to use our preferred alias: + + $ app alias local-redis 6de7993333b457445793f51f6f520ea8 + +That we can open via command-line: + + $ app open local-redis + +Or URL Scheme: + + app://local-redis + +Or if we want to run our own modified copy of the Redis Desktop App, we can [mix](https://docs.servicestack.net/mix-tool) the Gist files to our +local directory: + + $ app mix local-redis + +Make the changes we want, then run our local copy by running `app` (or `x`) without arguments: + + $ app + +Other alias command include: + +#### View all aliases + + $ app alias + +#### View single alias + + $ app alias db + +#### Remove an alias + + $ app unalias db + + +### Custom App Settings + +Each time a Gist Desktop App is opened it downloads and overrides the existing Gist with the latest version which it loads in a [Gist VFS](https://docs.servicestack.net/virtual-file-system#gistvirtualfiles) where any of its files can be overridden with a local copy. + +As the App's working directory is preserved between restarts you can provide a custom `app.settings` at: + + %USERPROFILE%\.sharp-apps\sharpdata\app.settings + +Where you can customize which RDBMS's and tables you want to be able to access, e.g: + +``` +debug false +name Northwind SharpData +appName sharpdata + +db sqlite +db.connection northwind.sqlite +db.connections[techstacks] { db:postgres, connection:$TECHSTACKS_DB } + +args.tables Customer,Order,OrderDetail,Category,Product,Employee,EmployeeTerritory,Shipper,Supplier,Region,Territory +args.tables_techstacks technology,technology_stack,technology_choice,organization,organization_member,post,post_comment,post_vote,custom_user_auth,user_auth_details,user_activity,page_stats +``` + +Which will display both RDBMS Databases, showing only the user-specified tables in app.settings above: + +![](https://raw.githubusercontent.com/ServiceStack/docs/master/docs/images/release-notes/v5.9/sharpdata-custom-appsettings.png) + +### Open in Excel + +SharpData detects if **Excel** is installed and lets you open the un-paged filtered resultset directly by clicking the **Excel** button + +![](https://raw.githubusercontent.com/ServiceStack/docs/master/docs/images/release-notes/v5.9/sharpdata-excel.png) + +This works seamlessly as it's able to "by-pass" the browser download where the query is performed by the back-end .NET Core Server who streams the response directly to the Users **Downloads** folder and launches it in Excel as soon as it's finished. + +### Custom Row Components + +Whilst a tabular grid view might be a natural UI for browsing a database for devs, we can do better since we have the full UI source code of the Vue components. +A filtered tabular view makes it fast to find the record you're interested in, but it's not ideal for quickly finding related information about an Entity. + +To provide a more customized UX for different App UIs, **SharpData** includes support for **"Row Components"** +(defined in [/components/Custom](https://github.com/NetCoreApps/SharpData/tree/master/src/components/Custom)) to be able to quickly drill down & view +richer info on any record. + +For example when viewing an **Order**, it's natural to want to view the **Order Details** with it, enabled with the custom Vue component registration below: + +```ts +@Component({ template: +`
    + +
    +
    Order Id needs to be selected
    ` +}) +class Order extends Vue { + @Prop() public db: string; + @Prop() public table: string; + @Prop() row: any; + @Prop() columns: ColumnSchema[]; + + details:any[] = []; + + get id() { return this.row.Id; } + + async mounted() { + this.details = await sharpData(this.db,'OrderDetail',{ OrderId: this.id }); + } +} +registerRowComponent('main','Order', Order, 'order'); +``` + +All Row components are injected with the `db`, `table` properties, the entire `row` object that was selected as well as the Column Schema definition for that table. Inside the component you're free to display anything, in this case we're using the `sharpData` helper for calling the server `#Script` HTTP API to get it to fetch all `OrderDetail` entries for this order. + +> If the resultset is filtered without the Order `Id` PK it can't fetch its referenced data, so displays an error instead + +The [jsonviewer](https://github.com/NetCoreApps/SharpData/blob/master/src/JsonViewer.ts) component used is similar to ServiceStack's +[HTML5 auto pages](https://docs.servicestack.net/html5reportformat) to quickly view contents of any object. + +The `registerRowComponent(db,table,VueComponent,componentName)` API is used to register this component with **SharpData** to make it available to render any order. + +With the `Order` component registered we can now drill down into any **Order** to view its **Order Details**: + +![](https://raw.githubusercontent.com/ServiceStack/docs/master/docs/images/release-notes/v5.9/sharpdata-custom-rowcomponents.png) + +You're free to render any kind of UI in the row component, e.g. here's the [Customer.ts](https://github.com/NetCoreApps/SharpData/blob/master/src/components/Custom/Customer.ts) row component used to render a richer view for Customers: + +```ts +@Component({ template: +`
    +

    {{customer.ContactName}}

    +
    + + + + + + + + + + + + + + + + +
    Contact!{{ customer.ContactName }} ({{ customer.ContactTitle }})
    Address +
    {{ customer.Address }}
    +
    {{ customer.City }}, {{ customer.PostalCode }}, {{ customer.Country }}
    +
    Phone{{ customer.Phone }}
    Fax{{ customer.Fax }}
    + +
    +
    Customer Id needs to be selected
    `}) +class Customer extends Vue { + @Prop() public db: string; + @Prop() public table: string; + @Prop() row: any; + @Prop() columns: ColumnSchema[]; + + customer:any = null; + orders:any[] = []; + + get id() { return this.row.Id; } + + async mounted() { + this.customer = (await sharpData(this.db,this.table,{ Id: this.id }))[0]; + const fields = 'Id,EmployeeId,OrderDate,Freight,ShipVia,ShipCity,ShipCountry'; + this.orders = await sharpData(this.db,'Order',{ CustomerId: this.id, fields }) + } +} +registerRowComponent('main','customer', Customer, 'customer'); +``` + +Which looks like: + +![](https://raw.githubusercontent.com/ServiceStack/docs/master/docs/images/release-notes/v5.9/sharpdata-customer-rowcomponent.png) + +### SharpData .NET Core Project + +Whilst [NetCoreApps/SharpData](https://github.com/NetCoreApps/SharpData) can live a charmed life as a Desktop App, it's also just a regular ServiceStack .NET Core App with a [Startup.cs](https://github.com/NetCoreApps/SharpData/blob/master/Startup.cs) and `AppHost` that can be developed, published and deployed as you're used to, here's an instance of it [deployed as a .NET Core App on Linux](https://docs.servicestack.net/netcore-deploy-rsync): + +#### [sharpdata.netcore.io](https://sharpdata.netcore.io) + +> For best experience we recommend running locally to experience it without latency of our servers in Germany + +It's a unique ServiceStack App in that it doesn't contain any ServiceStack Services as it's only using pre-existing functionality already built into ServiceStack, +`#Script` for its HTTP APIs and a Vue SPA for its UI, so requires no `.dll's` need to be deployed with it. + +It uses the same Vue SPA solution as [vue-lite](https://github.com/NetCoreTemplates/vue-lite) to avoid npm's size & complexity where you only need to run TypeScript's `tsc -w` to enable its [live-reload](https://docs.servicestack.net/hot-reloading) dev UX which provides its instant feedback during development. + +Some other of its unique traits is that instead of manually including all the Vue framework `.js` libraries, it instead references the new `ServiceStack.Desktop.dll` for its Vue framework libraries and its Material design SVG icons which are [referenced as normal file references](https://github.com/NetCoreApps/SharpData/blob/0499e7c66ca4289d17158e79bcc91815bbcd7a99/wwwroot/_layout.html#L60-L66): + +```js +{{ [ + `/lib/js/vue/vue.min.js`, + `/lib/js/vue-router/vue-router.min.js`, + `/lib/js/vue-class-component/vue-class-component.min.js`, + `/lib/js/vue-property-decorator/vue-property-decorator.min.js`, + `/lib/js/@servicestack/desktop/servicestack-desktop.min.js`, + `/lib/js/@servicestack/client/servicestack-client.min.js`, + `/lib/js/@servicestack/vue/servicestack-vue.min.js`, +] |> map => `` |> joinln |> raw }} +``` + +But instead of needing to exist on disk & deployed with your project it's referencing the embedded resources in `ServiceStack.Desktop.dll` and only the bundled assets need to be [deployed with your project](https://github.com/NetCoreApps/SharpData/blob/0499e7c66ca4289d17158e79bcc91815bbcd7a99/SharpData.csproj#L17) which is using the built-in [NUglify](https://github.com/xoofx/NUglify) support in the [dotnet tools](https://docs.servicestack.net/dotnet-tool) to produce its highly optimized/minified bundle without needing to rely on any npm tooling when publishing the .NET Core App: + +```xml + + + +``` + +The included [/typings](https://github.com/NetCoreApps/SharpData/tree/master/typings) are just the TypeScript definitions for each library which +TypeScript uses for its static analysis & its great dev UX in IDEs & VSCode, but are only needed during development and not deployed with the project. + +### Publish to Gist Desktop App + +The primary way **SharpData** is distributed is as a [Gist Desktop App](https://sharpscript.net/docs/gist-desktop-apps), where it's able to provide instant utility by running on a users local machine inside a native Chromium Desktop App making it suitable for a much broader use-case as a fast, lightweight, always up-to-date Desktop App with deeper Windows integration all packaged in a tiny **20kb .zip** footprint. There's no need to provision servers, setup CI, manage cloud hosting resources, you can simply run a script to update a Gist where its latest features are immediately available to your end users the next time it's run. + +To run, test & publish it as a Desktop App you can use the pre-made scripts in [package.json](https://github.com/NetCoreApps/SharpData/blob/master/package.json). +Rider provides a nice UX here as it lets you run each individual script directly from their json editor: + +![](https://raw.githubusercontent.com/ServiceStack/docs/master/docs/images/release-notes/v5.9/sharpdata-scripts.png) + +Essentially to package it into a [Sharp App](https://sharpscript.net/docs/sharp-apps) you just need to run the `pack` script which will bundle & copy all required assets into the `/dist` folder which you can then test locally in a [.NET Core Desktop App](https://docs.servicestack.net/netcore-windows-desktop) by running `app` in that folder: + + $ cd dist + $ app + +The `init-test` script just copies an example **northwind.sqlite** database and sample `app.settings` so you have something to test it with if you need it. + +The `publish-app` script is if you want to publish it to a Gist, you will need it to provide the GitHub **AccessToken** with write access to the Gist User Account you want to publish it to. Adding an `appName` and `description` to `app.settings` will publish it to the [Global App Registry](https://sharpscript.net/docs/gist-desktop-apps#instant-run-without-installation), make it publicly discoverable and allow anyone to open your App using your user-friendly `appName` alias, otherwise they can run it using the **Gist Id** or **Gist URL**. + +Alternatively the contents of the `dist/` folder can be published to a GitHub repo (public or private) and run with: + + $ app open / + +Or link to it with its custom URL Scheme: + + app:///repo + +If it's in a private repo they'll need to either provide an **AccessToken** in the `GITHUB_TOKEN` Environment variable or using the `-token` argument: + + $ app open / -token + +URL Scheme: + + app:///repo?token= + +### RDBMS Configuration + +When running as a .NET Core App you'd need to register which RDBMS's you want to use with OrmLite's configuration, e.g. the screenshot above registers an SQLite `northwind.sqlite` database and the https://techstacks.io PostgreSQL Database: + +```csharp +container.Register(c => new OrmLiteConnectionFactory( + MapProjectPath("~/northwind.sqlite"), SqliteDialect.Provider)); + +var dbFactory = container.Resolve(); +dbFactory.RegisterConnection("techstacks", + Environment.GetEnvironmentVariable("TECHSTACKS_DB"), + PostgreSqlDialect.Provider); +``` + +By default it shows all Tables in each RDBMS, but you can limit it to only show a user-defined list of tables with `#Script` Arguments: + +```csharp +Plugins.Add(new SharpPagesFeature { + //... + Args = { + //Only display user-defined list of tables: + ["tables"] = "Customer,Order,OrderDetail,Category,Product,Employee,EmployeeTerritory,Shipper,Supplier,Region,Territory", + ["tables_techstacks"] = "technology,technology_stack,technology_choice,organization,organization_member,post,post_comment,post_vote,custom_user_auth,user_auth_details,user_activity,page_stats", + } +}); +``` + +When running as a Sharp App it's instead configured in its +[app.settings](https://github.com/NetCoreApps/SharpData/blob/master/scripts/deploy/app.northwind.settings), here's equivalent settings for the above configuration: + +``` +# Configure below. Supported dialects: sqlite, mysql, postgres, sqlserver +db sqlite +db.connection northwind.sqlite +# db.connections[techstacks] { db:postgres, connection:$TECHSTACKS_DB } + +args.tables Customer,Order,OrderDetail,Category,Product,Employee,EmployeeTerritory,Shipper,Supplier,Region,Territory +args.tables_techstacks technology,technology_stack,technology_choice,organization,organization_member,post,post_comment,post_vote,custom_user_auth,user_auth_details,user_activity,page_stats +``` + +### Feedback + +We hope [SharpData](https://github.com/NetCoreApps/SharpData) serves useful in some capacity, whether it's being able to quickly develop and Ship a UI to stakeholders or as a template to develop .NET Core Apps that you can distribute as **Sharp Apps**, as an example to explore the delivery and platform potential of URL schemes and install-less Desktop Apps or just as an inspiration for areas where `#Script` shines & the different kind of Apps you can create with it. + +Whilst `app` is Windows 64 only, you can use the `x` cross-platform tool and its `xapp://` URL scheme to run Sharp Apps on macOS/Linux, it just wont have access to any of its Window Integration features. diff --git a/src/wwwroot/index.html b/src/wwwroot/index.html index 52f443d..4b1e8de 100644 --- a/src/wwwroot/index.html +++ b/src/wwwroot/index.html @@ -196,7 +196,7 @@

    Sharp Apps

    - See the Making of Spirals for a walk through on how to create + See the Making of Spirals for a walk through on how to create the Spirals Web App from scratch.
    diff --git a/src/wwwroot/sharp-apps/blog.html b/src/wwwroot/sharp-apps/blog.html new file mode 100644 index 0000000..14bdb30 --- /dev/null +++ b/src/wwwroot/sharp-apps/blog.html @@ -0,0 +1,81 @@ + + +
    + x open blog - + blog.web-app.io - + sharp-apps/Blog +
    + +{{ 'gfm/sharp-apps/14.md' |> githubMarkdown }} + +

    Customizable Auth Providers

    + +

    + Authentication can now be configured using plain text config in your app.settings where initially you need register the AuthFeature + plugin as normal by specifying it in the + features list: +

    + +
    features AuthFeature
    + +

    + Then using AuthFeature.AuthProviders you can specify which Auth Providers you want to have registered, e.g: +

    + +
    AuthFeature.AuthProviders TwitterAuthProvider, GithubAuthProvider
    + +

    + Each Auth Provider checks the Sharp Apps app.settings for its Auth Provider specific configuration it needs, e.g. to configure both + Twitter and GitHub Auth Providers you would populate it with your + OAuth Apps details: +

    + +
    oauth.RedirectUrl http://127.0.0.1:5000/
    +oauth.CallbackUrl http://127.0.0.1:5000/auth/{0}
    +
    +oauth.twitter.ConsumerKey {Twitter App Consumer Key}
    +oauth.twitter.ConsumerSecret {Twitter App Consumer Secret Key}
    +
    +oauth.github.ClientId {GitHub Client Id}
    +oauth.github.ClientSecret {GitHub Client Secret}
    +oauth.github.Scopes {GitHub Auth Scopes}
    + +

    Customizable Markdown Providers

    + +

    + By default Sharp Apps now utilize Markdig implementation to render its Markdown. + You can also switch it back to the built-in Markdown provider that ServiceStack uses with: +

    + +
    markdownProvider MarkdownDeep
    + +

    Rich Config Arguments

    + +

    + Any app.settings configs that are prefixed with args. are made available to #Script Pages and any arguments starting with + a { or [ are automatically converted into a JS object: +

    + +
    args.blog { name:'blog.web-app.io', href:'/' }
    +args.tags ['technology','marketing']
    + +

    + Where they can be referenced as an object or an array directly: +

    + +{{#raw appendTo configExample}}{{blog.name}}{{/raw}} + +
    {{#raw}}{{#each tags}} {{it}} {{/each}}{{/raw}}
    {{configExample}}
    + +

    + The alternative approach is to give each argument value a different name: +

    + +
    args.blogName blog.web-app.io
    +args.blogHref /
    +
    + +{{ "apps-links" |> partial({ order }) }} diff --git a/src/wwwroot/sharp-apps/chat.html b/src/wwwroot/sharp-apps/chat.html new file mode 100644 index 0000000..89a7c58 --- /dev/null +++ b/src/wwwroot/sharp-apps/chat.html @@ -0,0 +1,84 @@ + + +
    + x open chat - + chat.web-app.io - + sharp-apps/Chat +
    + +

    + /chat is an example of the ultimate form + of extensibility where instead of just being able to add Services, Filters and Plugins, etc. You can add your entire + AppHost which Sharp Apps will use instead of its own. This vastly expands the use-cases that can be built with + Sharp Apps as it gives you complete fine-grained control over how your App is configured. +

    + +Chat WebApp screenshot + +

    Develop back-end using .NET IDE's

    + +

    + For chat.web-app.io we've taken a copy of the existing .NET Core 3.1 + Chat App and moved its C# code to + /example-plugins/Chat + and its files to /apps/chat + where it can be developed like any other Web App except it utilizes the Chat AppHost and implementation in the + SelfHost Chat App. +

    + +

    + Customizations from the original + .NET Core Chat implementation + includes removing MVC and Razor dependencies and configuration, extracting its + _layout.html and + converting index.html + to use #Script from its original + default.cshtml. + It's also been enhanced with the ability to evaluate scripts from the Chat window, as seen in the screenshot above. +

    + +

    Chat AppHost

    + +{{ 'gfm/sharp-apps/06.md' |> githubMarkdown }} + +
    Reusing Web App's app.setting and files
    + +

    + One nice thing from being able to reuse existing AppHost's is being able to develop all back-end C# Services and Custom Filters + as a stand-alone .NET Core Project where it's more productive with access to .NET IDE tooling and debugging. +

    + +

    + To account for these 2 modes we use AddIfNotExists to only register the SharpPagesFeature plugin + when running as a stand-alone App and add an additional constructor so it reuses the existing app.settings as its + IAppSettings provider for is custom App configuration like OAuth App keys + required for enabling Sign-In's via with Twitter, Facebook and GitHub when running on http://localhost:5000: +

    + +
    debug true
    +name Chat Web App
    +contentRoot ~/../chat
    +webRoot ~/../chat
    +
    +oauth.RedirectUrl http://localhost:5000/
    +oauth.CallbackUrl http://localhost:5000/auth/{0}
    +oauth.twitter.ConsumerKey JvWZokH73rdghDdCFCFkJtCEU
    +oauth.twitter.ConsumerSecret WNeOT6YalxXDR4iWZjc4jVjFaydoDcY8jgRrGc5FVLjsVlY2Y8
    +oauth.facebook.Permissions email
    +oauth.facebook.AppId 447523305684110
    +oauth.facebook.AppSecret 7d8a16d5c7cbbfab4b49fd51183c93a0
    +oauth.github.Scopes user
    +oauth.github.ClientId dbe8c242e3d1099f4558
    +oauth.github.ClientSecret 42c8db8d0ca72a0ef202e0c197d1377670c990f4
    +
    + +

    + After the back-end has been implemented we can build and copy the compiled Chat.dll into the Chat's + /plugins folder where + we can take advantage of the improved development experience for rapidly developing its UI. +

    + +{{ "apps-links" |> partial({ order }) }} diff --git a/src/wwwroot/sharp-apps/deploying-sharp-apps.html b/src/wwwroot/sharp-apps/deploying-sharp-apps.html new file mode 100644 index 0000000..3eb592a --- /dev/null +++ b/src/wwwroot/sharp-apps/deploying-sharp-apps.html @@ -0,0 +1,238 @@ + + +{{#markdown}} +### Gist or GitHub App Server Deployments + +As Sharp Apps are just .NET Core Web Apps, the same App can be run within a [Chromium Desktop App](/docs/gist-desktop-apps) with +[app](https://docs.servicestack.net/netcore-windows-desktop), cross-platform on Windows, macOS or Linux with +[x](https://docs.servicestack.net/dotnet-tool) in the preferred browser or hosted on a server where it's accessible to everyone with an internet connection. + +Not only does not needing to compile Sharp Apps dramatically simplify App development but it also dramatically simplifies +App deployment where you can **completely skip all CI and build steps** as there's nothing to build or deploy with the built-in +support for Gist publishing. + +All that's required is to run the App on your server with `x open `, e.g. to run `redis` Gist App: + + $ x open redis + +Which runs on port `5000` by default, you can run it under your preferred domain by configuring nginx below. + +Or to "install" the Gist App (without launching it with "open") so it can be run offline locally: + + $ x install redis + +## Deploying Sharp Apps to Ubuntu + +A common way for reliably hosting .NET Core Apps on Ubuntu is to use [supervisor](http://supervisord.org/index.html) +to monitor the `dotnet` self-hosting processes behind an nginx reverse proxy which handles external HTTP requests to +your website and proxies them to the dotnet process running your Web App on a local port. You'll need access to a Unix +environment on your client Desktop, either using Linux, OSX or +[Installing Windows Subsystem for Linux (WSL)](https://github.com/ServiceStack/redis-windows#option-1-install-redis-on-ubuntu-on-windows). + +### Setup the deploy User Account + +Using a Unix command-line or [Windows Subsystem for Linux (WSL)](https://github.com/ServiceStack/redis-windows#option-1-install-redis-on-ubuntu-on-windows) +ssh into your remote server: + + $ ssh deploy@web-apps.io + +We'll start by creating a dedicated user account for hosting and running your .NET Core Apps to mitigate potential abuse. +SSH into your Ubuntu server and create the `deploy` user account with a `/home/deploy` home directory and add +them to the `sudo` group: + + sudo useradd -m deploy + sudo usermod -aG sudo deploy + +For seamless deployments use `visudo` to allow `deploy` to run `supervisorctl` without +prompting for a password: + + # Allow members of group sudo to execute any command + %sudo ALL=(ALL:ALL) ALL + %deploy ALL=NOPASSWD: /usr/bin/supervisorctl, /home/deploy/.dotnet/tools/x + +> In vi type `i` to start editing a file and `ESC` to quit edit mode and `:wq` to save your changes before exiting. + +#### Install the dotnet `x` tool: + + $ dotnet tool install -g x + +For simplifying the one-time setup, it's easier to sign-in as super user: + + $ sudo su - + +#### Configure Nginx + +To configure your App quickly you can start with **nginx** and **supervisor** config templates using the [mix dotnet tool](https://docs.servicestack.net/mix-tool): + + $ x mix nginx supervisor-sharp -name redis + +Where after confirming, will write the config files to the appropriate locations: + + Write files from 'nginx' https://gist.github.com/gistlyn/38a125eede8228ddf40651e2529a5c70 to: + + /etc/nginx/sites-available/redis.web-app.io + + Proceed? (n/Y): + + Writing files from 'supervisor-sharp' https://gist.github.com/gistlyn/2db295508517a4eed59906320e95d98a to: + /etc/supervisor/conf.d/app.redis.conf + +Then to modify the virtual host configuration of the App, change into the nginx virtual host directory: + + $ cd /etc/nginx/sites-available/ + +and rename `redis.web-app.io` file to the domain you want it hosted on instead, e.g: + + $ mv redis.web-app.io redis.your-domain.com + +You'll also need to rename the virtual host in the config file, which in vi you can do with: + + :%s/redis.web-app.io/redis.your-domain.com/g + +Which should now look something like: + + server { + listen 80; + server_name redis.your-domain; + + location / { + proxy_pass http://localhost:5000; + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection keep-alive; + proxy_cache_bypass $http_upgrade; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_buffering off; + proxy_ignore_client_abort off; + proxy_intercept_errors on; + + client_max_body_size 500m; + } + } + +You'll also need to update the port number if you want to run your .NET Core App on a different port. + +Now to enable the site in nginx, link it with: + + $ ln -s /etc/nginx/sites-available/redis.your-domain.com /etc/nginx/sites-enabled/redis.your-domain.com + +Then reload nginx to pick up changes: + + $ /etc/init.d/nginx reload + +And voila! your Gist Sharp App is now being served at **redis.your-domain.com** + +#### Configure Supervisor + +We'll then configure [supervisord](http://supervisord.org/index.html) to further harden the .NET Core App process by having it run under a managed process, +by [creating a deploy User Account](https://sharpscript.net/docs/deploying-sharp-apps#setup-the-deploy-user-account) and giving it +permission to run the `supervisorctl` and `web` programs, then change directory to: + + $ cd /etc/supervisor/conf.d + +Where we can further modify the supervisor config file created in the mix tool, where in most cases you'll only need to change the port number if +you've selected a different port in your nginx virtual host: + + [program:app-redis] + command=/home/deploy/.dotnet/tools/x run redis --release + directory=/home/deploy/.sharp-apps/redis + autostart=true + autorestart=true + stderr_logfile=/var/log/app-redis.err.log + stdout_logfile=/var/log/app-redis.out.log + environment=ASPNETCORE_ENVIRONMENT=Production,ASPNETCORE_URLS="http://*:5000/" + user=deploy + stopsignal=INT + +> The --release flag overrides debug in app.settings so it's always run in release mode. + +After reviewing the changes, tell supervisor to register and start the supervisor process with: + + $ supervisorctl update + +Where your website will now be up and running under a managed process at: **redis.your-domain.com** + +#### Deploy Updates + +Now that's everything's configured, deploying app updates are easily done by installing the app again (which downloads the latest version), +then restarting the supervisor managed process, in these 2 commands: + + $ x install redis + $ supervisorctl restart app-redis + +Which can also be deployed from the Windows Command Prompt using a remote SSH command by combining the above commands in a `deploy-redis.sh` text file: + + ssh -t deploy@web-app.io "sudo /home/deploy/.dotnet/tools/x install redis && sudo supervisorctl restart app-redis" + +Where App updates can then be performed with a single WSL bash command from the Windows Command Prompt: + + $ bash deploy-redis.sh + +### Customized App Settings + +If you need to customize the App's settings, like we've needed to do with [blog.web-app.io](http://blog.web-app.io) **app.settings** to replace +its OAuth keys, you can add a modified copy in its App folder which will take precedence over the read-only gist version: + + $HOME/.sharp-apps/blog/app.settings + +### Hosted Gist Apps + +All our Gist Apps are now hosted this way, by running a locally downloaded Gist App that's hosted at the following URLs: + + - [redis.web-app.io](http://redis.web-app.io) + - [blog.web-app.io](http://blog.web-app.io) + - [plugins.web-app.io](http://plugins.web-app.io) + - [spirals.web-app.io](http://spirals.web-app.io) + - [bare.web-app.io](http://bare.web-app.io) + +### Using Travis CI to deploy using Docker to AWS ECS + +A popular combination for deploying .NET Core Apps is to use the online [Travis CI](https://travis-ci.org) +Continuous Integration Service to package your App in a Docker Container and deploy it to AWS ECS which takes care of +the management and deployment of Docker instances over a configured cluster of EC2 compute instances. + +The easiest way to set this up is to clone the [rockwind-aws](https://github.com/NetCoreWebApps/rockwind-aws) +Web App which is preconfigured with a working scripts using Travis CI to package the Web App in a Docker container +and deploy it to AWS ECS. In your local copy replace the +[/app](https://github.com/NetCoreWebApps/rockwind-aws/tree/master/app) folder with your App files, e.g: + +#### [Dockerfile](https://github.com/NetCoreWebApps/rockwind-aws/blob/master/Dockerfile) + +{{/markdown}} + +{{ 'gfm/deploying-sharp-apps/02.md' |> githubMarkdown }} + +

    + The only other file that needs to change is deploy-envs.sh to configure it to use your App's deployment settings: +

    + +

    deploy-envs.sh

    + +{{ 'gfm/deploying-sharp-apps/03.md' |> githubMarkdown }} + +

    Setup AWS ECS and Travis CI

    + +

    + After configuring your App deployment scripts you'll then need to + Setup your AWS ECS + with an EC2 instance to deploy to and + Create your project in Travis CI. + You'll then need to add your AWS Account details in the Travis CI project using + Secure Environment Variables + to store your AWS_ACCOUNT_ID, AWS_ACCESS_KEY and AWS_SECRET_KEY as well as any sensitive info and + connection strings your App uses. +

    + +

    Let us know what you create!

    + +We hope you're excited about these new features as we are and can't wait to see what you build with them - please +share them with us +so we can include it in the App Gallery and make it easy for everyone else to discover and use. + +{{ "apps-links" |> partial({ order }) }} diff --git a/src/wwwroot/sharp-apps/gist-desktop-apps.html b/src/wwwroot/sharp-apps/gist-desktop-apps.html new file mode 100644 index 0000000..a7c0d8a --- /dev/null +++ b/src/wwwroot/sharp-apps/gist-desktop-apps.html @@ -0,0 +1,488 @@ + + +{{#markdown}} +[Sharp Apps](https://sharpscript.net/docs/sharp-apps) can also be published to Gists where they can be run on-the-fly without installation, +they're always up-to-date, have tiny footprints that are fast to download and launch, that can also run locally, off-line and cross-platform across +Windows, macOS and Linux OS's. + +To recap [Sharp Apps](https://sharpscript.net/docs/sharp-apps) enable a dramatically simplified and productive workflow where +entire Apps can be **developed in a real-time without compilation** using a [JavaScript inspired syntax](https://sharpscript.net/docs/syntax) +and access to a [comprehensive default library](https://sharpscript.net/docs/scripts-reference) including built-in support +for the [most popular RDBMS's](https://sharpscript.net/docs/sharp-apps#multi-platform-configurations), +[Redis](https://sharpscript.net/docs/redis-scripts), AWS, and Azure that can be further extended with support for +[plugins](https://sharpscript.net/docs/sharp-apps#plugins). + +### Windows .NET Core Desktop Apps + +Major disadvantages to developing Desktop Apps today include limitations in available UI frameworks not being as flexible and feature-rich +as modern browser rendering engines, slow development/iteration times, large downloads, forced upfront installations, stale versions, +and cumbersome upgrades - resulting in both increased cost to develop Desktop Apps and reduced accessibility and potential popularity with +the additional barrier to entry of forced installations. + +[Electron](https://electronjs.org/) resolves some of these issues and has seen a surge of popularity vs Native Apps with its more productive web development model +and partial support for auto updating, but it still requires a large download and upfront installation. + +#### Tiny Footprint + +By contrast, the majority of the download size of Sharp Apps is in the local .NET Core installation and [app](https://docs.servicestack.net/netcore-windows-desktop) +dotnet tool which are **shared by all Sharp Apps**, only the app-specific web assets and `#Script` source files need to be downloaded, making +them a lot smaller and quicker to download (and run instantly). + +They can even be further reduced by utilizing the resources embedded into **[ServiceStack](https://docs.servicestack.net/)** like the built-in +[SVG images](https://docs.servicestack.net/svg) and stylesheet and `/css/bootstrap.css`, which many Sharp Apps take advantage of to reduce their footprint. + +### Running Gist Desktop Apps + +If you haven't already, install the [app](https://docs.servicestack.net/netcore-windows-desktop) dotnet tool which is the +**only app required** to run **all Gist and Sharp Desktop Apps**: + + $ dotnet tool install -g app + + + +> OSX or Linux users can run Sharp Apps with the cross-platform [x](https://docs.servicestack.net/dotnet-tool) dotnet tool instead + +Now everyone can launch a Windows Desktop Sharp App by specifying the name of the App they want to open with: + + $ app open redis + +#### redis + +![](https://raw.githubusercontent.com/ServiceStack/docs/master/docs/images/app/app-redis.png) + +### Instant Run Without Installation + +This searches the [global App registry](https://gist.github.com/gistlyn/802daba52b6fe6e2ed1430348dc596cb) for the link to the +App, and in this case launches the Redis Sharp App within a Chromium Desktop shell as seen above. + +The list of available apps is also visible from the command-line with: + + $ app open + + 1. redis Simple, lightweight, versatile Redis Admin UI by @ServiceStack + 2. spirals Explore and generate different Spirals with SVG by @ServiceStack + 3. blog Minimal, multi-user Twitter Auth blogging app by @ServiceStack + 4. rockwind Example combining Rockstars website + data-driven Northwind Browser by @ServiceStack + 5. redis-html Redis Admin Viewer developed as server-generated HTML Website by @sharp-apps + 6. plugins Extend Apps with Plugins, ServiceStack Services and other C# extensions by @sharp-apps + 7. chat Extensible App with custom AppHost using OAuth + SSE for real-time Chat by @sharp-apps + +### Run Apps From URLs + +Publishing your App to the [global registry](https://gist.github.com/gistlyn/802daba52b6fe6e2ed1430348dc596cb) makes it more +accessible via a friendly name, but if you don't want your App shared publicly or want to test it before publishing, +it can also be run directly from a **Gist URL**, **Gist Id**, **GitHub Repo** or **Release .zip Archive**, e.g: + + $ app open https://gist.github.com/gistlyn/6de7993333b457445793f51f6f520ea8 + $ app open 189cd72bfaf480526e4b34814c80f2c0 + $ app open https://github.com/sharp-apps/redis + $ app open https://github.com/sharp-apps/plugins/archive/v5.zip + +Local Sharp Apps can simply run `app` in the Sharp Apps directory: + + $ app + +### Cross Platform + +If you're using macOS or Linux you can run all Sharp Apps using the cross-platform [x](https://docs.servicestack.net/dotnet-tool) dotnet tool where it will launch +in your preferred Web Browser instead: + + $ x open redis + +> Every `app` command is substitutable with `web` to run it within your preferred browser in Windows, macOS or Linux + +### Always Up-To-Date + +Another unique characteristic of Sharp Apps launched with `open` is that they **always run the latest version** of the App, thereby avoiding the need to implement an Update feature or maintain patch release versions. + +### Run Apps Offline + +When Apps are launched with `open`, a folder is created in the Users `.sharp-apps` directory, e.g: + + $HOME\.sharp-apps\redis + +This is the current directory that the App is run from and where any files created by Apps will be saved to and preserved across App runs. + +For **Gist Apps this is an empty folder** as the Gist files are loaded into memory, however to support being able to run Apps offline +it also serializes all Gist files (after fetching all truncated files) to JSON at: + + $HOME\.sharp-apps\redis.gist + +This is so after Apps **are launched once** with `open`, they can then be **run locally** with: + + $ app run redis + +Which will load the Gist files from the serialized **redis.gist** JSON blob instead of downloading them from the gist on GitHub. This is useful for times you don't have an Internet connection, or GitHub is down. However as small Apps like **redis** start instantly, +it's preferred to run them with `app open redis` so you're always running the latest version. + +### Run Local Modified Versions Of Existing Sharp Apps + +Installing or running a Sharp App will install it in the `$HOME\.sharp-apps\` folder but if you want to make changes to it, it's easier +to install it in a local directory. For Sharp Apps published in a GitHub Repo you can either clone the repo or use the dotnet tools to +install it from the command-line with the `new` command to specify the Repo you want to download and the `-source` parameter to specify +which GitHub User or Organization. + +E.g. you can install [https://github.com/sharp-apps/redis](https://github.com/sharp-apps/redis) with: + + $ app new redis -source sharp-apps + +Where you can open it in a text editor like VS Code: + + $ code redis + +And run it in the VS Code terminal window, which during development you'll likely want to run it with your preferred Web Browser to access its Dev tools: + + $ app + +Then you'll be able to make changes to the App whilst it's running to see any changes in real-time. + +#### Gist Apps + +To modify Gist Apps you can use the [mix dotnet tool](https://docs.servicestack.net/mix-tool) to download a Gist files contents. +For example, you can download the **redis** Gist App with: + + $ app mix https://gist.github.com/gistlyn/6de7993333b457445793f51f6f520ea8 + +#### Uninstall Apps + +As Gist Apps are downloaded on-the-fly and loaded into memory there's not much to uninstall just an empty folder and a `.gist` +JSON blob which you can either manually delete or get the dotnet tool to do it for you. + +To view all App's you've opened, run: + + $ app uninstall + +This displays a list of Sharp App's you've run at least once: + + Usage: app uninstall + + Installed Apps: + blog + chat + plugins + redis + rockwind + spirals + +To delete all traces of the `redis` App from your system, run: + + $ app uninstall redis + +Which removes the empty `$HOME\.sharp-apps\redis` folder and `$HOME\.sharp-apps\redis.gist`. + +### Gist Sharp Apps + +If we peek into the [markdown of app.md](https://gist.githubusercontent.com/gistlyn/802daba52b6fe6e2ed1430348dc596cb/raw/2b90536198e14fb0e3494bc5f89dbc55c72c8e88/apps.md) we can see the different ways Sharp Apps can be hosted, +for **redis** we see that the entire App is [published in a Gist](https://gist.github.com/gistlyn/6de7993333b457445793f51f6f520ea8): + + - [redis](https://gist.github.com/gistlyn/6de7993333b457445793f51f6f520ea8) + +Thanks to ServiceStacks' [GistVirtualFiles support](https://docs.servicestack.net/virtual-file-system), Gist Apps were trivial to support which only required launching +the ServiceStack App with a configured `GistVirtualFiles` that references the **redis** Gist, i.e: + + appHost.InsertVirtualFileSources.Add(new GistVirtualFiles("6de7993333b457445793f51f6f520ea8")); + +Hosting Apps in Gists provides numerous benefits: predominantly they're effectively a free, public, distributed app host which are +tied to Authenticated GitHub Accounts and have a public version history of every commit so each change is visible. + +GitHub also provides both a Web UI and HTTP UI to manage gists making them easy to modify, both manually and programmatically where +you can use their Web UI to make a quick fix - which is instantly available the next time the app is launched. + +We've already had quick look at the **redis** Gist App that provides a nice UI for querying and editing a Redis instance +(an example of an App that can't be implemented as a website). Let's have a look at some other Gist Apps that are well suited +for development as Sharp Apps: + +#### Spirals + +![](https://raw.githubusercontent.com/ServiceStack/docs/master/docs/images/app/app-spirals.png) + +Open with: + + $ app open spirals + +Spirals in an example of a minimally useful App to explore and generate different spirals with SVG that showcases the productivity and +live Development experience of Sharp Apps where you can create an App from scratch with just a text editor and the `web` tool, without +a single re-compile or app restart: + + + +### Publishing Gist Apps + +Now that we've created an App, it's time to publish it to a Gist. To do this we need to +[create a personal access token](https://help.github.com/en/articles/creating-a-personal-access-token-for-the-command-line) with the **gist** +permission so it's able to create gists. + +You can provide your access token via the `-token` command-line argument: + + $ app publish -token {GITHUB_TOKEN} + +But **our recommendation** is instead to set it in the `GITHUB_GIST_TOKEN` Environment Variable to avoid needing to provide it each time. + +Before publishing our App, our **app.settings** looks something like: + + debug true + name Spirals + CefConfig { width:1100, height:900 } + +Then in your App's home directory (containing the **app.settings**), run: + + $ app publish + +This creates a new Gist with your App and is confirmed by a successful response: + + App published to: https://api.github.com/gists/4e06df1f1b9099526a7c97721aa7f69c + + Publish App to the public registry by re-publishing with app.settings: + + appName # required: alpha-numeric snake-case characters only, 30 chars max + description # optional: 20-150 chars + tags # optional: space delimited, alpha-numeric snake-case, 3 tags max + +It also modifies your `app.settings` to include the gist that your App was published to: + + debug true + name Spirals + CefConfig { width:1100, height:900 } + publish https://gist.github.com/gistlyn/4e06df1f1b9099526a7c97721aa7f69c + +Containing the location your App will be published to in the future. + +At this point anyone will now be able to run your App locally with the link it's published to: + + $ app open https://gist.github.com/gistlyn/4e06df1f1b9099526a7c97721aa7f69c + +Or using just the **gist id**: + + $ app open 4e06df1f1b9099526a7c97721aa7f69c + +Or you can give it a friendlier name and make it more discoverable by publishing it to the global App Directory +by updating your app.settings to include **appName**, **description** and **tags** settings, e.g: + + debug true + name Spirals + CefConfig { width:1100, height:900 } + publish https://gist.github.com/gistlyn/4e06df1f1b9099526a7c97721aa7f69c + appName spirals + description Explore and generate different Spirals with SVG + tags svg + +Now when you re-publish your App: + + $ app publish + +It will update your Apps gist, register **spirals** with the App directory and output the command everyone will be able to run your App with: + + App updated at: https://gist.github.com/gistlyn/4e06df1f1b9099526a7c97721aa7f69c + + Run published App: + + app open spirals + +Users that are not on Windows can use the `web` tool instead to launch your App in their preferred browser: + + $ x open spirals + +With its built-in publishing support, you can **create an App from scratch, publish it to a gist, register it in the App directory** - +where your creations are ready for the world to use **in minutes!** + +We are not aware of any other Desktop App solution that comes close to this level of turn around time. + +#### Blog + +Spirals are cool, but lets explore some more useful real-world Apps: + +![](https://raw.githubusercontent.com/ServiceStack/docs/master/docs/images/app/app-blog.png) + +Open with: + + $ app open blog + +**blog** is an sqlite-powered multi-user blogging system, that in addition to supporting Markdown, also lets you use `#Script` in your +posts so you're able to post **"live executable documents"** that can mix both content and executable scripts. + +As we envisage sqlite to a popular storage option we'll go through a couple of ways to make use of it in Gist Apps as your Apps +Gist files are read-only and loaded in memory where as SQLite is on disk. + +To configure your App to use SQLite, add **db sqlite** and **db.connection dbname.sqlite** to: + +#### [app.settings](https://gist.github.com/gistlyn/ddc064f62cafc91ead200552d9d8ad89#file-app-settings) + + debug false + name Blog Web App + db sqlite + db.connection blog.sqlite + +This will create an empty file at `$HOME\.sharp-apps\blog\blog.sqlite` when your App is first launched. + +You can then create your DB Schema and populate your database using [Database Scripts](https://sharpscript.net/docs/db-scripts) +in a file called `_init.html` which is executed just before your App is launched. + +The **blog** App uses this approach for creating its' database and populating it with initial seed data if the database is empty, e.g: + +#### [_init.html](https://gist.github.com/gistlyn/ddc064f62cafc91ead200552d9d8ad89#file-_init-html) + + {{ `CREATE TABLE IF NOT EXISTS "Post" + ( + "Id" INTEGER PRIMARY KEY AUTOINCREMENT, + "Slug" VARCHAR(8000) NULL, + "Title" VARCHAR(8000) NULL, + "Content" VARCHAR(8000) NULL, + "Created" VARCHAR(8000) NOT NULL, + "CreatedBy" VARCHAR(8000) NOT NULL, + "Modified" VARCHAR(8000) NOT NULL, + "ModifiedBy" VARCHAR(8000) NOT NULL + ); + + CREATE TABLE IF NOT EXISTS "UserInfo" + ( + "UserName" VARCHAR(8000) PRIMARY KEY, + "DisplayName" VARCHAR(8000) NULL, + "AvatarUrl" VARCHAR(8000) NULL, + "AvatarUrlLarge" VARCHAR(8000) NULL, + "Created" VARCHAR(8000) NOT NULL, + "Modified" VARCHAR(8000) NOT NULL + );` + + |> dbExec + }} + + {{ dbScalar(`SELECT COUNT(*) FROM Post`) |> to => postsCount }} + + {{#if postsCount == 0 }} + + {{ `datetime(CURRENT_TIMESTAMP,'localtime')` |> to => sqlNow }} + {{ `ServiceStack` |> to => user }} + + ======================== + Create ServiceStack User - Contains same info as if was @ServiceStack authenticated via Twitter + ======================== + + {{ `INSERT INTO UserInfo (UserName, DisplayName, AvatarUrl, AvatarUrlLarge, Created, Modified) + VALUES (@user, @user, @avatarUrl, @avatarUrlLarge, ${sqlNow}, ${sqlNow})` + |> dbExec({ + user: 'ServiceStack', + avatarUrl: 'https://pbs.twimg.com/profile_images/876249730078056448/JuTVEkWX_normal.jpg', + avatarUrlLarge: 'https://pbs.twimg.com/profile_images/876249730078056448/JuTVEkWX.jpg' + }) + }} + + ... + + {{/if}} + +The next time the `blog` App is run it uses the existing `$HOME\.sharp-apps\blog\blog.sqlite` and skips populating the database above. + +#### rockwind + +![](https://raw.githubusercontent.com/ServiceStack/docs/master/docs/images/app/app-rockwind.png) + +**Querying Embedded Northwind SQLite Database:** + +![](https://raw.githubusercontent.com/ServiceStack/docs/master/docs/images/app/app-rockwind2.png) + +Open with: + + $ app open rockwind + +Rockwind is an example of a larger (60+ files) multi-versatile App of hybrid content, data driven App with a UI and Web API +over northwind tables that also combines multiple different layouts in a single App. + +Its data-driven Web UI and Web API requires the northwind database which is included in the gist as `northwind.readonly.sqlite` +then in the init script it saves a copy to **northwind.sqlite** if it doesn't already exist: + +#### [_init.html](https://gist.github.com/gistlyn/0148c87e154fb4731c7fa6219375d989#file-_init-html) + + vfsFileSystem('.') |> to => fs + + #if !fs.fileExists('northwind.sqlite') || fs.file('northwind.sqlite').Length == 0 + fs.writeFile('northwind.sqlite', file('northwind.readonly.sqlite')) + /if + +rockwind contains a number of other hidden useful gems like how easy it is to create multi-linked query reports: + +#### [northwind\order-report\_id.html](https://gist.github.com/gistlyn/0148c87e154fb4731c7fa6219375d989#file-northwind-order-report-_id-html) + +![](https://raw.githubusercontent.com/ServiceStack/docs/master/docs/images/app/app-rockwind3.png) + +As well as a dynamic SQL Studio UI that re-queries as-you-type: + +#### [northwind\sql\index.html](https://gist.github.com/gistlyn/0148c87e154fb4731c7fa6219375d989#file-northwind-sql-api-html) + +![](https://raw.githubusercontent.com/ServiceStack/docs/master/docs/images/app/app-rockwind4.png) + +### GitHub Sharp Apps + +Up to this point we've only seen running Sharp Apps from Gists, but they can also be run from traditional GitHub repos as we can +see from the **chat** App which links to its repo: + + - [chat](https://github.com/sharp-apps/chat) + +The difference between Gist an GitHub Repo Apps is that GitHub repos need to be installed before they're run. +When you run a GitHub Repo App with `open`, e.g: + + $ app open chat + +It downloads the repo archive (either the last released version or current master archive), extracts its contents to the Apps +`$HOME\.sharp-apps\chat` folder then runs it as a traditional Web App where its files are maintained on disk. + +As this process takes a little longer to start than Gist Apps you may prefer to use `run` for subsequent App runs: + + $ app run chat + +Where it will run it from disk, whereas `open` will nuke the existing install, re-download the archive and extract it again before launching. + +For GitHub Repo apps, `open` is equivalent to re-running `install` and `run` each time: + + $ app install chat + $ app run chat + +### GitHub Sharp App Commands + +Whilst they work differently, `open`, `install`, `run` and `uninstall` have the **same behavior** for both Gist and GitHub Repo Apps, i.e: + + - `open` - Always run the latest version of the App + - `install` - Download the App only so it can be run offline + - `run` - Run the locally downloaded App + - `uninstall` - Remove all traces of previously installed or opened Apps + +#### chat + +![](https://raw.githubusercontent.com/ServiceStack/docs/master/docs/images/app/app-chat.png) + +Open with: + + $ app open chat + + +**[chat](https://sharpscript.net/docs/sharp-apps#chat)** is an example of the ultimate form of extensibility where instead of just being able to add Services, Filters and Plugins, you can add your entire AppHost which Sharp Apps will use instead of its own. This vastly expands the use-cases that can be built with Sharp Apps as it gives you complete fine-grained control over exactly how your App is configured. + +#### Plugins + +The last packaging option supported for running Sharp Apps is being able to link to a specific GitHub Release version of your App, e.g: + + - [plugins](https://github.com/sharp-apps/plugins/archive/v5.zip) + +Which will ensure you're always running the same version of the App which is useful in being able to easily run and compare different App versions +or be able to support beta releases of your App that you don't want everyone to use yet. + +![](https://raw.githubusercontent.com/ServiceStack/docs/master/docs/images/app/app-plugins.png) + +Open with: + + $ app open plugins + +**[plugins](https://sharpscript.net/docs/sharp-apps#plugins)** showcases the easy extensibility of Sharp Apps which allow "no touch" sharing of +[ServiceStack Plugins](https://docs.servicestack.net/plugins), [Services](https://docs.servicestack.net/create-your-first-webservice), +[Script Methods](http://sharpscript.net/docs/methods), [Sharp Code Pages](http://sharpscript.net/docs/code-pages) and +[Validators](https://docs.servicestack.net/validation) contained within **.dll's** or **.exe's** dropped in a Sharp Apps +[/plugins](https://github.com/ServiceStack/dotnet-app/tree/master/src/apps/plugins/plugins) folder which are auto-registered +on startup. The source code for all plugins used in this App were built from the .NET Core 3.1 projects in the +[example-plugins](https://github.com/ServiceStack/dotnet-app/tree/master/src/example-plugins) folder. + +{{/markdown}} + + +{{ "apps-links" |> partial({ order }) }} diff --git a/src/wwwroot/sharp-apps/index.html b/src/wwwroot/sharp-apps/index.html new file mode 100644 index 0000000..80a32c7 --- /dev/null +++ b/src/wwwroot/sharp-apps/index.html @@ -0,0 +1,312 @@ + + +

    + Sharp Apps are a new approach to dramatically simplify .NET Wep App development + and provide the most productive development experience possible whilst maximizing reuse and component sharing. + They also open up a number of new use-cases for maintaining clean isolation between front-end and back-end development with + front-end developers not needing any knowledge of C#/.NET to be able to develop UIs for high-performance .NET Web Apps. + Sharp Apps also make it easy to establish and share an approved suite of functionality amongst multiple websites all + consuming the same back-end systems and data stores. +

    + +

    + Sharp Apps leverages #Script to develop entire content-rich, data-driven websites without needing to write any C#, + compile projects or manually refresh pages - resulting in the easiest and fastest way to develop Web Apps in .NET! +

    + +

    Ultimate Simplicity

    + +

    + Not having to write any C# code or perform any app builds dramatically reduces the cognitive overhead and conceptual knowledge + required for development where the only thing front-end Web developers need to know is #Script's familiar syntax + and what methods are available to call. + Because of #Script's JavaScript compatibility, developing a Website with #Script will be instantly familiar to + JavaScript devs despite calling and binding directly to .NET APIs behind the scenes. +

    + +

    + All complexity with C#, .NET, namespaces, references, .dlls, strong naming, packages, MVC, Razor, build tools, IDE environments, etc + has been eliminated leaving all Web Developers needing to do is run the cross-platform + x dotnet tool + and configure a simple + app.settings + text file to specify which website folder to use, which ServiceStack features to enable, which db or redis providers to connect to, etc. + Not needing to build also greatly simplifies deployments where multiple websites can be deployed with a + single rsync or xcopy command or + if deploying your App in a Docker Container, you just need to + copy your website files, or just the app.settings + if you're using an S3 or Azure Virtual File System. +

    + +

    Rapid Development Workflow

    + +

    + The iterative development experience is also unparalleled for a .NET App, no compilation is required so you can just leave + the web dotnet tool running whilst you add .html files needed to build your App and thanks + to the built-in Hot Reloading support, pages will refresh automatically as you save. + You'll just need to do a full page refresh when modifying external .css/.js files to bypass the browser cache and + you'll need to restart web to pick up any changes to your app.settings or added any + .dlls to your /plugins folder. +

    + +

    Getting Started

    + +

    + All Sharp Apps can be run either as .NET Core Web Apps by installing the x dotnet tool: +

    + +
    $ dotnet tool install -g x
    + +Then you can run x open to see which apps are available to install: + +
    $ x open
    + +

    + By default this will list all sharp-apps available: +

    + +
        1. redis       Simple, lightweight, versatile Redis Admin UI
    +    2. spirals     Explore and generate different Spirals with SVG
    +    3. blog        Minimal, multi-user Twitter Auth blogging app
    +    4. rockwind    Example combining Rockstars website + data-driven Northwind Browser
    +    5. redis-html  Redis Admin Viewer developed as server-generated HTML Website
    +    6. plugins     Extend Apps with Plugins, ServiceStack Services and other C# extensions
    +    7. chat        Extensible App with custom AppHost leveraging OAuth + SSE for real-time Chat
    +    8. bare        Basic Sharp Content Website
    +
    +Usage: web open <name>
    + +

    + Where any of the apps can be installed by specifying the name, e.g. + spirals can be run with: +

    + +
    $ x open spirals
    + +

    + Which will run the latest version of the spirals App each time. + After an App has been run once or installed, you can run its local version with x run: +

    + +
    $ x run spirals
    + +

    + Each Sharp App can also be run as a + .NET Core Windows Desktop App + by installing the app dotnet tool: +

    + +
    $ dotnet tool install -g app
    + +

    + Then running the Web App with app instead of web. +

    + +
    $ app open spirals
    + +

    Spirals

    +
    + x install spirals - + spirals.web-app.io - + sharp-apps/spirals +
    + +

    + Spirals is a good example showing how easy it is to create .NET Core Desktop Web Apps utilizing HTML5's familiar and simple development + model to leverage advanced Web Technologies like SVG in a fun, interactive and live development experience. +

    + + +

    + +

    + + +
    + +

    + +

    + See the Making of Spirals for a walk through on how to create + the Spirals Web App from scratch. +

    + +

    Sharp App

    + +

    + The easiest way to create an empty Sharp App is to run app init from an Empty App Directory: +

    + +
    $ md Acme && cd Acme && web init
    + +

    + Which will create an empty Sharp App as seen in the Spirals video above: +

    + + + +

    SharpApp Project Templates

    + +

    + A more complete starting option is to start from one of the project templates below. All project templates can be installed using the + x new tool, which if not already can be installed with: +

    + +
    $ dotnet tool install --global x
    + +

    Bare SharpApp

    + +

    + To start with a simple and minimal website, create a new bare-app project template: +

    + + + +
    $ x new bare-app ProjectName
    + +

    + This creates a multi-page Bootstrap Website with Menu navigation that's ideal for content-heavy Websites. +

    + +

    Parcel SharpApp

    + +

    + For more sophisticated JavaScript Sharp Apps we recommended starting with the + parcel-app project template: +

    + + + +
    $ x new parcel-app ProjectName
    + +

    + This provides a simple and powerful starting template for developing modern JavaScript .NET Core Sharp Apps utilizing the + zero-configuration Parcel bundler to enable a rapid development workflow with access to + best-of-class web technologies like TypeScript that's managed by pre-configured + npm scripts to handle its entire dev workflow. +

    + +

    + If needed, it includes an optional server + component containing any C# extensions needed that's automatically built and deployed to the app's /plugins folder when started. + This enables an extensible .NET Core Web App with an integrated Parcel bundler to enable building sophisticated JavaScript + Apps out-of-the-box within a live development environment. +

    + +

    Cloud Apps Starter Projects

    + +

    + If you intend to deploy your Web App on AWS or Azure you may prefer to start with one of the example + Cloud Apps below which come pre-configured with deployment scripts for deploying with Travis CI and Docker: +

    + + + +

    About Bare WebApp

    + + +

    + The Getting Started project contains a copy of the bare-app.web-templates.io project below + which is representative of a typical Company splash Website: +

    + +Bare WebApp screenshot + +

    + The benefits over using a static website is improved maintenance + as you can extract and use its common _layout.html + instead of having it duplicated in each page. + The menu.html partial also makes menu items + easier to maintain by just adding an entry in the JavaScript object literal. The dynamic menu also takes care of highlighting the active menu item. +

    + +
    {{ 'examples/webapps-menu.html' |> includeFile }}
    + +

    Ideal for Web Designers and Content Authors

    + +

    + The other primary benefit is that this is an example of a website that can be maintained by employees who don't have any + programming experience as #Script in their basic form are intuitive and approachable to non-developers, e.g: + The title of each page is maintained as metadata HTML comments: +

    + +
    <!--
    +title: About Us
    +-->
    +
    + +

    + #Script syntax is also the ideal way to convey variable substitution, e.g: <title>{{ pass: title }}</title> + and even embedding a partial reads like english {{ pass: 'menu' |> partial }} which is both intuitive and works well + with GUI HTML designers. +

    + +
    app.settings
    + +

    + Below is the app.settings for a Basic App, with contentRoot being the only setting required as the + rest can be inferred but including the other relevant settings is both more descriptive to other developers as well + making it easier to + use tools like sed or + powershell to replace them + during deployment. +

    + +
    debug true
    +name Bare Web App
    +contentRoot ~/../bare
    +webRoot ~/../bare
    + +
    + debug true controls the level of internal diagnostics available and whether or not Hot Reloading is enabled. +
    + +

    About Parcel WebApp

    + + +{{ 'gfm/sharp-apps/13.md' |> githubMarkdown }} + +

    Example Sharp Apps

    + +

    + In addition to the templates there's a number of Sharp Apps to illustrate the various features available and to showcase the different + kind of Web Apps that can easily be developed. The source code for each app is available either individually from + github.com/sharp-apps. +

    + +{{#markdown}} + +- [Spirals](/sharp-apps/spirals) +- [Redis](/sharp-apps/redis) +- [Rockwind](/sharp-apps/rockwind) +- [Plugins](/sharp-apps/plugins) +- [Chat](/sharp-apps/chat) +- [Blog](/sharp-apps/blog) +- [SharpData](/sharp-apps/sharpdata) + +{{/markdown}} + +{{ "apps-links" |> partial({ order }) }} + diff --git a/src/wwwroot/sharp-apps/plugins.html b/src/wwwroot/sharp-apps/plugins.html new file mode 100644 index 0000000..70d5a93 --- /dev/null +++ b/src/wwwroot/sharp-apps/plugins.html @@ -0,0 +1,133 @@ + + +
    + x open plugins - + plugins.web-app.io - + sharp-apps/Plugins +
    + +

    + Up till now the Apps above only have only used functionality built into ServiceStack, to enable even greater functionality + but still retain all the benefits of developing Web Apps you can drop .dll with custom functionality into your + Web App's /plugins folder. The plugins support in Web Apps is as friction-less as we could make it, there's no + configuration to maintain or special interfaces to implement, you're able to drop your existing implementation .dll's + as-is into the App's `/plugins` folder. +

    + +

    + Plugins allow "no touch" sharing of + ServiceStack Plugins, + Services, + Script Methods + Sharp Code Pages, + Validators, etc. + contained within .dll's or .exe's dropped in a Sharp App's + /plugins folder which are auto-registered + on startup. The source code for all plugins used in this App were built from the .NET Core 3.1 projects in the + /example-plugins folder. The + plugins.web-app.io Sharp App below walks through examples of using Custom Filters, + Services and Validators: +

    + +Plugins WebApp screenshot + +

    Registering ServiceStack Plugins

    + +

    + ServiceStack Plugins can be added to your App by + listing it's Type Name in the features config entry in + app.settings: +

    + +
    debug true
    +name Web App Plugins
    +contentRoot ~/../plugins
    +webRoot ~/../plugins
    +features CustomPlugin, OpenApiFeature, PostmanFeature, CorsFeature, ValidationFeature
    +CustomPlugin { ShowProcessLinks: true }
    +ValidationFeature { ScanAppHostAssemblies: true }
    +
    + +

    + All plugins listed in features will be added to your Web App's AppHost in the order they're specified. + They can further customized by adding a separate config entry with the Plugin Name and a JavaScript Object literal to + populate the Plugin at registration, e.g the config above is equivalent to: +

    + +{{ 'gfm/sharp-apps/04.md' |> githubMarkdown }} + +

    Custom Plugin

    + +

    + In this case it tells our CustomPlugin + from /plugins/ServerInfo.dll to also show Process Links in its + /metadata Page: +

    + +{{ 'gfm/sharp-apps/05.md' |> githubMarkdown }} + +

    + Where as it was first registered in the list will appear before any links registered by other plugins: +

    + +Metadata screenshot + +

    Built-in Plugins

    + +

    + It also tells the ValidationFeature to scan all Service Assemblies for Validators and to automatically register them + which is how ServiceStack was able to find the + ContactValidator + used to validate the StoreContact request. +

    + +

    + Other optional plugins registered in this Web App is the metadata Services required for + Open API, + Postman as well as + support for CORS. + You can check the /metadata/debug Inspector for all Plugins loaded in your AppHost. +

    + +

    .NET Extensibility

    + +

    + Plugins can also implement .NET Core's IStartup to be able to register any required dependencies without any coupling to any Custom AppHost. +

    + +

    + To simplify configuration you can use the plugins/* wildcard in + app.settings + at the end of an ordered plugin list to register all remaining Plugins it finds in the apps `/plugins` folder: +

    + + +
    features OpenApiFeature, PostmanFeature, CorsFeature, ValidationFeature, plugins/*
    +CustomPlugin { ShowProcessLinks: true }
    +
    + +

    + Each plugin registered can continue to be furthered configured by specifying its name and a JavaScript object literal as seen above. +

    + +

    + The /plugins2 App shows an example of this with the + StartupPlugin + registering a StartupDep dependency which is used by its StartupServices at runtime: +

    + +{{ 'gfm/sharp-apps/12.md' |> githubMarkdown }} + +

    ServiceStack Ecosystem

    + +

    + All Services loaded by plugins continue to benefit from ServiceStack's rich metadata services, including being listed + in the /metadata page, being able to explore and interact with Services using + /swagger-ui/ as well as being able to generate Typed APIs for the most popular + Mobile, Web and Desktop platforms. +

    + +{{ "apps-links" |> partial({ order }) }} diff --git a/src/wwwroot/sharp-apps/redis.html b/src/wwwroot/sharp-apps/redis.html new file mode 100644 index 0000000..c4542ea --- /dev/null +++ b/src/wwwroot/sharp-apps/redis.html @@ -0,0 +1,195 @@ + + +

    Redis HTML

    +
    + x open redis-html - + redis-html.web-app.io - + sharp-apps/redis-html +
    + +

    + For the Redis Browser Web App, we wanted to implement an App that was an ideal candidate for a Single Page App but constrain ourselves + to do all HTML rendering on the server and have each interaction request a full-page reload to see how a traditional server-generated + Web App feels like with the performance of .NET Core 3.1 and #Script. We're pleasantly surprised with the result as when + the App is run locally the responsiveness is effectively indistinguishable from an Ajax App. When hosted on the Internet + there is a sub-second delay which causes a noticeable flicker but it still retains a pleasant UX that's faster than most websites. +

    + +

    + The benefits of a traditional website is that it doesn't break the web where the back button and deep linking work without effort + and you get to avoid the complexity train of adopting a premier JavaScript SPA Framework's configuration, dependencies, workflow + and build system which has become overkill for small projects. +

    + +Redis HTML WebApp Screenshot + +

    + We've had a sordid history developing Redis UI's which we're built using the popular JavaScript frameworks that appeared + dominant at the time but have since seen their ecosystem decline, starting with the + Redis Admin UI (src) + built using + Google's Closure Library that as it works different to everything else + needed a complete rewrite when creating redisreact.servicestack.net + (src) using the hot new React framework, unfortunately it uses React's old + deprecated ES5 syntax and Reflux which is sufficiently different from our current recommended + TypeScript + React + Redux + WebPack JavaScript SPA Stack, + that is going to require a significant refactor to adopt our preferred SPA tech stack. +

    + +
    Beautiful, succinct, declarative code
    + +

    + The nice thing about generating HTML is that it's the one true constant in Web development that will always be there. + The entire functionality for the Redis Web App is contained in a single + /redis-html/app/index.html which includes + all Template and JavaScript Source Code in < 200 lines which also includes all as server logic as it doesn't rely on any + back-end Services and just uses the Redis Scripts to interface with Redis directly. + The source code also serves as a good + demonstration of the declarative coding style that #Script encourages that in addition to being highly-readable requires orders + of magnitude less code than our previous Redis JavaScript SPA's with a comparable feature-set. +

    + +

    + Having a much smaller code-base makes it much easier to maintain and enhance whilst being less susceptible to becoming obsolete + by the next new JavaScript framework as it would only require rewriting 75 lines of JavaScript instead of the complete rewrite + that would be required to convert the existing JavaScript Apps to a use different JavaScript fx. +

    + +
    app.settings
    + +

    + The app.settings for Redis is similar to Web App Starter above except it adds a redis.connection + to configure a RedisManagerPool at the + connection string provided + as well as Redis Scripts to give the scripts access to the Redis instance. +

    + +
    debug true
    +name Redis Web App
    +contentRoot ~/../redis-html
    +webRoot ~/../redis-html
    +redis.connection localhost:6379
    + +

    Redis Vue

    +
    + x open redis - + redis.web-app.io - + sharp-apps/Redis +
    + +

    + Whilst the above server-generated HTML Redis UI shows how you can easily develop traditional Web Apps using #Script, we've also + rewritten the Redis UI as a Single Page App which is the more suitable choice for an App like this as it provides a more + optimal and responsive UX by only loading the HTML page once on Startup then utilizes Ajax to only download and update + the incremental parts of the App's UI that needs changing. +

    + +

    + Instead of using jQuery and server-side HTML this version has been rewritten to use Vue + where the UI has been extracted into isolated Vue components utilizing + Vue X-Templates to render the App on the client where + all Redis Vue's functionality is contained within the + Redis/app/index.html page. +

    + +Redis Vue WebApp Screenshot + +

    Simple Vue App

    + +

    + #Script also provides a great development experience for Single Page Apps which for the most part gets out of your way letting you + develop the Single Page App as if it were a static .html file, but also benefits from the flexibility of a + dynamic web page when needed. +

    +

    + The containing _layout.html + page can be separated from the index.html page + that contains the App's functionality, where it's able to extract the title of the page and embed it in the + HTML <head/> as well as embed the page's <script /> in its optimal location at the bottom of the + HTML <body/>, after the page's blocking script dependencies: +

    + +{{ 'gfm/sharp-apps/07.md' |> githubMarkdown }} + +

    + Redis Vue avoids the complexity of adopting a npm build system by referencing Vue libraries as a simple script include: +

    + +
    <script src="../assets/js/vue{{ pass: '.min' |> if(!debug) }}.js">
    + +

    + Where it uses the more verbose and developer-friendly + vue.js + during development whilst using the production optimized + vue.min.js + for deployments. So despite avoiding the complexity tax of an npm-based build system it still gets some of its benefits + like conditional deployments and effortless hot reloading. +

    + +

    Server Pages

    + +

    + Whilst most of index.html is a static Vue + app, #Script is leveraged to generate the body of the <redis-info/> Component on the initial home page render: +

    + +{{ 'gfm/sharp-apps/08.md' |> githubMarkdown }} + +

    + This technique increases the time to first paint by being able to render the initial Vue page without waiting for an Ajax call response + whilst benefiting from improved SEO from server-generated HTML. +

    + +

    Server Handling

    + +

    + Another area #Script is used is to handle the HTTP POST where it calls the redisChangeConnection filter to change + the current Redis connection before rendering the + connection-info.html partial with the + current connection info: +

    + +{{ 'gfm/sharp-apps/09.md' |> githubMarkdown }} + +

    Vue Ajax Server APIs

    + +

    + All other Server functionality is invoked by Vue using Ajax to call one of the Ajax APIs below implemented as + API Pages: +

    + +
    search.html
    + +

    + Called when searching for Redis keys where the query is forwarded to the redisSearchKeys filter: +

    + +{{ 'gfm/sharp-apps/10.md' |> githubMarkdown }} + +
    call.html
    + +

    + Called to execute an arbitrary Redis command on the connected instance, with the response from Redis is returned as a + plain-text HTTP Response: +

    + +{{ 'gfm/sharp-apps/11.md' |> githubMarkdown }} + +

    + The benefits of using API Pages instead of a normal C# Service is being able to retain Web App's productive development workflow + where the entire Redis Vue App is built without requiring any compilation. +

    + +

    Deep linking and full page reloads

    + +

    + The Redis Vue Single Page App also takes advantage of HTML5's history.pushState API to enable deep-linking and back-button + support where most UI state changes is captured on the query string and used to initialize the Vue state on page navigation or + full-page reloads where it provides transparent navigation and back-button support that functions like a traditional Web App but with + the instant performance of a Single Page App. +

    + +{{ "apps-links" |> partial({ order }) }} diff --git a/src/wwwroot/sharp-apps/rockwind.html b/src/wwwroot/sharp-apps/rockwind.html new file mode 100644 index 0000000..2863e20 --- /dev/null +++ b/src/wwwroot/sharp-apps/rockwind.html @@ -0,0 +1,352 @@ + + +
    + x open rockwind - + rockwind-sqlite.web-app.io - + sharp-apps/Rockwind +
    + +

    + The Rockwind website shows an example of combining multiple websites in a single Web App - a + Rockstars Content Website and a dynamic data-driven UI for the Northwind database which can + run against either SQL Server, MySql or SQLite database using just configuration. It also includes + API Pages examples for rapidly developing Web APIs. +

    + +

    Rockstars

    + +

    + /rockstars is an + example of a Content Website that itself maintains multiple sub sections with their own layouts - + /rockstars/alive + for living Rockstars and + /rockstars/dead + for the ones that have died. Each Rockstar maintains their own encapsulated + mix of HTML, markdown content and splash image + that intuitively uses the closest _layout.html, content.md and splash.jpg from the page they're + referenced from. This approach makes it easy to move entire sub sections over by just moving a folder and it will automatically + use the relevant layout and partials of its parent. +

    + +Rockwind WebApp screenshot + +

    Northwind

    + +

    + /northwind is an example of + a dynamic UI for a database containing a + form to filter results, multi-nested + detail pages and + deep-linking for quickly navigating between + referenced data. #Script is also a great solution for rapidly developing Web APIs where the + /api/customers.html + API Page below: +

    + +{{ 'gfm/sharp-apis/02.md' |> githubMarkdown }} + +

    + Is all the code needed to generate the following API endpoints: +

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    /customers API
    All Customers + + + + +
    Accept HTTP Header also supported
    +
    Alfreds Futterkiste Details + + + +
    As List + + + +
    Customers in Germany + + + +
    Customers in London + + + +
    Combination Query + /api/customers?city=London&country=UK&limit=3 +
    + +

    Multi platform configurations

    + +

    + In addition to being a .NET Core 3.1 App that runs flawlessly cross-platform on Windows, Linux and OSX, Sharp Apps can also support + multiple RDBMS's and Virtual File Systems using just configuration. +

    + +

    app.sqlite.settings

    + +

    + SQLite uses a file system database letting you bundle your database with your App. So we can share the + northwind.sqlite database across multiple Apps, + the contentRoot is set to the /apps directory which can only be accessed by your App, whilst + the webRoot is configured to use the Sharp Apps folder that hosts all the publicly accessible files of your App. +

    + +
    debug true
    +name Rockwind SQLite Web App
    +contentRoot ..
    +webRoot .
    +db sqlite
    +db.connection ~/northwind.sqlite
    + +

    + To run the Rockwind app using the northwind.sqlite database, run the command below on Windows, Linux or OSX: +

    + +
    x app.sqlite.settings
    + +
    app.sqlserver.settings
    + +

    + To switch to use the Northwind database in SQL Server we just need to update the configuration to point to a SQL Server database + instance. Since the App no longer need access to the northwind.sqlite database, the contentRoot can be reverted + back to the Sharp Apps folder: +

    + +
    debug true
    +name Rockwind SQL Server Web App
    +port 5000
    +db sqlserver
    +db.connection Server=localhost;Database=northwind;User Id=test;Password=test;
    + +

    + The /support/northwind-data + project lets you quickly try out Rockwind against your local RDBMS by populating it with a copy of the Northwind database + using the same sqlserver identifier and connection string from the App, e.g: +

    + +
    dotnet run sqlserver "Server=localhost;Database=northwind;User Id=test;Password=test;"
    + +

    app.mysql.settings

    + +

    + You can run against a MySql database in the same way as SQL Server above but using a MySql db connection string: +

    + +
    debug true
    +name Rockwind MySql Web App
    +port 5000
    +db mysql
    +db.connection Server=localhost;Database=northwind;UID=root;Password=test;SslMode=none
    + +

    app.azure.settings

    + +

    + The example app.azure.settings + Azure configuration is also configured to use a different Virtual File System where instead of sourcing + Web App files from the filesystem they're sourced from an + Azure Blob Container. + In this case we're not using any files from the App so we don't need to set a contentRoot or webRoot path. + This also means that for deployment we're just deploying the WebApp binaries with just this app.settings since both the + Web App files and database are sourced remotely. +

    + +
    # Note: values prefixed with '$' are resolved from Environment Variables
    +debug false
    +name Azure Blob SQL Server Web App
    +bind *
    +port 5000
    +db sqlserver
    +db.connection $AZURE_SQL_CONNECTION_STRING
    +files azure
    +files.config {ConnectionString:$AZURE_BLOB_CONNECTION_STRING,ContainerName:rockwind-fs}
    +
    +# Reduces a Blob Storage API call, but takes longer for modified pages to appear
    +checkForModifiedPagesAfterSecs 60
    +defaultFileCacheExpirySecs     60
    + +

    + The /support/copy-files + project lets you run Rockwind against your own Azure Blob Container by populating it with a copy of the + /rockwind App's files using + the same configuration above: +

    + +
    dotnet run azure "{ConnectionString:$AZURE_BLOB_CONNECTION_STRING,ContainerName:rockwind}"
    + +

    Multi-RDBMS SQL

    + +

    + As #Script is unable to use a Typed ORM like OrmLite + to hide the nuances of each database, we need to be a bit more diligent in #Script to use parameterized SQL that works across + multiple databases by using the + sql* DB Filters to avoid using RDBMS-specific + SQL syntax. The + /northwind/customer.html + contains a good example containing a number of things to watch out for: +

    + +{{ 'gfm/sharp-apps/01.md' |> githubMarkdown }} + +

    + Use sqlConcat to concatenate strings using the RDBMS-specific SQL for the configured database. Likewise + sqlCurrency utilizes RDBMS-specific SQL functions to return monetary values in a currency format, whilst + sqlQuote is used for quoting tables named after a reserved word. +

    + +
    + Of course if you don't intend on supporting multiple RDBMS's, you can ignore this and use RDBMS-specific syntax. +
    + +

    Rockwind VFS

    +
    + x open rockwind-aws - + rockwind-aws.web-app.io - + sharp-apps/rockwind-aws +
    + +

    + /rockwind-vfs is a clone of + the Rockwind Web App with 3 differences: It uses the resolveAsset filter for each .js, .css and + image web asset so that it's able to generate external URLs directly to the S3 Bucket, Azure Blob Container or CDN + hosting a copy of your files to both reduce the load on your Web App and maximize the responsiveness to the end user. +

    + +

    + To maximize responsiveness when using remote storage, all + embedded files utilize caching: +

    + +
    {{ pass: "content.md" |> includeFileWithCache |> markdown }}
    + +

    + The other difference is that each table and column has been quoted in "double-quotes" so that it works in PostgreSQL which + otherwise treats unquoted symbols as lowercase. This version of Rockwind also works with SQL Server and SQLite as they also + support "Table" quotes but not MySql which uses `BackTicks` or [SquareBrackets]. It's therefore + infeasible to develop Apps that support both PostgreSQL and MySql unless you're willing to use all lowercase, + snake_case or the sqlQuote filter for every table and column. +

    + +Rockwind VFS WebApp screenshot + +

    resolveAsset

    + +

    + If using a remote file storage like AWS S3 or Azure Blob Storage it's a good idea to use the resolveAsset filter + for each external file reference. By default it returns the same path it was called with so it will continue to work locally + but then ServiceStack effectively becomes a proxy where it has to call the remote Storage Service for each requested download. +

    + +{{ 'gfm/sharp-apps/02.md' |> githubMarkdown }} + +

    + ServiceStack asynchronously writes each file to the Response Stream with the last Last-Modified HTTP Header to + enable browser caching so it's still a workable solution but for optimal performance you can specify an args.assetsBase + in your app.settings to populate the assetsBase ScriptContext Argument the resolveAsset filter uses to generate + an external URL reference to the file on the remote storage service, reducing the load and improving the performance of your App, + especially if it's configured to use a CDN. +

    + +

    Pure Cloud Apps

    + +
    rockwind-aws/app/app.settings
    + +

    + The AWS settings shows an example of this where every external resource + rockwind-aws.web-app.io has been replaced with a direct reference to the + asset on the S3 bucket: +

    + +
    # Note: values prefixed with '$' are resolved from Environment Variables
    +debug false
    +name AWS S3 PostgreSQL Web App
    +db postgres
    +db.connection $AWS_RDS_POSTGRES
    +files s3
    +files.config {AccessKey:$AWS_S3_ACCESS_KEY,SecretKey:$AWS_S3_SECRET_KEY,Region:us-east-1,Bucket:rockwind}
    +args.assetsBase http://s3-postgresql.s3-website-us-east-1.amazonaws.com/
    +
    +# Reduces an S3 API call, but takes longer for modified pages to appear
    +checkForModifiedPagesAfterSecs 60
    +defaultFileCacheExpirySecs     60
    + +

    + With all files being sourced from S3 and the App configured to use AWS RDS PostgreSQL, the AWS settings is an example of + a Pure Cloud App where the entire App is hosted on managed cloud services that's decoupled from the .NET Core 3.1 binary + that runs it that for the most part won't require redeploying the Web App binary unless making configuration changes or + upgrading the x dotnet tool + as any App changes can just be uploaded straight to S3 which changes reflected within the + checkForModifiedPagesAfterSecs setting, which tells the Web App how long to wait before checking for file changes + whilst defaultFileCacheExpirySecs specifies how long to cache files like content.md for. +

    + +
    DockerFile
    + +

    + Deployments are also greatly simplified as all that's needed is to deploy the WebApp binary and app.settings of your Cloud App, + e.g. here's the DockerFile for rockwind-aws.web-app.io - deployed to AWS ECS + using the deployment scripts in rockwind-aws and following our + .NET Core Docker Deployment Guideline: +

    + +{{ 'gfm/sharp-apps/03.md' |> githubMarkdown }} + +
    rockwind-azure/app/app.settings
    + +

    + We can also create Azure Cloud Apps in the same we've done for AWS above, which runs the same + /rockwind-vfs + Web App but using an Azure hosted SQL Server database and its files hosted on Azure Blob Storage: +

    + +
    # Note: values prefixed with '$' are resolved from Environment Variables
    +debug false
    +name Azure Blob SQL Server Web App
    +bind *
    +db sqlserver
    +db.connection $AZURE_SQL_CONNECTION_STRING
    +files azure
    +files.config {ConnectionString:$AZURE_BLOB_CONNECTION_STRING,ContainerName:rockwind}
    +args.assetsBase https://servicestack.blob.core.windows.net/rockwind/
    +
    +# Reduces an S3 API call, but takes longer for modified pages to appear
    +checkForModifiedPagesAfterSecs 60
    +defaultFileCacheExpirySecs     60
    + +{{ "apps-links" |> partial({ order }) }} diff --git a/src/wwwroot/sharp-apps/sharpdata.html b/src/wwwroot/sharp-apps/sharpdata.html new file mode 100644 index 0000000..909c673 --- /dev/null +++ b/src/wwwroot/sharp-apps/sharpdata.html @@ -0,0 +1,14 @@ + + +
    + x open sharpdata - + sharpdata.netcore.io - + NetCoreApps/SharpData +
    + +{{ 'gfm/apps/02.md' |> githubMarkdown }} + +{{ "apps-links" |> partial({ order }) }} diff --git a/src/wwwroot/sharp-apps/spirals.html b/src/wwwroot/sharp-apps/spirals.html new file mode 100644 index 0000000..af76219 --- /dev/null +++ b/src/wwwroot/sharp-apps/spirals.html @@ -0,0 +1,39 @@ + + +
    + x open spirals - + spirals.web-app.io - + sharp-apps/spirals +
    + +

    + Spirals is a good example showing how easy it is to create .NET Core Desktop Web Apps utilizing HTML5's familiar and simple development + model to leverage advanced Web Technologies like SVG in a fun, interactive and live development experience. +

    + + +

    + +

    + + +
    + +

    + +{{ 'gfm/apps/01.md' |> githubMarkdown }} + +{{ "apps-links" |> partial({ order }) }} diff --git a/src/wwwroot/sidebar.html b/src/wwwroot/sidebar.html index 40c067a..e61dbde 100644 --- a/src/wwwroot/sidebar.html +++ b/src/wwwroot/sidebar.html @@ -11,6 +11,15 @@
    docs
    +
  • +
    sharp apps
    +
      + {{#each appsLinks}} +
    • htmlClass }}>{{Value}}
    • + {{/each}} +
    +
  • +
  • code
      From d4e303e79f3d35d72100819347cabe26b6a0a297 Mon Sep 17 00:00:00 2001 From: Demis Bellot Date: Mon, 15 Jun 2020 21:35:17 +0800 Subject: [PATCH 147/206] Update Sharp Apps + add Studio + youtube preview --- src/wwwroot/assets/css/default.css | 13 ++ src/wwwroot/assets/img/youtube-play-hover.png | Bin 0 -> 2891 bytes src/wwwroot/assets/img/youtube-play.png | Bin 0 -> 2007 bytes src/wwwroot/assets/js/default.js | 51 +++++++ src/wwwroot/gfm/apps/01.html | 8 +- src/wwwroot/gfm/apps/01.md | 7 + src/wwwroot/gfm/apps/03.html | 113 ++++++++++++++ src/wwwroot/gfm/apps/03.md | 142 ++++++++++++++++++ src/wwwroot/index.html | 24 +-- src/wwwroot/sharp-apps/index.html | 2 + src/wwwroot/sharp-apps/sharpdata.html | 2 +- src/wwwroot/sharp-apps/spirals.html | 25 --- src/wwwroot/sharp-apps/studio.html | 26 ++++ src/wwwroot/sharp-apps/win32.html | 13 ++ 14 files changed, 380 insertions(+), 46 deletions(-) create mode 100644 src/wwwroot/assets/img/youtube-play-hover.png create mode 100644 src/wwwroot/assets/img/youtube-play.png create mode 100644 src/wwwroot/gfm/apps/03.html create mode 100644 src/wwwroot/gfm/apps/03.md create mode 100644 src/wwwroot/sharp-apps/studio.html create mode 100644 src/wwwroot/sharp-apps/win32.html diff --git a/src/wwwroot/assets/css/default.css b/src/wwwroot/assets/css/default.css index 2ad40dd..a8fab6c 100644 --- a/src/wwwroot/assets/css/default.css +++ b/src/wwwroot/assets/css/default.css @@ -247,6 +247,19 @@ caption { background-color: #f8f8f8; } +.youtube-play { + background: no-repeat url(/assets/img/youtube-play.png); + display: block; + position: absolute; + opacity: 0.8; +} +.youtube-play:hover { + background: no-repeat url(/assets/img/youtube-play-hover.png); + opacity: 1; + background-color: rgba(255,255,255,.1); + cursor: pointer; +} + @media (max-width: 1366px) { #content { margin-left: 240px; diff --git a/src/wwwroot/assets/img/youtube-play-hover.png b/src/wwwroot/assets/img/youtube-play-hover.png new file mode 100644 index 0000000000000000000000000000000000000000..bddb0ef3b5e9a922420ee12623d2a2f825ebb28c GIT binary patch literal 2891 zcmV-R3$*l!P)Px#1ZP1_K>z@;j|==^1poj532;bRa{vGi!vFvd!vV){sAK>D3gAgZK~#8N?Oc0o z9K{{}&CK4zcYfUk*CaS~f(ao3LYneYC<+y7Ap+X|Q`%BmmHq=Xw3Uz$AS%>{DkvaC zt5#G6wSSPdqN@6$s1gWJi9{)?pb#}q$b-BcC${7B`SxxfGt=+4KD&u?20w_`srFlc z-ksT*-Pvz``l8V{D4Vg`wp$W8GA3t~$O%gsNtD4c(>S!?#abZ|CqiS=QV2%~-e-leB2_N|$03YJ zB;yz<$Vb5}juU8W10`iZgd9Q`jzYY>Y$JjwbFq0mhanF<8x03A1H3`%9Sn6X}Zp_bd@Hv+!3l3 zCC{Ve85Gt*l4c0kB*~_*RuDnqsJxv*VB+|ErL)2N<(sx;Ou~4+NPP>L1qHa4aIDg- zL4=RWs9@VgMTN*v!?jdGV}~7*y@3t?IiSj~tbL~+X{N@M<+H0-r}s;JTT0k_5bT6` zWJ$1iv%aP?9;gh9TP#Fn)S$;M+#`2){b$Fk5X97zoP>r#9bUDn+c@D5aHw1Af+lDH zLDqwh`nmSRBi%c89Ii)C;1qH9U3A{398!;>mM*DBCHP>xTs7hCE4J<4RS!piPyZkG ztXkGBt$YZ=uBk^Qm>xDXN%`Qv*L3FV;Rq**D^&VRu;q2!Aai4zEYn_;A#rOiZ24SW z$_V3bLA4ma?BfXL<3YibyyMLdTx2 zoa)Brn7B@h^MrC31>%z@j`(gP%n})B}lOKHZ<{sGp7==V_6Os>>A3U zh2y9G4eDTiC~{3CFIhu6+Y~*yZj|QCBDEf=$*gp(Em~upMl~>B6sZ&~zUN-*{N3+~ zZvFz53q|x7ZNs!@25nl4@GRM?L@TUCA+BK^%qQDctf1xhKR}&7dy)>^a3dXu=qPBz zv#}8ZxVVUpEL%?D!2^`5R-y31lbG*3W9S9- zjbdRVe8l>Cd@PU|hcaBTHJ9crKl5D%$DpH3e?xNluApMkUKg`*`XnBOzJ6P z1j-N)y`pX}wr_NVxFT-1(8iWK4RIN4#rb@mmM&dN3cZV?2)h?vL<109?cPmITGvy? z1e8aUeM#NHZh_(!H!~w~$H5I=M@I*>x3^Q(^QhL=M#tB!i~7g1;*#UKBxAZTZ3yDF z7dA>kv0HBYEW~Au`{Hwum#z7DxlBGRdgOu&XaqeCe{g_uZiS6$-XO5x-gycA+ zH|K~9+(Qo$O*1 zMXTUZ^;oumiO6wKva0Guuce)C(}^_8#EF*H^+Snfi6<{Mh8 zWnjsltopnYh4~=p(g+Q|@FMkYyn*)Jdms5n`pKZD-tc%alTp>ylfEI3!xPRpmSt*a zjI;*-@JHIS=`MP2(3sf#g`L%1P_Upy~;O2Fp25Az`f(+<+)REGc|I%(5qll442@}qoJZ2cw;i28G z>&w@x)kSaaIy)hgHL^6Rov@lPug%nYaq4kP)@kgNneA0TJlrX8KP#*;N!!k2jA5`PsPH|Ya+{Ku=ScFMpsum>Ea?O z&lf`Qv{nhGfxw#5D^>T$e{xG(V71&+K&K)4J=z3x$(Z0Bzjmli$WoVbRw*H}kXKT{`Bc``L881`w_)+jYYDZC@x2YC2PGmyjs6YX4lG8x+g=Tji;Eyf$AUW zB&b6l3&X8Xj*dL?Y-MCinam!^04`}K)%E3k^JSN&vsbh`YAGCX zvm)7)79uCeVkH!JH^%Y*VC5$Y621Q7w=@t83_NRHZEbl}+ktnqx8Fq z)8;rBxr1>`WsU4_b6^oHz`WagQmKw5(kT>#(^62fwONRKyOOyK#Lg;_&03j7twhrf zY9Z1YR8op$3N?^&B(`ldKcliBDnsk)5SS3w7XA7tF0>L|O(Q}C!az_E!VT7p^*lkoBJ1;`R|x3WELDB}b_d&|+SFWY ztDdmc0j7tPgRfgX%NhA6q~+=NqBNWjN-0Px#1ZP1_K>z@;j|==^1poj532;bRa{vGi!2kdb!2!6DYwZ942W&}1K~#8N?VM|9 z6jv0-&(7?_ePojwo5ZSlV2u(=G)8=e+6F0!Sd4|(4}u6OZP8+}iUmxcrMhw*^u|Cq6WZmp0vyb$D?oO7(M;enQJCn{2F8AJZXE(F| zoIUrRJF{b=QBql1Y3=Ijvip3#spz`dW-^(ka2*bZEum1zYPZ{SU`tiU(gw!|iAJNc zqA2O8&8lg_kYzb&wOYGzP6Mim1_lNqsJc*xV-})dFnG&sHus{iEzDy$=Kx+COifKy z($dnpJsyt|J9=bB6nE9CRjGL6!ayLf4uWLk54{F+7Ru}503xSan=xip+qwt;fPPdg zdL;rV|%XO)?wze7L@qgB{j3lnKV8MbfQGbDwhBD#arcqc-7XKl< zc^kg1=PqBq9QXX8yhvPAQBl!6{C75^{aMUp@G>MxI*Ri3_3PLFR84W76_`;vc2|$+REG)@n7MD}tp_!V&IKdpo z``ePFD2feX!KT^`5&&Xu3Y=Dig*VVo>PjIcC57hCpHHc&sl0`!T6K-&a5$DoX0w^a zO)4&zi+1ncO~;NMqxI|83z78>oW#v*GLfojfr5ep+O};Q9X@=RN=r*st8Pi-@8GaK z-DHDVmUrmr;K74*{P=O2GiQ#l=@65gk&*EsCN$o|O&T0?%gf7Y>Quh4Eb?<+&d|)6 zGimke)s&NyL;d~z)Y;j|8zbTvtrHH18|2K)%=Ks&#!SiKS#gKLUYnnvPbDQKR9INZ zm%i!VJ+WP?Y2dKebu%@)-ELa3Vuc77H8nMqo13fom4&X>Jsd7XNwS`MRdEUK7G16q8)TAF2kOjot;e^Hf*5Nr%zK=Rh1B( z?OH(2%F6lxwPq)@(ZI2n!^Psoi^asp`2BuSFtLb!5~!OL;tnM}JzWUAbm>x>HfMHA!!8uZX35h2p@01Ju^mrjBSI329@O z+SU~8mAsVNvSkbH-@jkP*l_rTzw6K>PN(xehXqZ2w|Fz0McuV)7j4?Ki5eRlh4<xBy!G#^t(f_0T=%$Tt-5{bO0+L8kAYPGbq(5X|W=*Ep3 znp&%l6|2?ytps7))qc|O`FvDYS4WQ?J>t7nRI9$hk}<&IK0sR^msE9v$z%$^Vd^D| z#iF2Nh?_vnW^=%9w}<(Tn*lDW-5>!VB>1t!jY`PL1L*ivyFmhgp6!aFM5L6IlrGHH zk5s!s0zjy3hNdCD%Q1+4QZ6!0R^YkbS+iy-Qg?T^581>Ww#9}F#)%)|-tl-m5vjhu zJ_I-Z9>zdy%wRlVad6-1bh5L?wj3euB^dwUw!ye*C@n4hiyQG@rVZUmrir;u{ETq{FX9H_FElci1M_up3)DIy#h`oSZ8V_X)cBDEu5TgZ}`Ffz5uu z|2u@%VNN;BbD3+`uKC<<_ffo{0^KGQ4im=S!}CPpgtx3#>)X)lL`zG{bDPj3-pE>6 zSt;MWd-sz_B=Ro0OHlX+VphB<|Kjrh_6UJ694?5@&RYe4WBK|EbUEJ8(7;=^FJx2# zm6es*dU|>a@MhxqjG<5{1C!q@IrsqW_A6JeJXObE%2>Tm zDlRUTT`rdp5%Fedc(JH8U#&IS?RGJS55Q-ibZ>`10}y9en1i#36;d!5jNp8u5O=_4 pvx#FeGBTn_W%;u6D{c=E{R{O8;D1acKQsUU002ovPDHLkV1ivz>ev7P literal 0 HcmV?d00001 diff --git a/src/wwwroot/assets/js/default.js b/src/wwwroot/assets/js/default.js index 3a66d9e..05ccf16 100644 --- a/src/wwwroot/assets/js/default.js +++ b/src/wwwroot/assets/js/default.js @@ -165,3 +165,54 @@ function queryStringParams(qs) { } return params; } + + +function video(url) { + return ''; +} +function playVideo(el,id) { + var url = 'https://www.youtube.com/embed/' + id + '?autoplay=1'; + $(el).parent().html(video(url)); +} + +function splitOnFirst(s, c) { + if (!s) return [s]; + var pos = s.indexOf(c); + return pos >= 0 ? [s.substring(0, pos), s.substring(pos + 1)] : [s]; +} +function splitOnLast(s, c) { + if (!s) return [s]; + var pos = s.lastIndexOf(c); + return pos >= 0 + ? [s.substring(0, pos), s.substring(pos + 1)] + : [s]; +} +function preloadImage(src) { + var img = new Image(); + img.src = src; +} + +function bindYouTubePlay(img) { + var a = img.parent(); + var p = a.parent(); + if (p[0] && p[0].tagName === 'P') { + var id = splitOnFirst(splitOnLast(a.attr('href'),'/')[1],'?')[0]; + var width = Math.floor(img[0].offsetWidth/2 - 43); + var height = Math.floor(img[0].offsetHeight/2 - 31); + var html = ``; + p.prepend(html); + } +} + +function bindYouTubeImages() { + preloadImage('/images/youtube-play-hover.png'); + $("a[href^='https://youtu.be/']>img").each(function() { + if (this.complete) { + bindYouTubePlay($(this)); + } else { + this.onload = function() { bindYouTubePlay($(this)); } + } + }); +} + +$(bindYouTubeImages); diff --git a/src/wwwroot/gfm/apps/01.html b/src/wwwroot/gfm/apps/01.html index c6e630f..4ec5746 100644 --- a/src/wwwroot/gfm/apps/01.html +++ b/src/wwwroot/gfm/apps/01.html @@ -1,4 +1,10 @@ -

      +

      Spirals is a good example showing how easy it is to create .NET Core Desktop Web Apps utilizing HTML5's familiar and simple development +model to leverage advanced Web Technologies like SVG in a fun, interactive and live development experience.

      +
      +

      YouTube: youtu.be/2FFRLxs7orU

      +
      +

      +

      The Making of Spirals

      Spirals is a good example showing how easy it is to create .NET Core Desktop Web Apps utilizing HTML5's familiar and simple development model to leverage advanced Web Technologies like SVG in a fun, interactive and live development experience.

      diff --git a/src/wwwroot/gfm/apps/01.md b/src/wwwroot/gfm/apps/01.md index 23f2c35..0b59e3a 100644 --- a/src/wwwroot/gfm/apps/01.md +++ b/src/wwwroot/gfm/apps/01.md @@ -1,3 +1,10 @@ +Spirals is a good example showing how easy it is to create .NET Core Desktop Web Apps utilizing HTML5's familiar and simple development +model to leverage advanced Web Technologies like SVG in a fun, interactive and live development experience. + +> YouTube: [youtu.be/2FFRLxs7orU](https://youtu.be/Cf-vstYXrmY) + +[![](https://raw.githubusercontent.com/ServiceStack/docs/master/docs/images/app/spirals/animated.png)](https://youtu.be/Cf-vstYXrmY) + ## The Making of Spirals Spirals is a good example showing how easy it is to create .NET Core Desktop Web Apps utilizing HTML5's familiar and simple development model to diff --git a/src/wwwroot/gfm/apps/03.html b/src/wwwroot/gfm/apps/03.html new file mode 100644 index 0000000..d1b5ad6 --- /dev/null +++ b/src/wwwroot/gfm/apps/03.html @@ -0,0 +1,113 @@ +

      The win32 Sharp App contains an examples dashboard of invoking different native Win32 functions:

      +

      +

      You can run this Gist Desktop App via URL Scheme from:

      +
      [app://win32](app://win32)
      +
      +

      Or via command-line:

      +
      $ app open win32
      +
      +

      The main source code of this component is in Win32/index.ts, +which makes use of the built in TypeScript APIs below from @servicestack/desktop:

      +
      start('%USERPROFILE%\\\\.sharp-apps')
      +
      +openUrl('https://google.com')
      +
      +messageBox('The Title', 'Caption', MessageBoxType.YesNo | MessageBoxType.IconInformation)
      +
      +await openFile(  {
      +    title: 'Pick Images',
      +    filter: "Image files (*.png;*.jpeg)|*.png;*.jpeg|All files (*.*)|*.*",
      +    initialDir: await expandEnvVars('%USERPROFILE%\\\\Pictures'),
      +    defaultExt: '*.png',
      +})
      +
      +openFile({ isFolderPicker: true })
      +
      +deviceScreenResolution()
      +
      +primaryMonitorInfo()
      +
      +windowSetPosition(x, y)
      +
      +windowSetSize(width, height)
      +

      +Custom Win32 API

      +

      You're also not limited to calling the built-in Win32 APIs above as calling custom APIs just involves wrapping the C# inside +your preferred #Script method that you would like to make it available to JS as, +e.g. here's the win32 implementation for launching Win32's Color Dialog Box +and returning the selected color in HTML Color format:

      +
      public class CustomMethods : ScriptMethods
      +{
      +    [DllImport("ComDlg32.dll", CharSet = CharSet.Unicode)]
      +    internal static extern int CommDlgExtendedError();
      +
      +    [DllImport("ComDlg32.dll", CharSet = CharSet.Unicode)]
      +    internal static extern bool ChooseColor(ref ChooseColor cc);
      +    
      +    private int[] customColors = new int[16] {
      +        0x00FFFFFF, 0x00C0C0C0, 0x00808080, 0x00000000,
      +        0x00FF0000, 0x00800000, 0x00FFFF00, 0x00808000,
      +        0x0000FF00, 0x00008000, 0x0000FFFF, 0x00008080,
      +        0x000000FF, 0x00000080, 0x00FF00FF, 0x00800080,
      +    };
      +
      +    public string chooseColor(ScriptScopeContext scope) => chooseColor(scope, "#ffffff");
      +
      +    public string chooseColor(ScriptScopeContext scope, string defaultColor) => scope.DoWindow(w => {
      +        var cc = new ChooseColor();
      +        cc.lStructSize = Marshal.SizeOf(cc);
      +        var lpCustColors = Marshal.AllocCoTaskMem(16 * sizeof(int));
      +        try
      +        {
      +            Marshal.Copy(customColors, 0, lpCustColors,16);
      +            cc.hwndOwner = w;
      +            cc.lpCustColors = lpCustColors;
      +            cc.Flags = ChooseColorFlags.FullOpen | ChooseColorFlags.RgbInit;
      +            var c = ColorTranslator.FromHtml(defaultColor);
      +            cc.rgbResult = ColorTranslator.ToWin32(c);
      +
      +            if (!ChooseColor(ref cc)) 
      +                return (string) null;
      +        
      +            c = ColorTranslator.FromWin32(cc.rgbResult);
      +            return ColorTranslator.ToHtml(c);
      +        }
      +        finally
      +        {
      +            Marshal.FreeCoTaskMem(lpCustColors);
      +        }
      +    });
      +}
      +

      ServiceStack.Desktop's IPC takes care of invoking the #Script JS-compatible expression and returning the result:

      +
      var selectedColor = await evaluateCode('chooseColor(`#336699`)')
      +

      +

      The scope.DoWindow() extension method supports expressions being invoked in-process when launched by app.exe as well as when +invoked during development in "detached mode" if electing to run the .NET Core backend as a stand-alone Web App.

      +

      If your App calls your custom APIs a lot you can wrap it in a first-class TypeScript method that mirrors the server #Script method:

      +
      function chooseColor(defaultColor?:string) {
      +    return defaultColor
      +        ? evaluateCode(`chooseColor(${quote(defaultColor)})`)
      +        : evaluateCode(`chooseColor()`);
      +}
      +

      Where it can be called using the same syntax in JS and #Script:

      +
      var selectedColor = await chooseColor(`#336699`)
      +

      +Highly productive live-reloading Development experience

      +

      If it weren't for the productivity possible for being able to only needing to develop for Chrome's state-of-the-art rendering engine where you can use advanced features like CSS grid along with the productivity of high-level productive Reactive UI frameworks like Vue, the effort into create a Desktop App like ServiceStack Studio wouldn't be justifiable.

      +

      After the next release we'll create pre-packaged project templates for vue-desktop and react-desktop Desktop Apps to make it easy develop Vue & React Desktop Apps along with scripts to bundle it & publish it to gist. If preferred app.exe also lets you deploy the published app to your own private repo & limit access to only users accessible with a GitHub token which they can open with from a URL with:

      +
      app://user/repo?token={GITHUB_TOKEN}
      +
      +

      Or on the command line with:

      +
      $ app user/repo -token $GITHUB_TOKEN
      +
      +
      +

      Or without a token by setting it in the GITHUB_TOKEN Environment variable

      +
      +

      For offline deployments the published /dist folder can be copied and launched with app (or x) in the app's folder:

      +
      $ app
      +
      +

      For better Desktop integration this (or custom command-line arguments) can be wrapped in a new Windows Shortcut:

      +
      $ app shortcut
      +
      +

      For a look at example Desktop App projects built using this development model can checkout ServiceStack/Studio or NetCoreApps/SharpData.

      +
      \ No newline at end of file diff --git a/src/wwwroot/gfm/apps/03.md b/src/wwwroot/gfm/apps/03.md new file mode 100644 index 0000000..084ded0 --- /dev/null +++ b/src/wwwroot/gfm/apps/03.md @@ -0,0 +1,142 @@ +The [win32](https://github.com/sharp-apps/win32) Sharp App contains an examples dashboard of invoking different native Win32 functions: + +[![](https://raw.githubusercontent.com/ServiceStack/docs/master/docs/images/release-notes/v5.9/win32.png)](https://github.com/sharp-apps/win32) + +You can run this Gist Desktop App via URL Scheme from: + + [app://win32](app://win32) + +Or via command-line: + + $ app open win32 + +The main source code of this component is in [Win32/index.ts](https://github.com/sharp-apps/win32/blob/master/src/components/Win32/index.ts), +which makes use of the built in TypeScript APIs below from `@servicestack/desktop`: + +```ts +start('%USERPROFILE%\\\\.sharp-apps') + +openUrl('https://google.com') + +messageBox('The Title', 'Caption', MessageBoxType.YesNo | MessageBoxType.IconInformation) + +await openFile( { + title: 'Pick Images', + filter: "Image files (*.png;*.jpeg)|*.png;*.jpeg|All files (*.*)|*.*", + initialDir: await expandEnvVars('%USERPROFILE%\\\\Pictures'), + defaultExt: '*.png', +}) + +openFile({ isFolderPicker: true }) + +deviceScreenResolution() + +primaryMonitorInfo() + +windowSetPosition(x, y) + +windowSetSize(width, height) +``` + +#### Custom Win32 API + +You're also not limited to calling the built-in Win32 APIs above as calling custom APIs just involves wrapping the C# inside +your preferred [#Script method](/docs/methods) that you would like to make it available to JS as, +e.g. here's the **win32** implementation for launching [Win32's Color Dialog Box](https://docs.microsoft.com/en-us/windows/win32/dlgbox/color-dialog-box) +and returning the selected color in HTML Color format: + +```csharp +public class CustomMethods : ScriptMethods +{ + [DllImport("ComDlg32.dll", CharSet = CharSet.Unicode)] + internal static extern int CommDlgExtendedError(); + + [DllImport("ComDlg32.dll", CharSet = CharSet.Unicode)] + internal static extern bool ChooseColor(ref ChooseColor cc); + + private int[] customColors = new int[16] { + 0x00FFFFFF, 0x00C0C0C0, 0x00808080, 0x00000000, + 0x00FF0000, 0x00800000, 0x00FFFF00, 0x00808000, + 0x0000FF00, 0x00008000, 0x0000FFFF, 0x00008080, + 0x000000FF, 0x00000080, 0x00FF00FF, 0x00800080, + }; + + public string chooseColor(ScriptScopeContext scope) => chooseColor(scope, "#ffffff"); + + public string chooseColor(ScriptScopeContext scope, string defaultColor) => scope.DoWindow(w => { + var cc = new ChooseColor(); + cc.lStructSize = Marshal.SizeOf(cc); + var lpCustColors = Marshal.AllocCoTaskMem(16 * sizeof(int)); + try + { + Marshal.Copy(customColors, 0, lpCustColors,16); + cc.hwndOwner = w; + cc.lpCustColors = lpCustColors; + cc.Flags = ChooseColorFlags.FullOpen | ChooseColorFlags.RgbInit; + var c = ColorTranslator.FromHtml(defaultColor); + cc.rgbResult = ColorTranslator.ToWin32(c); + + if (!ChooseColor(ref cc)) + return (string) null; + + c = ColorTranslator.FromWin32(cc.rgbResult); + return ColorTranslator.ToHtml(c); + } + finally + { + Marshal.FreeCoTaskMem(lpCustColors); + } + }); +} +``` + +ServiceStack.Desktop's IPC takes care of invoking the `#Script` [JS-compatible expression](/docs/expression-viewer) and returning the result: + +```ts +var selectedColor = await evaluateCode('chooseColor(`#336699`)') +``` + +[![](https://raw.githubusercontent.com/ServiceStack/docs/master/docs/images/release-notes/v5.9/win32-choosecolor.png)](https://github.com/sharp-apps/win32) + +The `scope.DoWindow()` extension method supports expressions being invoked in-process when launched by `app.exe` as well as when +invoked during development in "detached mode" if electing to run the .NET Core backend as a stand-alone Web App. + +If your App calls your custom APIs a lot you can wrap it in a first-class TypeScript method that mirrors the server #Script method: + +```ts +function chooseColor(defaultColor?:string) { + return defaultColor + ? evaluateCode(`chooseColor(${quote(defaultColor)})`) + : evaluateCode(`chooseColor()`); +} +``` + +Where it can be called using the same syntax in JS and #Script: + +```ts +var selectedColor = await chooseColor(`#336699`) +``` + +### Highly productive live-reloading Development experience + +If it weren't for the productivity possible for being able to only needing to develop for Chrome's state-of-the-art rendering engine where you can use advanced features like CSS grid along with the productivity of high-level productive Reactive UI frameworks like Vue, the effort into create a Desktop App like ServiceStack Studio wouldn't be justifiable. + +After the next release we'll create pre-packaged project templates for **vue-desktop** and **react-desktop** Desktop Apps to make it easy develop Vue & React Desktop Apps along with scripts to bundle it & publish it to gist. If preferred `app.exe` also lets you deploy the published app to your own private repo & limit access to only users accessible with a GitHub token which they can open with from a URL with: + + app://user/repo?token={GITHUB_TOKEN} + +Or on the command line with: + + $ app user/repo -token $GITHUB_TOKEN + +> Or without a token by setting it in the `GITHUB_TOKEN` Environment variable + +For offline deployments the published `/dist` folder can be copied and launched with `app` (or `x`) in the app's folder: + + $ app + +For better Desktop integration this (or custom command-line arguments) can be wrapped in a new Windows Shortcut: + + $ app shortcut + +For a look at example Desktop App projects built using this development model can checkout [ServiceStack/Studio](https://github.com/ServiceStack/Studio) or [NetCoreApps/SharpData](https://github.com/NetCoreApps/SharpData). \ No newline at end of file diff --git a/src/wwwroot/index.html b/src/wwwroot/index.html index 4b1e8de..e19d995 100644 --- a/src/wwwroot/index.html +++ b/src/wwwroot/index.html @@ -175,25 +175,11 @@

      Sharp Apps

      for a glimpse of the Live Development experience available:

      - -

      - -

      - - -
      - -

      +{{#markdown}} +> YouTube: [youtu.be/2FFRLxs7orU](https://youtu.be/Cf-vstYXrmY) + +[![](https://raw.githubusercontent.com/ServiceStack/docs/master/docs/images/app/spirals/animated.png)](https://youtu.be/Cf-vstYXrmY) +{{/markdown}}
      See the Making of Spirals for a walk through on how to create diff --git a/src/wwwroot/sharp-apps/index.html b/src/wwwroot/sharp-apps/index.html index 80a32c7..6ce1a61 100644 --- a/src/wwwroot/sharp-apps/index.html +++ b/src/wwwroot/sharp-apps/index.html @@ -305,6 +305,8 @@

      Example Sharp Apps

      - [Chat](/sharp-apps/chat) - [Blog](/sharp-apps/blog) - [SharpData](/sharp-apps/sharpdata) +- [Win32](/sharp-apps/win32) +- [ServiceStack Studio](/sharp-apps/studio) {{/markdown}} diff --git a/src/wwwroot/sharp-apps/sharpdata.html b/src/wwwroot/sharp-apps/sharpdata.html index 909c673..a4d5101 100644 --- a/src/wwwroot/sharp-apps/sharpdata.html +++ b/src/wwwroot/sharp-apps/sharpdata.html @@ -4,7 +4,7 @@ -->
      - x open sharpdata - + app open sharpdata - sharpdata.netcore.io - NetCoreApps/SharpData
      diff --git a/src/wwwroot/sharp-apps/spirals.html b/src/wwwroot/sharp-apps/spirals.html index af76219..05d6069 100644 --- a/src/wwwroot/sharp-apps/spirals.html +++ b/src/wwwroot/sharp-apps/spirals.html @@ -9,31 +9,6 @@ sharp-apps/spirals
      -

      - Spirals is a good example showing how easy it is to create .NET Core Desktop Web Apps utilizing HTML5's familiar and simple development - model to leverage advanced Web Technologies like SVG in a fun, interactive and live development experience. -

      - - -

      - -

      - - -
      - -

      - {{ 'gfm/apps/01.md' |> githubMarkdown }} {{ "apps-links" |> partial({ order }) }} diff --git a/src/wwwroot/sharp-apps/studio.html b/src/wwwroot/sharp-apps/studio.html new file mode 100644 index 0000000..f9b403b --- /dev/null +++ b/src/wwwroot/sharp-apps/studio.html @@ -0,0 +1,26 @@ + + +
      + app open studio - + ServiceStack/Studio +
      + +{{#markdown}} +**[ServiceStack Studio](https://github.com/ServiceStack/Studio)** is a capability-based UI to manage multiple remote ServiceStack instances from +either a Chromium Desktop App or cross-platform .NET Core Web App. + +The richer metadata in ServiceStack Services allows Studio to logically group Services around Data Models, enabling its high-level semantic features +like its native data-grid like UX over all AutoQuery Services to quickly discover, search, create, update and delete entities based on the available +AutoQuery APIs and whether Authenticated Users have access to them. + +> YouTube: [youtu.be/2FFRLxs7orU](https://youtu.be/2FFRLxs7orU) + +[![](https://raw.githubusercontent.com/ServiceStack/docs/master/docs/images/release-notes/v5.9/studio-home.png)](https://youtu.be/2FFRLxs7orU) + +Checkout the [github.com/ServiceStack/Studio](https://github.com/ServiceStack/Studio) GitHub project to learn more about ServiceStack Studio. +{{/markdown}} + +{{ "apps-links" |> partial({ order }) }} diff --git a/src/wwwroot/sharp-apps/win32.html b/src/wwwroot/sharp-apps/win32.html new file mode 100644 index 0000000..0b37a96 --- /dev/null +++ b/src/wwwroot/sharp-apps/win32.html @@ -0,0 +1,13 @@ + + +
      + app open win32 - + sharp-apps/win32 +
      + +{{ 'gfm/apps/03.md' |> githubMarkdown }} + +{{ "apps-links" |> partial({ order }) }} From b64ac266b3e2125e05b383650002050c8aed9d3e Mon Sep 17 00:00:00 2001 From: Demis Bellot Date: Tue, 16 Jun 2020 05:20:57 +0800 Subject: [PATCH 148/206] Update Apps with URL Schemes --- src/wwwroot/assets/js/default.js | 6 + src/wwwroot/docs/script-pages.html | 566 ++++++++++++++++++++++ src/wwwroot/docs/sharp-pages.html | 567 +---------------------- src/wwwroot/gfm/apps/01.html | 8 + src/wwwroot/gfm/apps/01.md | 12 + src/wwwroot/gfm/apps/02.html | 16 + src/wwwroot/gfm/apps/02.md | 20 + src/wwwroot/gfm/apps/03.html | 21 +- src/wwwroot/gfm/apps/03.md | 18 +- src/wwwroot/gfm/sharp-apps/14.html | 54 ++- src/wwwroot/gfm/sharp-apps/14.md | 12 + src/wwwroot/sharp-apps/blog.html | 4 +- src/wwwroot/sharp-apps/chat.html | 17 +- src/wwwroot/sharp-apps/plugins.html | 17 +- src/wwwroot/sharp-apps/redis.html | 39 +- src/wwwroot/sharp-apps/rockwind.html | 20 +- src/wwwroot/sharp-apps/sharpdata.html | 2 +- src/wwwroot/sharp-apps/spirals.html | 2 +- src/wwwroot/sharp-apps/studio.html | 14 +- src/wwwroot/sharp-apps/win32.html | 2 +- src/wwwroot/usecases/live-documents.html | 2 +- 21 files changed, 806 insertions(+), 613 deletions(-) create mode 100644 src/wwwroot/docs/script-pages.html diff --git a/src/wwwroot/assets/js/default.js b/src/wwwroot/assets/js/default.js index 05ccf16..8d8e39a 100644 --- a/src/wwwroot/assets/js/default.js +++ b/src/wwwroot/assets/js/default.js @@ -216,3 +216,9 @@ function bindYouTubeImages() { } $(bindYouTubeImages); + +$(function(){ + $("a[name*='app://']").each(function() { + this.href = 'app://' + splitOnFirst(this.getAttribute('name'), '://')[1]; + }); +}); diff --git a/src/wwwroot/docs/script-pages.html b/src/wwwroot/docs/script-pages.html new file mode 100644 index 0000000..528920a --- /dev/null +++ b/src/wwwroot/docs/script-pages.html @@ -0,0 +1,566 @@ + + +

      + One of the most popular use-cases for a high-performance and versatile scripting language like #Script is as a server-side + HTML #Script Pages for .NET Web Applications where it can provide a simpler, cleaner and portable alternative than Razor and + Razor Pages in ASP.NET and ASP.NET Core Web Apps. +

      + +

      #Script Pages in ServiceStack

      + +

      + The SharpPagesFeature plugin provides a first-class experience for generating dynamic websites where it's able to + generate complete server-generated websites (like this one) without requiring any additional Controllers or Services. +

      + +

      + To enable #Script Pages in ServiceStack you just need to register the SharpPagesFeature plugin: +

      + +{{ 'gfm/sharp-pages/01.md' |> githubMarkdown }} + +

      + SharpPagesFeature is a subclass of ScriptContext which defines the context on which all ServiceStack + #Script Pages are executed within. It provides deep integration within ServiceStack by replacing the ScriptContext's stand-alone + dependencies with ServiceStack AppHost providers, where it: +

      + +
        +
      • Configures it to use ServiceStack's Virtual File Sources + allowing Pages to be loaded from any configured VFS Source
      • +
      • Configures it to use ServiceStack's Funq IOC Container + so all ServiceStack dependencies are available to Code Pages
      • +
      • Configures it to use ServiceStack's AppSettings + so all AppHost AppSettings are available to #Script Pages as well
      • +
      • Configures ScanAssemblies to use AppHost Service Assemblies so it auto-registers all Filters in Service .dlls
      • +
      • Registers the ProtectedScripts allowing pages to access richer server-side functionality
      • +
      • Registers the markdown Filter Transformer using ServiceStack's built-in MarkdownDeep implementation
      • +
      • Makes the ServiceStackCodePage subclass available so Code Pages has access to same functionality as Services
      • +
      • Registers a Request Handler which enables all requests .html pages to be handled by #Script Pages
      • +
      + +

      If preferred, you can change which .html extension gets handled by #Script Pages with:

      + +{{ 'gfm/sharp-pages/02.md' |> githubMarkdown }} + +

      Register additional functionality

      + +

      + The same APIs for extending a ScriptContext is also how to extend + SharpPagesFeature to enable additional functionality in your #Script Pages: +

      + +{{ 'gfm/sharp-pages/14.md' |> githubMarkdown }} + +

      Runs Everywhere

      + +

      + The beauty of #Script working natively with ServiceStack is that it runs everywhere ServiceStack does + which is in all major .NET Server Platforms. That is, your same #Script-based Web Application is able to use + the same #Script implementation, "flavour" and feature-set and is portable across whichever platform you choose to host it on: +

      + + + +

      + Once registered, SharpPagesFeature gives all your .html pages scripting super powers where sections can be + compartmentalized and any duplicated content can now be extracted into reusable partials, metadata can be added to the top of + each page and its page navigation dynamically generated, contents of files and urls can be embedded directly and otherwise + static pages can come alive with access to Default Scripts. +

      + +

      + The Starter Projects below provide a quick way to get started with a pre-configured ServiceStack App with #Script Pages: +

      + +

      .NET Core #Script Pages Project

      + +

      + Create a new #Script Pages Website .NET Core 3.1 App with + x new: +

      + +
      +
      $ dotnet tool install --global x 
      +
      +$ x new script ProjectName
      + + +.NET Core Starter Template + +

      ASP.NET Core #Script Pages Project on .NET Framework

      + +

      + To create ASP.NET Core Project on the .NET Framework: +

      + +
      +
      $ x new script-corefx ProjectName
      + +

      ASP.NET v4.5 #Script Pages Project

      + +

      + For ASP.NET v4.5+ projects create a new ServiceStack ASP.NET #Script Pages with Bootstrap from the VS.NET Templates in + ServiceStackVS VS.NET Extension + to create an ASP.NET v4.5 Project using + ServiceStack's recommended project structure: +

      + +

      + + ASP.NET v4.5 Starter Template + +

      + + +

      Content Pages

      + +

      + There are a number of different ways you can use #Script to render dynamic pages, requests that calls and renders + #Script .html pages directly are called "Content Pages" which don't use any Services or Controllers + and can be called using a number of different styles and calling conventions: +

      + +

      Page Based Routing

      + +

      + Any .html page available from your AppHost's configured Virtual File Sources + can be called directly, typically this would mean the File System which in a .NET Core Web App starts from the WebRootPath + (e.g /wwwroot) so a request to /docs/sharp-pages goes through all configured VirtualFileSources to find the first + match, which for this website is the file + /src/wwwroot/docs/sharp-pages.html. +

      + +

      Pretty URLs by default

      + +

      + Essentially #Script Pages embraces conventional page-based routing which enables pretty urls inferred from the pages file and directory names + where each page can be requested with or without its .html extension: +

      + + + + + + + + + + + + + + + + + + + + + + + + + + +
      pathpage
      /db 
      /db.html/db.html
      /posts/new 
      /posts/new.html/posts/new.html
      + +

      + The default route / maps to the index.html in the directory if it exists, e.g: +

      + + + + + + + + + + + + + + + + + + +
      pathpage
      //index.html
      /index.html/index.html
      + +

      Dynamic Page Routes

      + +

      + In addition to these static conventions, #Script Pages now supports Nuxt-like + Dynamic Routes + where any file or directory names prefixed with an _underscore enables a wildcard path which assigns the matching path component + to the arguments name: +

      + + + + + + + + + + + + + + + + + + + + + + + + + + +
      pathpagearguments
      /ServiceStack/_user/index.htmluser=ServiceStack
      /posts/markdown-example/posts/_slug/index.htmlslug=markdown-example
      /posts/markdown-example/edit/posts/_slug/edit.htmlslug=markdown-example
      + +

      Layout and partial recommended naming conventions

      + +

      + The _underscore prefix for declaring wildcard pages is also what is used to declare "hidden" pages, to distinguish them from hidden + partials and layouts, the recommendation is for them to include layout and partial their name, e,g: +

      + +
        +
      • _layout.html
      • +
      • _alt-layout.html
      • +
      • _menu-partial.html
      • +
      + +
      + Pages with layout or partial in their name remain hidden and are ignored in wildcard path resolution. +
      + +

      + If following the recommended _{name}-partial.html naming convention, you'll be able to reference them using just their name: +

      + +{{#raw}} +
      +{{ 'menu' |> partial }}          // Equivalent to:
      +{{ '_menu-partial' |> partial }}
      +
      +{{/raw}} + +

      View Pages

      + +

      + "View Pages" are pages that are rendered after a Service is executed, where it's typically used to provide the "HTML UI" + for the Service's Response DTO where it's populated in the Model page argument + as done in Razor ViewPages. +

      + +

      + View Pages can be in any nested folder within the /Views folder but all page names within the /Views folder need to be unique. + The name should use the format using the format {PageName}.html where PageName can be either: +

      + +
        +
      • Request DTO Name (e.g. GetContact)
      • +
      • Response DTO Name (e.g. GetContactResponse)
      • +
      + +

      + There are a number of other ways to specify which View you want to render: +

      + +

      Specify the Services DefaultView

      + +

      + You can specify which view all Services should use to render their responses by using the [DefaultView] Request Filter Attribute: +

      + +{{ 'gfm/sharp-pages/12.md' |> githubMarkdown }} + +

      Specify View with custom HttpResult

      + +

      + Just like ServiceStack.Razor, you can specify to use different Views or Layouts by returning a + decorated response in custom HttpResult with the View or Template you want the Service rendered in , e.g: +

      + +{{ 'gfm/sharp-pages/09.md' |> githubMarkdown }} + +

      Return Custom PageResult

      + +

      + For maximum flexibility to control how views are rendered you can return a custom PageResult using + Request.GetPage() or Request.GetCodePage() extension methods as seen in + Model View Controller: +

      + +{{ 'gfm/model-view-controller/02.md' |> githubMarkdown }} + +

      Allow Views to be specified on the QueryString

      + +

      + You can use the [ClientCanSwapTemplates] Request Filter attribute to let the View and Template by specified on the QueryString, + e.g: ?View=CustomPage&Template=_custom-layout +

      + +{{ 'gfm/sharp-pages/13.md' |> githubMarkdown }} + +

      + Additional examples of dynamically specifying the View and Template are available in + SharpViewsTests.cs. +

      + +

      Cascading Layouts

      + +

      + One difference from Razor is that it uses a cascading _layout.html instead of /Views/Shared/_Layout.cshtml. +

      + +

      So if your view page was in:

      + +
      +
      +/Views/dir/MyRequest.html
      +
      +
      + +

      It will use the closest `_layout.html` it can find starting from:

      + +
      +
      +/Views/dir/_layout.html
      +/Views/_layout.html
      +/_layout.html
      +
      +
      + +

      Layout Selection

      + +

      + Unless it's a complete HTML Page (e.g. starts with html or HTML5 tag) the page gets rendered using the closest _layout.html + page it can find starting from the directory where the page is located, traversing all the way up until it reaches the root directory. + Which for this page uses the + /src/wwwroot/_layout.html template + in the WebRoot directory, which as it's in the root directory, is the fallback Layout for all .html pages. +

      + +

      + Pages can change the layout they use by either adding their own _layout.html page in their sub directory or specifying + a different layout in their pages metadata header, e.g: +

      + +{{ 'gfm/sharp-pages/03.md' |> githubMarkdown }} + +

      + Where it instead embed the page using the closest mobile-layout.html it can find, starting from the Page's directory. + If your pages are instead embedded in the different folder you can request it directly from the root dir: +

      + +{{ 'gfm/sharp-pages/04.md' |> githubMarkdown }} + +

      Request Variables

      + +

      + The QueryString and FORM variables sent to the page are available as arguments within the page using the + form and query (or its shorter qs alias) collections, so a request like + /docs/sharp-pages?id=1 + can access the id param with {{ pass: qs.id }}. The combined {{pass: 'id' | formQuery }} + method enables the popular use-case of checking for the param in POST FormData before falling back to + the QueryString. Use {{pass: 'ids' | formQueryValues }} for accessing multiple values sent by multiple checkboxes + or multiple select inputs. The {{pass: 'id' | httpParam }} method searches all Request params including HTTP Headers, QueryString, + FormData, Cookies and Request.Items. +

      + +

      + To help with generating navigation, the following Request Variables are also available: +

      + +
        +
      • {{ pass: Verb }} evaluates to {{ Verb }}
      • +
      • {{ pass: AbsoluteUri }} evaluates to {{ AbsoluteUri }}
      • +
      • {{ pass: RawUrl }} evaluates to {{ RawUrl }}
      • +
      • {{ pass: PathInfo }} evaluates to {{ PathInfo }}
      • +
      + +

      + You can use {{ pass: PathInfo }} to easily highlight the active link in a links menu as done in + sidebar.html: +

      + +{{ 'gfm/sharp-pages/05.md' |> githubMarkdown }} + +

      Init Pages

      + +

      + Just as how Global.asax.cs can be used to run Startup initialization logic in ASP.NET Web Applications and + Startup.cs in .NET Core Apps, you can now add a /_init.html page for any script logic that's only executed once on Startup. +

      + +

      + This is used in the Blog Web App's _init.html + where it will create a new blog.sqlite database if it doesn't exist seeded with the + UserInfo and Posts Tables and initial data, e.g: +

      + +{{ 'gfm/sharp-pages/10.md' |> githubMarkdown }} + +

      Ignoring Pages

      + +

      + You can ignore #Script from evaluating static .html files with the following page arguments: +

      + +{{ 'gfm/sharp-pages/06.md' |> githubMarkdown }} + +{{ 'gfm/sharp-pages/07.md' |> githubMarkdown }} + +{{ 'gfm/sharp-pages/08.md' |> githubMarkdown }} + +
      + Complete .html pages starting with <!DOCTYPE HTML> or <html have their layouts ignored by default. +
      + +

      Server UI Controls

      + +{{ 'gfm/sharp-pages/15.md' |> githubMarkdown }} + +

      Admin Service

      + +

      + The new Admin Service lets you run admin actions against a running instance which by default is only accessible to Admin + users and can be called with: +

      + +
      /script/admin
      + +

      + Which will display the available actions which are currently only: +

      + +
        +
      • invalidateAllCaches - Invalidate all caches and force pages to check if they've been modified on next request
      • +
      • RunInitPage - Runs the Init page again
      • +
      + +

      Zero downtime deployments

      + +

      + These actions are useful after an xcopy/rsync deployment to enable zero downtime deployments by getting a running instance to invalidate all + internal caches and force existing pages to check if it has been modified, the next time their called. +

      + +

      + Actions can be invoked in the format with: +

      + +
      /script/admin/{Actions}
      + +

      + Which can be used to call 1 or more actions: +

      + +
      /script/admin/invalidateAllCaches
      +/script/admin/invalidateAllCaches,RunInitPage
      + +

      + By default it's only available to be called by **Admin** Users (or AuthSecret) + but can be changed with: +

      + +{{ 'gfm/sharp-pages/11.md' |> githubMarkdown }} + +

      ServiceStack Scripts

      + +

      + Methods for integrating with ServiceStack are available in + ServiceStack Scripts and Info Scripts both + of which are pre-registered when registering the SharpPagesFeature Plugin. +

      + +

      Markdown Support

      + +

      + Markdown is a great way to maintain and embed content of which #Script has great support for, which can be rendered using the + markdown filter to render markdown text to HTML: +

      + +
      {{#raw}}{{ `## Heading` |> markdown }}{{/raw}}
      + +

      + This can be combined with other composable filters like includeFile to easily and + efficiently render markdown content maintained in a separate file: +

      + +
      {{#raw}}{{ 'doc.md' |> includeFile |> markdown }}{{/raw}}
      + +

      + Or you could use includeUrlWithCache to efficiently render markdown from an + external URL: +

      + +
      {{#raw}}{{ url |> includeUrlWithCache |> markdown }}{{/raw}}
      + +

      + A popular way for embedding static markdown content inside a page is to use the markdown script block + which this website makes extensive use of: +

      + +
      {{#raw}}
      +{{#markdown}}
      +### Heading
      +> Static Markdown Example
      +{{/markdown }}
      +{{/raw}}
      + +

      + Providing an easy way for mixing in markdown content inside a dynamic web page. To embed dynamically rendered markdown content you can use + the capture script block to capture dynamic markdown that you can render with the markdown filter: +

      + +
      {{#raw}}{{#capture md}}
      +### Dynamic Markdown Example
      +{{#each i in range(1,5)}}
      +  - {{i}}
      +{{/each}}
      +{{/capture}}
      +{{ md |> markdown }}
      +{{/raw}}
      + +{{ "doc-links" |> partial({ order }) }} diff --git a/src/wwwroot/docs/sharp-pages.html b/src/wwwroot/docs/sharp-pages.html index 528920a..3cd1834 100644 --- a/src/wwwroot/docs/sharp-pages.html +++ b/src/wwwroot/docs/sharp-pages.html @@ -1,566 +1 @@ - - -

      - One of the most popular use-cases for a high-performance and versatile scripting language like #Script is as a server-side - HTML #Script Pages for .NET Web Applications where it can provide a simpler, cleaner and portable alternative than Razor and - Razor Pages in ASP.NET and ASP.NET Core Web Apps. -

      - -

      #Script Pages in ServiceStack

      - -

      - The SharpPagesFeature plugin provides a first-class experience for generating dynamic websites where it's able to - generate complete server-generated websites (like this one) without requiring any additional Controllers or Services. -

      - -

      - To enable #Script Pages in ServiceStack you just need to register the SharpPagesFeature plugin: -

      - -{{ 'gfm/sharp-pages/01.md' |> githubMarkdown }} - -

      - SharpPagesFeature is a subclass of ScriptContext which defines the context on which all ServiceStack - #Script Pages are executed within. It provides deep integration within ServiceStack by replacing the ScriptContext's stand-alone - dependencies with ServiceStack AppHost providers, where it: -

      - -
        -
      • Configures it to use ServiceStack's Virtual File Sources - allowing Pages to be loaded from any configured VFS Source
      • -
      • Configures it to use ServiceStack's Funq IOC Container - so all ServiceStack dependencies are available to Code Pages
      • -
      • Configures it to use ServiceStack's AppSettings - so all AppHost AppSettings are available to #Script Pages as well
      • -
      • Configures ScanAssemblies to use AppHost Service Assemblies so it auto-registers all Filters in Service .dlls
      • -
      • Registers the ProtectedScripts allowing pages to access richer server-side functionality
      • -
      • Registers the markdown Filter Transformer using ServiceStack's built-in MarkdownDeep implementation
      • -
      • Makes the ServiceStackCodePage subclass available so Code Pages has access to same functionality as Services
      • -
      • Registers a Request Handler which enables all requests .html pages to be handled by #Script Pages
      • -
      - -

      If preferred, you can change which .html extension gets handled by #Script Pages with:

      - -{{ 'gfm/sharp-pages/02.md' |> githubMarkdown }} - -

      Register additional functionality

      - -

      - The same APIs for extending a ScriptContext is also how to extend - SharpPagesFeature to enable additional functionality in your #Script Pages: -

      - -{{ 'gfm/sharp-pages/14.md' |> githubMarkdown }} - -

      Runs Everywhere

      - -

      - The beauty of #Script working natively with ServiceStack is that it runs everywhere ServiceStack does - which is in all major .NET Server Platforms. That is, your same #Script-based Web Application is able to use - the same #Script implementation, "flavour" and feature-set and is portable across whichever platform you choose to host it on: -

      - - - -

      - Once registered, SharpPagesFeature gives all your .html pages scripting super powers where sections can be - compartmentalized and any duplicated content can now be extracted into reusable partials, metadata can be added to the top of - each page and its page navigation dynamically generated, contents of files and urls can be embedded directly and otherwise - static pages can come alive with access to Default Scripts. -

      - -

      - The Starter Projects below provide a quick way to get started with a pre-configured ServiceStack App with #Script Pages: -

      - -

      .NET Core #Script Pages Project

      - -

      - Create a new #Script Pages Website .NET Core 3.1 App with - x new: -

      - -
      -
      $ dotnet tool install --global x 
      -
      -$ x new script ProjectName
      - - -.NET Core Starter Template - -

      ASP.NET Core #Script Pages Project on .NET Framework

      - -

      - To create ASP.NET Core Project on the .NET Framework: -

      - -
      -
      $ x new script-corefx ProjectName
      - -

      ASP.NET v4.5 #Script Pages Project

      - -

      - For ASP.NET v4.5+ projects create a new ServiceStack ASP.NET #Script Pages with Bootstrap from the VS.NET Templates in - ServiceStackVS VS.NET Extension - to create an ASP.NET v4.5 Project using - ServiceStack's recommended project structure: -

      - -

      - - ASP.NET v4.5 Starter Template - -

      - - -

      Content Pages

      - -

      - There are a number of different ways you can use #Script to render dynamic pages, requests that calls and renders - #Script .html pages directly are called "Content Pages" which don't use any Services or Controllers - and can be called using a number of different styles and calling conventions: -

      - -

      Page Based Routing

      - -

      - Any .html page available from your AppHost's configured Virtual File Sources - can be called directly, typically this would mean the File System which in a .NET Core Web App starts from the WebRootPath - (e.g /wwwroot) so a request to /docs/sharp-pages goes through all configured VirtualFileSources to find the first - match, which for this website is the file - /src/wwwroot/docs/sharp-pages.html. -

      - -

      Pretty URLs by default

      - -

      - Essentially #Script Pages embraces conventional page-based routing which enables pretty urls inferred from the pages file and directory names - where each page can be requested with or without its .html extension: -

      - - - - - - - - - - - - - - - - - - - - - - - - - - -
      pathpage
      /db 
      /db.html/db.html
      /posts/new 
      /posts/new.html/posts/new.html
      - -

      - The default route / maps to the index.html in the directory if it exists, e.g: -

      - - - - - - - - - - - - - - - - - - -
      pathpage
      //index.html
      /index.html/index.html
      - -

      Dynamic Page Routes

      - -

      - In addition to these static conventions, #Script Pages now supports Nuxt-like - Dynamic Routes - where any file or directory names prefixed with an _underscore enables a wildcard path which assigns the matching path component - to the arguments name: -

      - - - - - - - - - - - - - - - - - - - - - - - - - - -
      pathpagearguments
      /ServiceStack/_user/index.htmluser=ServiceStack
      /posts/markdown-example/posts/_slug/index.htmlslug=markdown-example
      /posts/markdown-example/edit/posts/_slug/edit.htmlslug=markdown-example
      - -

      Layout and partial recommended naming conventions

      - -

      - The _underscore prefix for declaring wildcard pages is also what is used to declare "hidden" pages, to distinguish them from hidden - partials and layouts, the recommendation is for them to include layout and partial their name, e,g: -

      - -
        -
      • _layout.html
      • -
      • _alt-layout.html
      • -
      • _menu-partial.html
      • -
      - -
      - Pages with layout or partial in their name remain hidden and are ignored in wildcard path resolution. -
      - -

      - If following the recommended _{name}-partial.html naming convention, you'll be able to reference them using just their name: -

      - -{{#raw}} -
      -{{ 'menu' |> partial }}          // Equivalent to:
      -{{ '_menu-partial' |> partial }}
      -
      -{{/raw}} - -

      View Pages

      - -

      - "View Pages" are pages that are rendered after a Service is executed, where it's typically used to provide the "HTML UI" - for the Service's Response DTO where it's populated in the Model page argument - as done in Razor ViewPages. -

      - -

      - View Pages can be in any nested folder within the /Views folder but all page names within the /Views folder need to be unique. - The name should use the format using the format {PageName}.html where PageName can be either: -

      - -
        -
      • Request DTO Name (e.g. GetContact)
      • -
      • Response DTO Name (e.g. GetContactResponse)
      • -
      - -

      - There are a number of other ways to specify which View you want to render: -

      - -

      Specify the Services DefaultView

      - -

      - You can specify which view all Services should use to render their responses by using the [DefaultView] Request Filter Attribute: -

      - -{{ 'gfm/sharp-pages/12.md' |> githubMarkdown }} - -

      Specify View with custom HttpResult

      - -

      - Just like ServiceStack.Razor, you can specify to use different Views or Layouts by returning a - decorated response in custom HttpResult with the View or Template you want the Service rendered in , e.g: -

      - -{{ 'gfm/sharp-pages/09.md' |> githubMarkdown }} - -

      Return Custom PageResult

      - -

      - For maximum flexibility to control how views are rendered you can return a custom PageResult using - Request.GetPage() or Request.GetCodePage() extension methods as seen in - Model View Controller: -

      - -{{ 'gfm/model-view-controller/02.md' |> githubMarkdown }} - -

      Allow Views to be specified on the QueryString

      - -

      - You can use the [ClientCanSwapTemplates] Request Filter attribute to let the View and Template by specified on the QueryString, - e.g: ?View=CustomPage&Template=_custom-layout -

      - -{{ 'gfm/sharp-pages/13.md' |> githubMarkdown }} - -

      - Additional examples of dynamically specifying the View and Template are available in - SharpViewsTests.cs. -

      - -

      Cascading Layouts

      - -

      - One difference from Razor is that it uses a cascading _layout.html instead of /Views/Shared/_Layout.cshtml. -

      - -

      So if your view page was in:

      - -
      -
      -/Views/dir/MyRequest.html
      -
      -
      - -

      It will use the closest `_layout.html` it can find starting from:

      - -
      -
      -/Views/dir/_layout.html
      -/Views/_layout.html
      -/_layout.html
      -
      -
      - -

      Layout Selection

      - -

      - Unless it's a complete HTML Page (e.g. starts with html or HTML5 tag) the page gets rendered using the closest _layout.html - page it can find starting from the directory where the page is located, traversing all the way up until it reaches the root directory. - Which for this page uses the - /src/wwwroot/_layout.html template - in the WebRoot directory, which as it's in the root directory, is the fallback Layout for all .html pages. -

      - -

      - Pages can change the layout they use by either adding their own _layout.html page in their sub directory or specifying - a different layout in their pages metadata header, e.g: -

      - -{{ 'gfm/sharp-pages/03.md' |> githubMarkdown }} - -

      - Where it instead embed the page using the closest mobile-layout.html it can find, starting from the Page's directory. - If your pages are instead embedded in the different folder you can request it directly from the root dir: -

      - -{{ 'gfm/sharp-pages/04.md' |> githubMarkdown }} - -

      Request Variables

      - -

      - The QueryString and FORM variables sent to the page are available as arguments within the page using the - form and query (or its shorter qs alias) collections, so a request like - /docs/sharp-pages?id=1 - can access the id param with {{ pass: qs.id }}. The combined {{pass: 'id' | formQuery }} - method enables the popular use-case of checking for the param in POST FormData before falling back to - the QueryString. Use {{pass: 'ids' | formQueryValues }} for accessing multiple values sent by multiple checkboxes - or multiple select inputs. The {{pass: 'id' | httpParam }} method searches all Request params including HTTP Headers, QueryString, - FormData, Cookies and Request.Items. -

      - -

      - To help with generating navigation, the following Request Variables are also available: -

      - -
        -
      • {{ pass: Verb }} evaluates to {{ Verb }}
      • -
      • {{ pass: AbsoluteUri }} evaluates to {{ AbsoluteUri }}
      • -
      • {{ pass: RawUrl }} evaluates to {{ RawUrl }}
      • -
      • {{ pass: PathInfo }} evaluates to {{ PathInfo }}
      • -
      - -

      - You can use {{ pass: PathInfo }} to easily highlight the active link in a links menu as done in - sidebar.html: -

      - -{{ 'gfm/sharp-pages/05.md' |> githubMarkdown }} - -

      Init Pages

      - -

      - Just as how Global.asax.cs can be used to run Startup initialization logic in ASP.NET Web Applications and - Startup.cs in .NET Core Apps, you can now add a /_init.html page for any script logic that's only executed once on Startup. -

      - -

      - This is used in the Blog Web App's _init.html - where it will create a new blog.sqlite database if it doesn't exist seeded with the - UserInfo and Posts Tables and initial data, e.g: -

      - -{{ 'gfm/sharp-pages/10.md' |> githubMarkdown }} - -

      Ignoring Pages

      - -

      - You can ignore #Script from evaluating static .html files with the following page arguments: -

      - -{{ 'gfm/sharp-pages/06.md' |> githubMarkdown }} - -{{ 'gfm/sharp-pages/07.md' |> githubMarkdown }} - -{{ 'gfm/sharp-pages/08.md' |> githubMarkdown }} - -
      - Complete .html pages starting with <!DOCTYPE HTML> or <html have their layouts ignored by default. -
      - -

      Server UI Controls

      - -{{ 'gfm/sharp-pages/15.md' |> githubMarkdown }} - -

      Admin Service

      - -

      - The new Admin Service lets you run admin actions against a running instance which by default is only accessible to Admin - users and can be called with: -

      - -
      /script/admin
      - -

      - Which will display the available actions which are currently only: -

      - -
        -
      • invalidateAllCaches - Invalidate all caches and force pages to check if they've been modified on next request
      • -
      • RunInitPage - Runs the Init page again
      • -
      - -

      Zero downtime deployments

      - -

      - These actions are useful after an xcopy/rsync deployment to enable zero downtime deployments by getting a running instance to invalidate all - internal caches and force existing pages to check if it has been modified, the next time their called. -

      - -

      - Actions can be invoked in the format with: -

      - -
      /script/admin/{Actions}
      - -

      - Which can be used to call 1 or more actions: -

      - -
      /script/admin/invalidateAllCaches
      -/script/admin/invalidateAllCaches,RunInitPage
      - -

      - By default it's only available to be called by **Admin** Users (or AuthSecret) - but can be changed with: -

      - -{{ 'gfm/sharp-pages/11.md' |> githubMarkdown }} - -

      ServiceStack Scripts

      - -

      - Methods for integrating with ServiceStack are available in - ServiceStack Scripts and Info Scripts both - of which are pre-registered when registering the SharpPagesFeature Plugin. -

      - -

      Markdown Support

      - -

      - Markdown is a great way to maintain and embed content of which #Script has great support for, which can be rendered using the - markdown filter to render markdown text to HTML: -

      - -
      {{#raw}}{{ `## Heading` |> markdown }}{{/raw}}
      - -

      - This can be combined with other composable filters like includeFile to easily and - efficiently render markdown content maintained in a separate file: -

      - -
      {{#raw}}{{ 'doc.md' |> includeFile |> markdown }}{{/raw}}
      - -

      - Or you could use includeUrlWithCache to efficiently render markdown from an - external URL: -

      - -
      {{#raw}}{{ url |> includeUrlWithCache |> markdown }}{{/raw}}
      - -

      - A popular way for embedding static markdown content inside a page is to use the markdown script block - which this website makes extensive use of: -

      - -
      {{#raw}}
      -{{#markdown}}
      -### Heading
      -> Static Markdown Example
      -{{/markdown }}
      -{{/raw}}
      - -

      - Providing an easy way for mixing in markdown content inside a dynamic web page. To embed dynamically rendered markdown content you can use - the capture script block to capture dynamic markdown that you can render with the markdown filter: -

      - -
      {{#raw}}{{#capture md}}
      -### Dynamic Markdown Example
      -{{#each i in range(1,5)}}
      -  - {{i}}
      -{{/each}}
      -{{/capture}}
      -{{ md |> markdown }}
      -{{/raw}}
      - -{{ "doc-links" |> partial({ order }) }} +{{ httpResult({ status: 301, Location:'/docs/script-pages' }) |> return }} \ No newline at end of file diff --git a/src/wwwroot/gfm/apps/01.html b/src/wwwroot/gfm/apps/01.html index 4ec5746..1e2f8f1 100644 --- a/src/wwwroot/gfm/apps/01.html +++ b/src/wwwroot/gfm/apps/01.html @@ -4,6 +4,14 @@

      YouTube: youtu.be/2FFRLxs7orU

      +

      You can run this Gist Desktop App via URL Scheme from (Windows Desktop App):

      +

      app://spirals

      +

      Or via command-line:

      +
      $ app open spirals
      +
      +

      Cross platform (Default Browser):

      +
      $ x open spirals
      +

      The Making of Spirals

      Spirals is a good example showing how easy it is to create .NET Core Desktop Web Apps utilizing HTML5's familiar and simple development model to diff --git a/src/wwwroot/gfm/apps/01.md b/src/wwwroot/gfm/apps/01.md index 0b59e3a..6456859 100644 --- a/src/wwwroot/gfm/apps/01.md +++ b/src/wwwroot/gfm/apps/01.md @@ -5,6 +5,18 @@ model to leverage advanced Web Technologies like SVG in a fun, interactive and l [![](https://raw.githubusercontent.com/ServiceStack/docs/master/docs/images/app/spirals/animated.png)](https://youtu.be/Cf-vstYXrmY) +You can run this Gist Desktop App via URL Scheme from (Windows Desktop App): + +app://spirals + +Or via command-line: + + $ app open spirals + +Cross platform (Default Browser): + + $ x open spirals + ## The Making of Spirals Spirals is a good example showing how easy it is to create .NET Core Desktop Web Apps utilizing HTML5's familiar and simple development model to diff --git a/src/wwwroot/gfm/apps/02.html b/src/wwwroot/gfm/apps/02.html index aa496af..aa6c416 100644 --- a/src/wwwroot/gfm/apps/02.html +++ b/src/wwwroot/gfm/apps/02.html @@ -3,6 +3,22 @@

      YouTube: youtu.be/EJ-lDWshjcY

      +

      You can run this Gist Desktop App via URL Scheme from (Windows Desktop App):

      +

      app://sharpdata?mix=northwind.sharpdata

      +

      Or via command-line:

      +
      $ app open sharpdata mix northwind.sharpdata
      +
      +

      Cross platform (Default Browser):

      +
      $ x open sharpdata mix northwind.sharpdata
      +
      +

      +Hosted as a .NET Core App

      +

      As NetCoreApps/SharpData it can also be deployed as a normal stand-alone .NET Core Web App:

      + +

      +Tiny footprint

      An impressively capable .NET Core App that fits into a tiny 20kb .zip footprint thanks to Gist Desktop App's Architecture. It's small dynamic #Script & Vue TypeScript code-base also makes it highly customizable to tailor & further extend with App-specific requirements - suitable for offering advanced system users a quick, capable customized read-only UI of your DBs.

      SharpData started as a demonstration showing how productive #Script can be in the number of areas where diff --git a/src/wwwroot/gfm/apps/02.md b/src/wwwroot/gfm/apps/02.md index a7a1cf0..400fead 100644 --- a/src/wwwroot/gfm/apps/02.md +++ b/src/wwwroot/gfm/apps/02.md @@ -4,6 +4,26 @@ The [SharpData](https://github.com/NetCoreApps/SharpData) .NET Core project is a [![](https://raw.githubusercontent.com/ServiceStack/docs/master/docs/images/release-notes/v5.9/sharpdata-custom-appsettings.png)](https://youtu.be/EJ-lDWshjcY) +You can run this Gist Desktop App via URL Scheme from (Windows Desktop App): + +

      app://sharpdata?mix=northwind.sharpdata

      + +Or via command-line: + + $ app open sharpdata mix northwind.sharpdata + +Cross platform (Default Browser): + + $ x open sharpdata mix northwind.sharpdata + +#### Hosted as a .NET Core App + +As [NetCoreApps/SharpData](https://github.com/NetCoreApps/SharpData) it can also be deployed as a normal stand-alone .NET Core Web App: + + - [https://sharpdata.netcore.io](https://sharpdata.netcore.io) + +### Tiny footprint + An impressively capable .NET Core App that fits into a tiny **20kb .zip** footprint thanks to [Gist Desktop App's Architecture](/gist-desktop-apps). It's small dynamic `#Script` & Vue TypeScript code-base also makes it highly customizable to tailor & further extend with App-specific requirements - suitable for offering advanced system users a quick, capable customized read-only UI of your DBs. diff --git a/src/wwwroot/gfm/apps/03.html b/src/wwwroot/gfm/apps/03.html index d1b5ad6..f66848b 100644 --- a/src/wwwroot/gfm/apps/03.html +++ b/src/wwwroot/gfm/apps/03.html @@ -1,11 +1,18 @@

      The win32 Sharp App contains an examples dashboard of invoking different native Win32 functions:

      -

      You can run this Gist Desktop App via URL Scheme from:

      -
      [app://win32](app://win32)
      -
      +

      You can run this Gist Desktop App via URL Scheme from (Windows Desktop App):

      +

      app://win32

      Or via command-line:

      $ app open win32
       
      +

      Cross platform (Default Browser):

      +
      $ x open win32
      +
      +

      +Kiosk Mode

      +

      This app starts in full-screen Kiosk mode, enabled in its app.settings by:

      +
      CefConfig { Kiosk:true }
      +

      The main source code of this component is in Win32/index.ts, which makes use of the built in TypeScript APIs below from @servicestack/desktop:

      start('%USERPROFILE%\\\\.sharp-apps')
      @@ -30,8 +37,8 @@
       windowSetPosition(x, y)
       
       windowSetSize(width, height)
      -

      -Custom Win32 API

      +

      +Custom Win32 API

      You're also not limited to calling the built-in Win32 APIs above as calling custom APIs just involves wrapping the C# inside your preferred #Script method that you would like to make it available to JS as, e.g. here's the win32 implementation for launching Win32's Color Dialog Box @@ -91,8 +98,8 @@

      }

  • Where it can be called using the same syntax in JS and #Script:

    var selectedColor = await chooseColor(`#336699`)
    -

    -Highly productive live-reloading Development experience

    +

    +Highly productive live-reloading Development experience

    If it weren't for the productivity possible for being able to only needing to develop for Chrome's state-of-the-art rendering engine where you can use advanced features like CSS grid along with the productivity of high-level productive Reactive UI frameworks like Vue, the effort into create a Desktop App like ServiceStack Studio wouldn't be justifiable.

    After the next release we'll create pre-packaged project templates for vue-desktop and react-desktop Desktop Apps to make it easy develop Vue & React Desktop Apps along with scripts to bundle it & publish it to gist. If preferred app.exe also lets you deploy the published app to your own private repo & limit access to only users accessible with a GitHub token which they can open with from a URL with:

    app://user/repo?token={GITHUB_TOKEN}
    diff --git a/src/wwwroot/gfm/apps/03.md b/src/wwwroot/gfm/apps/03.md
    index 084ded0..32f31f5 100644
    --- a/src/wwwroot/gfm/apps/03.md
    +++ b/src/wwwroot/gfm/apps/03.md
    @@ -2,14 +2,24 @@ The [win32](https://github.com/sharp-apps/win32) Sharp App contains an examples
     
     [![](https://raw.githubusercontent.com/ServiceStack/docs/master/docs/images/release-notes/v5.9/win32.png)](https://github.com/sharp-apps/win32)
     
    -You can run this Gist Desktop App via URL Scheme from:
    +You can run this Gist Desktop App via URL Scheme from (Windows Desktop App):
     
    -    [app://win32](app://win32)
    +app://win32
     
     Or via command-line:
     
         $ app open win32
     
    +Cross platform (Default Browser):
    +
    +    $ x open win32
    +
    +### Kiosk Mode
    +
    +This app starts in full-screen **Kiosk** mode, enabled in its [app.settings](https://github.com/sharp-apps/win32/blob/master/scripts/deploy/app.settings) by:
    +
    +    CefConfig { Kiosk:true }
    +
     The main source code of this component is in [Win32/index.ts](https://github.com/sharp-apps/win32/blob/master/src/components/Win32/index.ts),
     which makes use of the built in TypeScript APIs below from `@servicestack/desktop`:
     
    @@ -38,7 +48,7 @@ windowSetPosition(x, y)
     windowSetSize(width, height)
     ```
     
    -#### Custom Win32 API
    +### Custom Win32 API
     
     You're also not limited to calling the built-in Win32 APIs above as calling custom APIs just involves wrapping the C# inside
     your preferred [#Script method](/docs/methods) that you would like to make it available to JS as, 
    @@ -117,7 +127,7 @@ Where it can be called using the same syntax in JS and #Script:
     var selectedColor = await chooseColor(`#336699`)
     ```
     
    -### Highly productive live-reloading Development experience
    +## Highly productive live-reloading Development experience
     
     If it weren't for the productivity possible for being able to only needing to develop for Chrome's state-of-the-art rendering engine where you can use advanced features like CSS grid along with the productivity of high-level productive Reactive UI frameworks like Vue, the effort into create a Desktop App like ServiceStack Studio wouldn't be justifiable. 
     
    diff --git a/src/wwwroot/gfm/sharp-apps/14.html b/src/wwwroot/gfm/sharp-apps/14.html
    index f07e777..b23cdf9 100644
    --- a/src/wwwroot/gfm/sharp-apps/14.html
    +++ b/src/wwwroot/gfm/sharp-apps/14.html
    @@ -2,6 +2,14 @@
     

    Live Demo: blog.web-app.io

    +

    You can run this Gist Desktop App via URL Scheme from (Windows Desktop App):

    +

    app://blog

    +

    Or via command-line:

    +
    $ app open blog
    +
    +

    Cross platform (Default Browser):

    +
    $ x open blog
    +

    To maximize approachability the /Blog Web App has no C# source code, plugins and uses no JavaScript or CSS frameworks yielding an enjoyable development experiences as all the usual complexities of developing a C# Server and modern JS App has been dispensed and you can just focus on the App you want to create, using a plain-text editor on the left, a live updating browser on @@ -58,35 +66,35 @@

    Creating and Posting content benefit from Live Previews where its rendered output can be visualized in real-time before it's published.

    Any textarea can easily be enhanced to enable Live Preview by including the data-livepreview attribute with the element where the output should be rendered in, e.g:

    -
    <textarea data-livepreview=".preview"></textarea>
    -<div class="preview"></div>
    +
    <textarea data-livepreview=".preview"></textarea>
    +<div class="preview"></div>

    The implementation of which is surprisingly simple where the JavaScript snippet in default.js below is used to send its content on each change:

    -
    // Enable Live Preview of new Content
    -textAreas = document.querySelectorAll("textarea[data-livepreview]");
    -for (let i = 0; i < textAreas.length; i++) {
    -  textAreas[i].addEventListener("input", livepreview, false);
    -  livepreview({ target: textAreas[i] });
    -}
    +
    // Enable Live Preview of new Content
    +textAreas = document.querySelectorAll("textarea[data-livepreview]");
    +for (let i = 0; i < textAreas.length; i++) {
    +  textAreas[i].addEventListener("input", livepreview, false);
    +  livepreview({ target: textAreas[i] });
    +}
     
    -function livepreview(e) {
    -  let el = e.target;
    -  let sel = el.getAttribute("data-livepreview");
    +function livepreview(e) {
    +  let el = e.target;
    +  let sel = el.getAttribute("data-livepreview");
     
    -  if (el.value.trim() == "") {
    -    document.querySelector(sel).innerHTML = "<div>Live Preview</div>";
    -    return;
    -  }
    +  if (el.value.trim() == "") {
    +    document.querySelector(sel).innerHTML = "<div>Live Preview</div>";
    +    return;
    +  }
     
    -  let formData = new FormData();
    -  formData.append("content", el.value);
    +  let formData = new FormData();
    +  formData.append("content", el.value);
     
    -  fetch("/preview", {
    -    method: "post",
    -    body: formData
    -  }).then(function(r) { return r.text(); })
    -    .then(function(r) { document.querySelector(sel).innerHTML = r; });
    -}
    + fetch("/preview", { + method: "post", + body: formData + }).then(function(r) { return r.text(); }) + .then(function(r) { document.querySelector(sel).innerHTML = r; }); +}

    To the /preview.html API Page which just renders and captures any Template Content its sent and returns the output:

    {{ content  |> evalTemplate({use:{plugins:'MarkdownScriptPlugin'}}) |> to =>response }}
    diff --git a/src/wwwroot/gfm/sharp-apps/14.md b/src/wwwroot/gfm/sharp-apps/14.md
    index 298a636..398ae50 100644
    --- a/src/wwwroot/gfm/sharp-apps/14.md
    +++ b/src/wwwroot/gfm/sharp-apps/14.md
    @@ -2,6 +2,18 @@
     
     > Live Demo: [blog.web-app.io](http://blog.web-app.io)
     
    +You can run this Gist Desktop App via URL Scheme from (Windows Desktop App):
    +
    +app://blog
    +
    +Or via command-line:
    +
    +    $ app open blog
    +
    +Cross platform (Default Browser):
    +
    +    $ x open blog
    +
     To maximize approachability the [/Blog](https://github.com/NetCoreWebApps/Blog/tree/master/app) Web App has no C# source code, plugins and uses 
     no JavaScript or CSS frameworks yielding an enjoyable development experiences as all the usual complexities of developing a C# Server and modern 
     JS App has been dispensed and you can just focus on the App you want to create, using a plain-text editor on the left, a live updating browser on 
    diff --git a/src/wwwroot/sharp-apps/blog.html b/src/wwwroot/sharp-apps/blog.html
    index 14bdb30..53b5e75 100644
    --- a/src/wwwroot/sharp-apps/blog.html
    +++ b/src/wwwroot/sharp-apps/blog.html
    @@ -4,9 +4,9 @@
     -->
     
     
     
     {{ 'gfm/sharp-apps/14.md' |> githubMarkdown }}
    diff --git a/src/wwwroot/sharp-apps/chat.html b/src/wwwroot/sharp-apps/chat.html
    index 89a7c58..74a5c51 100644
    --- a/src/wwwroot/sharp-apps/chat.html
    +++ b/src/wwwroot/sharp-apps/chat.html
    @@ -4,7 +4,7 @@
     -->
     
     
    @@ -18,6 +18,21 @@
     
     Chat WebApp screenshot
     
    +{{#markdown}}
    +You can run this Gist Desktop App via URL Scheme from (Windows Desktop App):
    +
    +app://chat
    +
    +Or via command-line:
    +
    +    $ app open chat
    +
    +Cross platform (Default Browser):
    +
    +    $ x open chat
    +
    +{{/markdown}}
    +
     

    Develop back-end using .NET IDE's

    diff --git a/src/wwwroot/sharp-apps/plugins.html b/src/wwwroot/sharp-apps/plugins.html index 70d5a93..fd06dae 100644 --- a/src/wwwroot/sharp-apps/plugins.html +++ b/src/wwwroot/sharp-apps/plugins.html @@ -4,7 +4,7 @@ -->

    @@ -34,6 +34,21 @@ Plugins WebApp screenshot +{{#markdown}} +You can run this Gist Desktop App via URL Scheme from (Windows Desktop App): + +app://plugins + +Or via command-line: + + $ app open plugins + +Cross platform (Default Browser): + + $ x open plugins + +{{/markdown}} +

    Registering ServiceStack Plugins

    diff --git a/src/wwwroot/sharp-apps/redis.html b/src/wwwroot/sharp-apps/redis.html index c4542ea..d79c8ce 100644 --- a/src/wwwroot/sharp-apps/redis.html +++ b/src/wwwroot/sharp-apps/redis.html @@ -3,13 +3,34 @@ order: 4 --> +

    + The Redis HTML + and + Redis Vue Apps below + demonstrates the versatility of Sharp Apps showcasing the same App implemented in both server-generated HTML and a Vue SPA App. +

    +

    Redis HTML

    +{{#markdown}} +You can run this Gist Desktop App via URL Scheme from (Windows Desktop App): + +app://redis-html + +Or via command-line: + + $ app open redis-html + +Cross platform (Default Browser): + + $ x open redis-html +{{/markdown}} +

    For the Redis Browser Web App, we wanted to implement an App that was an ideal candidate for a Single Page App but constrain ourselves to do all HTML rendering on the server and have each interaction request a full-page reload to see how a traditional server-generated @@ -75,11 +96,25 @@

    Redis Vue

    +{{#markdown}} +You can run this Gist Desktop App via URL Scheme from (Windows Desktop App): + +app://redis + +Or via command-line: + + $ app open redis + +Cross platform (Default Browser): + + $ x open redis +{{/markdown}} +

    Whilst the above server-generated HTML Redis UI shows how you can easily develop traditional Web Apps using #Script, we've also rewritten the Redis UI as a Single Page App which is the more suitable choice for an App like this as it provides a more diff --git a/src/wwwroot/sharp-apps/rockwind.html b/src/wwwroot/sharp-apps/rockwind.html index 2863e20..f19ed40 100644 --- a/src/wwwroot/sharp-apps/rockwind.html +++ b/src/wwwroot/sharp-apps/rockwind.html @@ -4,9 +4,9 @@ -->

    @@ -16,6 +16,22 @@ API Pages examples for rapidly developing Web APIs.

    + +{{#markdown}} +You can run this Gist Desktop App via URL Scheme from (Windows Desktop App): + +app://rockwind + +Or via command-line: + + $ app open rockwind + +Cross platform (Default Browser): + + $ x open rockwind + +{{/markdown}} +

    Rockstars

    diff --git a/src/wwwroot/sharp-apps/sharpdata.html b/src/wwwroot/sharp-apps/sharpdata.html index a4d5101..5d6c188 100644 --- a/src/wwwroot/sharp-apps/sharpdata.html +++ b/src/wwwroot/sharp-apps/sharpdata.html @@ -4,7 +4,7 @@ -->

    diff --git a/src/wwwroot/sharp-apps/spirals.html b/src/wwwroot/sharp-apps/spirals.html index 05d6069..bb1a943 100644 --- a/src/wwwroot/sharp-apps/spirals.html +++ b/src/wwwroot/sharp-apps/spirals.html @@ -4,7 +4,7 @@ --> diff --git a/src/wwwroot/sharp-apps/studio.html b/src/wwwroot/sharp-apps/studio.html index f9b403b..e4aee3d 100644 --- a/src/wwwroot/sharp-apps/studio.html +++ b/src/wwwroot/sharp-apps/studio.html @@ -4,7 +4,7 @@ -->
    - app open studio - + app://studio - ServiceStack/Studio
    @@ -20,6 +20,18 @@ [![](https://raw.githubusercontent.com/ServiceStack/docs/master/docs/images/release-notes/v5.9/studio-home.png)](https://youtu.be/2FFRLxs7orU) +You can run this Gist Desktop App via URL Scheme from (Windows Desktop App): + +app://studio + +Or via command-line: + + $ app open studio + +Cross platform (Default Browser): + + $ x open studio + Checkout the [github.com/ServiceStack/Studio](https://github.com/ServiceStack/Studio) GitHub project to learn more about ServiceStack Studio. {{/markdown}} diff --git a/src/wwwroot/sharp-apps/win32.html b/src/wwwroot/sharp-apps/win32.html index 0b37a96..8accb9e 100644 --- a/src/wwwroot/sharp-apps/win32.html +++ b/src/wwwroot/sharp-apps/win32.html @@ -4,7 +4,7 @@ -->
    - app open win32 - + app://win32 - sharp-apps/win32
    diff --git a/src/wwwroot/usecases/live-documents.html b/src/wwwroot/usecases/live-documents.html index 981093d..3f35c57 100644 --- a/src/wwwroot/usecases/live-documents.html +++ b/src/wwwroot/usecases/live-documents.html @@ -31,7 +31,7 @@

    csv

    Tesla,Model 3,38990 Tesla,Model X,84990 {{/csv}} -{{ cars |> map => { Make: it[0], Model: it[1], Cost: it[2] } |> htmldump }} +{{ cars |> map => { Make: it[0], Model: it[1], Cost: it[2] } |> htmlDump }} Total Cost: {{ cars |> sum => it[2] |> currency }}{{/raw}}
    From 8dd6b4646633d38615e891c9381ad42241d8bed6 Mon Sep 17 00:00:00 2001 From: Demis Bellot Date: Tue, 16 Jun 2020 05:37:01 +0800 Subject: [PATCH 149/206] Update index.html --- src/wwwroot/sharp-apps/index.html | 81 ++++++++++++++++--------------- 1 file changed, 42 insertions(+), 39 deletions(-) diff --git a/src/wwwroot/sharp-apps/index.html b/src/wwwroot/sharp-apps/index.html index 6ce1a61..6f0a771 100644 --- a/src/wwwroot/sharp-apps/index.html +++ b/src/wwwroot/sharp-apps/index.html @@ -68,16 +68,19 @@

    Getting Started

    By default this will list all sharp-apps available:

    -
        1. redis       Simple, lightweight, versatile Redis Admin UI
    -    2. spirals     Explore and generate different Spirals with SVG
    -    3. blog        Minimal, multi-user Twitter Auth blogging app
    -    4. rockwind    Example combining Rockstars website + data-driven Northwind Browser
    -    5. redis-html  Redis Admin Viewer developed as server-generated HTML Website
    -    6. plugins     Extend Apps with Plugins, ServiceStack Services and other C# extensions
    -    7. chat        Extensible App with custom AppHost leveraging OAuth + SSE for real-time Chat
    -    8. bare        Basic Sharp Content Website
    -
    -Usage: web open <name>
    +
     1. redis       Simple, lightweight, versatile Redis Admin UI
    + 2. spirals     Explore and generate different Spirals with SVG
    + 3. blog        Minimal, multi-user Twitter Auth blogging app
    + 4. rockwind    Example combining Rockstars website + data-driven Northwind Browser
    + 5. redis-html  Redis Admin Viewer developed as server-generated HTML Website
    + 6. plugins     Extend Apps with Plugins, ServiceStack Services and other C# extensions
    + 7. chat        Extensible App with custom AppHost leveraging OAuth + SSE for real-time Chat
    + 8. bare        Basic Sharp Content Website
    + 9. studio      Admin UI for managing ServiceStack Instances (v5.9+)
    +10. sharpdata   Instant UI for browsing multiple RDBMS's
    +11. win32       Demo Desktop App showcasing Win32 APIs
    + 
    +Usage: x open <name>

    Where any of the apps can be installed by specifying the name, e.g. @@ -93,6 +96,8 @@

    Getting Started

    $ x run spirals
    +

    Windows 64 Desktop App

    +

    Each Sharp App can also be run as a .NET Core Windows Desktop App @@ -109,7 +114,7 @@

    Getting Started

    Spirals

    @@ -119,25 +124,11 @@

    Spirals

    model to leverage advanced Web Technologies like SVG in a fun, interactive and live development experience.

    - -

    - -

    - - -
    - -

    +{{#markdown}} +> YouTube: [youtu.be/2FFRLxs7orU](https://youtu.be/Cf-vstYXrmY) + +[![](https://raw.githubusercontent.com/ServiceStack/docs/master/docs/images/app/spirals/animated.png)](https://youtu.be/Cf-vstYXrmY) +{{/markdown}}

    See the Making of Spirals for a walk through on how to create @@ -298,15 +289,27 @@

    Example Sharp Apps

    {{#markdown}} -- [Spirals](/sharp-apps/spirals) -- [Redis](/sharp-apps/redis) -- [Rockwind](/sharp-apps/rockwind) -- [Plugins](/sharp-apps/plugins) -- [Chat](/sharp-apps/chat) -- [Blog](/sharp-apps/blog) -- [SharpData](/sharp-apps/sharpdata) -- [Win32](/sharp-apps/win32) -- [ServiceStack Studio](/sharp-apps/studio) +- [Spirals](/sharp-apps/spirals) - [app://spirals](app://spirals) +- [Redis](/sharp-apps/redis) - [app://redis](app://redis) +- [Rockwind](/sharp-apps/rockwind) - [app://rockwind](app://rockwind) +- [Plugins](/sharp-apps/plugins) - [app://plugins](app://plugins) +- [Chat](/sharp-apps/chat) - [app://chat](app://chat) +- [Blog](/sharp-apps/blog) - [app://blog](app://blog) +- [SharpData](/sharp-apps/sharpdata) - [app://sharpdata](app://sharpdata?mix=northwind.sharpdata) +- [Win32](/sharp-apps/win32) - [app://win32](app://win32) +- [ServiceStack Studio](/sharp-apps/studio) - [app://studio](app://studio) + +#### app URL Schemes + +To open Sharp Apps with the `app://` URL Scheme install the [app dotnet tool](https://docs.servicestack.net/netcore-windows-desktop): + + $ dotnet tool install -g app + $ app -version + +If on macOS/Linux you can use the cross-platform [x dotnet tool](https://docs.servicestack.net/dotnet-tool) to open Sharp Apps in your Default Browser: + + $ dotnet tool install -g x + $ x open spirals {{/markdown}} From 59f10eb473417686ba368525a203cb99e26618cb Mon Sep 17 00:00:00 2001 From: Demis Bellot Date: Tue, 16 Jun 2020 07:00:43 +0800 Subject: [PATCH 150/206] More App + .gfm tweaks --- src/GitHubMarkdownFilters.cs | 26 +++++++--- src/wwwroot/assets/css/gfm.css | 9 ++++ src/wwwroot/assets/js/default.js | 9 +++- src/wwwroot/gfm/apps/01.html | 24 +++++----- src/wwwroot/gfm/apps/02.html | 76 +++++++++++++++--------------- src/wwwroot/gfm/apps/02.md | 10 ++-- src/wwwroot/gfm/apps/03.html | 8 ++-- src/wwwroot/gfm/sharp-apps/13.html | 38 +++++++-------- src/wwwroot/gfm/sharp-apps/14.html | 14 +++--- src/wwwroot/gfm/sharp-apps/15.html | 57 ++++++++++++++++++++++ src/wwwroot/gfm/sharp-apps/15.md | 12 +++++ src/wwwroot/sharp-apps/index.html | 70 +++++++++++++++++++++++---- 12 files changed, 253 insertions(+), 100 deletions(-) create mode 100644 src/wwwroot/gfm/sharp-apps/15.html create mode 100644 src/wwwroot/gfm/sharp-apps/15.md diff --git a/src/GitHubMarkdownFilters.cs b/src/GitHubMarkdownFilters.cs index d414492..659d0b1 100644 --- a/src/GitHubMarkdownFilters.cs +++ b/src/GitHubMarkdownFilters.cs @@ -59,6 +59,8 @@ public static async Task convertScriptToLispBlocks(Stream renderedHtmlMa return MemoryStreamFactory.GetStream(html.ToUtf8Bytes()); } + const bool ReplaceUserContent = true; + public async Task githubMarkdown(ScriptScopeContext scope, string markdownPath) { var file = Context.ProtectedMethods.ResolveFile(nameof(githubMarkdown), scope, markdownPath); @@ -119,14 +121,24 @@ public async Task githubMarkdown(ScriptScopeContext scope, string markdownPath) .PostBytesToUrlAsync(new Dictionary { {"text", bytes.FromUtf8Bytes() }, {"mode", Mode}, {"context", RepositoryContext} }.ToJson().ToUtf8Bytes(), contentType:MimeTypes.Json, requestFilter:x => x.UserAgent = "#Script"); + byte[] wrappedBytes = null; - var headerBytes = "
    ".ToUtf8Bytes(); - var footerBytes = "
    ".ToUtf8Bytes(); - - var wrappedBytes = new byte[headerBytes.Length + htmlBytes.Length + footerBytes.Length]; - System.Buffer.BlockCopy(headerBytes, 0, wrappedBytes, 0, headerBytes.Length); - System.Buffer.BlockCopy(htmlBytes, 0, wrappedBytes, headerBytes.Length, htmlBytes.Length); - System.Buffer.BlockCopy(footerBytes, 0, wrappedBytes, headerBytes.Length + htmlBytes.Length, footerBytes.Length); + if (ReplaceUserContent) + { + var html = htmlBytes.FromUtf8Bytes(); + html = html.Replace("user-content-",""); + wrappedBytes = ("
    " + html + "
    ").ToUtf8Bytes(); + } + else + { + var headerBytes = "
    ".ToUtf8Bytes(); + var footerBytes = "
    ".ToUtf8Bytes(); + + wrappedBytes = new byte[headerBytes.Length + htmlBytes.Length + footerBytes.Length]; + System.Buffer.BlockCopy(headerBytes, 0, wrappedBytes, 0, headerBytes.Length); + System.Buffer.BlockCopy(htmlBytes, 0, wrappedBytes, headerBytes.Length, htmlBytes.Length); + System.Buffer.BlockCopy(footerBytes, 0, wrappedBytes, headerBytes.Length + htmlBytes.Length, footerBytes.Length); + } if (Context.VirtualFiles is IVirtualFiles vfs) { diff --git a/src/wwwroot/assets/css/gfm.css b/src/wwwroot/assets/css/gfm.css index 8d01b29..15a3881 100644 --- a/src/wwwroot/assets/css/gfm.css +++ b/src/wwwroot/assets/css/gfm.css @@ -767,3 +767,12 @@ .gfm blockquote p { padding: .5em 0; } +#content .gfm h2, #content .gfm h3, #content .gfm h4 { + padding: 0; +} +.gfm .h-link { + cursor: pointer; +} +.gfm h2.h-link:hover:after, .gfm h3.h-link:hover:after, .gfm h4.h-link:hover:after { + content: " \00B6"; +} \ No newline at end of file diff --git a/src/wwwroot/assets/js/default.js b/src/wwwroot/assets/js/default.js index 8d8e39a..a65431f 100644 --- a/src/wwwroot/assets/js/default.js +++ b/src/wwwroot/assets/js/default.js @@ -220,5 +220,12 @@ $(bindYouTubeImages); $(function(){ $("a[name*='app://']").each(function() { this.href = 'app://' + splitOnFirst(this.getAttribute('name'), '://')[1]; - }); + }); + $(".gfm h2 .anchor,.gfm h3 .anchor,.gfm h4 .anchor").each(function() { + var href = this.href; + if (!href) return; + var h = $(this).parent(); + h.addClass('h-link'); + h.on('click', function() { location.href = href; }); + }) }); diff --git a/src/wwwroot/gfm/apps/01.html b/src/wwwroot/gfm/apps/01.html index 1e2f8f1..d93ef34 100644 --- a/src/wwwroot/gfm/apps/01.html +++ b/src/wwwroot/gfm/apps/01.html @@ -5,7 +5,7 @@

    You can run this Gist Desktop App via URL Scheme from (Windows Desktop App):

    -

    app://spirals

    +

    app://spirals

    Or via command-line:

    $ app open spirals
     
    @@ -13,7 +13,7 @@
    $ x open spirals
     

    -The Making of Spirals

    +The Making of Spirals

    Spirals is a good example showing how easy it is to create .NET Core Desktop Web Apps utilizing HTML5's familiar and simple development model to leverage advanced Web Technologies like SVG in a fun, interactive and live development experience.

    To start install the dotnet app global tool:

    @@ -68,7 +68,7 @@

    We can now save changes to any of the pages and see our changes reflected instantly in the running App. But we also have access to an even better live-development experience than preview as you save with preview as you type :)

    -Live Previews

    +Live Previews

    To take advantage of this we can exploit one of the features available in all ServiceStack Apps by clicking on /metadata Menu Item to view the Metadata page containing links to our Apps Services, links to Metadata Services and any registered plugins:

    @@ -118,7 +118,7 @@

    Great, hitting save again will show us the effects of each change side-by-size:

    -Multiplying

    +Multiplying

    Now that we have the effect that we want, let's go back to the debug inspector and see what a number of different spirals look side-by-side by wrapping our last svg snippet in another each block:

    <table>{{#each i in range(0, 4) }}
    @@ -156,7 +156,7 @@ 

    Where upon save, our creation will reveal itself in the App's menu:

    -Animating

    +Animating

    With the help of SVG's <animate> we can easily bring our spirals to life by animating different properties on the generated SVG circles.

    As we have to wait for the animation to complete before trying out different effects, we won't benefit from Debug Inspector's live REPL @@ -191,7 +191,7 @@

    Checkout spirals.web-app.io for the animated version.

    -Navigating

    +Navigating

    Lets expand our App beyond these static Spirals by enabling some navigation, this is easily done by adding the snippet below on the top of the home page:

    {{ from ?? 1 | toInt |> to => from }}
     <div style="text-align:right;margin:-54px 0 30px 0">
    @@ -247,7 +247,7 @@ 

    into the rich colorful world of SVG is complete:

    -Recap

    +Recap

    If you've reached this far you've created a beautiful Desktop App without waiting for a single build or App restart, in a live and interactive environment which let you rapidly prototype and get real-time feedback of any experimental changes, to produce a rich graphical app with fast and fluid animations taking advantage of the impressive engineering invested in Chrome, implemented @@ -259,7 +259,7 @@

    of effort and friction to achieve the same result would take the enjoyment out of the experience and the end result would have much less utility where it wouldn't be able to be re-used on different OS's or other websites.

    -Publishing your App

    +Publishing your App

    To share our digital masterpiece with the world we just need to publish it in a GitHub repo, which I've already done for my Spirals app at: https://github.com/mythz/spirals.

    Anyone will then be able to install your App by first downloading the app tool themselves (.NET Core 2.1 Required):

    @@ -271,7 +271,7 @@

    Which installs instantly thanks to the 7kb .zip download that can then be opened by double-clicking on the generated Spirals Desktop Shortcut:

    -Publishing your App with binaries

    +Publishing your App with binaries

    The unique characteristics of Web Apps affords us different ways of publishing your App, e.g. to save users from needing to install the app tool you can run publish in your App's directory:

    $ app publish
    @@ -284,7 +284,7 @@ 

    This includes all app binaries needed to run Web Apps which compresses to 89 MB in a .zip or 61 MB in 7-zip .7z archive.

    -Publishing a self-contained Windows 64 executable

    +Publishing a self-contained Windows 64 executable

    But that's not all, we can even save end users who want to run your app the inconvenience of installing .NET Core :) by creating a self-contained executable with:

    $ app publish-exe
    @@ -294,7 +294,7 @@ 

    the app copied to publish/app.

    This publishing option includes a self-contained .NET Core with all app binaries which compresses to 121 MB in a .zip or 83 MB in 7-zip .7z archive.

    -Publish to the world

    +Publish to the world

    To maximize reach and accessibility of your App leave a comment on the App Gallery where after we link to it on NetCoreWebApps it will available to all users when they look for available apps in:

    $ app list
    @@ -303,7 +303,7 @@ 

    $ app install spirals
     

    -Learnable Living Apps

    +Learnable Living Apps

    A beautiful feature of Web Apps is that they can be enhanced and customized in real-time whilst the App is running. No pre-installation of dev tools or knowledge of .NET and C# is required, anyone can simply use any text editor to peek inside .html files to see how it works and see any of their customizations visible in real-time.

    diff --git a/src/wwwroot/gfm/apps/02.html b/src/wwwroot/gfm/apps/02.html index aa6c416..273e81b 100644 --- a/src/wwwroot/gfm/apps/02.html +++ b/src/wwwroot/gfm/apps/02.html @@ -4,7 +4,7 @@

    You can run this Gist Desktop App via URL Scheme from (Windows Desktop App):

    -

    app://sharpdata?mix=northwind.sharpdata

    +

    app://sharpdata?mix=northwind.sharpdata

    Or via command-line:

    $ app open sharpdata mix northwind.sharpdata
     
    @@ -12,13 +12,13 @@
    $ x open sharpdata mix northwind.sharpdata
     

    -Hosted as a .NET Core App

    +Hosted as a .NET Core App

    As NetCoreApps/SharpData it can also be deployed as a normal stand-alone .NET Core Web App:

    -Tiny footprint

    +Tiny footprint

    An impressively capable .NET Core App that fits into a tiny 20kb .zip footprint thanks to Gist Desktop App's Architecture. It's small dynamic #Script & Vue TypeScript code-base also makes it highly customizable to tailor & further extend with App-specific requirements - suitable for offering advanced system users a quick, capable customized read-only UI of your DBs.

    SharpData started as a demonstration showing how productive #Script can be in the number of areas where @@ -27,9 +27,9 @@

    (in all registered ServiceStack formats) around all registered RDBMS tables, in all OrmLite supported RBDMS's, that includes support for custom fields, multiple querying options, paging, multi OrderBy's in a parameterized SQL query executed with OrmLite's SQL async DB APIs:

    -AutoQuery Script

    +AutoQuery Script

    -/db/_db/_table/index.html +/db/_db/_table/index.html

    {{ {namedConnection:db} |> if (db && db != 'main') |> useDb }}
     
    @@ -81,19 +81,19 @@ 

    Clean URL routes without needing to define & maintain separate routes where the same script supports querying all registered multitenancy databases.

    -Instant Customizable RDBMS UI

    +Instant Customizable RDBMS UI

    The SharpData project essentially provides a UI around this script, surfacing its features & give it instant utility which ended up being so useful that it's become the quickest way to perform fast adhoc DB queries as it's easy to configure which RDBMS's & tables to show in a simple text file, easy to customize its UI, enables 1-click export into Excel and its shortcut syntax support in column filters is a fast way to perform quick adhoc queries.

    -Quick Tour

    +Quick Tour

    We'll quickly go through some of its features to give you an idea of its capabilities, from the above screenshot we can some of its filtering capabilities. All results displayed in the UI are queried using the above sharpdata #Script HTTP API which supports the following features:

    -Filters

    +Filters

    All query string parameter except for db,fields,format,skip,take,orderBy are treated as filters, where you can:

    • Use =null or !=null to search NULL columns
    • @@ -106,22 +106,22 @@

      Here's the filtered list used in the above screenshot:

      /db/main/Order?Id=>10200&CustomerId=V%&Freight=<=30&OrderDate=>1997-01-01

      -Custom Field Selection

      +Custom Field Selection

      The column selection icon on the top left of the results lets you query custom select columns which is specified using ?fields:

      -Multiple OrderBy's

      +Multiple OrderBy's

      You can use AutoQuery Syntax to specify multiple Order By's:

      -Paging

      +Paging

      Use ?skip and ?take to page through a result set

      -Format

      +Format

      Use ?format to specify which Content-Type to return the results in, e.g:

      -Multitenancy

      +Multitenancy

      You can specify which registered DB to search using the path info, use main to query the default database:

      /db/<named-db>/<table>
       

      -Launching SharpData

      +Launching SharpData

      To run SharpData in a .NET Core Desktop App you'll need latest app dotnet tool:

      $ dotnet tool update -g app
       
      @@ -142,7 +142,7 @@

      If on macOS/Linux you can use the x dotnet tool instead to view SharpData in your default browser

      -Configure RDBMS from command-line

      +Configure RDBMS from command-line

      You can override which database to connect to by specifying it on the command line, e.g. here's an example of connecting to https://techstacks.io RDBMS:

      $ app open sharpdata -db postgres -db.connection $TECHSTACKS_DB
       
      @@ -150,7 +150,7 @@

      find the table you want, e.g:

      -app URL Schemes

      +app URL Schemes

      What can be done with the open command on the command-line can also be done from a custom URL Scheme, a feature that opens up a myriad of new possibilities as app can open Gist Desktop Apps from Gists or in public & private GitHub repositories, where it's able to download and launch Apps on the fly with custom arguments - allowing a single URL to run a never installed Desktop App stored in a @@ -173,32 +173,34 @@

      or by specifying an Environment variable containing the connection string:

      app://sharpdata?db=postgres&db.connection=$TECHSTACKS_DB
       
      +

      +Mix in Gists

      In addition to Sharp Apps being downloaded and run on the fly, they're also able to take advantage of the dotnet tools mix support to also download another Gist's content into the Sharp App's working directory.

      With this you can publish a custom dataset in an SQLite database save it as a gist and generate a single URL that everyone can use to download the database and open it in SharpData, e.g:

      -
        -
      • app://sharpdata?mix=northwind.sqlite&db=sqlite&db.connection=northwind.sqlite
      • -
      +

      +app://sharpdata?mix=northwind.sqlite&db=sqlite&db.connection=northwind.sqlite +

      It's possible to use the user-friendly northwind.sqlite alias here as it's published in the global mix.md directory where it links to the northwind.sqlite gist.

      For your custom databases you use the Gist Id instead or if you plan to use this feature a lot you can override which mix.md document that app should source its links from by specifying another Gist Id in the MIX_SOURCE Environment variable (or see below - to create a local alias).

      But if you're already mixing in an external gist you may as well include a custom app.settings in the Gist so it's pre-configured with custom RDBMS registrations and table lists, e.g:

      -
        -
      • app://sharpdata?mix=northwind.sharpdata
      • -
      +

      +app://sharpdata?mix=northwind.sharpdata +

      Which applies the northwind.sharpdata gist, which can also be referenced by Gist Id:

      -
        -
      • app://sharpdata?mix=0ce0d5b828303f1cb4637450b563adbd
      • -
      +

      +app://sharpdata?mix=0ce0d5b828303f1cb4637450b563adbd +

      Alternatively you may instead prefer to publish it to a private GitHub repo instead of a Gist which anyone can open up with:

      app://user/sharpdata-private?token={TOKEN}
       

      The app dotnet tools will use the latest published GitHub release if there are any, otherwise will use the master.zip archive, this feature can be used to maintain a working master repo and maintain control ver when to publish new versions of your custom SharpData App.

      -app local aliases

      +app local aliases

      Where ever you can use a Gist Id, you can assign a local user-friendly alias to use instead. So if you had a custom sqlite database and sharpdata app.settings you could assign it to a local db alias with:

      $ app alias db 0ce0d5b828303f1cb4637450b563adbd
      @@ -228,19 +230,19 @@ 

      Other alias command include:

      -View all aliases

      +View all aliases
      $ app alias
       

      -View single alias

      +View single alias
      $ app alias db
       

      -Remove an alias

      +Remove an alias
      $ app unalias db
       

      -Custom App Settings

      +Custom App Settings

      Each time a Gist Desktop App is opened it downloads and overrides the existing Gist with the latest version which it loads in a Gist VFS where any of its files can be overridden with a local copy.

      As the App's working directory is preserved between restarts you can provide a custom app.settings at:

      %USERPROFILE%\.sharp-apps\sharpdata\app.settings
      @@ -260,12 +262,12 @@ 

      Which will display both RDBMS Databases, showing only the user-specified tables in app.settings above:

      -Open in Excel

      +Open in Excel

      SharpData detects if Excel is installed and lets you open the un-paged filtered resultset directly by clicking the Excel button

      This works seamlessly as it's able to "by-pass" the browser download where the query is performed by the back-end .NET Core Server who streams the response directly to the Users Downloads folder and launches it in Excel as soon as it's finished.

      -Custom Row Components

      +Custom Row Components

      Whilst a tabular grid view might be a natural UI for browsing a database for devs, we can do better since we have the full UI source code of the Vue components. A filtered tabular view makes it fast to find the record you're interested in, but it's not ideal for quickly finding related information about an Entity.

      To provide a more customized UX for different App UIs, SharpData includes support for "Row Components" @@ -351,10 +353,10 @@

      Which looks like:

      -SharpData .NET Core Project

      +SharpData .NET Core Project

      Whilst NetCoreApps/SharpData can live a charmed life as a Desktop App, it's also just a regular ServiceStack .NET Core App with a Startup.cs and AppHost that can be developed, published and deployed as you're used to, here's an instance of it deployed as a .NET Core App on Linux:

      -sharpdata.netcore.io +sharpdata.netcore.io

      For best experience we recommend running locally to experience it without latency of our servers in Germany

      @@ -379,7 +381,7 @@

      The included /typings are just the TypeScript definitions for each library which TypeScript uses for its static analysis & its great dev UX in IDEs & VSCode, but are only needed during development and not deployed with the project.

      -Publish to Gist Desktop App

      +Publish to Gist Desktop App

      The primary way SharpData is distributed is as a Gist Desktop App, where it's able to provide instant utility by running on a users local machine inside a native Chromium Desktop App making it suitable for a much broader use-case as a fast, lightweight, always up-to-date Desktop App with deeper Windows integration all packaged in a tiny 20kb .zip footprint. There's no need to provision servers, setup CI, manage cloud hosting resources, you can simply run a script to update a Gist where its latest features are immediately available to your end users the next time it's run.

      To run, test & publish it as a Desktop App you can use the pre-made scripts in package.json. Rider provides a nice UX here as it lets you run each individual script directly from their json editor:

      @@ -403,7 +405,7 @@

      app://<user>/repo?token=<token>
       

      -RDBMS Configuration

      +RDBMS Configuration

      When running as a .NET Core App you'd need to register which RDBMS's you want to use with OrmLite's configuration, e.g. the screenshot above registers an SQLite northwind.sqlite database and the https://techstacks.io PostgreSQL Database:

      container.Register<IDbConnectionFactory>(c => new OrmLiteConnectionFactory(
           MapProjectPath("~/northwind.sqlite"), SqliteDialect.Provider));
      @@ -432,7 +434,7 @@ 

      args.tables_techstacks technology,technology_stack,technology_choice,organization,organization_member,post,post_comment,post_vote,custom_user_auth,user_auth_details,user_activity,page_stats

      -Feedback

      +Feedback

      We hope SharpData serves useful in some capacity, whether it's being able to quickly develop and Ship a UI to stakeholders or as a template to develop .NET Core Apps that you can distribute as Sharp Apps, as an example to explore the delivery and platform potential of URL schemes and install-less Desktop Apps or just as an inspiration for areas where #Script shines & the different kind of Apps you can create with it.

      Whilst app is Windows 64 only, you can use the x cross-platform tool and its xapp:// URL scheme to run Sharp Apps on macOS/Linux, it just wont have access to any of its Window Integration features.

      \ No newline at end of file diff --git a/src/wwwroot/gfm/apps/02.md b/src/wwwroot/gfm/apps/02.md index 400fead..0024da2 100644 --- a/src/wwwroot/gfm/apps/02.md +++ b/src/wwwroot/gfm/apps/02.md @@ -167,7 +167,7 @@ find the table you want, e.g: ![](https://raw.githubusercontent.com/ServiceStack/docs/master/docs/images/release-notes/v5.9/sharpdata-technology.png) -### app URL Schemes +### app URL Schemes What can be done with the `open` command on the command-line can also be done from a **custom URL Scheme**, a feature that opens up a myriad of new possibilities as `app` can open [Gist Desktop Apps](https://sharpscript.net/docs/gist-desktop-apps) from Gists or in public & private GitHub repositories, @@ -195,13 +195,15 @@ or by specifying an Environment variable containing the connection string: app://sharpdata?db=postgres&db.connection=$TECHSTACKS_DB +### Mix in Gists + In addition to Sharp Apps being downloaded and run on the fly, they're also able to take advantage of the dotnet tools [mix support](https://docs.servicestack.net/mix-tool) to also download another Gist's content into the Sharp App's working directory. With this you can publish a custom dataset in an SQLite database save it as a gist and **generate a single URL** that everyone can use to download the database and open it in **SharpData**, e.g: - - [app://sharpdata?mix=northwind.sqlite&db=sqlite&db.connection=northwind.sqlite](app://sharpdata?mix=northwind.sqlite&db=sqlite&db.connection=northwind.sqlite) +

      app://sharpdata?mix=northwind.sqlite&db=sqlite&db.connection=northwind.sqlite

      It's possible to use the user-friendly `northwind.sqlite` alias here as it's published in the global [mix.md](https://gist.github.com/gistlyn/9b32b03f207a191099137429051ebde8) directory where it links to the [northwind.sqlite gist](https://gist.github.com/gistlyn/97d0bcd3ebd582e06c85f8400683e037). @@ -211,11 +213,11 @@ For your custom databases you use the **Gist Id** instead or if you plan to use But if you're already mixing in an external gist you may as well include a custom `app.settings` in the Gist so it's pre-configured with custom RDBMS registrations and table lists, e.g: - - [app://sharpdata?mix=northwind.sharpdata](app://sharpdata?mix=northwind.sharpdata) +

      app://sharpdata?mix=northwind.sharpdata

      Which applies the [northwind.sharpdata gist](https://gist.github.com/gistlyn/0ce0d5b828303f1cb4637450b563adbd), which can also be referenced by **Gist Id**: - - [app://sharpdata?mix=0ce0d5b828303f1cb4637450b563adbd](app://sharpdata?mix=0ce0d5b828303f1cb4637450b563adbd) +

      app://sharpdata?mix=0ce0d5b828303f1cb4637450b563adbd

      Alternatively you may instead prefer to publish it to a private GitHub repo instead of a Gist which anyone can open up with: diff --git a/src/wwwroot/gfm/apps/03.html b/src/wwwroot/gfm/apps/03.html index f66848b..77061b3 100644 --- a/src/wwwroot/gfm/apps/03.html +++ b/src/wwwroot/gfm/apps/03.html @@ -1,7 +1,7 @@

      The win32 Sharp App contains an examples dashboard of invoking different native Win32 functions:

      You can run this Gist Desktop App via URL Scheme from (Windows Desktop App):

      -

      app://win32

      +

      app://win32

      Or via command-line:

      $ app open win32
       
      @@ -9,7 +9,7 @@
      $ x open win32
       

      -Kiosk Mode

      +Kiosk Mode

      This app starts in full-screen Kiosk mode, enabled in its app.settings by:

      CefConfig { Kiosk:true }
       
      @@ -38,7 +38,7 @@

      windowSetSize(width, height)

    -Custom Win32 API

    +Custom Win32 API

    You're also not limited to calling the built-in Win32 APIs above as calling custom APIs just involves wrapping the C# inside your preferred #Script method that you would like to make it available to JS as, e.g. here's the win32 implementation for launching Win32's Color Dialog Box @@ -99,7 +99,7 @@

    Where it can be called using the same syntax in JS and #Script:

    var selectedColor = await chooseColor(`#336699`)

    -Highly productive live-reloading Development experience

    +Highly productive live-reloading Development experience

    If it weren't for the productivity possible for being able to only needing to develop for Chrome's state-of-the-art rendering engine where you can use advanced features like CSS grid along with the productivity of high-level productive Reactive UI frameworks like Vue, the effort into create a Desktop App like ServiceStack Studio wouldn't be justifiable.

    After the next release we'll create pre-packaged project templates for vue-desktop and react-desktop Desktop Apps to make it easy develop Vue & React Desktop Apps along with scripts to bundle it & publish it to gist. If preferred app.exe also lets you deploy the published app to your own private repo & limit access to only users accessible with a GitHub token which they can open with from a URL with:

    app://user/repo?token={GITHUB_TOKEN}
    diff --git a/src/wwwroot/gfm/sharp-apps/13.html b/src/wwwroot/gfm/sharp-apps/13.html
    index 4a58bdf..2d0679a 100644
    --- a/src/wwwroot/gfm/sharp-apps/13.html
    +++ b/src/wwwroot/gfm/sharp-apps/13.html
    @@ -11,7 +11,7 @@
     
     

    Most development will happen within /client which is automatically published to /app using parcel's CLI that's invoked from the included npm scripts.

    -client

    +client

    The difference with templates-app is that the client source code is maintained in the /client folder and uses Parcel JS to automatically bundle and publish your App to /app/wwwroot which is updated with live changes during development.

    The client folder also contains the npm package.json which contains all npm scripts required during development.

    If this is the first time using Parcel, you will need to install its global CLI:

    @@ -27,7 +27,7 @@

    Which will host your App at http://localhost:5000 which in debug mode will enable hot reloading which will automatically reload your web page as it detects any file changes made by parcel.

    -server

    +server

    To enable even richer functionality, this Sharp Apps template is also pre-configured with a custom Server project where you can extend your Web App with Plugins where all Plugins, Services, Filters, etc are automatically wired and made available to your Web App.

    This template includes a simple ServerPlugin.cs which contains an Empty ServerPlugin and Hello Service:

    public class ServerPlugin : IPlugin
    @@ -64,29 +64,29 @@ 

    Which saves generate DTOs for all your ServiceStack Services in dtos.ts which can then be accessed in your TypeScript source code.

    If preferred you can instead develop Server APIs with API Pages, an example is included in /client/hello/_name.html

    -
    {{ { result: `Hi ${name} from /hello/_name.html` } |> return }}
    +
    {{ { result: `Hi ${name} from /hello/_name.html` } |> return }}

    Which as it uses the same data structure as the Hello Service above, can be called with ServiceStack's JsonServiceClient and generated TypeScript DTOs.

    The /client/index.ts shows an example of this where initially the App calls the C# Hello ServiceStack Service:

    -
    import { client } from "./shared";
    -import { Hello, HelloResponse } from "./dtos";
    +
    import { client } from "./shared";
    +import { Hello, HelloResponse } from "./dtos";
     
    -const result = document.querySelector("#result")!;
    +const result = document.querySelector("#result")!;
     
    -document.querySelector("#Name")!.addEventListener("input", async e => {
    -  const value = (e.target as HTMLInputElement).value;
    -  if (value != "") {
    -    const request = new Hello();
    -    request.name = value;
    -    const response = await client.get(request);
    -    // const response = await client.get<HelloResponse>(`/hello/${request.name}`); //call /hello/_name.html
    -    result.innerHTML = response.result;
    -  } else {
    -    result.innerHTML = "";
    -  }
    -});
    +document.querySelector("#Name")!.addEventListener("input", async e => { + const value = (e.target as HTMLInputElement).value; + if (value != "") { + const request = new Hello(); + request.name = value; + const response = await client.get(request); + // const response = await client.get<HelloResponse>(`/hello/${request.name}`); //call /hello/_name.html + result.innerHTML = response.result; + } else { + result.innerHTML = ""; + } +});

    But while your App is running you can instead toggle the uncommented the line and hit Ctrl+S to save index.ts which Parcel will automatically transpile and publish to /app/wwwroot that will be detected by Hot Reload to automatically reload the page with the latest changes. Now typing in the text field will display the response from calling the /hello/_name.html API instead.

    -Deployments

    +Deployments

    During development Parcel maintains a debug and source-code friendly version of your App. Before deploying you can build an optimized production version of your App with:

    $ npm run build
     
    diff --git a/src/wwwroot/gfm/sharp-apps/14.html b/src/wwwroot/gfm/sharp-apps/14.html index b23cdf9..e06c808 100644 --- a/src/wwwroot/gfm/sharp-apps/14.html +++ b/src/wwwroot/gfm/sharp-apps/14.html @@ -3,7 +3,7 @@

    Live Demo: blog.web-app.io

    You can run this Gist Desktop App via URL Scheme from (Windows Desktop App):

    -

    app://blog

    +

    app://blog

    Or via command-line:

    $ app open blog
     
    @@ -18,13 +18,13 @@ automatically created an populated on first run if no database exists, or if preferred can be changed to use any other popular RDBMS using just config.

    -Multi User Blogging Platform

    +Multi User Blogging Platform

    Any number of users can Sign In via Twitter and publish content under their Twitter Username where only they'll be able to modify their own Content. Setting up Twitter is as easy as it can be which just requires modifying the Twitter Auth Config in app.settings with the URL where the blog is hosted and the OAuth Keys for the Twitter OAuth App created at apps.twitter.com.

    -Rich Content

    +Rich Content

    Whilst most blogging platforms just edit static text, each Post content has access to full Templates language features so they can be used to create Live Documents or Render Markdown which is itself just @@ -35,7 +35,7 @@

    By default the Markdig implementation is used to render Markdown but can also be configured to use an alternate Markdown provider.

    -Rich Markdown Editor

    +Rich Markdown Editor

    To make it easy to recall Markdown features, each Content is equipped with a Rich Text editor containing the most popular formatting controls along with common short-cuts for each feature, discoverable by hovering over each button:

    @@ -43,13 +43,13 @@

    Ctrl+/ or blocks with CTRL+SHIFT+/.

    Another nice productivity win is being able to CTRL+CLICK on any Content you created to navigate to its Edit page.

    -Auto saved drafts

    +Auto saved drafts

    The content in each Text input and textarea is saved to localStorage on each key-press so you can freely reload pages and navigate around the site where all unpublished content will be preserved upon return.

    When you want to revert back to the original published content you can clear the text boxes and reload the page which will load content from the database instead of localStorage.

    -Server Validation

    +Server Validation

    The new.html and edit.html pages shows examples of performing server validation with #Script:

    {{ assignErrorAndContinueExecuting: ex }}
     
    @@ -62,7 +62,7 @@ 

    For more info see the docs on Error Handling.

    -Live Previews

    +Live Previews

    Creating and Posting content benefit from Live Previews where its rendered output can be visualized in real-time before it's published.

    Any textarea can easily be enhanced to enable Live Preview by including the data-livepreview attribute with the element where the output should be rendered in, e.g:

    diff --git a/src/wwwroot/gfm/sharp-apps/15.html b/src/wwwroot/gfm/sharp-apps/15.html new file mode 100644 index 0000000..f56de26 --- /dev/null +++ b/src/wwwroot/gfm/sharp-apps/15.html @@ -0,0 +1,57 @@ +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    AppURL SchemeCommand line (x-plat)
    Spiralsapp://spiralsx open spirals
    Redisapp://redisx open redis
    Rockwindapp://rockwindx open rockwind
    Pluginsapp://pluginsx open plugins
    Chatapp://chatx open chat
    Blogapp://blogx open blog
    Win32app://win32x open win32
    ServiceStack Studioapp://studiox open studio
    SharpDataapp://sharpdata?mix=northwind.sharpdatax open sharpdata mix northwind.sharpdata
    +
    \ No newline at end of file diff --git a/src/wwwroot/gfm/sharp-apps/15.md b/src/wwwroot/gfm/sharp-apps/15.md new file mode 100644 index 0000000..e236dfc --- /dev/null +++ b/src/wwwroot/gfm/sharp-apps/15.md @@ -0,0 +1,12 @@ +| App | URL Scheme | Command line (x-plat) | +|-------------------------------------------|----------------------------------|-----------------------| +| [Spirals](/sharp-apps/spirals) | [app://spirals](app://spirals) | $ x open spirals | +| [Redis](/sharp-apps/redis) | [app://redis](app://redis) | $ x open redis | +| [Rockwind](/sharp-apps/rockwind) | [app://rockwind](app://rockwind) | $ x open rockwind | +| [Plugins](/sharp-apps/plugins) | [app://plugins](app://plugins) | $ x open plugins | +| [Chat](/sharp-apps/chat) | [app://chat](app://chat) | $ x open chat | +| [Blog](/sharp-apps/blog) | [app://blog](app://blog) | $ x open blog | +| [Win32](/sharp-apps/win32) | [app://win32](app://win32) | $ x open win32 | +| [ServiceStack Studio](/sharp-apps/studio) | [app://studio](app://studio) | $ x open studio | +| [SharpData](/sharp-apps/sharpdata) | [app://sharpdata?mix=northwind.sharpdata](app://sharpdata?mix=northwind.sharpdata) | $ x open sharpdata mix northwind.sharpdata | + diff --git a/src/wwwroot/sharp-apps/index.html b/src/wwwroot/sharp-apps/index.html index 6f0a771..ad47733 100644 --- a/src/wwwroot/sharp-apps/index.html +++ b/src/wwwroot/sharp-apps/index.html @@ -287,17 +287,69 @@

    Example Sharp Apps

    github.com/sharp-apps.

    +{{* 'gfm/sharp-apps/15.md' |> githubMarkdown *}} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    AppURL SchemeCommand line (Win/macOS/Linux)
    Spiralsapp://spiralsx open spirals
    Redisapp://redis$ x open redis
    Rockwindapp://rockwind$ x open rockwind
    Pluginsapp://plugins$ x open plugins
    Chatapp://chat$ x open chat
    Blogapp://blog$ x open blog
    Win32app://win32$ x open win32
    ServiceStack Studioapp://studio$ x open studio
    SharpDataapp://sharpdata?mix=northwind.sharpdata$ x open sharpdata mix northwind.sharpdata
    + {{#markdown}} -- [Spirals](/sharp-apps/spirals) - [app://spirals](app://spirals) -- [Redis](/sharp-apps/redis) - [app://redis](app://redis) -- [Rockwind](/sharp-apps/rockwind) - [app://rockwind](app://rockwind) -- [Plugins](/sharp-apps/plugins) - [app://plugins](app://plugins) -- [Chat](/sharp-apps/chat) - [app://chat](app://chat) -- [Blog](/sharp-apps/blog) - [app://blog](app://blog) -- [SharpData](/sharp-apps/sharpdata) - [app://sharpdata](app://sharpdata?mix=northwind.sharpdata) -- [Win32](/sharp-apps/win32) - [app://win32](app://win32) -- [ServiceStack Studio](/sharp-apps/studio) - [app://studio](app://studio) +> As [SharpData](/sharp-apps/sharpdata) is a generic RDBMS UI it needs to [mix in Gists](/sharp-apps/sharpdata#mix-in-gists) containing the DB +info and/or **.sqlite** db if using SQLite #### app URL Schemes From 79b8966574fe879002f81b96587f69f9ac0563dd Mon Sep 17 00:00:00 2001 From: Demis Bellot Date: Tue, 16 Jun 2020 07:01:38 +0800 Subject: [PATCH 151/206] Update index.html --- src/wwwroot/sharp-apps/index.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/wwwroot/sharp-apps/index.html b/src/wwwroot/sharp-apps/index.html index ad47733..6d7111c 100644 --- a/src/wwwroot/sharp-apps/index.html +++ b/src/wwwroot/sharp-apps/index.html @@ -301,7 +301,7 @@

    Example Sharp Apps

    Spirals app://spirals - x open spirals + $ x open spirals Redis From 7f4deb3e2ba6cc0c5f750d388607935851aff68e Mon Sep 17 00:00:00 2001 From: Demis Bellot Date: Tue, 16 Jun 2020 07:04:35 +0800 Subject: [PATCH 152/206] Update 02.md --- src/wwwroot/gfm/apps/02.html | 6 +++--- src/wwwroot/gfm/apps/02.md | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/wwwroot/gfm/apps/02.html b/src/wwwroot/gfm/apps/02.html index 273e81b..3ac508a 100644 --- a/src/wwwroot/gfm/apps/02.html +++ b/src/wwwroot/gfm/apps/02.html @@ -14,9 +14,9 @@

    Hosted as a .NET Core App

    As NetCoreApps/SharpData it can also be deployed as a normal stand-alone .NET Core Web App:

    - +

    +https://sharpdata.netcore.io +

    Tiny footprint

    An impressively capable .NET Core App that fits into a tiny 20kb .zip footprint thanks to Gist Desktop App's Architecture. It's small dynamic #Script & Vue TypeScript code-base also makes it highly customizable to tailor & further extend with diff --git a/src/wwwroot/gfm/apps/02.md b/src/wwwroot/gfm/apps/02.md index 0024da2..3b841db 100644 --- a/src/wwwroot/gfm/apps/02.md +++ b/src/wwwroot/gfm/apps/02.md @@ -20,7 +20,7 @@ Cross platform (Default Browser): As [NetCoreApps/SharpData](https://github.com/NetCoreApps/SharpData) it can also be deployed as a normal stand-alone .NET Core Web App: - - [https://sharpdata.netcore.io](https://sharpdata.netcore.io) +#### [https://sharpdata.netcore.io](https://sharpdata.netcore.io) ### Tiny footprint From ef3f1ac95f8a90293182d11fd12ce09f735e872f Mon Sep 17 00:00:00 2001 From: Demis Bellot Date: Wed, 17 Jun 2020 12:43:55 +0800 Subject: [PATCH 153/206] Update links --- src/CustomScriptMethods.cs | 6 +-- src/wwwroot/docs/db-scripts.html | 26 ++++++------ src/wwwroot/docs/redis-scripts.html | 4 +- src/wwwroot/docs/script-pages.html | 16 ++++---- src/wwwroot/gfm/apps/01.html | 2 +- src/wwwroot/gfm/apps/01.md | 2 +- src/wwwroot/gfm/sharp-apis/04.html | 10 ++--- src/wwwroot/gfm/sharp-apis/04.md | 10 ++--- src/wwwroot/gfm/sharp-apps/06.html | 16 ++------ src/wwwroot/gfm/sharp-apps/06.md | 16 ++------ src/wwwroot/gfm/sharp-apps/14.html | 8 ++-- src/wwwroot/gfm/sharp-apps/14.md | 8 ++-- src/wwwroot/gfm/sharp-pages/10.html | 2 +- src/wwwroot/gfm/sharp-pages/10.md | 2 +- src/wwwroot/sharp-apps/chat.html | 40 +++++++++---------- .../sharp-apps/deploying-sharp-apps.html | 8 ++-- 16 files changed, 78 insertions(+), 98 deletions(-) diff --git a/src/CustomScriptMethods.cs b/src/CustomScriptMethods.cs index bad175d..98a97cb 100644 --- a/src/CustomScriptMethods.cs +++ b/src/CustomScriptMethods.cs @@ -163,11 +163,11 @@ public async Task includeContentFile(ScriptScopeContext scope, string virtualPat nameof(ProtectedScripts) => typeof(ProtectedScripts), nameof(InfoScripts) => typeof(InfoScripts), nameof(RedisScripts) => typeof(RedisScripts), - nameof(DbScripts) => typeof(DbScripts), nameof(DbScriptsAsync) => typeof(DbScriptsAsync), nameof(ValidateScripts) => typeof(ValidateScripts), nameof(AutoQueryScripts) => typeof(AutoQueryScripts), nameof(ServiceStackScripts) => typeof(ServiceStackScripts), + _ => throw new NotSupportedException(name) }; public IRawString methodLinkToSrc(string name) @@ -183,8 +183,8 @@ public IRawString methodLinkToSrc(string name) ? "https://github.com/ServiceStack/ServiceStack/blob/master/src/ServiceStack/InfoScripts.cs" : type == typeof(RedisScripts) ? "https://github.com/ServiceStack/ServiceStack.Redis/blob/master/src/ServiceStack.Redis/RedisScripts.cs" - : type == typeof(DbScripts) || type == typeof(DbScriptsAsync) - ? $"https://github.com/ServiceStack/ServiceStack.OrmLite/tree/master/src/ServiceStack.OrmLite/{type.Name}.cs" + : type == typeof(DbScriptsAsync) + ? "https://github.com/ServiceStack/ServiceStack.OrmLite/tree/master/src/ServiceStack.OrmLite/DbScriptsAsync.cs" : type == typeof(ValidateScripts) ? "https://github.com/ServiceStack/ServiceStack/blob/master/src/ServiceStack/ValidateScripts.cs" : type == typeof(AutoQueryScripts) diff --git a/src/wwwroot/docs/db-scripts.html b/src/wwwroot/docs/db-scripts.html index fd54fab..db15c6a 100644 --- a/src/wwwroot/docs/db-scripts.html +++ b/src/wwwroot/docs/db-scripts.html @@ -78,32 +78,32 @@

    DB Filter Examples

    Customers /northwind/customers - /northwind/customers.html + /northwind/customers.html Employees /northwind/employees - /northwind/employees.html + /northwind/employees.html Products /northwind/products - /northwind/products.html + /northwind/products.html Categories /northwind/categories - /northwind/categories.html + /northwind/categories.html Suppliers /northwind/suppliers - /northwind/suppliers.html + /northwind/suppliers.html Shipppers /northwind/shippers - /northwind/shippers.html + /northwind/shippers.html Page Queries @@ -111,7 +111,7 @@

    DB Filter Examples

    Customers in Germany /northwind/customers?country=Germany - /northwind/customers.html + /northwind/customers.html Customers in London @@ -121,22 +121,22 @@

    DB Filter Examples

    Alfreds Futterkiste Details /northwind/customer?id=ALFKI - /northwind/customer.html + /northwind/customer.html Order #10643 /northwind/order?id=10643 - /northwind/order.html + /northwind/order.html Employee Nancy Davolio Details /northwind/employee?id=1 - /northwind/employee.html + /northwind/employee.html Chai Product Details /northwind/products?id=1 - /northwind/products.html + /northwind/products.html Beverage Products @@ -165,7 +165,7 @@

    DB Filter Examples

    Accept HTTP Header also supported
    - /api/customers.html + /api/customers.html Alfreds Futterkiste Details @@ -210,7 +210,7 @@

    DB Filter Examples

    - /api/products.html + /api/products.html Chai Product Details diff --git a/src/wwwroot/docs/redis-scripts.html b/src/wwwroot/docs/redis-scripts.html index 094f75e..48187b5 100644 --- a/src/wwwroot/docs/redis-scripts.html +++ b/src/wwwroot/docs/redis-scripts.html @@ -92,7 +92,7 @@

    Redis Vue

    redis.web-app.io is a generic Redis Database Viewer that provides a human-friendly view of any Redis data Type, including a dedicated UI to create and populate any Redis data type which just utilizes the above Redis Scripts - and a single Vue index.html App to power the Redis UI: + and a single Vue index.html App to power the Redis UI:

    @@ -104,7 +104,7 @@

    Redis HTML

    redis-html.web-app.io is a version of Redis UI built using just #Script and Redis - methods where all functionality is maintained in a single index.html + methods where all functionality is maintained in a single index.html weighing under <200 LOC including HTML and JavaScript. It's a good example of how the declarative style of programming that #Script encourages a highly-readable code-base that packs a lot of functionality in a tiny foot print.

    diff --git a/src/wwwroot/docs/script-pages.html b/src/wwwroot/docs/script-pages.html index 528920a..ce46fb3 100644 --- a/src/wwwroot/docs/script-pages.html +++ b/src/wwwroot/docs/script-pages.html @@ -184,7 +184,7 @@

    Pretty URLs by default

    /db.html - /db.html + /db.html /posts/new @@ -192,7 +192,7 @@

    Pretty URLs by default

    /posts/new.html - /posts/new.html + /posts/new.html @@ -211,11 +211,11 @@

    Pretty URLs by default

    / - /index.html + /index.html /index.html - /index.html + /index.html @@ -240,17 +240,17 @@

    Dynamic Page Routes

    /ServiceStack - /_user/index.html + /_user/index.html user=ServiceStack /posts/markdown-example - /posts/_slug/index.html + /posts/_slug/index.html slug=markdown-example /posts/markdown-example/edit - /posts/_slug/edit.html + /posts/_slug/edit.html slug=markdown-example @@ -434,7 +434,7 @@

    Init Pages

    - This is used in the Blog Web App's _init.html + This is used in the Blog Web App's _init.html where it will create a new blog.sqlite database if it doesn't exist seeded with the UserInfo and Posts Tables and initial data, e.g:

    diff --git a/src/wwwroot/gfm/apps/01.html b/src/wwwroot/gfm/apps/01.html index d93ef34..3311b92 100644 --- a/src/wwwroot/gfm/apps/01.html +++ b/src/wwwroot/gfm/apps/01.html @@ -296,7 +296,7 @@

    Publish to the world

    To maximize reach and accessibility of your App leave a comment on the App Gallery -where after we link to it on NetCoreWebApps it will available to all users when they look for available apps in:

    +where after we link to it on Sharp Apps it will available to all users when they look for available apps in:

    $ app list
     

    Which can then be installed with:

    diff --git a/src/wwwroot/gfm/apps/01.md b/src/wwwroot/gfm/apps/01.md index 6456859..1aee461 100644 --- a/src/wwwroot/gfm/apps/01.md +++ b/src/wwwroot/gfm/apps/01.md @@ -419,7 +419,7 @@ This publishing option includes a self-contained .NET Core with all `app` binari ### Publish to the world To maximize reach and accessibility of your App leave a comment on the [App Gallery](https://gist.github.com/gistlyn/f555677c98fb235dccadcf6d87b9d098) -where after we link to it on [NetCoreWebApps](https://github.com/NetCoreWebApps) it will available to all users when they look for available apps in: +where after we link to it on [Sharp Apps](https://github.com/sharp-apps) it will available to all users when they look for available apps in: $ app list diff --git a/src/wwwroot/gfm/sharp-apis/04.html b/src/wwwroot/gfm/sharp-apis/04.html index 370e894..327ec3f 100644 --- a/src/wwwroot/gfm/sharp-apis/04.html +++ b/src/wwwroot/gfm/sharp-apis/04.html @@ -17,7 +17,7 @@

    Usage: /hello/{name}

    An API which returns the same wire response as above can be implemented in API Pages by creating a page at -/hello/_name/index.html +/hello/_name/index.html that includes the 1-liner:

    {{ { result: `Hello, ${name}!` } |> return }}

    Which supports the same content negotiation as a ServiceStack Service where calling it in a browser will generate a @@ -41,7 +41,7 @@

    Usage: /preview?content={templates}

    -

    The /preview.html API page uses this to force a plain-text response with:

    +

    The /preview.html API page uses this to force a plain-text response with:

    {{ content  |> evalTemplate({use:{plugins:'MarkdownScriptPlugin'}}) |> to =>response }}
     {{ response |> return({ contentType:'text/plain' }) }}

    The preview API above is what provides the new Blog Web App's Live Preview feature where it will render any @@ -66,7 +66,7 @@

    Usage: /{user}/api

    -

    The /_user/api.html API page shows an example of how easy it is to +

    The /_user/api.html API page shows an example of how easy it is to create data-driven APIs where you can literally return the response of a parameterized SQL query using the dbSelect filter and returning the results:

    {{ `SELECT * 
    @@ -92,7 +92,7 @@ 

    Usage: /posts/{slug}/api

    -

    The /posts/_slug/api.html page shows an example of using the +

    The /posts/_slug/api.html page shows an example of using the httpResult filter to return a custom HTTP Response where if the post with the specified slug does not exist it will return a 404 Post was not found HTTP Response:

    {{ `SELECT * 
    @@ -119,7 +119,7 @@ 

    {{ post |> return({ format:'json', 'X-Powered-By':'#Script' }) }}

    Using the explicit httpResult filter is useful for returning a custom HTTP Response without a Response Body, e.g. the New Post page uses httpFilter to -redirect back to the Users posts page +redirect back to the Users posts page after they've successfully created a new Post:

    {{#if success}}
         {{ httpResult({ status:301, Location:`/${userName}` }) |> return }}
    diff --git a/src/wwwroot/gfm/sharp-apis/04.md b/src/wwwroot/gfm/sharp-apis/04.md
    index 6c0d576..e518073 100644
    --- a/src/wwwroot/gfm/sharp-apis/04.md
    +++ b/src/wwwroot/gfm/sharp-apis/04.md
    @@ -19,7 +19,7 @@ public class HelloService : Service
     > Usage: /hello/{name}
     
     An API which returns the same wire response as above can be implemented in API Pages by creating a page at 
    -[/hello/_name/index.html](https://github.com/NetCoreWebApps/Blog/blob/master/hello/_name/index.html) 
    +[/hello/_name/index.html](https://github.com/sharp-apps/blog/blob/master/hello/_name/index.html) 
     that includes the 1-liner:
     
     ```hbs
    @@ -51,7 +51,7 @@ Templates and Dynamic API Pages to implement all of its functionality.
     
     > Usage: /preview?content={templates}
     
    -The [/preview.html](https://github.com/NetCoreWebApps/blog/blob/master/preview.html) API page uses this to force a plain-text response with:
    +The [/preview.html](https://github.com/sharp-apps/blog/blob/master/preview.html) API page uses this to force a plain-text response with:
     
     ```hbs
     {{ content  |> evalTemplate({use:{plugins:'MarkdownScriptPlugin'}}) |> to =>response }}
    @@ -80,7 +80,7 @@ Which renders the plain text response:
     
     > Usage: /{user}/api
     
    -The [/_user/api.html](https://github.com/NetCoreWebApps/blog/blob/master/_user/api.html) API page shows an example of how easy it is to 
    +The [/_user/api.html](https://github.com/sharp-apps/blog/blob/master/_user/api.html) API page shows an example of how easy it is to 
     create data-driven APIs where you can literally return the response of a parameterized SQL query using the `dbSelect` filter and returning 
     the results:
     
    @@ -109,7 +109,7 @@ Which also benefits from ServiceStack's multiple built-in formats where the same
     
     > Usage:  /posts/{slug}/api
     
    -The [/posts/_slug/api.html](https://github.com/NetCoreWebApps/blog/blob/master/posts/_slug/api.html) page shows an example of using the 
    +The [/posts/_slug/api.html](https://github.com/sharp-apps/blog/blob/master/posts/_slug/api.html) page shows an example of using the 
     `httpResult` filter to return a custom HTTP Response where if the post with the specified slug does not exist it will return a 
     `404 Post was not found` HTTP Response:
     
    @@ -149,7 +149,7 @@ Returning the `httpResult` above behaves similarly to customizing a HTTP respons
     
     Using the explicit `httpResult` filter is useful for returning a custom HTTP Response without a Response Body, e.g. the **New Post** page 
     uses `httpFilter` to 
    -[redirect back to the Users posts page](https://github.com/NetCoreWebApps/Blog/blob/e8bb7249192c5797348ced091ad5fd434db9a619/app/posts/new.html#L33) 
    +[redirect back to the Users posts page](https://github.com/sharp-apps/blog/blob/e8bb7249192c5797348ced091ad5fd434db9a619/app/posts/new.html#L33) 
     after they've successfully created a new Post:
     
     ```hbs
    diff --git a/src/wwwroot/gfm/sharp-apps/06.html b/src/wwwroot/gfm/sharp-apps/06.html
    index b0729a1..07deff7 100644
    --- a/src/wwwroot/gfm/sharp-apps/06.html
    +++ b/src/wwwroot/gfm/sharp-apps/06.html
    @@ -1,22 +1,21 @@
     
    public class Startup
     {
    -    public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
    +    public void Configure(IApplicationBuilder app)
         {
    -        loggerFactory.AddConsole();
    -        var appSettings = new TextFileSettings("~/../../apps/chat/app.settings".MapProjectPath());
    +        var appSettings = new TextFileSettings("~/../app.settings".MapProjectPath());
             app.UseServiceStack(new AppHost(appSettings));
         }
     }
     
     public class AppHost : AppHostBase
     {
    -    public AppHost() : base("Chat Web App", typeof(ServerEventsServices).Assembly) {}
    +    public AppHost() : base("Chat Sharp App", typeof(ServerEventsServices).Assembly) {}
         public AppHost(IAppSettings appSettings) : this() => AppSettings = appSettings;
     
         public override void Configure(Container container)
         {
             Plugins.AddIfNotExists(new SharpPagesFeature()); //Already added if it's running as a Web App
    -        
    +
             Plugins.Add(new ServerEventsFeature());
     
             SetConfig(new HostConfig
    @@ -31,17 +30,10 @@
             Plugins.Add(new AuthFeature(
                 () => new AuthUserSession(),
                 new IAuthProvider[] {
    -                new TwitterAuthProvider(AppSettings),   //Sign-in with Twitter
                     new FacebookAuthProvider(AppSettings),  //Sign-in with Facebook
    -                new GithubAuthProvider(AppSettings),    //Sign-in with GitHub
                 }));
     
             container.RegisterAutoWiredAs<MemoryChatHistory, IChatHistory>();
    -
    -        Plugins.Add(new CorsFeature(
    -            allowOriginWhitelist: new[] { "http://localhost", "http://null.jsbin.com" },
    -            allowCredentials: true,
    -            allowedHeaders: "Content-Type, Allow, Authorization"));
         }
     }
    \ No newline at end of file diff --git a/src/wwwroot/gfm/sharp-apps/06.md b/src/wwwroot/gfm/sharp-apps/06.md index 8e68dcf..9afd95c 100644 --- a/src/wwwroot/gfm/sharp-apps/06.md +++ b/src/wwwroot/gfm/sharp-apps/06.md @@ -1,23 +1,22 @@ ```csharp public class Startup { - public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory) + public void Configure(IApplicationBuilder app) { - loggerFactory.AddConsole(); - var appSettings = new TextFileSettings("~/../../apps/chat/app.settings".MapProjectPath()); + var appSettings = new TextFileSettings("~/../app.settings".MapProjectPath()); app.UseServiceStack(new AppHost(appSettings)); } } public class AppHost : AppHostBase { - public AppHost() : base("Chat Web App", typeof(ServerEventsServices).Assembly) {} + public AppHost() : base("Chat Sharp App", typeof(ServerEventsServices).Assembly) {} public AppHost(IAppSettings appSettings) : this() => AppSettings = appSettings; public override void Configure(Container container) { Plugins.AddIfNotExists(new SharpPagesFeature()); //Already added if it's running as a Web App - + Plugins.Add(new ServerEventsFeature()); SetConfig(new HostConfig @@ -32,17 +31,10 @@ public class AppHost : AppHostBase Plugins.Add(new AuthFeature( () => new AuthUserSession(), new IAuthProvider[] { - new TwitterAuthProvider(AppSettings), //Sign-in with Twitter new FacebookAuthProvider(AppSettings), //Sign-in with Facebook - new GithubAuthProvider(AppSettings), //Sign-in with GitHub })); container.RegisterAutoWiredAs(); - - Plugins.Add(new CorsFeature( - allowOriginWhitelist: new[] { "http://localhost", "http://null.jsbin.com" }, - allowCredentials: true, - allowedHeaders: "Content-Type, Allow, Authorization")); } } ``` diff --git a/src/wwwroot/gfm/sharp-apps/14.html b/src/wwwroot/gfm/sharp-apps/14.html index e06c808..66b86b9 100644 --- a/src/wwwroot/gfm/sharp-apps/14.html +++ b/src/wwwroot/gfm/sharp-apps/14.html @@ -10,7 +10,7 @@

    Cross platform (Default Browser):

    $ x open blog
     
    -

    To maximize approachability the /Blog Web App has no C# source code, plugins and uses +

    To maximize approachability the /Blog Web App has no C# source code, plugins and uses no JavaScript or CSS frameworks yielding an enjoyable development experiences as all the usual complexities of developing a C# Server and modern JS App has been dispensed and you can just focus on the App you want to create, using a plain-text editor on the left, a live updating browser on the right and nothing else to interrupt your flow.

    @@ -50,7 +50,7 @@

    the database instead of localStorage.

    Server Validation

    -

    The new.html and edit.html pages shows examples of performing server validation with #Script:

    +

    The new.html and edit.html pages shows examples of performing server validation with #Script:

    {{ assignErrorAndContinueExecuting: ex }}
     
     {{ 'Title must be between 5 and 200 characters'      
    @@ -69,7 +69,7 @@ 

    <textarea data-livepreview=".preview"></textarea>
     <div class="preview"></div>

    The implementation of which is surprisingly simple where the JavaScript snippet in -default.js below is used to send its content on each change:

    +default.js below is used to send its content on each change:

    // Enable Live Preview of new Content
     textAreas = document.querySelectorAll("textarea[data-livepreview]");
     for (let i = 0; i < textAreas.length; i++) {
    @@ -95,7 +95,7 @@ 

    }).then(function(r) { return r.text(); }) .then(function(r) { document.querySelector(sel).innerHTML = r; }); }

    -

    To the /preview.html API Page which just renders and captures any +

    To the /preview.html API Page which just renders and captures any Template Content its sent and returns the output:

    {{ content  |> evalTemplate({use:{plugins:'MarkdownScriptPlugin'}}) |> to =>response }}
     {{ response |> return({ contentType:'text/plain' }) }}
    diff --git a/src/wwwroot/gfm/sharp-apps/14.md b/src/wwwroot/gfm/sharp-apps/14.md index 398ae50..9828824 100644 --- a/src/wwwroot/gfm/sharp-apps/14.md +++ b/src/wwwroot/gfm/sharp-apps/14.md @@ -14,7 +14,7 @@ Cross platform (Default Browser): $ x open blog -To maximize approachability the [/Blog](https://github.com/NetCoreWebApps/Blog/tree/master/app) Web App has no C# source code, plugins and uses +To maximize approachability the [/Blog](https://github.com/sharp-apps/blog/tree/master/app) Web App has no C# source code, plugins and uses no JavaScript or CSS frameworks yielding an enjoyable development experiences as all the usual complexities of developing a C# Server and modern JS App has been dispensed and you can just focus on the App you want to create, using a plain-text editor on the left, a live updating browser on the right and nothing else to interrupt your flow. @@ -68,7 +68,7 @@ the database instead of `localStorage`. ### Server Validation -The [new.html](https://github.com/NetCoreWebApps/Blog/blob/master/posts/new.html) and [edit.html](https://github.com/NetCoreWebApps/Blog/blob/master/posts/_slug/edit.html) pages shows examples of performing server validation with #Script: +The [new.html](https://github.com/sharp-apps/blog/blob/master/posts/new.html) and [edit.html](https://github.com/sharp-apps/blog/blob/master/posts/_slug/edit.html) pages shows examples of performing server validation with #Script: ```hbs {{ assignErrorAndContinueExecuting: ex }} @@ -98,7 +98,7 @@ should be rendered in, e.g: ``` The implementation of which is surprisingly simple where the JavaScript snippet in -[default.js](https://github.com/NetCoreWebApps/Blog/blob/master/default.js) below is used to send its content on each change: +[default.js](https://github.com/sharp-apps/blog/blob/master/default.js) below is used to send its content on each change: ```js // Enable Live Preview of new Content @@ -128,7 +128,7 @@ function livepreview(e) { } ``` -To the [/preview.html](https://github.com/NetCoreWebApps/blog/blob/master/preview.html) API Page which just renders and captures any +To the [/preview.html](https://github.com/sharp-apps/blog/blob/master/preview.html) API Page which just renders and captures any Template Content its sent and returns the output: ```hbs diff --git a/src/wwwroot/gfm/sharp-pages/10.html b/src/wwwroot/gfm/sharp-pages/10.html index 6a7bd2a..fe300da 100644 --- a/src/wwwroot/gfm/sharp-pages/10.html +++ b/src/wwwroot/gfm/sharp-pages/10.html @@ -24,7 +24,7 @@ {{ htmlError }}

    The output of the _init page is captured in the initout argument which can be later inspected as a normal template argument as seen in -log.html:

    +log.html:

    <div>
         Output from init.html:
     
    diff --git a/src/wwwroot/gfm/sharp-pages/10.md b/src/wwwroot/gfm/sharp-pages/10.md
    index 43ce767..f4f1ae2 100644
    --- a/src/wwwroot/gfm/sharp-pages/10.md
    +++ b/src/wwwroot/gfm/sharp-pages/10.md
    @@ -27,7 +27,7 @@
     ```
     
     The output of the `_init` page is captured in the `initout` argument which can be later inspected as a normal template argument as seen in 
    -[log.html](https://github.com/NetCoreWebApps/Blog/blob/master/log.html):
    +[log.html](https://github.com/sharp-apps/blog/blob/master/log.html):
     
     ```html
     
    diff --git a/src/wwwroot/sharp-apps/chat.html b/src/wwwroot/sharp-apps/chat.html index 74a5c51..52daa02 100644 --- a/src/wwwroot/sharp-apps/chat.html +++ b/src/wwwroot/sharp-apps/chat.html @@ -6,11 +6,11 @@

    - /chat is an example of the ultimate form + /chat is an example of the ultimate form of extensibility where instead of just being able to add Services, Filters and Plugins, etc. You can add your entire AppHost which Sharp Apps will use instead of its own. This vastly expands the use-cases that can be built with Sharp Apps as it gives you complete fine-grained control over how your App is configured. @@ -38,18 +38,18 @@

    Develop back-end using .NET IDE's

    For chat.web-app.io we've taken a copy of the existing .NET Core 3.1 Chat App and moved its C# code to - /example-plugins/Chat - and its files to /apps/chat + /chat/src + and its files to /chat where it can be developed like any other Web App except it utilizes the Chat AppHost and implementation in the - SelfHost Chat App. + SelfHost Chat App.

    Customizations from the original .NET Core Chat implementation includes removing MVC and Razor dependencies and configuration, extracting its - _layout.html and - converting index.html + _layout.html and + converting index.html to use #Script from its original default.cshtml. It's also been enhanced with the ability to evaluate scripts from the Chat window, as seen in the screenshot above. @@ -70,29 +70,25 @@

    Reusing Web App's app.setting and files
    To account for these 2 modes we use AddIfNotExists to only register the SharpPagesFeature plugin when running as a stand-alone App and add an additional constructor so it reuses the existing app.settings as its IAppSettings provider for is custom App configuration like OAuth App keys - required for enabling Sign-In's via with Twitter, Facebook and GitHub when running on http://localhost:5000: + required for enabling Sign-In's via with Twitter, Facebook and GitHub when running on https://localhost:5001:

    debug true
    -name Chat Web App
    -contentRoot ~/../chat
    -webRoot ~/../chat
    -
    -oauth.RedirectUrl http://localhost:5000/
    -oauth.CallbackUrl http://localhost:5000/auth/{0}
    -oauth.twitter.ConsumerKey JvWZokH73rdghDdCFCFkJtCEU
    -oauth.twitter.ConsumerSecret WNeOT6YalxXDR4iWZjc4jVjFaydoDcY8jgRrGc5FVLjsVlY2Y8
    +name Chat Sharp App
    +port 5001
    +
    +oauth.RedirectUrl https://localhost:5001/
    +oauth.CallbackUrl https://localhost:5001/auth/{0}
     oauth.facebook.Permissions email
    -oauth.facebook.AppId 447523305684110
    -oauth.facebook.AppSecret 7d8a16d5c7cbbfab4b49fd51183c93a0
    -oauth.github.Scopes user
    -oauth.github.ClientId dbe8c242e3d1099f4558
    -oauth.github.ClientSecret 42c8db8d0ca72a0ef202e0c197d1377670c990f4
    +oauth.facebook.AppId 531608123577340
    +oauth.facebook.AppSecret 9e1e6591a7f15cbc1b305729f4b14c0b
    +
    +CefConfig { width:1150, height:1050 }
     

    After the back-end has been implemented we can build and copy the compiled Chat.dll into the Chat's - /plugins folder where + /plugins folder where we can take advantage of the improved development experience for rapidly developing its UI.

    diff --git a/src/wwwroot/sharp-apps/deploying-sharp-apps.html b/src/wwwroot/sharp-apps/deploying-sharp-apps.html index 3eb592a..a6ea441 100644 --- a/src/wwwroot/sharp-apps/deploying-sharp-apps.html +++ b/src/wwwroot/sharp-apps/deploying-sharp-apps.html @@ -197,12 +197,12 @@ Continuous Integration Service to package your App in a Docker Container and deploy it to AWS ECS which takes care of the management and deployment of Docker instances over a configured cluster of EC2 compute instances. -The easiest way to set this up is to clone the [rockwind-aws](https://github.com/NetCoreWebApps/rockwind-aws) +The easiest way to set this up is to clone the [rockwind-aws](https://github.com/sharp-apps/rockwind-aws) Web App which is preconfigured with a working scripts using Travis CI to package the Web App in a Docker container and deploy it to AWS ECS. In your local copy replace the -[/app](https://github.com/NetCoreWebApps/rockwind-aws/tree/master/app) folder with your App files, e.g: +[/app](https://github.com/sharp-apps/rockwind-aws/tree/master/app) folder with your App files, e.g: -#### [Dockerfile](https://github.com/NetCoreWebApps/rockwind-aws/blob/master/Dockerfile) +#### [Dockerfile](https://github.com/sharp-apps/rockwind-aws/blob/master/Dockerfile) {{/markdown}} @@ -212,7 +212,7 @@ The only other file that needs to change is deploy-envs.sh to configure it to use your App's deployment settings:

    -

    deploy-envs.sh

    +

    deploy-envs.sh

    {{ 'gfm/deploying-sharp-apps/03.md' |> githubMarkdown }} From 34cdf428e1ac489111a193c490a20b00d574240a Mon Sep 17 00:00:00 2001 From: Demis Bellot Date: Wed, 17 Jun 2020 13:16:05 +0800 Subject: [PATCH 154/206] Update App Index --- src/wwwroot/sharp-apps/app-index.html | 131 ++++++++++++++++++ .../sharp-apps/deploying-sharp-apps.html | 3 +- src/wwwroot/sharp-apps/gist-desktop-apps.html | 4 +- src/wwwroot/sharp-apps/index.html | 86 ------------ 4 files changed, 135 insertions(+), 89 deletions(-) create mode 100644 src/wwwroot/sharp-apps/app-index.html diff --git a/src/wwwroot/sharp-apps/app-index.html b/src/wwwroot/sharp-apps/app-index.html new file mode 100644 index 0000000..f0dcffb --- /dev/null +++ b/src/wwwroot/sharp-apps/app-index.html @@ -0,0 +1,131 @@ + + +

    + In addition to the Sharp App Templates + there's a number of Sharp Apps to illustrate the various features available and to showcase the different + kind of Web Apps that can easily be developed. The source code for each app is available either individually from + github.com/sharp-apps or published as + Gist Desktop Apps. +

    + +{{* 'gfm/sharp-apps/15.md' |> githubMarkdown *}} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    AppURL SchemeCommand line (Win/macOS/Linux)
    Spiralsapp://spirals$ x open spirals
    Redisapp://redis$ x open redis
    Rockwindapp://rockwind$ x open rockwind
    Pluginsapp://plugins$ x open plugins
    Chatapp://chat$ x open chat
    Blogapp://blog$ x open blog
    Win32app://win32$ x open win32
    ServiceStack Studioapp://studio$ x open studio
    SharpDataapp://sharpdata?mix=northwind.sharpdata$ x open sharpdata mix northwind.sharpdata
    + +{{#markdown}} + +> As [SharpData](/sharp-apps/sharpdata) is a generic RDBMS UI it needs to [mix in Gists](/sharp-apps/sharpdata#mix-in-gists) containing the DB +info and/or **.sqlite** db if using SQLite + +### app URL Schemes + +To open Sharp Apps with the `app://` URL Scheme install the [app dotnet tool](https://docs.servicestack.net/netcore-windows-desktop): + + $ dotnet tool install -g app + $ app -version + +If on macOS/Linux you can use the cross-platform [x dotnet tool](https://docs.servicestack.net/dotnet-tool) to open Sharp Apps in your Default Browser: + + $ dotnet tool install -g x + $ x open spirals + +### app local aliases + +Where ever you can use a **Gist Id**, you can assign a local user-friendly alias to use instead such as referencing +[Gist Desktop Apps](https://sharpscript.net/docs/gist-desktop-apps), e.g. we can assign the +[redis gist app](https://gist.github.com/gistlyn/6de7993333b457445793f51f6f520ea8) to use our preferred alias: + + $ app alias local-redis 6de7993333b457445793f51f6f520ea8 + +That we can open via command-line: + + $ app open local-redis + +Or URL Scheme: + + app://local-redis + +Or if we want to run our own modified copy of the Redis Desktop App, we can [mix](https://docs.servicestack.net/mix-tool) the Gist files to our +local directory: + + $ app mix local-redis + +Make the changes we want, then run our local copy by running `app` (or `x`) without arguments: + + $ app + +Other alias command include: + +#### View all aliases + + $ app alias + +#### View single alias + + $ app alias db + +#### Remove an alias + + $ app unalias db + + +{{/markdown}} + diff --git a/src/wwwroot/sharp-apps/deploying-sharp-apps.html b/src/wwwroot/sharp-apps/deploying-sharp-apps.html index a6ea441..5300586 100644 --- a/src/wwwroot/sharp-apps/deploying-sharp-apps.html +++ b/src/wwwroot/sharp-apps/deploying-sharp-apps.html @@ -1,6 +1,6 @@ {{#markdown}} @@ -186,6 +186,7 @@ All our Gist Apps are now hosted this way, by running a locally downloaded Gist App that's hosted at the following URLs: - [redis.web-app.io](http://redis.web-app.io) + - [chat.web-app.io](http://chat.web-app.io) - [blog.web-app.io](http://blog.web-app.io) - [plugins.web-app.io](http://plugins.web-app.io) - [spirals.web-app.io](http://spirals.web-app.io) diff --git a/src/wwwroot/sharp-apps/gist-desktop-apps.html b/src/wwwroot/sharp-apps/gist-desktop-apps.html index a7c0d8a..717fd5d 100644 --- a/src/wwwroot/sharp-apps/gist-desktop-apps.html +++ b/src/wwwroot/sharp-apps/gist-desktop-apps.html @@ -79,7 +79,7 @@ $ app open https://gist.github.com/gistlyn/6de7993333b457445793f51f6f520ea8 $ app open 189cd72bfaf480526e4b34814c80f2c0 $ app open https://github.com/sharp-apps/redis - $ app open https://github.com/sharp-apps/plugins/archive/v5.zip + $ app open https://github.com/sharp-apps/plugins/archive/v7.zip Local Sharp Apps can simply run `app` in the Sharp Apps directory: @@ -463,7 +463,7 @@ The last packaging option supported for running Sharp Apps is being able to link to a specific GitHub Release version of your App, e.g: - - [plugins](https://github.com/sharp-apps/plugins/archive/v5.zip) + - [plugins](https://github.com/sharp-apps/plugins/archive/v7.zip) Which will ensure you're always running the same version of the App which is useful in being able to easily run and compare different App versions or be able to support beta releases of your App that you don't want everyone to use yet. diff --git a/src/wwwroot/sharp-apps/index.html b/src/wwwroot/sharp-apps/index.html index 6d7111c..6c3c1cd 100644 --- a/src/wwwroot/sharp-apps/index.html +++ b/src/wwwroot/sharp-apps/index.html @@ -279,91 +279,5 @@

    About Parcel WebApp

    {{ 'gfm/sharp-apps/13.md' |> githubMarkdown }} -

    Example Sharp Apps

    - -

    - In addition to the templates there's a number of Sharp Apps to illustrate the various features available and to showcase the different - kind of Web Apps that can easily be developed. The source code for each app is available either individually from - github.com/sharp-apps. -

    - -{{* 'gfm/sharp-apps/15.md' |> githubMarkdown *}} - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    AppURL SchemeCommand line (Win/macOS/Linux)
    Spiralsapp://spirals$ x open spirals
    Redisapp://redis$ x open redis
    Rockwindapp://rockwind$ x open rockwind
    Pluginsapp://plugins$ x open plugins
    Chatapp://chat$ x open chat
    Blogapp://blog$ x open blog
    Win32app://win32$ x open win32
    ServiceStack Studioapp://studio$ x open studio
    SharpDataapp://sharpdata?mix=northwind.sharpdata$ x open sharpdata mix northwind.sharpdata
    - -{{#markdown}} - -> As [SharpData](/sharp-apps/sharpdata) is a generic RDBMS UI it needs to [mix in Gists](/sharp-apps/sharpdata#mix-in-gists) containing the DB -info and/or **.sqlite** db if using SQLite - -#### app URL Schemes - -To open Sharp Apps with the `app://` URL Scheme install the [app dotnet tool](https://docs.servicestack.net/netcore-windows-desktop): - - $ dotnet tool install -g app - $ app -version - -If on macOS/Linux you can use the cross-platform [x dotnet tool](https://docs.servicestack.net/dotnet-tool) to open Sharp Apps in your Default Browser: - - $ dotnet tool install -g x - $ x open spirals - -{{/markdown}} - {{ "apps-links" |> partial({ order }) }} From 3ce871722fda68043a01496bea362fda4c0696bd Mon Sep 17 00:00:00 2001 From: Demis Bellot Date: Wed, 17 Jun 2020 13:27:52 +0800 Subject: [PATCH 155/206] docs --- src/wwwroot/index.html | 2 +- src/wwwroot/sharp-apps/index.html | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/wwwroot/index.html b/src/wwwroot/index.html index e19d995..bf7ea18 100644 --- a/src/wwwroot/index.html +++ b/src/wwwroot/index.html @@ -189,7 +189,7 @@

    Sharp Apps

    The integrated development experience extends to end-to-end to provide a seamless installation and deployment experience where the - same web dotnet tool used to develop Sharp Apps is all users need to find and install Apps locally or hosted as a server App + same x dotnet tool used to develop Sharp Apps is all users need to find and install Apps locally or hosted as a server App where they're easily deployed and hosted on Linux or run within Docker - the overall Sharp Apps experience results in the easiest and fastest way to develop and deploy Web Apps in .NET!

    diff --git a/src/wwwroot/sharp-apps/index.html b/src/wwwroot/sharp-apps/index.html index 6c3c1cd..94cdb6b 100644 --- a/src/wwwroot/sharp-apps/index.html +++ b/src/wwwroot/sharp-apps/index.html @@ -45,10 +45,10 @@

    Rapid Development Workflow

    The iterative development experience is also unparalleled for a .NET App, no compilation is required so you can just leave - the web dotnet tool running whilst you add .html files needed to build your App and thanks + the x dotnet tool running whilst you add .html files needed to build your App and thanks to the built-in Hot Reloading support, pages will refresh automatically as you save. You'll just need to do a full page refresh when modifying external .css/.js files to bypass the browser cache and - you'll need to restart web to pick up any changes to your app.settings or added any + you'll need to restart x to pick up any changes to your app.settings or added any .dlls to your /plugins folder.

    @@ -107,7 +107,7 @@

    Windows 64 Desktop App

    $ dotnet tool install -g app

    - Then running the Web App with app instead of web. + Then running the Web App with app instead of x.

    $ app open spirals
    From 29ffdef5d76e08beb2f1beae2bca35a99a9fcaa7 Mon Sep 17 00:00:00 2001 From: Demis Bellot Date: Wed, 17 Jun 2020 13:53:02 +0800 Subject: [PATCH 156/206] Update plugins.html --- src/wwwroot/sharp-apps/plugins.html | 41 +++++++++++++---------------- 1 file changed, 19 insertions(+), 22 deletions(-) diff --git a/src/wwwroot/sharp-apps/plugins.html b/src/wwwroot/sharp-apps/plugins.html index fd06dae..4be4c96 100644 --- a/src/wwwroot/sharp-apps/plugins.html +++ b/src/wwwroot/sharp-apps/plugins.html @@ -6,7 +6,7 @@

    @@ -14,25 +14,23 @@ but still retain all the benefits of developing Web Apps you can drop .dll with custom functionality into your Web App's /plugins folder. The plugins support in Web Apps is as friction-less as we could make it, there's no configuration to maintain or special interfaces to implement, you're able to drop your existing implementation .dll's - as-is into the App's `/plugins` folder. + as-is into the App's /plugins folder.

    Plugins allow "no touch" sharing of ServiceStack Plugins, Services, - Script Methods - Sharp Code Pages, + Script Methods + Sharp Code Pages, Validators, etc. contained within .dll's or .exe's dropped in a Sharp App's - /plugins folder which are auto-registered - on startup. The source code for all plugins used in this App were built from the .NET Core 3.1 projects in the - /example-plugins folder. The - plugins.web-app.io Sharp App below walks through examples of using Custom Filters, - Services and Validators: + /plugins folder which are auto-registered + on startup. This source code for plugins used in this App were built from the .NET Core 3.1 projects in the + /plugins/src folder.

    -Plugins WebApp screenshot +Plugins Sharp App Screenshot {{#markdown}} You can run this Gist Desktop App via URL Scheme from (Windows Desktop App): @@ -54,16 +52,15 @@

    Registering ServiceStack Plugins

    ServiceStack Plugins can be added to your App by listing it's Type Name in the features config entry in - app.settings: + app.settings:

    debug true
    -name Web App Plugins
    -contentRoot ~/../plugins
    -webRoot ~/../plugins
    +name Plugins Sharp App
     features CustomPlugin, OpenApiFeature, PostmanFeature, CorsFeature, ValidationFeature
     CustomPlugin { ShowProcessLinks: true }
     ValidationFeature { ScanAppHostAssemblies: true }
    +CefConfig { width:1150, height:1050 }
     

    @@ -77,7 +74,7 @@

    Registering ServiceStack Plugins

    Custom Plugin

    - In this case it tells our CustomPlugin + In this case it tells our CustomPlugin from /plugins/ServerInfo.dll to also show Process Links in its /metadata Page:

    @@ -88,23 +85,24 @@

    Custom Plugin

    Where as it was first registered in the list will appear before any links registered by other plugins:

    -Metadata screenshot +Metadata screenshot

    Built-in Plugins

    It also tells the ValidationFeature to scan all Service Assemblies for Validators and to automatically register them which is how ServiceStack was able to find the - ContactValidator + ContactValidator used to validate the StoreContact request.

    - Other optional plugins registered in this Web App is the metadata Services required for + Other optional plugins registered in this App is the metadata Services required for Open API, Postman as well as support for CORS. - You can check the /metadata/debug Inspector for all Plugins loaded in your AppHost. + You can check the Metadata Debug Inspector for all Plugins + loaded in your AppHost.

    .NET Extensibility

    @@ -115,8 +113,7 @@

    .NET Extensibility

    To simplify configuration you can use the plugins/* wildcard in - app.settings - at the end of an ordered plugin list to register all remaining Plugins it finds in the apps `/plugins` folder: + app.settings at the end of an ordered plugin list to register all remaining Plugins it finds in the apps /plugins folder:

    @@ -130,7 +127,7 @@

    .NET Extensibility

    The /plugins2 App shows an example of this with the - StartupPlugin + StartupPlugin registering a StartupDep dependency which is used by its StartupServices at runtime:

    From cff8723778a8913d962be020f0c2c56c69ab32c5 Mon Sep 17 00:00:00 2001 From: Demis Bellot Date: Wed, 17 Jun 2020 14:07:09 +0800 Subject: [PATCH 157/206] update links --- src/wwwroot/docs/db-scripts.html | 6 +++--- src/wwwroot/docs/redis-scripts.html | 2 +- src/wwwroot/gfm/adhoc-querying/04.html | 4 ++-- src/wwwroot/gfm/adhoc-querying/04.md | 4 ++-- src/wwwroot/sharp-apps/gist-desktop-apps.html | 4 ++-- src/wwwroot/sharp-apps/index.html | 21 +++++++------------ src/wwwroot/sharp-apps/rockwind.html | 6 +++--- 7 files changed, 20 insertions(+), 27 deletions(-) diff --git a/src/wwwroot/docs/db-scripts.html b/src/wwwroot/docs/db-scripts.html index db15c6a..a59b1d2 100644 --- a/src/wwwroot/docs/db-scripts.html +++ b/src/wwwroot/docs/db-scripts.html @@ -262,7 +262,7 @@

    DB Filter Examples

    Run Rockwind against your Local RDBMS

    - You can run the Rockwind Website + You can run the Rockwind Website against either an SQLite, SQL Server or MySql database by just changing which app.settings the App is run with, e.g:

    @@ -280,7 +280,7 @@
    Populate local RDBMS with Northwind database

    As Rockwind is a Web Template Sharp App it doesn't need any compilation so after - running the Rockwind Sharp App + running the Rockwind Sharp App you can modify the source code and see changes in real-time thanks to its built-in Hot Reloading support.

    @@ -293,7 +293,7 @@

    PostgreSQL Support

    use sqlQuote to quote every column or table or are willing to use lowercase or snake_case for all table and column names. As a result we've developed an alternate version of Rockwind website called Rockwind VFS which quotes every Table and Column in double quotes so PostgreSQL preserves casing. The - Rockwind VFS Project + Rockwind VFS Project can be run against either PostgreSQL, SQL Server or SQLite by changing the configuration it's run with, e.g:

    diff --git a/src/wwwroot/docs/redis-scripts.html b/src/wwwroot/docs/redis-scripts.html index 48187b5..7b0f595 100644 --- a/src/wwwroot/docs/redis-scripts.html +++ b/src/wwwroot/docs/redis-scripts.html @@ -137,7 +137,7 @@

    Run against your local Redis instance

    As both are Sharp Apps it doesn't need any compilation so after - running the Redis Web App locally + running the Redis Web App locally you can modify the source code and see changes in real-time thanks to its built-in Hot Reloading support.

    diff --git a/src/wwwroot/gfm/adhoc-querying/04.html b/src/wwwroot/gfm/adhoc-querying/04.html index 84d02df..4fa86a1 100644 --- a/src/wwwroot/gfm/adhoc-querying/04.html +++ b/src/wwwroot/gfm/adhoc-querying/04.html @@ -53,7 +53,7 @@

    The Northwind SQL Viewer above was developed using the 2 #Script Pages below:

    -/northwind/sql/index.html +/northwind/sql/index.html

    A Sharp Page to render the UI, shortcut links to quickly see the last 10 rows of each table, a <textarea/> to capture the SQL Query which is sent to an API on every keystroke where the results are displayed instantly:

    @@ -102,7 +102,7 @@

    } </script>

    -/northwind/sql/api.html +/northwind/sql/api.html

    All that's left is to implement the API which just needs to check to ensure the SQL does not contain any destructive operations using the isUnsafeSql DB filter, if it doesn't execute the SQL with the dbSelect DB Filter, generate a HTML Table with htmlDump and return diff --git a/src/wwwroot/gfm/adhoc-querying/04.md b/src/wwwroot/gfm/adhoc-querying/04.md index d0381f8..ec786ba 100644 --- a/src/wwwroot/gfm/adhoc-querying/04.md +++ b/src/wwwroot/gfm/adhoc-querying/04.md @@ -60,7 +60,7 @@ To take the ad hoc SQL Query example even further, it also becomes trivial to im The [Northwind SQL Viewer](http://rockwind-sqlite.web-app.io/northwind/sql/) above was developed using the 2 #Script Pages below: -#### [/northwind/sql/index.html](https://github.com/ServiceStack/dotnet-app/blob/master/src/apps/rockwind/northwind/sql/index.html) +#### [/northwind/sql/index.html](https://github.com/sharp-apps/rockwind/blob/master/northwind/sql/index.html) A Sharp Page to render the UI, shortcut links to quickly see the last 10 rows of each table, a `