From dfe8f2acc611d39abebef07c4a41a363020ed248 Mon Sep 17 00:00:00 2001 From: purkayasta Date: Sat, 17 Jun 2023 01:31:58 +0600 Subject: [PATCH 01/20] added documentation (docfx project) experimental --- README.md | 4 +- documentation/docfx_project/.gitignore | 9 + documentation/docfx_project/articles/intro.md | 1 + documentation/docfx_project/articles/toc.yml | 2 + documentation/docfx_project/docfx.json | 54 ++ documentation/docfx_project/index.md | 4 + .../templates/singulinkfx/layout/_master.tmpl | 72 ++ .../singulinkfx/partials/footer.tmpl.partial | 4 + .../singulinkfx/partials/head.tmpl.partial | 24 + .../singulinkfx/partials/li.tmpl.partial | 31 + .../singulinkfx/partials/logo.tmpl.partial | 6 + .../partials/namespace.tmpl.partial | 13 + .../singulinkfx/partials/navbar.tmpl.partial | 19 + .../singulinkfx/partials/scripts.tmpl.partial | 13 + .../partials/searchResults.tmpl.partial | 9 + .../singulinkfx/partials/toc.tmpl.partial | 5 + .../templates/singulinkfx/styles/config.css | 124 ++++ .../templates/singulinkfx/styles/discord.css | 680 ++++++++++++++++++ .../singulinkfx/styles/down-arrow.svg | 44 ++ .../styles/jquery.twbsPagination.js | 317 ++++++++ .../styles/jquery.twbsPagination.min.js | 8 + .../templates/singulinkfx/styles/main.css | 0 .../templates/singulinkfx/styles/main.js | 0 .../singulinkfx/styles/singulink.css | 572 +++++++++++++++ .../templates/singulinkfx/styles/singulink.js | 51 ++ .../templates/singulinkfx/styles/url.min.js | 1 + .../templates/singulinkfx/toc.html.tmpl | 22 + documentation/docfx_project/toc.yml | 5 + .../Extensions/DateTimeExtension.cs | 1 + src/UtilityVerse/UtilityVerse.csproj | 2 +- 30 files changed, 2095 insertions(+), 2 deletions(-) create mode 100644 documentation/docfx_project/.gitignore create mode 100644 documentation/docfx_project/articles/intro.md create mode 100644 documentation/docfx_project/articles/toc.yml create mode 100644 documentation/docfx_project/docfx.json create mode 100644 documentation/docfx_project/index.md create mode 100644 documentation/docfx_project/templates/singulinkfx/layout/_master.tmpl create mode 100644 documentation/docfx_project/templates/singulinkfx/partials/footer.tmpl.partial create mode 100644 documentation/docfx_project/templates/singulinkfx/partials/head.tmpl.partial create mode 100644 documentation/docfx_project/templates/singulinkfx/partials/li.tmpl.partial create mode 100644 documentation/docfx_project/templates/singulinkfx/partials/logo.tmpl.partial create mode 100644 documentation/docfx_project/templates/singulinkfx/partials/namespace.tmpl.partial create mode 100644 documentation/docfx_project/templates/singulinkfx/partials/navbar.tmpl.partial create mode 100644 documentation/docfx_project/templates/singulinkfx/partials/scripts.tmpl.partial create mode 100644 documentation/docfx_project/templates/singulinkfx/partials/searchResults.tmpl.partial create mode 100644 documentation/docfx_project/templates/singulinkfx/partials/toc.tmpl.partial create mode 100644 documentation/docfx_project/templates/singulinkfx/styles/config.css create mode 100644 documentation/docfx_project/templates/singulinkfx/styles/discord.css create mode 100644 documentation/docfx_project/templates/singulinkfx/styles/down-arrow.svg create mode 100644 documentation/docfx_project/templates/singulinkfx/styles/jquery.twbsPagination.js create mode 100644 documentation/docfx_project/templates/singulinkfx/styles/jquery.twbsPagination.min.js create mode 100644 documentation/docfx_project/templates/singulinkfx/styles/main.css create mode 100644 documentation/docfx_project/templates/singulinkfx/styles/main.js create mode 100644 documentation/docfx_project/templates/singulinkfx/styles/singulink.css create mode 100644 documentation/docfx_project/templates/singulinkfx/styles/singulink.js create mode 100644 documentation/docfx_project/templates/singulinkfx/styles/url.min.js create mode 100644 documentation/docfx_project/templates/singulinkfx/toc.html.tmpl create mode 100644 documentation/docfx_project/toc.yml diff --git a/README.md b/README.md index 48a0eaf..0cfb988 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,9 @@ To Invoke this library, you may want to use ```Utility``` class. ------------------------------- -### Documentation Work in progress. ⚒️ +### Documentation Work in progress. (Experimental build) ⚒️ +-- `cd documentation/docfx_project/` +-- `docfx ./docfx.json --serve` ------------------------------- diff --git a/documentation/docfx_project/.gitignore b/documentation/docfx_project/.gitignore new file mode 100644 index 0000000..b6af25f --- /dev/null +++ b/documentation/docfx_project/.gitignore @@ -0,0 +1,9 @@ +############### +# folder # +############### +/**/DROP/ +/**/TEMP/ +/**/packages/ +/**/bin/ +/**/obj/ + diff --git a/documentation/docfx_project/articles/intro.md b/documentation/docfx_project/articles/intro.md new file mode 100644 index 0000000..c0478ce --- /dev/null +++ b/documentation/docfx_project/articles/intro.md @@ -0,0 +1 @@ +# Add your introductions here! diff --git a/documentation/docfx_project/articles/toc.yml b/documentation/docfx_project/articles/toc.yml new file mode 100644 index 0000000..ff89ef1 --- /dev/null +++ b/documentation/docfx_project/articles/toc.yml @@ -0,0 +1,2 @@ +- name: Introduction + href: intro.md diff --git a/documentation/docfx_project/docfx.json b/documentation/docfx_project/docfx.json new file mode 100644 index 0000000..d8fb536 --- /dev/null +++ b/documentation/docfx_project/docfx.json @@ -0,0 +1,54 @@ +{ + "metadata": [ + { + "src": [ + { + "files": [ + "**.csproj" + ], + "src": "C:\\Users\\pritom\\source\\repos\\TheUtilityVerse\\src" + } + ], + "dest": "api", + "includePrivateMembers": false, + "disableGitFeatures": false, + "disableDefaultFilter": false, + "noRestore": false, + "namespaceLayout": "flattened", + "memberLayout": "samePage", + "allowCompilationErrors": false + } + ], + "build": { + "content": [ + { + "files": [ + "api/**.yml", + "api/index.md" + ] + }, + { + "files": [ + "articles/**.md", + "articles/**/toc.yml", + "toc.yml", + "*.md" + ] + } + ], + "resource": [ + { + "files": [ + "images/**" + ] + } + ], + "output": "_site", + "globalMetadataFiles": [], + "fileMetadataFiles": [], + "template": ["default", "templates/singulinkfx"], + "postProcessors": [], + "keepFileLink": false, + "disableGitFeatures": false + } +} \ No newline at end of file diff --git a/documentation/docfx_project/index.md b/documentation/docfx_project/index.md new file mode 100644 index 0000000..3ae2506 --- /dev/null +++ b/documentation/docfx_project/index.md @@ -0,0 +1,4 @@ +# This is the **HOMEPAGE**. +Refer to [Markdown](http://daringfireball.net/projects/markdown/) for how to write markdown files. +## Quick Start Notes: +1. Add images to the *images* folder if the file is referencing an image. diff --git a/documentation/docfx_project/templates/singulinkfx/layout/_master.tmpl b/documentation/docfx_project/templates/singulinkfx/layout/_master.tmpl new file mode 100644 index 0000000..8e43309 --- /dev/null +++ b/documentation/docfx_project/templates/singulinkfx/layout/_master.tmpl @@ -0,0 +1,72 @@ +{{!Copyright (c) Microsoft. All rights reserved. Licensed under the MIT license. See LICENSE file in the project root for full license information.}} +{{!include(/^styles/.*/)}} +{{!include(/^fonts/.*/)}} +{{!include(favicon.ico)}} +{{!include(logo.svg)}} +{{!include(search-stopwords.json)}} + + + + {{>partials/head}} + + +
+ + + + + {{>partials/logo}} +
+ +
+
+ + + +
+ {{#_enableSearch}} + {{>partials/searchResults}} + {{/_enableSearch}} + + + + + + {{#_copyrightFooter}} + + {{/_copyrightFooter}} +
+
+ + {{>partials/scripts}} + + diff --git a/documentation/docfx_project/templates/singulinkfx/partials/footer.tmpl.partial b/documentation/docfx_project/templates/singulinkfx/partials/footer.tmpl.partial new file mode 100644 index 0000000..dd601a9 --- /dev/null +++ b/documentation/docfx_project/templates/singulinkfx/partials/footer.tmpl.partial @@ -0,0 +1,4 @@ + \ No newline at end of file diff --git a/documentation/docfx_project/templates/singulinkfx/partials/head.tmpl.partial b/documentation/docfx_project/templates/singulinkfx/partials/head.tmpl.partial new file mode 100644 index 0000000..2034340 --- /dev/null +++ b/documentation/docfx_project/templates/singulinkfx/partials/head.tmpl.partial @@ -0,0 +1,24 @@ +{{!Copyright (c) Microsoft. All rights reserved. Licensed under the MIT license. See LICENSE file in the project root for full license information.}} + + + + + {{#title}}{{title}}{{/title}}{{^title}}{{>partials/title}}{{/title}} {{#_appTitle}}| {{_appTitle}} {{/_appTitle}} + + + + {{#_description}}{{/_description}} + + + + + + + + + + + {{#_noindex}}{{/_noindex}} + {{#_enableSearch}}{{/_enableSearch}} + {{#_enableNewTab}}{{/_enableNewTab}} + \ No newline at end of file diff --git a/documentation/docfx_project/templates/singulinkfx/partials/li.tmpl.partial b/documentation/docfx_project/templates/singulinkfx/partials/li.tmpl.partial new file mode 100644 index 0000000..2c8a3d0 --- /dev/null +++ b/documentation/docfx_project/templates/singulinkfx/partials/li.tmpl.partial @@ -0,0 +1,31 @@ +{{!Copyright (c) Microsoft. All rights reserved. Licensed under the MIT license. See LICENSE file in the project root for full license information.}} + + diff --git a/documentation/docfx_project/templates/singulinkfx/partials/logo.tmpl.partial b/documentation/docfx_project/templates/singulinkfx/partials/logo.tmpl.partial new file mode 100644 index 0000000..738ab5b --- /dev/null +++ b/documentation/docfx_project/templates/singulinkfx/partials/logo.tmpl.partial @@ -0,0 +1,6 @@ +{{!Copyright (c) Microsoft. All rights reserved. Licensed under the MIT license. See LICENSE file in the project root for full license information.}} + + + {{_appName}} + {{_appName}} + \ No newline at end of file diff --git a/documentation/docfx_project/templates/singulinkfx/partials/namespace.tmpl.partial b/documentation/docfx_project/templates/singulinkfx/partials/namespace.tmpl.partial new file mode 100644 index 0000000..42d64e6 --- /dev/null +++ b/documentation/docfx_project/templates/singulinkfx/partials/namespace.tmpl.partial @@ -0,0 +1,13 @@ +{{!Copyright (c) Microsoft. All rights reserved. Licensed under the MIT license. See LICENSE file in the project root for full license information.}} + +

{{>partials/title}}

+
{{{summary}}}
+
{{{conceptual}}}
+
{{{remarks}}}
+{{#children}} +

{{>partials/namespaceSubtitle}}

+ {{#children}} +
+
{{{summary}}}
+ {{/children}} +{{/children}} diff --git a/documentation/docfx_project/templates/singulinkfx/partials/navbar.tmpl.partial b/documentation/docfx_project/templates/singulinkfx/partials/navbar.tmpl.partial new file mode 100644 index 0000000..cfddfd8 --- /dev/null +++ b/documentation/docfx_project/templates/singulinkfx/partials/navbar.tmpl.partial @@ -0,0 +1,19 @@ +{{!Copyright (c) Microsoft. All rights reserved. Licensed under the MIT license. See LICENSE file in the project root for full license information.}} + +
+
+ {{>partials/logo}} +
+ + {{#_enableSearch}} +
+ +
+ {{/_enableSearch}} + + +
\ No newline at end of file diff --git a/documentation/docfx_project/templates/singulinkfx/partials/scripts.tmpl.partial b/documentation/docfx_project/templates/singulinkfx/partials/scripts.tmpl.partial new file mode 100644 index 0000000..8ef7f26 --- /dev/null +++ b/documentation/docfx_project/templates/singulinkfx/partials/scripts.tmpl.partial @@ -0,0 +1,13 @@ +{{!Copyright (c) Microsoft. All rights reserved. Licensed under the MIT license. See LICENSE file in the project root for full license information.}} + + + + + + + + + + + + \ No newline at end of file diff --git a/documentation/docfx_project/templates/singulinkfx/partials/searchResults.tmpl.partial b/documentation/docfx_project/templates/singulinkfx/partials/searchResults.tmpl.partial new file mode 100644 index 0000000..9f08c90 --- /dev/null +++ b/documentation/docfx_project/templates/singulinkfx/partials/searchResults.tmpl.partial @@ -0,0 +1,9 @@ +{{!Copyright (c) Microsoft. All rights reserved. Licensed under the MIT license. See LICENSE file in the project root for full license information.}} + + \ No newline at end of file diff --git a/documentation/docfx_project/templates/singulinkfx/partials/toc.tmpl.partial b/documentation/docfx_project/templates/singulinkfx/partials/toc.tmpl.partial new file mode 100644 index 0000000..c660966 --- /dev/null +++ b/documentation/docfx_project/templates/singulinkfx/partials/toc.tmpl.partial @@ -0,0 +1,5 @@ +{{!Copyright (c) Microsoft. All rights reserved. Licensed under the MIT license. See LICENSE file in the project root for full license information.}} + +
+
+
diff --git a/documentation/docfx_project/templates/singulinkfx/styles/config.css b/documentation/docfx_project/templates/singulinkfx/styles/config.css new file mode 100644 index 0000000..cb39b51 --- /dev/null +++ b/documentation/docfx_project/templates/singulinkfx/styles/config.css @@ -0,0 +1,124 @@ +/* Theme Configuration Options */ + +:root +{ + /* General */ + + --base-font-size: 16px; + --smalldevice-base-font-size: 14px; /* Base font size for devices < 1024px */ + + --main-bg-color: #1f1f23; + --footer-bg-color: rgba(0,0,0,.4); + --separator-color: #42474f; + + --table-strip-bg-color: #151515; + --table-header-bg-color: black; + --table-header-color: hsla(0,0%,100%,.8); + --table-header-border-color: #040405; + + /* Text */ + + --appname-color: white; + + --h1-color: white; + --h2-color: #f2f2f2; + --h3-color: #e3e3e3; + --h4-color: #ffffff; + --h5-color: #e0e0e0; + + --text-color: #e1e1e1; + --link-color: #00b0f4; + --link-hover-color: #2ec4ff; + + /* Mobile Topbar */ + + --topbar-bg-color: #18191c; + + /* Button */ + + --button-color: #747f8d; + + /* Sidebar */ + + --sidebar-width: 400px; + --sidebar-bg-color: #292B30; + + --search-color: #bdbdbd; + --search-bg-color: #1b1e21; + --search-searchicon-color: #e3e3e3; + --search-border-color: black; + + --sidebar-item-color: white; + --sidebar-active-item-color: #00b0f4; + --sidebar-level1-item-bg-color: #222429; + --sidebar-level1-item-hover-bg-color: #1D1F22; + + --toc-filter-color: #bdbdbd; + --toc-filter-bg-color: #1b1e21; + --toc-filter-filtericon-color: #e3e3e3; + --toc-filter-clearicon-color: #e68585; + --toc-filter-border-color: black; + + /* Scrollbars */ + + --scrollbar-bg-color: transparent; + --scrollbar-thumb-bg-color: rgba(0,0,0,.4); + --scrollbar-thumb-border-color: transparent; + + /* Alerts and Blocks */ + + --alert-info-border-color: rgba(114,137,218,.5); + --alert-info-bg-color: rgba(114,137,218,.1); + + --alert-warning-border-color: rgba(250,166,26,.5); + --alert-warning-bg-color: rgba(250,166,26,.1); + + --alert-danger-border-color: rgba(240,71,71,.5); + --alert-danger-bg-color: rgba(240,71,71,.1); + + --alert-tip-border-color: rgba(255,255,255,.5); + --alert-tip-bg-color: rgba(255,255,255,.1); + + --blockquote-border-color: rgba(255,255,255,.5); + --blockquote-bg-color: rgba(255,255,255,.1); + + --breadcrumb-bg-color: #2f3136; + + /* Tabs */ + + --nav-tabs-border-width: 1px; + --nav-tabs-border-color: #495057; + --nav-tabs-border-radius: .375rem; + --nav-tabs-link-hover-border-color: #303336 #303336 transparent; + --nav-tabs-link-active-color: white; + --nav-tabs-link-active-bg: var(--main-bg-color); + --nav-tabs-link-active-border-color: var(--nav-tabs-border-color) var(--nav-tabs-border-color) var(--main-bg-color); + + /* Inline Code */ + + --ref-bg-color: black; + --ref-color: #89d4f1; + + /* Code Blocks */ + + --code-bg-color: #151515; + --code-color: #d6deeb; + --code-keyword-color: #569cd6; + --code-comment-color: #57a64a; + --code-macro-color: #beb7ff; + --code-string-color: #d69d85; + --code-string-escape-color: #ffd68f; + --code-field-color: #c8c8c8; + --code-function-color: #dcdcaa; + --code-control-color: #d8a0df; + --code-class-color: #4ec9b0; + --code-number-color: #b5cea8; + --code-params-color: #9a9a9a; + --code-breakpoint-color: #8c2f2f; +} + +/* Code Block Overrides */ + +pre, legend { + --scrollbar-thumb-bg-color: #333; +} \ No newline at end of file diff --git a/documentation/docfx_project/templates/singulinkfx/styles/discord.css b/documentation/docfx_project/templates/singulinkfx/styles/discord.css new file mode 100644 index 0000000..fd53fc7 --- /dev/null +++ b/documentation/docfx_project/templates/singulinkfx/styles/discord.css @@ -0,0 +1,680 @@ +/* Discord Style */ + +::-webkit-scrollbar { + width: 10px; +} + +::-webkit-scrollbar-track { + background: var(--scrollbar-bg-color); +} + +::-webkit-scrollbar-thumb { + background: var(--scrollbar-thumb-bg-color); + border-color: var(--scrollbar-thumb-border-color); + border-radius: 5px; +} + +::marker { + unicode-bidi: isolate; + font-variant-numeric: tabular-nums; + text-transform: none; + text-indent: 0px !important; + text-align: start !important; + text-align-last: start !important; +} + +*, :after, :before +{ + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; +} + +html, body +{ + padding: 0; + margin: 0; + font: 15px/150% 'Roboto', sans-serif; + overflow: hidden; + color: var(--text-color); + background-color: var(--main-bg-color); + + position: absolute; + top: 0; + bottom: 0; + left: 0; + right: 0; +} + +img { + max-width: 100%; +} + +ul > li, ol > li { + display: list-item; +} + +h1,h2,h3,h4,h5 +{ + color: var(--link-active-color); + position: relative; +} + +h1, h2 +{ + margin-block-start: 2em; +} + +h3 +{ + margin-block-start: 1em; + font-weight: 300; + font-size: 1.5em; + color: var(--h3-color); + margin-block-start: 3em; +} + +h4 +{ + opacity: 1; + color: var(--h4-color); + font-size: large; + border-bottom: 2px solid var(--separator-color); + margin: 20px 0 0 0; +} + + +h5 { + margin-block-end: .8em; + margin-block-start: 1em; + font-size: .85em; + font-weight: 500; + color: var(--h5-color); +} + +h6 { + font-size: .75em; + margin: 0; +} + +p +{ + font-weight: 400; +} + +ul +{ + position: relative; +} + +ul, ol +{ + padding-inline-start: 3em; +} + +ul.level1 +{ + list-style-type: none; + padding-inline-start: 0; +} + +ul.level2, ul.level3 +{ + padding-inline-start: 1em; + list-style-type: none; + font-size: .9em; +} + +a +{ + color: var(--link-color); + text-decoration: none; + transition: color .25s; +} + +a:focus, a:hover +{ + color: var(--link-hover-color); + text-decoration: underline; +} + +a.anchorjs-link:hover { + text-decoration: none; +} + +a.active, a:active +{ + color: var(--link-active-color); +} + +.body-content +{ + display: flex; + flex-direction: row; + height: 100%; + overflow-x: hidden; + overflow-y: hidden; +} + +.page-title +{ + margin-block-start: 0; +} + +nav +{ + width: 300px; + transition: left .5s ease-out; + position: fixed; + left: -350px; + top: 40px; + bottom: 0; + background-color: var(--sidebar-bg-color); + overflow-y: auto; + + display: flex; + flex-direction: column; + + z-index: 1000; +} + +h1:first-child +{ + margin-block-start: 1.1em; + margin-top: 1.1em; +} + +.sidebar +{ + padding: 32px 17px 32px 32px; + flex: 1; +} + +.sidebar-item +{ + font-size: 1em; + font-weight: 400; + display: block; + padding: 4px 16px; + color: var(--sidebar-item-color); +} + +.sidebar-item.large, #navbar .sidebar-item +{ + padding: 8px 16px; +} + +a.sidebar-item:hover, a.sidebar-item:focus +{ + color: var(--link-active-color); + text-decoration: none; +} + +a.sidebar-item.active +{ + color: var(--link-active-color); +} + +ul.level1 > li > a.sidebar-item +{ + background-color: transparent; + border-radius: 4px; +} + +#toc ul.level1 > li > a.sidebar-item.active +{ + background-color: var(--link-active-bg-color); +} + +.sidebar-item-separator +{ + height: 2px; + width: 100%; + background-color: var(--separator-color); + margin: 2em 0; + opacity: .8; +} + +span.sidebar-item +{ + font-weight: 700; + text-transform: uppercase; + font-size: .8em; + color: var(--text-color); + margin-block-start: 1.25em; +} + +.main-panel +{ + background-color: var(--main-bg-color); + flex: 1; + overflow-y: auto; + padding: 20px 40px; +} + +.top-navbar +{ + display: flex; + flex-direction: row; + align-items: center; + padding: 0 40px; + height: 40px; + background-color: var(--topbar-bg-color); +} + +.burger-icon +{ + margin-right: 1em; + color: var(--button-color); +} + +.burger-icon:hover, .burger-icon:focus +{ + color: var(--link-active-color); +} + +.burger-icon.active, .burger-icon:active +{ + color: var(--link-active-color); +} + +.brand +{ + display: flex; + align-items: center; + justify-content: start; +} + +.logomark +{ + height: 28px; +} + +.brand-title +{ + padding: 0 .5em; + font-size: .9em; + color: var(--link-active-color); +} + +.footer +{ + background-color: var(--footer-bg-color); + padding: 20px; + margin: 0 20px 20px 20px; + border-radius: 8px; + color: var(--link-active-color); +} + +.footer > h4 +{ + margin-block-start: 0; +} + +.blackout +{ + display: block; + visibility: hidden; + position: absolute; + z-index: 100; + top: 40px; + bottom: 0; + left: 0; + right: 0; + background-color: var(--footer-bg-color); +} + +@keyframes showThat { + 0% { opacity: 0; visibility: hidden; } + 1% { opacity: 0; visibility: visible; } + 100% { opacity: 1; visibility: visible;} +} + +@keyframes hideThat { + 0% { opacity: 1; visibility: visible; } + 99% { opacity: 0; visibility: visible; } + 100% { opacity: 0; visibility: hidden;} +} + +.showThat +{ + animation: showThat .5s forwards; +} + +.hideThat +{ + animation: hideThat .5s forwards; +} + + + +@media (min-width: 1024px) +{ + nav + { + position: relative; + left: 0!important; + top: 0; + bottom: 0; + } + + .top-navbar + { + display: none; + } + + .blackout + { + display: none; + } +} + +/* Table */ + +.table-responsive +{ + overflow-x: auto; + margin-bottom: 64px; +} + +table +{ + background-color: var(--code-bg-color); + border-collapse: collapse; + width: 100%; + table-layout: auto; +} + +table.table-striped tbody tr:nth-child(2n) +{ + background-color: var(--table-strip-bg-color); +} + +table thead +{ + background: var(--table-header-bg-color); +} + +table th +{ + color: var(--table-header-color); + text-transform: uppercase; + font-size: 12px; + line-height: 15px; + border-bottom: 1px solid var(--table-header-border-color); + padding: 8px; +} + +.table-condensed th { + text-align: left; +} + +table td +{ + padding: 8px; + font-weight: 300; +} + +table td > p +{ + margin: 0; +} + +/* Alerts */ +.alert { + border-radius: 4px; + padding: 8px; + margin: 25px 0; +} + +.alert > h5 +{ + display: none; + margin: 0; +} + +.alert > p +{ + margin: 0; + font-weight: 300; + font-size: 13px; +} + +.alert.alert-info +{ + border: 2px solid var(--alert-info-border-color); + background: var(--alert-info-bg-color); +} + +.alert.alert-warning +{ + border: 2px solid var(--alert-warning-border-color); + background: var(--alert-warning-bg-color); +} + +.alert.alert-danger +{ + border: 2px solid var(--alert-danger-border-color); + background: var(--alert-danger-bg-color); +} + +.TIP.alert.alert-info +{ + border: 2px solid var(--alert-tip-border-color); + background: var(--alert-tip-bg-color); +} + +blockquote { + margin: 8px 0; + border-left: 4px solid var(--blockquote-border-color); + padding: 8px; + background: var(--blockquote-bg-color); + border-radius: 4px; +} + +blockquote > p { + margin: 0; + font-style: italic; + font-size: 13px; +} + + +/* Breadcrumb */ + +#breadcrumb +{ + padding: 8px 16px; + background: var(--breadcrumb-bg-color); + border-radius: 4px; + margin-bottom: 30px; +} + +#breadcrumb:empty +{ + display: none; +} + +ul.breadcrumb +{ + display: flex; + flex-direction: row; + margin: 0; +} + +ul.breadcrumb > li { + margin-right: 6px; +} + +ul.breadcrumb > li::before +{ + content: "/"; + margin-right: 5px; +} + +ul.breadcrumb > li:first-child::before +{ + content: ""; + margin: 0; +} + + +/* Code */ + +legend, pre +{ + display: block; + background-color: var(--code-bg-color); + padding: 16px; + border-radius: 4px; +} + +code +{ + background-color: var(--code-bg-color); + padding: 2px 4px; + border-radius: 4px; +} + +.hljs +{ + background: transparent; +} + +/* DocFX related */ + +.small { + font-size: .9em; +} + +.pull-right +{ + float: right; +} + +.hide +{ + display: none; +} + +@media (max-width: 1023.98px) +{ + .mobile-hide + { + display: none; + } +} + +li +{ + display: block; + position: relative; +} + +.expand-stub +{ + cursor: pointer; + position: absolute; + width: 20px; + height: 20px; + left: -10px; +} + +ul.level1 > li > .expand-stub +{ + display: none; +} + +.toc .nav > li > .expand-stub::before, .toc .nav > li.active > .expand-stub::before +{ + content: " "; + position: absolute; + transform: rotate(-90deg); + width: 10px; + height: 10px; + top: 5px; + left: 5px; + background-repeat: no-repeat; + background: url(down-arrow.svg); +} + +.toc .nav > li.active > .expand-stub::before, .toc .nav > li.in > .expand-stub::before, .toc .nav > li.in.active > .expand-stub::before, .toc .nav > li.filtered > .expand-stub::before +{ + transform: none; +} + +li > ul +{ + display: none; +} + +li.in > ul +{ + display: block; +} + +ul.level2 > li > a.sidebar-item, +ul.level3 > li > a.sidebar-item +{ + font-size: .95em; + padding: 0; + margin: 2px 16px; +} + +ul.level2 > li > a.sidebar-item +{ + color: var(--sidebar-item-2nd-color); +} + +ul.level3 > li > a.sidebar-item +{ + color: var(--sidebar-item-3rd-color); +} + +ul.level2 > li > a.sidebar-item:hover, +ul.level2 > li > a.sidebar-item:focus, +ul.level3 > li > a.sidebar-item:hover, +ul.level3 > li > a.sidebar-item:focus +{ + color: var(--link-active-color); + text-decoration: underline; +} + +ul.level2 > li > a.sidebar-item.active, +ul.level3 > li > a.sidebar-item.active +{ + color: var(--link-active-color); +} + +.inheritance .level0:before, +.inheritance .level1:before, +.inheritance .level2:before, +.inheritance .level3:before, +.inheritance .level4:before, +.inheritance .level5:before { + content: '↳'; + margin-right: 5px; +} + +.inheritance .level0 { + margin-left: 0em; +} + +.inheritance .level1 { + margin-left: 1em; +} + +.inheritance .level2 { + margin-left: 2em; +} + +.inheritance .level3 { + margin-left: 3em; +} + +.inheritance .level4 { + margin-left: 4em; +} + +.inheritance .level5 { + margin-left: 5em; +} \ No newline at end of file diff --git a/documentation/docfx_project/templates/singulinkfx/styles/down-arrow.svg b/documentation/docfx_project/templates/singulinkfx/styles/down-arrow.svg new file mode 100644 index 0000000..e086126 --- /dev/null +++ b/documentation/docfx_project/templates/singulinkfx/styles/down-arrow.svg @@ -0,0 +1,44 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/documentation/docfx_project/templates/singulinkfx/styles/jquery.twbsPagination.js b/documentation/docfx_project/templates/singulinkfx/styles/jquery.twbsPagination.js new file mode 100644 index 0000000..332c01c --- /dev/null +++ b/documentation/docfx_project/templates/singulinkfx/styles/jquery.twbsPagination.js @@ -0,0 +1,317 @@ +/*! + * jQuery pagination plugin v1.4.1 + * http://esimakin.github.io/twbs-pagination/ + * + * Copyright 2014-2016, Eugene Simakin + * Released under Apache 2.0 license + * http://apache.org/licenses/LICENSE-2.0.html + */ +(function ($, window, document, undefined) { + + 'use strict'; + + var old = $.fn.twbsPagination; + + // PROTOTYPE AND CONSTRUCTOR + + var TwbsPagination = function (element, options) { + this.$element = $(element); + this.options = $.extend({}, $.fn.twbsPagination.defaults, options); + + if (this.options.startPage < 1 || this.options.startPage > this.options.totalPages) { + throw new Error('Start page option is incorrect'); + } + + this.options.totalPages = parseInt(this.options.totalPages); + if (isNaN(this.options.totalPages)) { + throw new Error('Total pages option is not correct!'); + } + + this.options.visiblePages = parseInt(this.options.visiblePages); + if (isNaN(this.options.visiblePages)) { + throw new Error('Visible pages option is not correct!'); + } + + if (this.options.onPageClick instanceof Function) { + this.$element.first().on('page', this.options.onPageClick); + } + + // hide if only one page exists + if (this.options.hideOnlyOnePage && this.options.totalPages == 1) { + this.$element.trigger('page', 1); + return this; + } + + if (this.options.totalPages < this.options.visiblePages) { + this.options.visiblePages = this.options.totalPages; + } + + if (this.options.href) { + this.options.startPage = this.getPageFromQueryString(); + if (!this.options.startPage) { + this.options.startPage = 1; + } + } + + var tagName = (typeof this.$element.prop === 'function') ? + this.$element.prop('tagName') : this.$element.attr('tagName'); + + if (tagName === 'UL') { + this.$listContainer = this.$element; + } else { + this.$listContainer = $(''); + } + + this.$listContainer.addClass(this.options.paginationClass); + + if (tagName !== 'UL') { + this.$element.append(this.$listContainer); + } + + if (this.options.initiateStartPageClick) { + this.show(this.options.startPage); + } else { + this.render(this.getPages(this.options.startPage)); + this.setupEvents(); + } + + return this; + }; + + TwbsPagination.prototype = { + + constructor: TwbsPagination, + + destroy: function () { + this.$element.empty(); + this.$element.removeData('twbs-pagination'); + this.$element.off('page'); + + return this; + }, + + show: function (page) { + if (page < 1 || page > this.options.totalPages) { + throw new Error('Page is incorrect.'); + } + this.currentPage = page; + + this.render(this.getPages(page)); + this.setupEvents(); + + this.$element.trigger('page', page); + + return this; + }, + + buildListItems: function (pages) { + var listItems = []; + + if (this.options.first) { + listItems.push(this.buildItem('first', 1)); + } + + if (this.options.prev) { + var prev = pages.currentPage > 1 ? pages.currentPage - 1 : this.options.loop ? this.options.totalPages : 1; + listItems.push(this.buildItem('prev', prev)); + } + + for (var i = 0; i < pages.numeric.length; i++) { + listItems.push(this.buildItem('page', pages.numeric[i])); + } + + if (this.options.next) { + var next = pages.currentPage < this.options.totalPages ? pages.currentPage + 1 : this.options.loop ? 1 : this.options.totalPages; + listItems.push(this.buildItem('next', next)); + } + + if (this.options.last) { + listItems.push(this.buildItem('last', this.options.totalPages)); + } + + return listItems; + }, + + buildItem: function (type, page) { + var $itemContainer = $('
  • '), + $itemContent = $(''), + itemText = this.options[type] ? this.makeText(this.options[type], page) : page; + + $itemContainer.addClass(this.options[type + 'Class']); + $itemContainer.data('page', page); + $itemContainer.data('page-type', type); + $itemContainer.append($itemContent.attr('href', this.makeHref(page)).addClass(this.options.anchorClass).html(itemText)); + + return $itemContainer; + }, + + getPages: function (currentPage) { + var pages = []; + + var half = Math.floor(this.options.visiblePages / 2); + var start = currentPage - half + 1 - this.options.visiblePages % 2; + var end = currentPage + half; + + // handle boundary case + if (start <= 0) { + start = 1; + end = this.options.visiblePages; + } + if (end > this.options.totalPages) { + start = this.options.totalPages - this.options.visiblePages + 1; + end = this.options.totalPages; + } + + var itPage = start; + while (itPage <= end) { + pages.push(itPage); + itPage++; + } + + return {"currentPage": currentPage, "numeric": pages}; + }, + + render: function (pages) { + var _this = this; + this.$listContainer.children().remove(); + var items = this.buildListItems(pages); + jQuery.each(items, function(key, item){ + _this.$listContainer.append(item); + }); + + this.$listContainer.children().each(function () { + var $this = $(this), + pageType = $this.data('page-type'); + + switch (pageType) { + case 'page': + if ($this.data('page') === pages.currentPage) { + $this.addClass(_this.options.activeClass); + } + break; + case 'first': + $this.toggleClass(_this.options.disabledClass, pages.currentPage === 1); + break; + case 'last': + $this.toggleClass(_this.options.disabledClass, pages.currentPage === _this.options.totalPages); + break; + case 'prev': + $this.toggleClass(_this.options.disabledClass, !_this.options.loop && pages.currentPage === 1); + break; + case 'next': + $this.toggleClass(_this.options.disabledClass, + !_this.options.loop && pages.currentPage === _this.options.totalPages); + break; + default: + break; + } + + }); + }, + + setupEvents: function () { + var _this = this; + this.$listContainer.off('click').on('click', 'li', function (evt) { + var $this = $(this); + if ($this.hasClass(_this.options.disabledClass) || $this.hasClass(_this.options.activeClass)) { + return false; + } + // Prevent click event if href is not set. + !_this.options.href && evt.preventDefault(); + _this.show(parseInt($this.data('page'))); + }); + }, + + makeHref: function (page) { + return this.options.href ? this.generateQueryString(page) : "#"; + }, + + makeText: function (text, page) { + return text.replace(this.options.pageVariable, page) + .replace(this.options.totalPagesVariable, this.options.totalPages) + }, + getPageFromQueryString: function (searchStr) { + var search = this.getSearchString(searchStr), + regex = new RegExp(this.options.pageVariable + '(=([^&#]*)|&|#|$)'), + page = regex.exec(search); + if (!page || !page[2]) { + return null; + } + page = decodeURIComponent(page[2]); + page = parseInt(page); + if (isNaN(page)) { + return null; + } + return page; + }, + generateQueryString: function (pageNumber, searchStr) { + var search = this.getSearchString(searchStr), + regex = new RegExp(this.options.pageVariable + '=*[^&#]*'); + if (!search) return ''; + return '?' + search.replace(regex, this.options.pageVariable + '=' + pageNumber); + + }, + getSearchString: function (searchStr) { + var search = searchStr || window.location.search; + if (search === '') { + return null; + } + if (search.indexOf('?') === 0) search = search.substr(1); + return search; + } + + }; + + // PLUGIN DEFINITION + + $.fn.twbsPagination = function (option) { + var args = Array.prototype.slice.call(arguments, 1); + var methodReturn; + + var $this = $(this); + var data = $this.data('twbs-pagination'); + var options = typeof option === 'object' ? option : {}; + + if (!data) $this.data('twbs-pagination', (data = new TwbsPagination(this, options) )); + if (typeof option === 'string') methodReturn = data[ option ].apply(data, args); + + return ( methodReturn === undefined ) ? $this : methodReturn; + }; + + $.fn.twbsPagination.defaults = { + totalPages: 1, + startPage: 1, + visiblePages: 5, + initiateStartPageClick: true, + hideOnlyOnePage: false, + href: false, + pageVariable: '{{page}}', + totalPagesVariable: '{{total_pages}}', + page: null, + first: 'First', + prev: 'Previous', + next: 'Next', + last: 'Last', + loop: false, + onPageClick: null, + paginationClass: 'pagination', + nextClass: 'page-item next', + prevClass: 'page-item prev', + lastClass: 'page-item last', + firstClass: 'page-item first', + pageClass: 'page-item', + activeClass: 'active', + disabledClass: 'disabled', + anchorClass: 'page-link' + }; + + $.fn.twbsPagination.Constructor = TwbsPagination; + + $.fn.twbsPagination.noConflict = function () { + $.fn.twbsPagination = old; + return this; + }; + + $.fn.twbsPagination.version = "1.4.1"; + +})(window.jQuery, window, document); diff --git a/documentation/docfx_project/templates/singulinkfx/styles/jquery.twbsPagination.min.js b/documentation/docfx_project/templates/singulinkfx/styles/jquery.twbsPagination.min.js new file mode 100644 index 0000000..a9e11c6 --- /dev/null +++ b/documentation/docfx_project/templates/singulinkfx/styles/jquery.twbsPagination.min.js @@ -0,0 +1,8 @@ +/*! + * jQuery pagination plugin v1.4.1 + * http://esimakin.github.io/twbs-pagination/ + * + * Copyright 2014-2016, Eugene Simakin + * Released under Apache 2.0 license + * http://apache.org/licenses/LICENSE-2.0.html + */ !function(t,s,i,e){"use strict";var a=t.fn.twbsPagination,o=function(s,i){if(this.$element=t(s),this.options=t.extend({},t.fn.twbsPagination.defaults,i),this.options.startPage<1||this.options.startPage>this.options.totalPages)throw Error("Start page option is incorrect");if(this.options.totalPages=parseInt(this.options.totalPages),isNaN(this.options.totalPages))throw Error("Total pages option is not correct!");if(this.options.visiblePages=parseInt(this.options.visiblePages),isNaN(this.options.visiblePages))throw Error("Visible pages option is not correct!");if(this.options.onPageClick instanceof Function&&this.$element.first().on("page",this.options.onPageClick),this.options.hideOnlyOnePage&&1==this.options.totalPages)return this.$element.trigger("page",1),this;this.options.totalPages"),this.$listContainer.addClass(this.options.paginationClass),"UL"!==e&&this.$element.append(this.$listContainer),this.options.initiateStartPageClick?this.show(this.options.startPage):(this.render(this.getPages(this.options.startPage)),this.setupEvents()),this};o.prototype={constructor:o,destroy:function(){return this.$element.empty(),this.$element.removeData("twbs-pagination"),this.$element.off("page"),this},show:function(t){if(t<1||t>this.options.totalPages)throw Error("Page is incorrect.");return this.currentPage=t,this.render(this.getPages(t)),this.setupEvents(),this.$element.trigger("page",t),this},buildListItems:function(t){var s=[];if(this.options.first&&s.push(this.buildItem("first",1)),this.options.prev){var i=t.currentPage>1?t.currentPage-1:this.options.loop?this.options.totalPages:1;s.push(this.buildItem("prev",i))}for(var e=0;e"),a=t(""),o=this.options[s]?this.makeText(this.options[s],i):i;return e.addClass(this.options[s+"Class"]),e.data("page",i),e.data("page-type",s),e.append(a.attr("href",this.makeHref(i)).addClass(this.options.anchorClass).html(o)),e},getPages:function(t){var s=[],i=Math.floor(this.options.visiblePages/2),e=t-i+1-this.options.visiblePages%2,a=t+i;e<=0&&(e=1,a=this.options.visiblePages),a>this.options.totalPages&&(e=this.options.totalPages-this.options.visiblePages+1,a=this.options.totalPages);for(var o=e;o<=a;)s.push(o),o++;return{currentPage:t,numeric:s}},render:function(s){var i=this;this.$listContainer.children().remove();var e=this.buildListItems(s);jQuery.each(e,function(t,s){i.$listContainer.append(s)}),this.$listContainer.children().each(function(){var e=t(this),a=e.data("page-type");switch(a){case"page":e.data("page")===s.currentPage&&e.addClass(i.options.activeClass);break;case"first":e.toggleClass(i.options.disabledClass,1===s.currentPage);break;case"last":e.toggleClass(i.options.disabledClass,s.currentPage===i.options.totalPages);break;case"prev":e.toggleClass(i.options.disabledClass,!i.options.loop&&1===s.currentPage);break;case"next":e.toggleClass(i.options.disabledClass,!i.options.loop&&s.currentPage===i.options.totalPages)}})},setupEvents:function(){var s=this;this.$listContainer.off("click").on("click","li",function(i){var e=t(this);if(e.hasClass(s.options.disabledClass)||e.hasClass(s.options.activeClass))return!1;s.options.href||i.preventDefault(),s.show(parseInt(e.data("page")))})},makeHref:function(t){return this.options.href?this.generateQueryString(t):"#"},makeText:function(t,s){return t.replace(this.options.pageVariable,s).replace(this.options.totalPagesVariable,this.options.totalPages)},getPageFromQueryString:function(t){var s=this.getSearchString(t),i=RegExp(this.options.pageVariable+"(=([^&#]*)|&|#|$)").exec(s);return i&&i[2]?(i=parseInt(i=decodeURIComponent(i[2])),isNaN(i))?null:i:null},generateQueryString:function(t,s){var i=this.getSearchString(s),e=RegExp(this.options.pageVariable+"=*[^&#]*");return i?"?"+i.replace(e,this.options.pageVariable+"="+t):""},getSearchString:function(t){var i=t||s.location.search;return""===i?null:(0===i.indexOf("?")&&(i=i.substr(1)),i)}},t.fn.twbsPagination=function(s){var i,e=Array.prototype.slice.call(arguments,1),a=t(this),n=a.data("twbs-pagination");return n||a.data("twbs-pagination",n=new o(this,"object"==typeof s?s:{})),"string"==typeof s&&(i=n[s].apply(n,e)),void 0===i?a:i},t.fn.twbsPagination.defaults={totalPages:1,startPage:1,visiblePages:5,initiateStartPageClick:!0,hideOnlyOnePage:!1,href:!1,pageVariable:"{{page}}",totalPagesVariable:"{{total_pages}}",page:null,first:"First",prev:"Previous",next:"Next",last:"Last",loop:!1,onPageClick:null,paginationClass:"pagination",nextClass:"page-item next",prevClass:"page-item prev",lastClass:"page-item last",firstClass:"page-item first",pageClass:"page-item",activeClass:"active",disabledClass:"disabled",anchorClass:"page-link"},t.fn.twbsPagination.Constructor=o,t.fn.twbsPagination.noConflict=function(){return t.fn.twbsPagination=a,this},t.fn.twbsPagination.version="1.4.1"}(window.jQuery,window,document); \ No newline at end of file diff --git a/documentation/docfx_project/templates/singulinkfx/styles/main.css b/documentation/docfx_project/templates/singulinkfx/styles/main.css new file mode 100644 index 0000000..e69de29 diff --git a/documentation/docfx_project/templates/singulinkfx/styles/main.js b/documentation/docfx_project/templates/singulinkfx/styles/main.js new file mode 100644 index 0000000..e69de29 diff --git a/documentation/docfx_project/templates/singulinkfx/styles/singulink.css b/documentation/docfx_project/templates/singulinkfx/styles/singulink.css new file mode 100644 index 0000000..b478f84 --- /dev/null +++ b/documentation/docfx_project/templates/singulinkfx/styles/singulink.css @@ -0,0 +1,572 @@ +body { + font-size: var(--base-font-size); +} + +@media (max-width: 1024px) { + body { + font-size: var(--smalldevice-base-font-size); + } +} + +/* Headings */ + +h1, h2, h3, h4, h5 { + line-height: initial; +} + +h1, h1:first-child { + font-size: 2.25em; + letter-spacing: 0.5px; + color: var(--h1-color); + margin-block-start: 1em; + margin-block-end: -0.05em; +} + +.article h1 { + margin-block-end: -0.2em; +} + +h2 { + font-size: 2.1em; + color: var(--h2-color); +} + +.article h2 { + margin-block-start: 1.3em; + padding-bottom: 6px; + border-bottom: 1px solid var(--separator-color); +} + +h3 { + font-size: 1.95em; + font-weight: 500; + margin-block-start: 1.7em; +} + +.article h3 { + font-size: 1.85em; + font-weight: 500; + margin-block-start: 1.2em; + margin-block-end: 0.9em; +} + +h4 { + font-size: 1.8em; + font-weight: 400; + margin-block-start: 2em; + padding-bottom: 10px; +} + +.article h4 { + font-size: 1.5em; + font-weight: 300; + margin-block-start: 1em; + margin-block-end: 1em; + padding-bottom: 0; + border-bottom: none; +} + +h5 { + font-size: 1.1em; +} + +.article h5 { + font-size: 1.13em; + font-weight: 400; + text-decoration: underline; + margin-block-start: 1.5em; + margin-block-end: 1.0em; +} + +a.brand:hover +{ + text-decoration: none; +} + +a.brand .brand-title { + font-size: 1.4em; + font-weight: 500; + letter-spacing: 0.5px; + color: var(--appname-color); + margin-top: 1px; + padding: 0 0 0 0.4em; +} + +@media (min-width: 1024px) { + a.brand .brand-title { + font-size: 1.55em; + } +} + + +a.brand .logomark { + height: 35px; +} + +/* Top bar */ + +.top-navbar { + height: 60px; + padding: 0 0 0 10px; +} + +.burger-icon { + margin-right: 10px; +} + +/* Side Bar */ + +.sidebar { + padding: 25px 17px 32px 17px; +} + +.blackout { + top: 60px; +} + +@media (max-width: 1023.98px) { + .navbar-nav { + margin-top: 0; + } +} + +nav { + width: 94%; + max-width: var(--sidebar-width); + left: calc(var(--sidebar-width) * -1); +} + +@media (max-width: 1023.98px) { + nav { + top: 60px; + } +} + +nav ul { + list-style-type: none; +} + +nav .nav a, nav .nav a:hover { + text-decoration: none; + cursor: pointer; + display: block; +} + +nav a.sidebar-item { + padding: 4px 0 4px 10px; + cursor: pointer; +} + +nav a:focus, nav a.sidebar-item:hover, nav a.sidebar-item:focus { + text-decoration: underline; +} + +nav a, nav a:hover, nav a:focus { + color: var(--sidebar-item-color) !important; +} + +nav a.active, nav a.active:hover, nav a.active:focus { + color: var(--sidebar-active-item-color) !important; +} + +.sidebar-item-separator { + margin: 20px 0; +} + +#toc ul li a { + padding: 0 0 0 10px; +} + +.search { + background: var(--search-bg-color); + border: 1px solid var(--search-border-color); + border-radius: 5px; + position: relative; + margin-block-start: 25px; +} + +@media (max-width: 1023.98px) { + .search { + margin-block-start: 0; + margin-block-end: 15px; + } +} + +.search > input { + font-size: 0.95em; + color: var(--search-color); + border: 0; + background: none; + padding: 11px 30px 10px 37px; + width: 100%; +} + +.search > input:focus { + outline: 0; +} + +.search > .search-icon { + font-size: 1.2em; + color: var(--search-searchicon-color); + position: absolute; + top: 9px; + left: 9px; +} + +.toc-filter { + background: var(--toc-filter-bg-color); + border: 1px solid var(--toc-filter-border-color); + border-radius: 5px; + position: relative; +} + +.toc-filter > input { + font-size: 0.95em; + color: var(--toc-filter-color); + border: 0; + background: none; + padding: 11px 30px 10px 37px; + width: 100%; +} + +.toc-filter > input:focus { + outline: 0; +} + +.toc-filter > .filter-icon { + font-size: 1.2em; + color: var(--toc-filter-filtericon-color); + position: absolute; + top: 9px; + left: 9px; +} + +.toc-filter > .clear-icon { + color: var(--toc-filter-clearicon-color); + position: absolute; + top: 9px; + right: 9px; + cursor: pointer; +} + +.toc .nav > li > .expand-stub::before, .toc .nav > li.active > .expand-stub::before +{ + width: 8px; + height: 8px; + top: 6px; + left: 6px; +} + +#toc ul.level2 +{ + margin-bottom: 20px; +} + +#toc ul.level1 > li > a { + font-weight: 500; + margin-bottom: 10px; + padding: 5px 10px; +} + +#toc ul.level1 > li > a, #toc ul.level1 > li > a.active { + background-color: var(--sidebar-level1-item-bg-color) !important; + border-radius: 2px; +} + +#toc ul.level1 > li > a:hover, #toc ul.level1 > li > a.active:hover, +#toc ul.level1 > li > a:focus, #toc ul.level1 > li > a.active:focus { + background-color: var(--sidebar-level1-item-hover-bg-color) !important; + text-decoration: none; +} + +ul.level2 { + padding-inline-start: 0.7em; +} + +ul.level2 .expand-stub { + top: 1px; +} + +ul.level2 > li > a, ul.level2 > li > a.sidebar-item { + font-weight: 400; + color: var(--sidebar-item-color); + margin: 4px 0 4px; +} + +ul.level3 { + padding-inline-start: 1em; +} + +ul.level3 > li > a, ul.level3 > li > a.sidebar-item { + font-size: 1.05em; + color: var(--sidebar-item-color); + margin: 4px 0; +} + +ul.level4 { + padding-inline-start: 0; + margin-bottom: 12px; +} + +ul.level4 > li > a, ul.level4 > li > a.sidebar-item { + font-size: 1.05em; + color: var(--sidebar-item-color); + margin: 5px 0 5px 10px; +} + +/* Breadcrumbs */ + +.subnav.navbar { + margin: 0 -15px; +} + +#breadcrumb { + overflow: scroll; + margin-bottom: 0; +} + +#breadcrumb::-webkit-scrollbar { + display: none; +} + +#breadcrumb a { + white-space: nowrap; +} + +#breadcrumb wbr { + display: none; +} + +/* Search Results */ + +#search-results h1 { + margin-block-start: 0.5em; +} + +#search-results .item-title { + font-size: 1.3em; + margin-top: 1.5em; +} + +#search-results .item-href { + font-size: 0.85em; +} + +#search-results .item-brief { + margin-top: 0.7em; +} + +#search-results ul.pagination { + text-align: center; + padding: 10px 0 0 0; + margin-block-start: 40px; + border-top: 1px solid var(--separator-color); +} + +#search-results ul.pagination > li { + display: inline-block; + margin: 0 10px; +} + +#search-results ul.pagination > li.disabled a, #search-results ul.pagination > li.disabled a:hover { + color: var(--text-color); + cursor: txt; + text-decoration: none; +} + +/* Content */ + +.main-panel { + margin-bottom: 60px; + padding: 20px; +} + +@media (min-width: 1024px) { + .main-panel { + margin-bottom: 0; + padding: 20px 40px; + } +} + +.pull-right { + margin-top: 70px; + /* Fix unclickable links */ + position: relative; + z-index: 1; +} + +.divider { + margin-left: 4px; +} + +article ul li, article ol li { + margin-bottom: 10px; +} + +legend, pre { + padding: 6px; +} + +.hljs { + color: var(--code-color); +} + +.hljs::-webkit-scrollbar { + height: 6px; +} + +.hljs-keyword, .hljs-title, .hljs-built_in { + font-style: normal; +} + +p .xref, code { + background-color: var(--ref-bg-color); + color: var(--ref-color); + padding: 2px 3px; + font-family: monospace; + font-size: 0.95em; + border-radius: 6px; +} + +span.parametername { + font-family: monospace; +} + +.table { + width: auto; +} + +.table-responsive { + margin-bottom: 0; +} + +table th { + font-size: 14px; + padding: 9px 10px; +} + +table td p { + font-weight: 300; +} + +table td { + padding: 6px 10px; +} + +.footer { + text-align: center; + color: var(--text-color); + padding: 10px; +} + +.footer a:hover, .footer a:focus { + text-decoration: underline; +} + +.copyright-footer { + font-size: 0.85em; + font-weight: bold; + text-align: center; + margin-block-start: 30px; +} + +#contribution { + float: right; + margin-top: 1.1em; +} + + +/* Tabs */ + +.tabGroup { + margin-top: 1rem; + margin-bottom: 1rem; +} + +.tabGroup ul[role="tablist"] { + margin: 0; + padding: 0; + list-style: none; + border-bottom: var(--nav-tabs-border-width) solid var(--nav-tabs-border-color); +} + +.tabGroup ul[role="tablist"] > li { + list-style: none; + display: inline-block; + margin-bottom: 0; +} + +.tabGroup ul[role="tablist"] .dropdown-menu { + /* Make dropdown border overlap tab border */ + margin-top: calc(-1 * var(--nav-tabs-border-width)); + /* Remove the top rounded corners here since there is a hard edge above the menu */ + border-top-left-radius: 0; + border-top-right-radius: 0; +} + +.tabGroup a[role="tab"] { + display: block; + text-decoration: none; + padding: 0.5rem 1rem; + + margin-bottom: calc(-1 * var(--nav-tabs-border-width)); + border: var(--nav-tabs-border-width) solid transparent; + border-top-left-radius: var(--nav-tabs-border-radius); + border-top-right-radius: var(--nav-tabs-border-radius); +} + +.tabGroup a[role="tab"]:hover, .tabGroup a[role="tab"]:focus { + /* Prevents active .nav-link tab overlapping focus outline of previous/next .nav-link */ + isolation: isolate; + border-color: var(--nav-tabs-link-hover-border-color); +} + +.tabGroup a[role="tab"][aria-selected="true"] { + color: var(--nav-tabs-link-active-color); + background-color: var(--nav-tabs-link-active-bg); + border-color: var(--nav-tabs-link-active-border-color); +} + +.tabGroup a[role="tab"]:disabled { + color: var(--nav-link-disabled-color); + background-color: transparent; + border-color: transparent; +} + +.tabGroup section[role="tabpanel"] { + padding: 15px; + margin: 0; + overflow: hidden; + + border: var(--nav-tabs-border-width) solid var(--nav-tabs-border-color); + border-top: none; + border-bottom-left-radius: var(--nav-tabs-border-radius); + border-bottom-right-radius: var(--nav-tabs-border-radius); +} + +.tabGroup section[role="tabpanel"] > .codeHeader, +.tabGroup section[role="tabpanel"] > pre { + margin-left: -16px; + margin-right: -16px; +} + +.tabGroup section[role="tabpanel"] > :first-child { + margin-top: 0; +} + +.tabGroup section[role="tabpanel"] > pre:last-child { + display: block; + margin-bottom: -16px; +} + +.tabGroup > section { + margin: 0; + padding: 1rem; + border-top: 0; + border-top-left-radius: 0; + border-top-right-radius: 0; +} + +.mainContainer[dir='rtl'] main ul[role="tablist"] { + margin: 0; +} \ No newline at end of file diff --git a/documentation/docfx_project/templates/singulinkfx/styles/singulink.js b/documentation/docfx_project/templates/singulinkfx/styles/singulink.js new file mode 100644 index 0000000..40b0d0b --- /dev/null +++ b/documentation/docfx_project/templates/singulinkfx/styles/singulink.js @@ -0,0 +1,51 @@ +// Copyright (c) Microsoft. All rights reserved. Licensed under the MIT license. See LICENSE file in the project root for full license information. + +function toggleMenu() { + + var sidebar = document.getElementById("sidebar"); + var blackout = document.getElementById("blackout"); + + if (sidebar.style.left === "0px") + { + sidebar.style.left = "-" + sidebar.offsetWidth + "px"; + blackout.classList.remove("showThat"); + blackout.classList.add("hideThat"); + } + else + { + sidebar.style.left = "0px"; + blackout.classList.remove("hideThat"); + blackout.classList.add("showThat"); + } +} + +$(function() { + $('table').each(function(a, tbl) { + var currentTableRows = $(tbl).find('tbody tr').length; + $(tbl).find('th').each(function(i) { + var remove = 0; + var currentTable = $(this).parents('table'); + + var tds = currentTable.find('tr td:nth-child(' + (i + 1) + ')'); + tds.each(function(j) { if ($(this).text().trim() === '') remove++; }); + + if (remove == currentTableRows) { + $(this).hide(); + tds.hide(); + } + }); + }); + + function scrollToc() { + var activeTocItem = $('.sidebar .sidebar-item.active:last')[0] + + if (activeTocItem) { + activeTocItem.scrollIntoView({ block: "center" }); + } + else{ + setTimeout(scrollToc, 500); + } + } + + setTimeout(scrollToc, 500); +}); \ No newline at end of file diff --git a/documentation/docfx_project/templates/singulinkfx/styles/url.min.js b/documentation/docfx_project/templates/singulinkfx/styles/url.min.js new file mode 100644 index 0000000..8057e0a --- /dev/null +++ b/documentation/docfx_project/templates/singulinkfx/styles/url.min.js @@ -0,0 +1 @@ +/*! url - v1.8.6 - 2013-11-22 */window.url=function(){function a(a){return!isNaN(parseFloat(a))&&isFinite(a)}return function(b,c){var d=c||window.location.toString();if(!b)return d;b=b.toString(),"//"===d.substring(0,2)?d="http:"+d:1===d.split("://").length&&(d="http://"+d),c=d.split("/");var e={auth:""},f=c[2].split("@");1===f.length?f=f[0].split(":"):(e.auth=f[0],f=f[1].split(":")),e.protocol=c[0],e.hostname=f[0],e.port=f[1]||("https"===e.protocol.split(":")[0].toLowerCase()?"443":"80"),e.pathname=(c.length>3?"/":"")+c.slice(3,c.length).join("/").split("?")[0].split("#")[0];var g=e.pathname;"/"===g.charAt(g.length-1)&&(g=g.substring(0,g.length-1));var h=e.hostname,i=h.split("."),j=g.split("/");if("hostname"===b)return h;if("domain"===b)return/^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])$/.test(h)?h:i.slice(-2).join(".");if("sub"===b)return i.slice(0,i.length-2).join(".");if("port"===b)return e.port;if("protocol"===b)return e.protocol.split(":")[0];if("auth"===b)return e.auth;if("user"===b)return e.auth.split(":")[0];if("pass"===b)return e.auth.split(":")[1]||"";if("path"===b)return e.pathname;if("."===b.charAt(0)){if(b=b.substring(1),a(b))return b=parseInt(b,10),i[0>b?i.length+b:b-1]||""}else{if(a(b))return b=parseInt(b,10),j[0>b?j.length+b:b]||"";if("file"===b)return j.slice(-1)[0];if("filename"===b)return j.slice(-1)[0].split(".")[0];if("fileext"===b)return j.slice(-1)[0].split(".")[1]||"";if("?"===b.charAt(0)||"#"===b.charAt(0)){var k=d,l=null;if("?"===b.charAt(0)?k=(k.split("?")[1]||"").split("#")[0]:"#"===b.charAt(0)&&(k=k.split("#")[1]||""),!b.charAt(1))return k;b=b.substring(1),k=k.split("&");for(var m=0,n=k.length;n>m;m++)if(l=k[m].split("="),l[0]===b)return l[1]||"";return null}}return""}}(),"undefined"!=typeof jQuery&&jQuery.extend({url:function(a,b){return window.url(a,b)}}); \ No newline at end of file diff --git a/documentation/docfx_project/templates/singulinkfx/toc.html.tmpl b/documentation/docfx_project/templates/singulinkfx/toc.html.tmpl new file mode 100644 index 0000000..6549e62 --- /dev/null +++ b/documentation/docfx_project/templates/singulinkfx/toc.html.tmpl @@ -0,0 +1,22 @@ +{{!Copyright (c) Microsoft. All rights reserved. Licensed under the MIT license. See LICENSE file in the project root for full license information.}} + +
    +
    + {{^_disableSideFilter}} +
    +
    + + + +
    +
    + {{/_disableSideFilter}} +
    +
    + {{^leaf}} + {{>partials/li}} + {{/leaf}} +
    +
    +
    +
    \ No newline at end of file diff --git a/documentation/docfx_project/toc.yml b/documentation/docfx_project/toc.yml new file mode 100644 index 0000000..59f8010 --- /dev/null +++ b/documentation/docfx_project/toc.yml @@ -0,0 +1,5 @@ +- name: Articles + href: articles/ +- name: Api Documentation + href: api/ + homepage: api/index.md diff --git a/src/UtilityVerse/Extensions/DateTimeExtension.cs b/src/UtilityVerse/Extensions/DateTimeExtension.cs index ae99970..f2b10e9 100644 --- a/src/UtilityVerse/Extensions/DateTimeExtension.cs +++ b/src/UtilityVerse/Extensions/DateTimeExtension.cs @@ -50,6 +50,7 @@ public static bool IsInBetween(this DateTime dt, DateTime startDateTime, DateTim /// This method will convert date time object into UNIX time stamp /// /// + /// /// public static long ToUnixTimeStamp(this DateTime? dt, TimeEnum timeEnum) { diff --git a/src/UtilityVerse/UtilityVerse.csproj b/src/UtilityVerse/UtilityVerse.csproj index 15bb4ec..8d132be 100644 --- a/src/UtilityVerse/UtilityVerse.csproj +++ b/src/UtilityVerse/UtilityVerse.csproj @@ -20,7 +20,7 @@ Copyright (c) Pritom Purkayasta All rights reserved. FREE TO USE TO CONNECT THE WORLD - false + true TheUtilityVerse utility-verse.png nuget.md From 26d6a90b2e1b3ce532162369cd53cea4368f1415 Mon Sep 17 00:00:00 2001 From: purkayasta Date: Sat, 17 Jun 2023 20:39:36 +0600 Subject: [PATCH 02/20] docfx removed --- documentation/docfx_project/.gitignore | 9 - documentation/docfx_project/articles/intro.md | 1 - documentation/docfx_project/articles/toc.yml | 2 - documentation/docfx_project/docfx.json | 54 -- documentation/docfx_project/index.md | 4 - .../templates/singulinkfx/layout/_master.tmpl | 72 -- .../singulinkfx/partials/footer.tmpl.partial | 4 - .../singulinkfx/partials/head.tmpl.partial | 24 - .../singulinkfx/partials/li.tmpl.partial | 31 - .../singulinkfx/partials/logo.tmpl.partial | 6 - .../partials/namespace.tmpl.partial | 13 - .../singulinkfx/partials/navbar.tmpl.partial | 19 - .../singulinkfx/partials/scripts.tmpl.partial | 13 - .../partials/searchResults.tmpl.partial | 9 - .../singulinkfx/partials/toc.tmpl.partial | 5 - .../templates/singulinkfx/styles/config.css | 124 ---- .../templates/singulinkfx/styles/discord.css | 680 ------------------ .../singulinkfx/styles/down-arrow.svg | 44 -- .../styles/jquery.twbsPagination.js | 317 -------- .../styles/jquery.twbsPagination.min.js | 8 - .../templates/singulinkfx/styles/main.css | 0 .../templates/singulinkfx/styles/main.js | 0 .../singulinkfx/styles/singulink.css | 572 --------------- .../templates/singulinkfx/styles/singulink.js | 51 -- .../templates/singulinkfx/styles/url.min.js | 1 - .../templates/singulinkfx/toc.html.tmpl | 22 - documentation/docfx_project/toc.yml | 5 - 27 files changed, 2090 deletions(-) delete mode 100644 documentation/docfx_project/.gitignore delete mode 100644 documentation/docfx_project/articles/intro.md delete mode 100644 documentation/docfx_project/articles/toc.yml delete mode 100644 documentation/docfx_project/docfx.json delete mode 100644 documentation/docfx_project/index.md delete mode 100644 documentation/docfx_project/templates/singulinkfx/layout/_master.tmpl delete mode 100644 documentation/docfx_project/templates/singulinkfx/partials/footer.tmpl.partial delete mode 100644 documentation/docfx_project/templates/singulinkfx/partials/head.tmpl.partial delete mode 100644 documentation/docfx_project/templates/singulinkfx/partials/li.tmpl.partial delete mode 100644 documentation/docfx_project/templates/singulinkfx/partials/logo.tmpl.partial delete mode 100644 documentation/docfx_project/templates/singulinkfx/partials/namespace.tmpl.partial delete mode 100644 documentation/docfx_project/templates/singulinkfx/partials/navbar.tmpl.partial delete mode 100644 documentation/docfx_project/templates/singulinkfx/partials/scripts.tmpl.partial delete mode 100644 documentation/docfx_project/templates/singulinkfx/partials/searchResults.tmpl.partial delete mode 100644 documentation/docfx_project/templates/singulinkfx/partials/toc.tmpl.partial delete mode 100644 documentation/docfx_project/templates/singulinkfx/styles/config.css delete mode 100644 documentation/docfx_project/templates/singulinkfx/styles/discord.css delete mode 100644 documentation/docfx_project/templates/singulinkfx/styles/down-arrow.svg delete mode 100644 documentation/docfx_project/templates/singulinkfx/styles/jquery.twbsPagination.js delete mode 100644 documentation/docfx_project/templates/singulinkfx/styles/jquery.twbsPagination.min.js delete mode 100644 documentation/docfx_project/templates/singulinkfx/styles/main.css delete mode 100644 documentation/docfx_project/templates/singulinkfx/styles/main.js delete mode 100644 documentation/docfx_project/templates/singulinkfx/styles/singulink.css delete mode 100644 documentation/docfx_project/templates/singulinkfx/styles/singulink.js delete mode 100644 documentation/docfx_project/templates/singulinkfx/styles/url.min.js delete mode 100644 documentation/docfx_project/templates/singulinkfx/toc.html.tmpl delete mode 100644 documentation/docfx_project/toc.yml diff --git a/documentation/docfx_project/.gitignore b/documentation/docfx_project/.gitignore deleted file mode 100644 index b6af25f..0000000 --- a/documentation/docfx_project/.gitignore +++ /dev/null @@ -1,9 +0,0 @@ -############### -# folder # -############### -/**/DROP/ -/**/TEMP/ -/**/packages/ -/**/bin/ -/**/obj/ - diff --git a/documentation/docfx_project/articles/intro.md b/documentation/docfx_project/articles/intro.md deleted file mode 100644 index c0478ce..0000000 --- a/documentation/docfx_project/articles/intro.md +++ /dev/null @@ -1 +0,0 @@ -# Add your introductions here! diff --git a/documentation/docfx_project/articles/toc.yml b/documentation/docfx_project/articles/toc.yml deleted file mode 100644 index ff89ef1..0000000 --- a/documentation/docfx_project/articles/toc.yml +++ /dev/null @@ -1,2 +0,0 @@ -- name: Introduction - href: intro.md diff --git a/documentation/docfx_project/docfx.json b/documentation/docfx_project/docfx.json deleted file mode 100644 index d8fb536..0000000 --- a/documentation/docfx_project/docfx.json +++ /dev/null @@ -1,54 +0,0 @@ -{ - "metadata": [ - { - "src": [ - { - "files": [ - "**.csproj" - ], - "src": "C:\\Users\\pritom\\source\\repos\\TheUtilityVerse\\src" - } - ], - "dest": "api", - "includePrivateMembers": false, - "disableGitFeatures": false, - "disableDefaultFilter": false, - "noRestore": false, - "namespaceLayout": "flattened", - "memberLayout": "samePage", - "allowCompilationErrors": false - } - ], - "build": { - "content": [ - { - "files": [ - "api/**.yml", - "api/index.md" - ] - }, - { - "files": [ - "articles/**.md", - "articles/**/toc.yml", - "toc.yml", - "*.md" - ] - } - ], - "resource": [ - { - "files": [ - "images/**" - ] - } - ], - "output": "_site", - "globalMetadataFiles": [], - "fileMetadataFiles": [], - "template": ["default", "templates/singulinkfx"], - "postProcessors": [], - "keepFileLink": false, - "disableGitFeatures": false - } -} \ No newline at end of file diff --git a/documentation/docfx_project/index.md b/documentation/docfx_project/index.md deleted file mode 100644 index 3ae2506..0000000 --- a/documentation/docfx_project/index.md +++ /dev/null @@ -1,4 +0,0 @@ -# This is the **HOMEPAGE**. -Refer to [Markdown](http://daringfireball.net/projects/markdown/) for how to write markdown files. -## Quick Start Notes: -1. Add images to the *images* folder if the file is referencing an image. diff --git a/documentation/docfx_project/templates/singulinkfx/layout/_master.tmpl b/documentation/docfx_project/templates/singulinkfx/layout/_master.tmpl deleted file mode 100644 index 8e43309..0000000 --- a/documentation/docfx_project/templates/singulinkfx/layout/_master.tmpl +++ /dev/null @@ -1,72 +0,0 @@ -{{!Copyright (c) Microsoft. All rights reserved. Licensed under the MIT license. See LICENSE file in the project root for full license information.}} -{{!include(/^styles/.*/)}} -{{!include(/^fonts/.*/)}} -{{!include(favicon.ico)}} -{{!include(logo.svg)}} -{{!include(search-stopwords.json)}} - - - - {{>partials/head}} - - -
    - - - - - {{>partials/logo}} -
    - -
    -
    - - - -
    - {{#_enableSearch}} - {{>partials/searchResults}} - {{/_enableSearch}} - - - - - - {{#_copyrightFooter}} - - {{/_copyrightFooter}} -
    -
    - - {{>partials/scripts}} - - diff --git a/documentation/docfx_project/templates/singulinkfx/partials/footer.tmpl.partial b/documentation/docfx_project/templates/singulinkfx/partials/footer.tmpl.partial deleted file mode 100644 index dd601a9..0000000 --- a/documentation/docfx_project/templates/singulinkfx/partials/footer.tmpl.partial +++ /dev/null @@ -1,4 +0,0 @@ - \ No newline at end of file diff --git a/documentation/docfx_project/templates/singulinkfx/partials/head.tmpl.partial b/documentation/docfx_project/templates/singulinkfx/partials/head.tmpl.partial deleted file mode 100644 index 2034340..0000000 --- a/documentation/docfx_project/templates/singulinkfx/partials/head.tmpl.partial +++ /dev/null @@ -1,24 +0,0 @@ -{{!Copyright (c) Microsoft. All rights reserved. Licensed under the MIT license. See LICENSE file in the project root for full license information.}} - - - - - {{#title}}{{title}}{{/title}}{{^title}}{{>partials/title}}{{/title}} {{#_appTitle}}| {{_appTitle}} {{/_appTitle}} - - - - {{#_description}}{{/_description}} - - - - - - - - - - - {{#_noindex}}{{/_noindex}} - {{#_enableSearch}}{{/_enableSearch}} - {{#_enableNewTab}}{{/_enableNewTab}} - \ No newline at end of file diff --git a/documentation/docfx_project/templates/singulinkfx/partials/li.tmpl.partial b/documentation/docfx_project/templates/singulinkfx/partials/li.tmpl.partial deleted file mode 100644 index 2c8a3d0..0000000 --- a/documentation/docfx_project/templates/singulinkfx/partials/li.tmpl.partial +++ /dev/null @@ -1,31 +0,0 @@ -{{!Copyright (c) Microsoft. All rights reserved. Licensed under the MIT license. See LICENSE file in the project root for full license information.}} - - diff --git a/documentation/docfx_project/templates/singulinkfx/partials/logo.tmpl.partial b/documentation/docfx_project/templates/singulinkfx/partials/logo.tmpl.partial deleted file mode 100644 index 738ab5b..0000000 --- a/documentation/docfx_project/templates/singulinkfx/partials/logo.tmpl.partial +++ /dev/null @@ -1,6 +0,0 @@ -{{!Copyright (c) Microsoft. All rights reserved. Licensed under the MIT license. See LICENSE file in the project root for full license information.}} - - - {{_appName}} - {{_appName}} - \ No newline at end of file diff --git a/documentation/docfx_project/templates/singulinkfx/partials/namespace.tmpl.partial b/documentation/docfx_project/templates/singulinkfx/partials/namespace.tmpl.partial deleted file mode 100644 index 42d64e6..0000000 --- a/documentation/docfx_project/templates/singulinkfx/partials/namespace.tmpl.partial +++ /dev/null @@ -1,13 +0,0 @@ -{{!Copyright (c) Microsoft. All rights reserved. Licensed under the MIT license. See LICENSE file in the project root for full license information.}} - -

    {{>partials/title}}

    -
    {{{summary}}}
    -
    {{{conceptual}}}
    -
    {{{remarks}}}
    -{{#children}} -

    {{>partials/namespaceSubtitle}}

    - {{#children}} -
    -
    {{{summary}}}
    - {{/children}} -{{/children}} diff --git a/documentation/docfx_project/templates/singulinkfx/partials/navbar.tmpl.partial b/documentation/docfx_project/templates/singulinkfx/partials/navbar.tmpl.partial deleted file mode 100644 index cfddfd8..0000000 --- a/documentation/docfx_project/templates/singulinkfx/partials/navbar.tmpl.partial +++ /dev/null @@ -1,19 +0,0 @@ -{{!Copyright (c) Microsoft. All rights reserved. Licensed under the MIT license. See LICENSE file in the project root for full license information.}} - -
    -
    - {{>partials/logo}} -
    - - {{#_enableSearch}} -
    - -
    - {{/_enableSearch}} - - -
    \ No newline at end of file diff --git a/documentation/docfx_project/templates/singulinkfx/partials/scripts.tmpl.partial b/documentation/docfx_project/templates/singulinkfx/partials/scripts.tmpl.partial deleted file mode 100644 index 8ef7f26..0000000 --- a/documentation/docfx_project/templates/singulinkfx/partials/scripts.tmpl.partial +++ /dev/null @@ -1,13 +0,0 @@ -{{!Copyright (c) Microsoft. All rights reserved. Licensed under the MIT license. See LICENSE file in the project root for full license information.}} - - - - - - - - - - - - \ No newline at end of file diff --git a/documentation/docfx_project/templates/singulinkfx/partials/searchResults.tmpl.partial b/documentation/docfx_project/templates/singulinkfx/partials/searchResults.tmpl.partial deleted file mode 100644 index 9f08c90..0000000 --- a/documentation/docfx_project/templates/singulinkfx/partials/searchResults.tmpl.partial +++ /dev/null @@ -1,9 +0,0 @@ -{{!Copyright (c) Microsoft. All rights reserved. Licensed under the MIT license. See LICENSE file in the project root for full license information.}} - - \ No newline at end of file diff --git a/documentation/docfx_project/templates/singulinkfx/partials/toc.tmpl.partial b/documentation/docfx_project/templates/singulinkfx/partials/toc.tmpl.partial deleted file mode 100644 index c660966..0000000 --- a/documentation/docfx_project/templates/singulinkfx/partials/toc.tmpl.partial +++ /dev/null @@ -1,5 +0,0 @@ -{{!Copyright (c) Microsoft. All rights reserved. Licensed under the MIT license. See LICENSE file in the project root for full license information.}} - -
    -
    -
    diff --git a/documentation/docfx_project/templates/singulinkfx/styles/config.css b/documentation/docfx_project/templates/singulinkfx/styles/config.css deleted file mode 100644 index cb39b51..0000000 --- a/documentation/docfx_project/templates/singulinkfx/styles/config.css +++ /dev/null @@ -1,124 +0,0 @@ -/* Theme Configuration Options */ - -:root -{ - /* General */ - - --base-font-size: 16px; - --smalldevice-base-font-size: 14px; /* Base font size for devices < 1024px */ - - --main-bg-color: #1f1f23; - --footer-bg-color: rgba(0,0,0,.4); - --separator-color: #42474f; - - --table-strip-bg-color: #151515; - --table-header-bg-color: black; - --table-header-color: hsla(0,0%,100%,.8); - --table-header-border-color: #040405; - - /* Text */ - - --appname-color: white; - - --h1-color: white; - --h2-color: #f2f2f2; - --h3-color: #e3e3e3; - --h4-color: #ffffff; - --h5-color: #e0e0e0; - - --text-color: #e1e1e1; - --link-color: #00b0f4; - --link-hover-color: #2ec4ff; - - /* Mobile Topbar */ - - --topbar-bg-color: #18191c; - - /* Button */ - - --button-color: #747f8d; - - /* Sidebar */ - - --sidebar-width: 400px; - --sidebar-bg-color: #292B30; - - --search-color: #bdbdbd; - --search-bg-color: #1b1e21; - --search-searchicon-color: #e3e3e3; - --search-border-color: black; - - --sidebar-item-color: white; - --sidebar-active-item-color: #00b0f4; - --sidebar-level1-item-bg-color: #222429; - --sidebar-level1-item-hover-bg-color: #1D1F22; - - --toc-filter-color: #bdbdbd; - --toc-filter-bg-color: #1b1e21; - --toc-filter-filtericon-color: #e3e3e3; - --toc-filter-clearicon-color: #e68585; - --toc-filter-border-color: black; - - /* Scrollbars */ - - --scrollbar-bg-color: transparent; - --scrollbar-thumb-bg-color: rgba(0,0,0,.4); - --scrollbar-thumb-border-color: transparent; - - /* Alerts and Blocks */ - - --alert-info-border-color: rgba(114,137,218,.5); - --alert-info-bg-color: rgba(114,137,218,.1); - - --alert-warning-border-color: rgba(250,166,26,.5); - --alert-warning-bg-color: rgba(250,166,26,.1); - - --alert-danger-border-color: rgba(240,71,71,.5); - --alert-danger-bg-color: rgba(240,71,71,.1); - - --alert-tip-border-color: rgba(255,255,255,.5); - --alert-tip-bg-color: rgba(255,255,255,.1); - - --blockquote-border-color: rgba(255,255,255,.5); - --blockquote-bg-color: rgba(255,255,255,.1); - - --breadcrumb-bg-color: #2f3136; - - /* Tabs */ - - --nav-tabs-border-width: 1px; - --nav-tabs-border-color: #495057; - --nav-tabs-border-radius: .375rem; - --nav-tabs-link-hover-border-color: #303336 #303336 transparent; - --nav-tabs-link-active-color: white; - --nav-tabs-link-active-bg: var(--main-bg-color); - --nav-tabs-link-active-border-color: var(--nav-tabs-border-color) var(--nav-tabs-border-color) var(--main-bg-color); - - /* Inline Code */ - - --ref-bg-color: black; - --ref-color: #89d4f1; - - /* Code Blocks */ - - --code-bg-color: #151515; - --code-color: #d6deeb; - --code-keyword-color: #569cd6; - --code-comment-color: #57a64a; - --code-macro-color: #beb7ff; - --code-string-color: #d69d85; - --code-string-escape-color: #ffd68f; - --code-field-color: #c8c8c8; - --code-function-color: #dcdcaa; - --code-control-color: #d8a0df; - --code-class-color: #4ec9b0; - --code-number-color: #b5cea8; - --code-params-color: #9a9a9a; - --code-breakpoint-color: #8c2f2f; -} - -/* Code Block Overrides */ - -pre, legend { - --scrollbar-thumb-bg-color: #333; -} \ No newline at end of file diff --git a/documentation/docfx_project/templates/singulinkfx/styles/discord.css b/documentation/docfx_project/templates/singulinkfx/styles/discord.css deleted file mode 100644 index fd53fc7..0000000 --- a/documentation/docfx_project/templates/singulinkfx/styles/discord.css +++ /dev/null @@ -1,680 +0,0 @@ -/* Discord Style */ - -::-webkit-scrollbar { - width: 10px; -} - -::-webkit-scrollbar-track { - background: var(--scrollbar-bg-color); -} - -::-webkit-scrollbar-thumb { - background: var(--scrollbar-thumb-bg-color); - border-color: var(--scrollbar-thumb-border-color); - border-radius: 5px; -} - -::marker { - unicode-bidi: isolate; - font-variant-numeric: tabular-nums; - text-transform: none; - text-indent: 0px !important; - text-align: start !important; - text-align-last: start !important; -} - -*, :after, :before -{ - -webkit-box-sizing: border-box; - -moz-box-sizing: border-box; - box-sizing: border-box; -} - -html, body -{ - padding: 0; - margin: 0; - font: 15px/150% 'Roboto', sans-serif; - overflow: hidden; - color: var(--text-color); - background-color: var(--main-bg-color); - - position: absolute; - top: 0; - bottom: 0; - left: 0; - right: 0; -} - -img { - max-width: 100%; -} - -ul > li, ol > li { - display: list-item; -} - -h1,h2,h3,h4,h5 -{ - color: var(--link-active-color); - position: relative; -} - -h1, h2 -{ - margin-block-start: 2em; -} - -h3 -{ - margin-block-start: 1em; - font-weight: 300; - font-size: 1.5em; - color: var(--h3-color); - margin-block-start: 3em; -} - -h4 -{ - opacity: 1; - color: var(--h4-color); - font-size: large; - border-bottom: 2px solid var(--separator-color); - margin: 20px 0 0 0; -} - - -h5 { - margin-block-end: .8em; - margin-block-start: 1em; - font-size: .85em; - font-weight: 500; - color: var(--h5-color); -} - -h6 { - font-size: .75em; - margin: 0; -} - -p -{ - font-weight: 400; -} - -ul -{ - position: relative; -} - -ul, ol -{ - padding-inline-start: 3em; -} - -ul.level1 -{ - list-style-type: none; - padding-inline-start: 0; -} - -ul.level2, ul.level3 -{ - padding-inline-start: 1em; - list-style-type: none; - font-size: .9em; -} - -a -{ - color: var(--link-color); - text-decoration: none; - transition: color .25s; -} - -a:focus, a:hover -{ - color: var(--link-hover-color); - text-decoration: underline; -} - -a.anchorjs-link:hover { - text-decoration: none; -} - -a.active, a:active -{ - color: var(--link-active-color); -} - -.body-content -{ - display: flex; - flex-direction: row; - height: 100%; - overflow-x: hidden; - overflow-y: hidden; -} - -.page-title -{ - margin-block-start: 0; -} - -nav -{ - width: 300px; - transition: left .5s ease-out; - position: fixed; - left: -350px; - top: 40px; - bottom: 0; - background-color: var(--sidebar-bg-color); - overflow-y: auto; - - display: flex; - flex-direction: column; - - z-index: 1000; -} - -h1:first-child -{ - margin-block-start: 1.1em; - margin-top: 1.1em; -} - -.sidebar -{ - padding: 32px 17px 32px 32px; - flex: 1; -} - -.sidebar-item -{ - font-size: 1em; - font-weight: 400; - display: block; - padding: 4px 16px; - color: var(--sidebar-item-color); -} - -.sidebar-item.large, #navbar .sidebar-item -{ - padding: 8px 16px; -} - -a.sidebar-item:hover, a.sidebar-item:focus -{ - color: var(--link-active-color); - text-decoration: none; -} - -a.sidebar-item.active -{ - color: var(--link-active-color); -} - -ul.level1 > li > a.sidebar-item -{ - background-color: transparent; - border-radius: 4px; -} - -#toc ul.level1 > li > a.sidebar-item.active -{ - background-color: var(--link-active-bg-color); -} - -.sidebar-item-separator -{ - height: 2px; - width: 100%; - background-color: var(--separator-color); - margin: 2em 0; - opacity: .8; -} - -span.sidebar-item -{ - font-weight: 700; - text-transform: uppercase; - font-size: .8em; - color: var(--text-color); - margin-block-start: 1.25em; -} - -.main-panel -{ - background-color: var(--main-bg-color); - flex: 1; - overflow-y: auto; - padding: 20px 40px; -} - -.top-navbar -{ - display: flex; - flex-direction: row; - align-items: center; - padding: 0 40px; - height: 40px; - background-color: var(--topbar-bg-color); -} - -.burger-icon -{ - margin-right: 1em; - color: var(--button-color); -} - -.burger-icon:hover, .burger-icon:focus -{ - color: var(--link-active-color); -} - -.burger-icon.active, .burger-icon:active -{ - color: var(--link-active-color); -} - -.brand -{ - display: flex; - align-items: center; - justify-content: start; -} - -.logomark -{ - height: 28px; -} - -.brand-title -{ - padding: 0 .5em; - font-size: .9em; - color: var(--link-active-color); -} - -.footer -{ - background-color: var(--footer-bg-color); - padding: 20px; - margin: 0 20px 20px 20px; - border-radius: 8px; - color: var(--link-active-color); -} - -.footer > h4 -{ - margin-block-start: 0; -} - -.blackout -{ - display: block; - visibility: hidden; - position: absolute; - z-index: 100; - top: 40px; - bottom: 0; - left: 0; - right: 0; - background-color: var(--footer-bg-color); -} - -@keyframes showThat { - 0% { opacity: 0; visibility: hidden; } - 1% { opacity: 0; visibility: visible; } - 100% { opacity: 1; visibility: visible;} -} - -@keyframes hideThat { - 0% { opacity: 1; visibility: visible; } - 99% { opacity: 0; visibility: visible; } - 100% { opacity: 0; visibility: hidden;} -} - -.showThat -{ - animation: showThat .5s forwards; -} - -.hideThat -{ - animation: hideThat .5s forwards; -} - - - -@media (min-width: 1024px) -{ - nav - { - position: relative; - left: 0!important; - top: 0; - bottom: 0; - } - - .top-navbar - { - display: none; - } - - .blackout - { - display: none; - } -} - -/* Table */ - -.table-responsive -{ - overflow-x: auto; - margin-bottom: 64px; -} - -table -{ - background-color: var(--code-bg-color); - border-collapse: collapse; - width: 100%; - table-layout: auto; -} - -table.table-striped tbody tr:nth-child(2n) -{ - background-color: var(--table-strip-bg-color); -} - -table thead -{ - background: var(--table-header-bg-color); -} - -table th -{ - color: var(--table-header-color); - text-transform: uppercase; - font-size: 12px; - line-height: 15px; - border-bottom: 1px solid var(--table-header-border-color); - padding: 8px; -} - -.table-condensed th { - text-align: left; -} - -table td -{ - padding: 8px; - font-weight: 300; -} - -table td > p -{ - margin: 0; -} - -/* Alerts */ -.alert { - border-radius: 4px; - padding: 8px; - margin: 25px 0; -} - -.alert > h5 -{ - display: none; - margin: 0; -} - -.alert > p -{ - margin: 0; - font-weight: 300; - font-size: 13px; -} - -.alert.alert-info -{ - border: 2px solid var(--alert-info-border-color); - background: var(--alert-info-bg-color); -} - -.alert.alert-warning -{ - border: 2px solid var(--alert-warning-border-color); - background: var(--alert-warning-bg-color); -} - -.alert.alert-danger -{ - border: 2px solid var(--alert-danger-border-color); - background: var(--alert-danger-bg-color); -} - -.TIP.alert.alert-info -{ - border: 2px solid var(--alert-tip-border-color); - background: var(--alert-tip-bg-color); -} - -blockquote { - margin: 8px 0; - border-left: 4px solid var(--blockquote-border-color); - padding: 8px; - background: var(--blockquote-bg-color); - border-radius: 4px; -} - -blockquote > p { - margin: 0; - font-style: italic; - font-size: 13px; -} - - -/* Breadcrumb */ - -#breadcrumb -{ - padding: 8px 16px; - background: var(--breadcrumb-bg-color); - border-radius: 4px; - margin-bottom: 30px; -} - -#breadcrumb:empty -{ - display: none; -} - -ul.breadcrumb -{ - display: flex; - flex-direction: row; - margin: 0; -} - -ul.breadcrumb > li { - margin-right: 6px; -} - -ul.breadcrumb > li::before -{ - content: "/"; - margin-right: 5px; -} - -ul.breadcrumb > li:first-child::before -{ - content: ""; - margin: 0; -} - - -/* Code */ - -legend, pre -{ - display: block; - background-color: var(--code-bg-color); - padding: 16px; - border-radius: 4px; -} - -code -{ - background-color: var(--code-bg-color); - padding: 2px 4px; - border-radius: 4px; -} - -.hljs -{ - background: transparent; -} - -/* DocFX related */ - -.small { - font-size: .9em; -} - -.pull-right -{ - float: right; -} - -.hide -{ - display: none; -} - -@media (max-width: 1023.98px) -{ - .mobile-hide - { - display: none; - } -} - -li -{ - display: block; - position: relative; -} - -.expand-stub -{ - cursor: pointer; - position: absolute; - width: 20px; - height: 20px; - left: -10px; -} - -ul.level1 > li > .expand-stub -{ - display: none; -} - -.toc .nav > li > .expand-stub::before, .toc .nav > li.active > .expand-stub::before -{ - content: " "; - position: absolute; - transform: rotate(-90deg); - width: 10px; - height: 10px; - top: 5px; - left: 5px; - background-repeat: no-repeat; - background: url(down-arrow.svg); -} - -.toc .nav > li.active > .expand-stub::before, .toc .nav > li.in > .expand-stub::before, .toc .nav > li.in.active > .expand-stub::before, .toc .nav > li.filtered > .expand-stub::before -{ - transform: none; -} - -li > ul -{ - display: none; -} - -li.in > ul -{ - display: block; -} - -ul.level2 > li > a.sidebar-item, -ul.level3 > li > a.sidebar-item -{ - font-size: .95em; - padding: 0; - margin: 2px 16px; -} - -ul.level2 > li > a.sidebar-item -{ - color: var(--sidebar-item-2nd-color); -} - -ul.level3 > li > a.sidebar-item -{ - color: var(--sidebar-item-3rd-color); -} - -ul.level2 > li > a.sidebar-item:hover, -ul.level2 > li > a.sidebar-item:focus, -ul.level3 > li > a.sidebar-item:hover, -ul.level3 > li > a.sidebar-item:focus -{ - color: var(--link-active-color); - text-decoration: underline; -} - -ul.level2 > li > a.sidebar-item.active, -ul.level3 > li > a.sidebar-item.active -{ - color: var(--link-active-color); -} - -.inheritance .level0:before, -.inheritance .level1:before, -.inheritance .level2:before, -.inheritance .level3:before, -.inheritance .level4:before, -.inheritance .level5:before { - content: '↳'; - margin-right: 5px; -} - -.inheritance .level0 { - margin-left: 0em; -} - -.inheritance .level1 { - margin-left: 1em; -} - -.inheritance .level2 { - margin-left: 2em; -} - -.inheritance .level3 { - margin-left: 3em; -} - -.inheritance .level4 { - margin-left: 4em; -} - -.inheritance .level5 { - margin-left: 5em; -} \ No newline at end of file diff --git a/documentation/docfx_project/templates/singulinkfx/styles/down-arrow.svg b/documentation/docfx_project/templates/singulinkfx/styles/down-arrow.svg deleted file mode 100644 index e086126..0000000 --- a/documentation/docfx_project/templates/singulinkfx/styles/down-arrow.svg +++ /dev/null @@ -1,44 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/documentation/docfx_project/templates/singulinkfx/styles/jquery.twbsPagination.js b/documentation/docfx_project/templates/singulinkfx/styles/jquery.twbsPagination.js deleted file mode 100644 index 332c01c..0000000 --- a/documentation/docfx_project/templates/singulinkfx/styles/jquery.twbsPagination.js +++ /dev/null @@ -1,317 +0,0 @@ -/*! - * jQuery pagination plugin v1.4.1 - * http://esimakin.github.io/twbs-pagination/ - * - * Copyright 2014-2016, Eugene Simakin - * Released under Apache 2.0 license - * http://apache.org/licenses/LICENSE-2.0.html - */ -(function ($, window, document, undefined) { - - 'use strict'; - - var old = $.fn.twbsPagination; - - // PROTOTYPE AND CONSTRUCTOR - - var TwbsPagination = function (element, options) { - this.$element = $(element); - this.options = $.extend({}, $.fn.twbsPagination.defaults, options); - - if (this.options.startPage < 1 || this.options.startPage > this.options.totalPages) { - throw new Error('Start page option is incorrect'); - } - - this.options.totalPages = parseInt(this.options.totalPages); - if (isNaN(this.options.totalPages)) { - throw new Error('Total pages option is not correct!'); - } - - this.options.visiblePages = parseInt(this.options.visiblePages); - if (isNaN(this.options.visiblePages)) { - throw new Error('Visible pages option is not correct!'); - } - - if (this.options.onPageClick instanceof Function) { - this.$element.first().on('page', this.options.onPageClick); - } - - // hide if only one page exists - if (this.options.hideOnlyOnePage && this.options.totalPages == 1) { - this.$element.trigger('page', 1); - return this; - } - - if (this.options.totalPages < this.options.visiblePages) { - this.options.visiblePages = this.options.totalPages; - } - - if (this.options.href) { - this.options.startPage = this.getPageFromQueryString(); - if (!this.options.startPage) { - this.options.startPage = 1; - } - } - - var tagName = (typeof this.$element.prop === 'function') ? - this.$element.prop('tagName') : this.$element.attr('tagName'); - - if (tagName === 'UL') { - this.$listContainer = this.$element; - } else { - this.$listContainer = $('
      '); - } - - this.$listContainer.addClass(this.options.paginationClass); - - if (tagName !== 'UL') { - this.$element.append(this.$listContainer); - } - - if (this.options.initiateStartPageClick) { - this.show(this.options.startPage); - } else { - this.render(this.getPages(this.options.startPage)); - this.setupEvents(); - } - - return this; - }; - - TwbsPagination.prototype = { - - constructor: TwbsPagination, - - destroy: function () { - this.$element.empty(); - this.$element.removeData('twbs-pagination'); - this.$element.off('page'); - - return this; - }, - - show: function (page) { - if (page < 1 || page > this.options.totalPages) { - throw new Error('Page is incorrect.'); - } - this.currentPage = page; - - this.render(this.getPages(page)); - this.setupEvents(); - - this.$element.trigger('page', page); - - return this; - }, - - buildListItems: function (pages) { - var listItems = []; - - if (this.options.first) { - listItems.push(this.buildItem('first', 1)); - } - - if (this.options.prev) { - var prev = pages.currentPage > 1 ? pages.currentPage - 1 : this.options.loop ? this.options.totalPages : 1; - listItems.push(this.buildItem('prev', prev)); - } - - for (var i = 0; i < pages.numeric.length; i++) { - listItems.push(this.buildItem('page', pages.numeric[i])); - } - - if (this.options.next) { - var next = pages.currentPage < this.options.totalPages ? pages.currentPage + 1 : this.options.loop ? 1 : this.options.totalPages; - listItems.push(this.buildItem('next', next)); - } - - if (this.options.last) { - listItems.push(this.buildItem('last', this.options.totalPages)); - } - - return listItems; - }, - - buildItem: function (type, page) { - var $itemContainer = $('
    • '), - $itemContent = $(''), - itemText = this.options[type] ? this.makeText(this.options[type], page) : page; - - $itemContainer.addClass(this.options[type + 'Class']); - $itemContainer.data('page', page); - $itemContainer.data('page-type', type); - $itemContainer.append($itemContent.attr('href', this.makeHref(page)).addClass(this.options.anchorClass).html(itemText)); - - return $itemContainer; - }, - - getPages: function (currentPage) { - var pages = []; - - var half = Math.floor(this.options.visiblePages / 2); - var start = currentPage - half + 1 - this.options.visiblePages % 2; - var end = currentPage + half; - - // handle boundary case - if (start <= 0) { - start = 1; - end = this.options.visiblePages; - } - if (end > this.options.totalPages) { - start = this.options.totalPages - this.options.visiblePages + 1; - end = this.options.totalPages; - } - - var itPage = start; - while (itPage <= end) { - pages.push(itPage); - itPage++; - } - - return {"currentPage": currentPage, "numeric": pages}; - }, - - render: function (pages) { - var _this = this; - this.$listContainer.children().remove(); - var items = this.buildListItems(pages); - jQuery.each(items, function(key, item){ - _this.$listContainer.append(item); - }); - - this.$listContainer.children().each(function () { - var $this = $(this), - pageType = $this.data('page-type'); - - switch (pageType) { - case 'page': - if ($this.data('page') === pages.currentPage) { - $this.addClass(_this.options.activeClass); - } - break; - case 'first': - $this.toggleClass(_this.options.disabledClass, pages.currentPage === 1); - break; - case 'last': - $this.toggleClass(_this.options.disabledClass, pages.currentPage === _this.options.totalPages); - break; - case 'prev': - $this.toggleClass(_this.options.disabledClass, !_this.options.loop && pages.currentPage === 1); - break; - case 'next': - $this.toggleClass(_this.options.disabledClass, - !_this.options.loop && pages.currentPage === _this.options.totalPages); - break; - default: - break; - } - - }); - }, - - setupEvents: function () { - var _this = this; - this.$listContainer.off('click').on('click', 'li', function (evt) { - var $this = $(this); - if ($this.hasClass(_this.options.disabledClass) || $this.hasClass(_this.options.activeClass)) { - return false; - } - // Prevent click event if href is not set. - !_this.options.href && evt.preventDefault(); - _this.show(parseInt($this.data('page'))); - }); - }, - - makeHref: function (page) { - return this.options.href ? this.generateQueryString(page) : "#"; - }, - - makeText: function (text, page) { - return text.replace(this.options.pageVariable, page) - .replace(this.options.totalPagesVariable, this.options.totalPages) - }, - getPageFromQueryString: function (searchStr) { - var search = this.getSearchString(searchStr), - regex = new RegExp(this.options.pageVariable + '(=([^&#]*)|&|#|$)'), - page = regex.exec(search); - if (!page || !page[2]) { - return null; - } - page = decodeURIComponent(page[2]); - page = parseInt(page); - if (isNaN(page)) { - return null; - } - return page; - }, - generateQueryString: function (pageNumber, searchStr) { - var search = this.getSearchString(searchStr), - regex = new RegExp(this.options.pageVariable + '=*[^&#]*'); - if (!search) return ''; - return '?' + search.replace(regex, this.options.pageVariable + '=' + pageNumber); - - }, - getSearchString: function (searchStr) { - var search = searchStr || window.location.search; - if (search === '') { - return null; - } - if (search.indexOf('?') === 0) search = search.substr(1); - return search; - } - - }; - - // PLUGIN DEFINITION - - $.fn.twbsPagination = function (option) { - var args = Array.prototype.slice.call(arguments, 1); - var methodReturn; - - var $this = $(this); - var data = $this.data('twbs-pagination'); - var options = typeof option === 'object' ? option : {}; - - if (!data) $this.data('twbs-pagination', (data = new TwbsPagination(this, options) )); - if (typeof option === 'string') methodReturn = data[ option ].apply(data, args); - - return ( methodReturn === undefined ) ? $this : methodReturn; - }; - - $.fn.twbsPagination.defaults = { - totalPages: 1, - startPage: 1, - visiblePages: 5, - initiateStartPageClick: true, - hideOnlyOnePage: false, - href: false, - pageVariable: '{{page}}', - totalPagesVariable: '{{total_pages}}', - page: null, - first: 'First', - prev: 'Previous', - next: 'Next', - last: 'Last', - loop: false, - onPageClick: null, - paginationClass: 'pagination', - nextClass: 'page-item next', - prevClass: 'page-item prev', - lastClass: 'page-item last', - firstClass: 'page-item first', - pageClass: 'page-item', - activeClass: 'active', - disabledClass: 'disabled', - anchorClass: 'page-link' - }; - - $.fn.twbsPagination.Constructor = TwbsPagination; - - $.fn.twbsPagination.noConflict = function () { - $.fn.twbsPagination = old; - return this; - }; - - $.fn.twbsPagination.version = "1.4.1"; - -})(window.jQuery, window, document); diff --git a/documentation/docfx_project/templates/singulinkfx/styles/jquery.twbsPagination.min.js b/documentation/docfx_project/templates/singulinkfx/styles/jquery.twbsPagination.min.js deleted file mode 100644 index a9e11c6..0000000 --- a/documentation/docfx_project/templates/singulinkfx/styles/jquery.twbsPagination.min.js +++ /dev/null @@ -1,8 +0,0 @@ -/*! - * jQuery pagination plugin v1.4.1 - * http://esimakin.github.io/twbs-pagination/ - * - * Copyright 2014-2016, Eugene Simakin - * Released under Apache 2.0 license - * http://apache.org/licenses/LICENSE-2.0.html - */ !function(t,s,i,e){"use strict";var a=t.fn.twbsPagination,o=function(s,i){if(this.$element=t(s),this.options=t.extend({},t.fn.twbsPagination.defaults,i),this.options.startPage<1||this.options.startPage>this.options.totalPages)throw Error("Start page option is incorrect");if(this.options.totalPages=parseInt(this.options.totalPages),isNaN(this.options.totalPages))throw Error("Total pages option is not correct!");if(this.options.visiblePages=parseInt(this.options.visiblePages),isNaN(this.options.visiblePages))throw Error("Visible pages option is not correct!");if(this.options.onPageClick instanceof Function&&this.$element.first().on("page",this.options.onPageClick),this.options.hideOnlyOnePage&&1==this.options.totalPages)return this.$element.trigger("page",1),this;this.options.totalPages"),this.$listContainer.addClass(this.options.paginationClass),"UL"!==e&&this.$element.append(this.$listContainer),this.options.initiateStartPageClick?this.show(this.options.startPage):(this.render(this.getPages(this.options.startPage)),this.setupEvents()),this};o.prototype={constructor:o,destroy:function(){return this.$element.empty(),this.$element.removeData("twbs-pagination"),this.$element.off("page"),this},show:function(t){if(t<1||t>this.options.totalPages)throw Error("Page is incorrect.");return this.currentPage=t,this.render(this.getPages(t)),this.setupEvents(),this.$element.trigger("page",t),this},buildListItems:function(t){var s=[];if(this.options.first&&s.push(this.buildItem("first",1)),this.options.prev){var i=t.currentPage>1?t.currentPage-1:this.options.loop?this.options.totalPages:1;s.push(this.buildItem("prev",i))}for(var e=0;e"),a=t(""),o=this.options[s]?this.makeText(this.options[s],i):i;return e.addClass(this.options[s+"Class"]),e.data("page",i),e.data("page-type",s),e.append(a.attr("href",this.makeHref(i)).addClass(this.options.anchorClass).html(o)),e},getPages:function(t){var s=[],i=Math.floor(this.options.visiblePages/2),e=t-i+1-this.options.visiblePages%2,a=t+i;e<=0&&(e=1,a=this.options.visiblePages),a>this.options.totalPages&&(e=this.options.totalPages-this.options.visiblePages+1,a=this.options.totalPages);for(var o=e;o<=a;)s.push(o),o++;return{currentPage:t,numeric:s}},render:function(s){var i=this;this.$listContainer.children().remove();var e=this.buildListItems(s);jQuery.each(e,function(t,s){i.$listContainer.append(s)}),this.$listContainer.children().each(function(){var e=t(this),a=e.data("page-type");switch(a){case"page":e.data("page")===s.currentPage&&e.addClass(i.options.activeClass);break;case"first":e.toggleClass(i.options.disabledClass,1===s.currentPage);break;case"last":e.toggleClass(i.options.disabledClass,s.currentPage===i.options.totalPages);break;case"prev":e.toggleClass(i.options.disabledClass,!i.options.loop&&1===s.currentPage);break;case"next":e.toggleClass(i.options.disabledClass,!i.options.loop&&s.currentPage===i.options.totalPages)}})},setupEvents:function(){var s=this;this.$listContainer.off("click").on("click","li",function(i){var e=t(this);if(e.hasClass(s.options.disabledClass)||e.hasClass(s.options.activeClass))return!1;s.options.href||i.preventDefault(),s.show(parseInt(e.data("page")))})},makeHref:function(t){return this.options.href?this.generateQueryString(t):"#"},makeText:function(t,s){return t.replace(this.options.pageVariable,s).replace(this.options.totalPagesVariable,this.options.totalPages)},getPageFromQueryString:function(t){var s=this.getSearchString(t),i=RegExp(this.options.pageVariable+"(=([^&#]*)|&|#|$)").exec(s);return i&&i[2]?(i=parseInt(i=decodeURIComponent(i[2])),isNaN(i))?null:i:null},generateQueryString:function(t,s){var i=this.getSearchString(s),e=RegExp(this.options.pageVariable+"=*[^&#]*");return i?"?"+i.replace(e,this.options.pageVariable+"="+t):""},getSearchString:function(t){var i=t||s.location.search;return""===i?null:(0===i.indexOf("?")&&(i=i.substr(1)),i)}},t.fn.twbsPagination=function(s){var i,e=Array.prototype.slice.call(arguments,1),a=t(this),n=a.data("twbs-pagination");return n||a.data("twbs-pagination",n=new o(this,"object"==typeof s?s:{})),"string"==typeof s&&(i=n[s].apply(n,e)),void 0===i?a:i},t.fn.twbsPagination.defaults={totalPages:1,startPage:1,visiblePages:5,initiateStartPageClick:!0,hideOnlyOnePage:!1,href:!1,pageVariable:"{{page}}",totalPagesVariable:"{{total_pages}}",page:null,first:"First",prev:"Previous",next:"Next",last:"Last",loop:!1,onPageClick:null,paginationClass:"pagination",nextClass:"page-item next",prevClass:"page-item prev",lastClass:"page-item last",firstClass:"page-item first",pageClass:"page-item",activeClass:"active",disabledClass:"disabled",anchorClass:"page-link"},t.fn.twbsPagination.Constructor=o,t.fn.twbsPagination.noConflict=function(){return t.fn.twbsPagination=a,this},t.fn.twbsPagination.version="1.4.1"}(window.jQuery,window,document); \ No newline at end of file diff --git a/documentation/docfx_project/templates/singulinkfx/styles/main.css b/documentation/docfx_project/templates/singulinkfx/styles/main.css deleted file mode 100644 index e69de29..0000000 diff --git a/documentation/docfx_project/templates/singulinkfx/styles/main.js b/documentation/docfx_project/templates/singulinkfx/styles/main.js deleted file mode 100644 index e69de29..0000000 diff --git a/documentation/docfx_project/templates/singulinkfx/styles/singulink.css b/documentation/docfx_project/templates/singulinkfx/styles/singulink.css deleted file mode 100644 index b478f84..0000000 --- a/documentation/docfx_project/templates/singulinkfx/styles/singulink.css +++ /dev/null @@ -1,572 +0,0 @@ -body { - font-size: var(--base-font-size); -} - -@media (max-width: 1024px) { - body { - font-size: var(--smalldevice-base-font-size); - } -} - -/* Headings */ - -h1, h2, h3, h4, h5 { - line-height: initial; -} - -h1, h1:first-child { - font-size: 2.25em; - letter-spacing: 0.5px; - color: var(--h1-color); - margin-block-start: 1em; - margin-block-end: -0.05em; -} - -.article h1 { - margin-block-end: -0.2em; -} - -h2 { - font-size: 2.1em; - color: var(--h2-color); -} - -.article h2 { - margin-block-start: 1.3em; - padding-bottom: 6px; - border-bottom: 1px solid var(--separator-color); -} - -h3 { - font-size: 1.95em; - font-weight: 500; - margin-block-start: 1.7em; -} - -.article h3 { - font-size: 1.85em; - font-weight: 500; - margin-block-start: 1.2em; - margin-block-end: 0.9em; -} - -h4 { - font-size: 1.8em; - font-weight: 400; - margin-block-start: 2em; - padding-bottom: 10px; -} - -.article h4 { - font-size: 1.5em; - font-weight: 300; - margin-block-start: 1em; - margin-block-end: 1em; - padding-bottom: 0; - border-bottom: none; -} - -h5 { - font-size: 1.1em; -} - -.article h5 { - font-size: 1.13em; - font-weight: 400; - text-decoration: underline; - margin-block-start: 1.5em; - margin-block-end: 1.0em; -} - -a.brand:hover -{ - text-decoration: none; -} - -a.brand .brand-title { - font-size: 1.4em; - font-weight: 500; - letter-spacing: 0.5px; - color: var(--appname-color); - margin-top: 1px; - padding: 0 0 0 0.4em; -} - -@media (min-width: 1024px) { - a.brand .brand-title { - font-size: 1.55em; - } -} - - -a.brand .logomark { - height: 35px; -} - -/* Top bar */ - -.top-navbar { - height: 60px; - padding: 0 0 0 10px; -} - -.burger-icon { - margin-right: 10px; -} - -/* Side Bar */ - -.sidebar { - padding: 25px 17px 32px 17px; -} - -.blackout { - top: 60px; -} - -@media (max-width: 1023.98px) { - .navbar-nav { - margin-top: 0; - } -} - -nav { - width: 94%; - max-width: var(--sidebar-width); - left: calc(var(--sidebar-width) * -1); -} - -@media (max-width: 1023.98px) { - nav { - top: 60px; - } -} - -nav ul { - list-style-type: none; -} - -nav .nav a, nav .nav a:hover { - text-decoration: none; - cursor: pointer; - display: block; -} - -nav a.sidebar-item { - padding: 4px 0 4px 10px; - cursor: pointer; -} - -nav a:focus, nav a.sidebar-item:hover, nav a.sidebar-item:focus { - text-decoration: underline; -} - -nav a, nav a:hover, nav a:focus { - color: var(--sidebar-item-color) !important; -} - -nav a.active, nav a.active:hover, nav a.active:focus { - color: var(--sidebar-active-item-color) !important; -} - -.sidebar-item-separator { - margin: 20px 0; -} - -#toc ul li a { - padding: 0 0 0 10px; -} - -.search { - background: var(--search-bg-color); - border: 1px solid var(--search-border-color); - border-radius: 5px; - position: relative; - margin-block-start: 25px; -} - -@media (max-width: 1023.98px) { - .search { - margin-block-start: 0; - margin-block-end: 15px; - } -} - -.search > input { - font-size: 0.95em; - color: var(--search-color); - border: 0; - background: none; - padding: 11px 30px 10px 37px; - width: 100%; -} - -.search > input:focus { - outline: 0; -} - -.search > .search-icon { - font-size: 1.2em; - color: var(--search-searchicon-color); - position: absolute; - top: 9px; - left: 9px; -} - -.toc-filter { - background: var(--toc-filter-bg-color); - border: 1px solid var(--toc-filter-border-color); - border-radius: 5px; - position: relative; -} - -.toc-filter > input { - font-size: 0.95em; - color: var(--toc-filter-color); - border: 0; - background: none; - padding: 11px 30px 10px 37px; - width: 100%; -} - -.toc-filter > input:focus { - outline: 0; -} - -.toc-filter > .filter-icon { - font-size: 1.2em; - color: var(--toc-filter-filtericon-color); - position: absolute; - top: 9px; - left: 9px; -} - -.toc-filter > .clear-icon { - color: var(--toc-filter-clearicon-color); - position: absolute; - top: 9px; - right: 9px; - cursor: pointer; -} - -.toc .nav > li > .expand-stub::before, .toc .nav > li.active > .expand-stub::before -{ - width: 8px; - height: 8px; - top: 6px; - left: 6px; -} - -#toc ul.level2 -{ - margin-bottom: 20px; -} - -#toc ul.level1 > li > a { - font-weight: 500; - margin-bottom: 10px; - padding: 5px 10px; -} - -#toc ul.level1 > li > a, #toc ul.level1 > li > a.active { - background-color: var(--sidebar-level1-item-bg-color) !important; - border-radius: 2px; -} - -#toc ul.level1 > li > a:hover, #toc ul.level1 > li > a.active:hover, -#toc ul.level1 > li > a:focus, #toc ul.level1 > li > a.active:focus { - background-color: var(--sidebar-level1-item-hover-bg-color) !important; - text-decoration: none; -} - -ul.level2 { - padding-inline-start: 0.7em; -} - -ul.level2 .expand-stub { - top: 1px; -} - -ul.level2 > li > a, ul.level2 > li > a.sidebar-item { - font-weight: 400; - color: var(--sidebar-item-color); - margin: 4px 0 4px; -} - -ul.level3 { - padding-inline-start: 1em; -} - -ul.level3 > li > a, ul.level3 > li > a.sidebar-item { - font-size: 1.05em; - color: var(--sidebar-item-color); - margin: 4px 0; -} - -ul.level4 { - padding-inline-start: 0; - margin-bottom: 12px; -} - -ul.level4 > li > a, ul.level4 > li > a.sidebar-item { - font-size: 1.05em; - color: var(--sidebar-item-color); - margin: 5px 0 5px 10px; -} - -/* Breadcrumbs */ - -.subnav.navbar { - margin: 0 -15px; -} - -#breadcrumb { - overflow: scroll; - margin-bottom: 0; -} - -#breadcrumb::-webkit-scrollbar { - display: none; -} - -#breadcrumb a { - white-space: nowrap; -} - -#breadcrumb wbr { - display: none; -} - -/* Search Results */ - -#search-results h1 { - margin-block-start: 0.5em; -} - -#search-results .item-title { - font-size: 1.3em; - margin-top: 1.5em; -} - -#search-results .item-href { - font-size: 0.85em; -} - -#search-results .item-brief { - margin-top: 0.7em; -} - -#search-results ul.pagination { - text-align: center; - padding: 10px 0 0 0; - margin-block-start: 40px; - border-top: 1px solid var(--separator-color); -} - -#search-results ul.pagination > li { - display: inline-block; - margin: 0 10px; -} - -#search-results ul.pagination > li.disabled a, #search-results ul.pagination > li.disabled a:hover { - color: var(--text-color); - cursor: txt; - text-decoration: none; -} - -/* Content */ - -.main-panel { - margin-bottom: 60px; - padding: 20px; -} - -@media (min-width: 1024px) { - .main-panel { - margin-bottom: 0; - padding: 20px 40px; - } -} - -.pull-right { - margin-top: 70px; - /* Fix unclickable links */ - position: relative; - z-index: 1; -} - -.divider { - margin-left: 4px; -} - -article ul li, article ol li { - margin-bottom: 10px; -} - -legend, pre { - padding: 6px; -} - -.hljs { - color: var(--code-color); -} - -.hljs::-webkit-scrollbar { - height: 6px; -} - -.hljs-keyword, .hljs-title, .hljs-built_in { - font-style: normal; -} - -p .xref, code { - background-color: var(--ref-bg-color); - color: var(--ref-color); - padding: 2px 3px; - font-family: monospace; - font-size: 0.95em; - border-radius: 6px; -} - -span.parametername { - font-family: monospace; -} - -.table { - width: auto; -} - -.table-responsive { - margin-bottom: 0; -} - -table th { - font-size: 14px; - padding: 9px 10px; -} - -table td p { - font-weight: 300; -} - -table td { - padding: 6px 10px; -} - -.footer { - text-align: center; - color: var(--text-color); - padding: 10px; -} - -.footer a:hover, .footer a:focus { - text-decoration: underline; -} - -.copyright-footer { - font-size: 0.85em; - font-weight: bold; - text-align: center; - margin-block-start: 30px; -} - -#contribution { - float: right; - margin-top: 1.1em; -} - - -/* Tabs */ - -.tabGroup { - margin-top: 1rem; - margin-bottom: 1rem; -} - -.tabGroup ul[role="tablist"] { - margin: 0; - padding: 0; - list-style: none; - border-bottom: var(--nav-tabs-border-width) solid var(--nav-tabs-border-color); -} - -.tabGroup ul[role="tablist"] > li { - list-style: none; - display: inline-block; - margin-bottom: 0; -} - -.tabGroup ul[role="tablist"] .dropdown-menu { - /* Make dropdown border overlap tab border */ - margin-top: calc(-1 * var(--nav-tabs-border-width)); - /* Remove the top rounded corners here since there is a hard edge above the menu */ - border-top-left-radius: 0; - border-top-right-radius: 0; -} - -.tabGroup a[role="tab"] { - display: block; - text-decoration: none; - padding: 0.5rem 1rem; - - margin-bottom: calc(-1 * var(--nav-tabs-border-width)); - border: var(--nav-tabs-border-width) solid transparent; - border-top-left-radius: var(--nav-tabs-border-radius); - border-top-right-radius: var(--nav-tabs-border-radius); -} - -.tabGroup a[role="tab"]:hover, .tabGroup a[role="tab"]:focus { - /* Prevents active .nav-link tab overlapping focus outline of previous/next .nav-link */ - isolation: isolate; - border-color: var(--nav-tabs-link-hover-border-color); -} - -.tabGroup a[role="tab"][aria-selected="true"] { - color: var(--nav-tabs-link-active-color); - background-color: var(--nav-tabs-link-active-bg); - border-color: var(--nav-tabs-link-active-border-color); -} - -.tabGroup a[role="tab"]:disabled { - color: var(--nav-link-disabled-color); - background-color: transparent; - border-color: transparent; -} - -.tabGroup section[role="tabpanel"] { - padding: 15px; - margin: 0; - overflow: hidden; - - border: var(--nav-tabs-border-width) solid var(--nav-tabs-border-color); - border-top: none; - border-bottom-left-radius: var(--nav-tabs-border-radius); - border-bottom-right-radius: var(--nav-tabs-border-radius); -} - -.tabGroup section[role="tabpanel"] > .codeHeader, -.tabGroup section[role="tabpanel"] > pre { - margin-left: -16px; - margin-right: -16px; -} - -.tabGroup section[role="tabpanel"] > :first-child { - margin-top: 0; -} - -.tabGroup section[role="tabpanel"] > pre:last-child { - display: block; - margin-bottom: -16px; -} - -.tabGroup > section { - margin: 0; - padding: 1rem; - border-top: 0; - border-top-left-radius: 0; - border-top-right-radius: 0; -} - -.mainContainer[dir='rtl'] main ul[role="tablist"] { - margin: 0; -} \ No newline at end of file diff --git a/documentation/docfx_project/templates/singulinkfx/styles/singulink.js b/documentation/docfx_project/templates/singulinkfx/styles/singulink.js deleted file mode 100644 index 40b0d0b..0000000 --- a/documentation/docfx_project/templates/singulinkfx/styles/singulink.js +++ /dev/null @@ -1,51 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. Licensed under the MIT license. See LICENSE file in the project root for full license information. - -function toggleMenu() { - - var sidebar = document.getElementById("sidebar"); - var blackout = document.getElementById("blackout"); - - if (sidebar.style.left === "0px") - { - sidebar.style.left = "-" + sidebar.offsetWidth + "px"; - blackout.classList.remove("showThat"); - blackout.classList.add("hideThat"); - } - else - { - sidebar.style.left = "0px"; - blackout.classList.remove("hideThat"); - blackout.classList.add("showThat"); - } -} - -$(function() { - $('table').each(function(a, tbl) { - var currentTableRows = $(tbl).find('tbody tr').length; - $(tbl).find('th').each(function(i) { - var remove = 0; - var currentTable = $(this).parents('table'); - - var tds = currentTable.find('tr td:nth-child(' + (i + 1) + ')'); - tds.each(function(j) { if ($(this).text().trim() === '') remove++; }); - - if (remove == currentTableRows) { - $(this).hide(); - tds.hide(); - } - }); - }); - - function scrollToc() { - var activeTocItem = $('.sidebar .sidebar-item.active:last')[0] - - if (activeTocItem) { - activeTocItem.scrollIntoView({ block: "center" }); - } - else{ - setTimeout(scrollToc, 500); - } - } - - setTimeout(scrollToc, 500); -}); \ No newline at end of file diff --git a/documentation/docfx_project/templates/singulinkfx/styles/url.min.js b/documentation/docfx_project/templates/singulinkfx/styles/url.min.js deleted file mode 100644 index 8057e0a..0000000 --- a/documentation/docfx_project/templates/singulinkfx/styles/url.min.js +++ /dev/null @@ -1 +0,0 @@ -/*! url - v1.8.6 - 2013-11-22 */window.url=function(){function a(a){return!isNaN(parseFloat(a))&&isFinite(a)}return function(b,c){var d=c||window.location.toString();if(!b)return d;b=b.toString(),"//"===d.substring(0,2)?d="http:"+d:1===d.split("://").length&&(d="http://"+d),c=d.split("/");var e={auth:""},f=c[2].split("@");1===f.length?f=f[0].split(":"):(e.auth=f[0],f=f[1].split(":")),e.protocol=c[0],e.hostname=f[0],e.port=f[1]||("https"===e.protocol.split(":")[0].toLowerCase()?"443":"80"),e.pathname=(c.length>3?"/":"")+c.slice(3,c.length).join("/").split("?")[0].split("#")[0];var g=e.pathname;"/"===g.charAt(g.length-1)&&(g=g.substring(0,g.length-1));var h=e.hostname,i=h.split("."),j=g.split("/");if("hostname"===b)return h;if("domain"===b)return/^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])$/.test(h)?h:i.slice(-2).join(".");if("sub"===b)return i.slice(0,i.length-2).join(".");if("port"===b)return e.port;if("protocol"===b)return e.protocol.split(":")[0];if("auth"===b)return e.auth;if("user"===b)return e.auth.split(":")[0];if("pass"===b)return e.auth.split(":")[1]||"";if("path"===b)return e.pathname;if("."===b.charAt(0)){if(b=b.substring(1),a(b))return b=parseInt(b,10),i[0>b?i.length+b:b-1]||""}else{if(a(b))return b=parseInt(b,10),j[0>b?j.length+b:b]||"";if("file"===b)return j.slice(-1)[0];if("filename"===b)return j.slice(-1)[0].split(".")[0];if("fileext"===b)return j.slice(-1)[0].split(".")[1]||"";if("?"===b.charAt(0)||"#"===b.charAt(0)){var k=d,l=null;if("?"===b.charAt(0)?k=(k.split("?")[1]||"").split("#")[0]:"#"===b.charAt(0)&&(k=k.split("#")[1]||""),!b.charAt(1))return k;b=b.substring(1),k=k.split("&");for(var m=0,n=k.length;n>m;m++)if(l=k[m].split("="),l[0]===b)return l[1]||"";return null}}return""}}(),"undefined"!=typeof jQuery&&jQuery.extend({url:function(a,b){return window.url(a,b)}}); \ No newline at end of file diff --git a/documentation/docfx_project/templates/singulinkfx/toc.html.tmpl b/documentation/docfx_project/templates/singulinkfx/toc.html.tmpl deleted file mode 100644 index 6549e62..0000000 --- a/documentation/docfx_project/templates/singulinkfx/toc.html.tmpl +++ /dev/null @@ -1,22 +0,0 @@ -{{!Copyright (c) Microsoft. All rights reserved. Licensed under the MIT license. See LICENSE file in the project root for full license information.}} - -
      -
      - {{^_disableSideFilter}} -
      -
      - - - -
      -
      - {{/_disableSideFilter}} -
      -
      - {{^leaf}} - {{>partials/li}} - {{/leaf}} -
      -
      -
      -
      \ No newline at end of file diff --git a/documentation/docfx_project/toc.yml b/documentation/docfx_project/toc.yml deleted file mode 100644 index 59f8010..0000000 --- a/documentation/docfx_project/toc.yml +++ /dev/null @@ -1,5 +0,0 @@ -- name: Articles - href: articles/ -- name: Api Documentation - href: api/ - homepage: api/index.md From 30e220d8f5216d2ace5428a5dca7f893e92685b4 Mon Sep 17 00:00:00 2001 From: purkayasta Date: Sun, 7 Jan 2024 20:23:47 +0600 Subject: [PATCH 03/20] upgraded to .net 8 and .net standard 2.1 --- .../UtilityVerse.ASPNET.csproj | 5 +- src/UtilityVerse/Contracts/PagedList.cs | 5 +- src/UtilityVerse/Contracts/RetryOption.cs | 5 +- src/UtilityVerse/Contracts/UtilityEnum.cs | 68 +++--- src/UtilityVerse/Extensions/BoolExtension.cs | 36 ++- src/UtilityVerse/Extensions/ByteExtension.cs | 50 +++-- .../Extensions/CharacterExtension.cs | 50 ++--- .../Extensions/CollectionExtension.cs | 211 +++++++++--------- .../Extensions/DateOnlyExtension.cs | 133 +++++------ .../Extensions/DateTimeExtension.cs | 185 +++++++-------- .../Extensions/DecimalExtension.cs | 51 +++-- .../Extensions/DoubleExtension.cs | 49 ++-- .../Extensions/GenericExtension.cs | 30 +-- src/UtilityVerse/Extensions/IntExtension.cs | 104 +++++---- src/UtilityVerse/Extensions/LongExtension.cs | 8 +- .../Extensions/ObjectExtension.cs | 27 ++- .../Extensions/PagedListExtension.cs | 9 +- src/UtilityVerse/Extensions/SpanExtension.cs | 8 +- .../Extensions/StringExtension.cs | 27 ++- .../Helpers/UtilityVerseException.cs | 20 ++ src/UtilityVerse/Utility.Currency.cs | 4 +- src/UtilityVerse/Utility.OS.cs | 54 +++-- src/UtilityVerse/UtilityRetry.cs | 148 ++++++------ src/UtilityVerse/UtilityVerse.csproj | 89 ++++---- .../DateTimeExtensionTest.cs | 62 +---- .../GenericExtensionTest.cs | 8 +- .../UtilityVerse.UnitTest/IntExtensionTest.cs | 26 +-- .../StringExtensionTest.cs | 10 +- .../UtilityVerse.UnitTest.csproj | 50 +++-- 29 files changed, 738 insertions(+), 794 deletions(-) create mode 100644 src/UtilityVerse/Helpers/UtilityVerseException.cs diff --git a/src/UtilityVerse.ASPNET/UtilityVerse.ASPNET.csproj b/src/UtilityVerse.ASPNET/UtilityVerse.ASPNET.csproj index 686bbd0..bcfb76b 100644 --- a/src/UtilityVerse.ASPNET/UtilityVerse.ASPNET.csproj +++ b/src/UtilityVerse.ASPNET/UtilityVerse.ASPNET.csproj @@ -1,14 +1,15 @@  - net6.0 + netstandard2.1; net6.0; net8.0 + Latest enable enable MIT true UtilityVerse.ASPNET - 0.4 + 0.5 utility, utils, C#, dotnet, aspnet, aspnet.core, dotnet core a curated list of utility methods that will help you, do your work at your best. Pritom Purkayasta diff --git a/src/UtilityVerse/Contracts/PagedList.cs b/src/UtilityVerse/Contracts/PagedList.cs index d145d55..cf018be 100644 --- a/src/UtilityVerse/Contracts/PagedList.cs +++ b/src/UtilityVerse/Contracts/PagedList.cs @@ -4,6 +4,9 @@ // --------------------------------------------------------------- +using System.Collections.Generic; +using System.Linq; + namespace UtilityVerse.Contracts; /// @@ -30,5 +33,5 @@ public class PagedList /// /// If there is any error, if found any this error object will be populated. /// - public string? Errors { get; set; } = string.Empty; + public string Errors { get; set; } = string.Empty; } \ No newline at end of file diff --git a/src/UtilityVerse/Contracts/RetryOption.cs b/src/UtilityVerse/Contracts/RetryOption.cs index 576c061..73a40b9 100644 --- a/src/UtilityVerse/Contracts/RetryOption.cs +++ b/src/UtilityVerse/Contracts/RetryOption.cs @@ -3,6 +3,7 @@ // FREE TO USE TO CONNECT THE WORLD // --------------------------------------------------------------- +using System; namespace UtilityVerse.Contracts; @@ -15,10 +16,10 @@ public class RetryOption /// /// When it will retry again. /// - public TimeSpan? Delay { get; set; } = TimeSpan.FromSeconds(1); + public TimeSpan Delay { get; set; } = TimeSpan.FromSeconds(1); /// /// Retry Count. How many time it will try? /// - public int? Count { get; set; } = 1; + public int Count { get; set; } = 1; } diff --git a/src/UtilityVerse/Contracts/UtilityEnum.cs b/src/UtilityVerse/Contracts/UtilityEnum.cs index 1954137..c1e087e 100644 --- a/src/UtilityVerse/Contracts/UtilityEnum.cs +++ b/src/UtilityVerse/Contracts/UtilityEnum.cs @@ -11,15 +11,15 @@ namespace UtilityVerse.Contracts; /// public enum TimeEnum { - /// - /// This represent second - /// - Second, - - /// - /// This represent millisecond - /// - MilliSecond + /// + /// This represent second + /// + Second, + + /// + /// This represent millisecond + /// + MilliSecond } /// @@ -27,28 +27,30 @@ public enum TimeEnum /// public enum EncodingEnum { - /// - /// UTF8 - /// - UTF8, - - /// - /// UTF32 - /// - UTF32, - - /// - /// UNICODE - /// - Unicode, - - /// - /// ASCII - /// - Ascii, - - /// - /// LATIN - /// - Latin + /// + /// UTF8 + /// + UTF8, + + /// + /// UTF32 + /// + UTF32, + + /// + /// UNICODE + /// + Unicode, + + /// + /// ASCII + /// + Ascii, + +#if NETCOREAPP3_1_OR_GREATER + /// + /// LATIN + /// + Latin +#endif } diff --git a/src/UtilityVerse/Extensions/BoolExtension.cs b/src/UtilityVerse/Extensions/BoolExtension.cs index 23c97fb..7dbe6b2 100644 --- a/src/UtilityVerse/Extensions/BoolExtension.cs +++ b/src/UtilityVerse/Extensions/BoolExtension.cs @@ -10,26 +10,22 @@ namespace UtilityVerse.Extensions; /// public static class BoolExtension { - /// - /// This will convert boolean to int - /// - /// - /// - public static int ToInt(this bool? val) - { - if (!val.HasValue) return 0; - return val.Value ? 1 : 0; - } + /// + /// This method will convert 1 or 0 to boolean + /// + /// + /// int + public static int ToInt(this bool value) => value ? 1 : 0; - /// - /// this method will convert boolean value into string - /// - /// - /// - public static string ToStr(this bool? val) - { - if (!val.HasValue) return string.Empty; - return val.Value ? "True" : "False"; - } + + /// + /// this method will convert boolean value into string + /// + /// + /// string + public static string ToStr(this bool val) + { + return val ? "True" : "False"; + } } diff --git a/src/UtilityVerse/Extensions/ByteExtension.cs b/src/UtilityVerse/Extensions/ByteExtension.cs index 1628cfb..d847701 100644 --- a/src/UtilityVerse/Extensions/ByteExtension.cs +++ b/src/UtilityVerse/Extensions/ByteExtension.cs @@ -3,7 +3,8 @@ // FREE TO USE TO CONNECT THE WORLD // --------------------------------------------------------------- -using System.Text.Json; +using System; +using UtilityVerse.Helpers; namespace UtilityVerse.Extensions; @@ -12,26 +13,31 @@ namespace UtilityVerse.Extensions; /// public static class ByteExtension { - /// - /// This method will convert to an object from byte array - /// - /// - /// - public static object? ToObject(this byte[]? byteArr) - { - ArgumentNullException.ThrowIfNull(byteArr); - return JsonSerializer.Deserialize(byteArr); - } +#if NETCOREAPP3_1_OR_GREATER + /// + /// This method will convert to an object from byte array + /// + /// + /// object + public static object ToObject(this byte[] byteArr) + { + UtilityVerseException.ThrowIfNull(byteArr); + return System.Text.Json.JsonSerializer.Deserialize(byteArr); + } +#endif - /// - /// This method will convert byte array into desired model. - /// - /// - /// - /// - public static T? To(this byte[]? byteArr) - { - ArgumentNullException.ThrowIfNull(byteArr); - return JsonSerializer.Deserialize(byteArr); - } +#if NETCOREAPP3_1_OR_GREATER + + /// + /// This method will convert byte array into desired model. + /// + /// + /// + /// T + public static T To(this byte[] byteArr) + { + UtilityVerseException.ThrowIfNull(byteArr); + return System.Text.Json.JsonSerializer.Deserialize(byteArr); + } +#endif } diff --git a/src/UtilityVerse/Extensions/CharacterExtension.cs b/src/UtilityVerse/Extensions/CharacterExtension.cs index 14196d0..aeb0c70 100644 --- a/src/UtilityVerse/Extensions/CharacterExtension.cs +++ b/src/UtilityVerse/Extensions/CharacterExtension.cs @@ -3,8 +3,6 @@ // FREE TO USE TO CONNECT THE WORLD // --------------------------------------------------------------- -using System.Text; - namespace UtilityVerse.Extensions; /// @@ -12,28 +10,28 @@ namespace UtilityVerse.Extensions; /// public static class CharacterExtension { - /// - /// This extension method will convert any double array into string with separator. - /// - /// - /// - /// - public static string ToStr(this char[]? characterArray, string? separator = null) - { - if (characterArray is null || characterArray.Length < 1) return string.Empty; - - var sb = new StringBuilder(); - - bool isSeparatorExist = !string.IsNullOrEmpty(separator); - - for (int index = 0; index < characterArray.Length; index++) - { - sb.Append(characterArray[index]); - - if (isSeparatorExist && index < characterArray.Length - 1) - sb.Append(separator); - } - - return sb.ToString(); - } + /// + /// This extension method will convert any double array into string with separator. + /// + /// + /// + /// string + public static string ToStr(this char[] characterArray, string separator = null) + { + if (characterArray is null || characterArray.Length < 1) return string.Empty; + + var sb = new System.Text.StringBuilder(); + + bool isSeparatorExist = !string.IsNullOrEmpty(separator); + + for (int index = 0; index < characterArray.Length; index++) + { + sb.Append(characterArray[index]); + + if (isSeparatorExist && index < characterArray.Length - 1) + sb.Append(separator); + } + + return sb.ToString(); + } } diff --git a/src/UtilityVerse/Extensions/CollectionExtension.cs b/src/UtilityVerse/Extensions/CollectionExtension.cs index a7abb5d..c399393 100644 --- a/src/UtilityVerse/Extensions/CollectionExtension.cs +++ b/src/UtilityVerse/Extensions/CollectionExtension.cs @@ -5,6 +5,8 @@ using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Linq; namespace UtilityVerse.Extensions; @@ -13,107 +15,110 @@ namespace UtilityVerse.Extensions; /// public static class CollectionExtension { - /// - /// If the collection is null or empty - /// - /// - /// - /// bool - public static bool IsNullOrEmpty(this IEnumerable? sources) => sources is null || !sources.Any(); - - /// - /// If the IList is null or empty. - /// - /// - /// - /// bool - public static bool IsNullOrEmpty(this IList? sources) => sources is null || sources.Count < 1; - - /// - /// If the list is null or empty. - /// - /// - /// - /// bool - public static bool IsNullOrEmpty(this List? sources) => sources is null || sources.Count < 1; - - /// - /// If the collection is null or empty. - /// - /// - /// - /// bool - public static bool IsNullOrEmpty(this ICollection? sources) => sources is null || sources.Count < 1; - - /// - /// If the readonly collection is null or empty. - /// - /// - /// - /// bool - public static bool IsNullOrEmpty(this IReadOnlyCollection? sources) => sources is null || sources.Count < 1; - - /// - /// If the readonly list is null or empty. - /// - /// - /// - /// bool - public static bool IsNullOrEmpty(this IReadOnlyList? sources) => sources is null || sources.Count < 1; - - /// - /// If the readonly set is null or empty. - /// - /// - /// - /// bool - public static bool IsNullOrEmpty(this IReadOnlySet? sources) => sources is null || sources.Count < 1; - - /// - /// If the IOrderedEnumerable is null or empty - /// - /// - /// - /// bool - public static bool IsNullOrEmpty(this IOrderedEnumerable? sources) => sources is null || !sources.Any(); - - /// - /// If the IOrderedQueryable is null or empty. - /// - /// - /// - /// bool - public static bool IsNullOrEmpty(this IOrderedQueryable? sources) => sources is null || !sources.Any(); - - /// - /// If the ConcurrentBag is null or empty. - /// - /// - /// - /// bool - public static bool IsNullOrEmpty(this ConcurrentBag? sources) => sources is null || sources.Count < 1; - - /// - /// If the concurrentQueue is empty or not. - /// - /// - /// - /// bool - public static bool IsNullOrEmpty(this ConcurrentQueue? sources) => sources is null || sources.Count < 1; - - /// - /// If the ConcurrentStack is null or empty. - /// - /// - /// - /// bool - public static bool IsNullOrEmpty(this ConcurrentStack? sources) => sources is null || sources.Count < 1; - - /// - /// If the IProducerConsumerCollection is null or empty. - /// - /// - /// - /// bool - public static bool IsNullOrEmpty(this IProducerConsumerCollection? sources) => sources is null || sources.Count < 1; + /// + /// If the collection is null or empty + /// + /// + /// + /// bool + public static bool IsNullOrEmpty(this IEnumerable sources) => sources is null || !sources.Any(); + + /// + /// If the IList is null or empty. + /// + /// + /// + /// bool + public static bool IsNullOrEmpty(this IList sources) => sources is null || sources.Count < 1; + + /// + /// If the list is null or empty. + /// + /// + /// + /// bool + public static bool IsNullOrEmpty(this List sources) => sources is null || sources.Count < 1; + + /// + /// If the collection is null or empty. + /// + /// + /// + /// bool + public static bool IsNullOrEmpty(this ICollection sources) => sources is null || sources.Count < 1; + + /// + /// If the readonly collection is null or empty. + /// + /// + /// + /// bool + public static bool IsNullOrEmpty(this IReadOnlyCollection sources) => sources is null || sources.Count < 1; + + /// + /// If the readonly list is null or empty. + /// + /// + /// + /// bool + public static bool IsNullOrEmpty(this IReadOnlyList sources) => sources is null || sources.Count < 1; + +#if NETCOREAPP3_1_OR_GREATER + /// + /// If the readonly set is null or empty. + /// + /// + /// + /// bool + public static bool IsNullOrEmpty(this IReadOnlySet sources) => sources is null || sources.Count < 1; + +#endif + + /// + /// If the IOrderedEnumerable is null or empty + /// + /// + /// + /// bool + public static bool IsNullOrEmpty(this IOrderedEnumerable sources) => sources is null || !sources.Any(); + + /// + /// If the IOrderedQueryable is null or empty. + /// + /// + /// + /// bool + public static bool IsNullOrEmpty(this IOrderedQueryable sources) => sources is null || !sources.Any(); + + /// + /// If the ConcurrentBag is null or empty. + /// + /// + /// + /// bool + public static bool IsNullOrEmpty(this ConcurrentBag sources) => sources is null || sources.IsEmpty; + + /// + /// If the concurrentQueue is empty or not. + /// + /// + /// + /// bool + public static bool IsNullOrEmpty(this ConcurrentQueue sources) => sources is null || sources.IsEmpty; + + /// + /// If the ConcurrentStack is null or empty. + /// + /// + /// + /// bool + public static bool IsNullOrEmpty(this ConcurrentStack sources) => sources is null || sources.IsEmpty; + + /// + /// If the IProducerConsumerCollection is null or empty. + /// + /// + /// + /// bool + public static bool IsNullOrEmpty(this IProducerConsumerCollection sources) => sources is null || sources.Count < 1; } diff --git a/src/UtilityVerse/Extensions/DateOnlyExtension.cs b/src/UtilityVerse/Extensions/DateOnlyExtension.cs index e20b65d..224ec8a 100644 --- a/src/UtilityVerse/Extensions/DateOnlyExtension.cs +++ b/src/UtilityVerse/Extensions/DateOnlyExtension.cs @@ -3,93 +3,74 @@ // FREE TO USE TO CONNECT THE WORLD // --------------------------------------------------------------- +using System; +using UtilityVerse.Helpers; namespace UtilityVerse.Extensions; +#if NETCOREAPP3_1_OR_GREATER /// /// DateOnly Extension /// public static class DateOnlyExtension { - /// - /// This method will help to determine is the date only instance is in between range or not. - /// - /// - /// - /// - /// - public static bool IsInBetween(this DateOnly? dt, DateOnly? startDate, DateOnly? endDate) - { - ArgumentNullException.ThrowIfNull(dt); - ArgumentNullException.ThrowIfNull(startDate, nameof(startDate)); - ArgumentNullException.ThrowIfNull(endDate, nameof(endDate)); - return IsInBetween(dt.Value, startDate.Value, endDate.Value); - } + /// + /// This method will help to determine is the date only instance is in between range or not. + /// + /// + /// + /// + /// bool + /// + public static bool IsInBetween(this DateOnly dt, DateOnly startDate, DateOnly endDate) + { + UtilityVerseException.ThrowIfNull(dt); + UtilityVerseException.ThrowIfNull(startDate, nameof(startDate)); + UtilityVerseException.ThrowIfNull(endDate, nameof(endDate)); - /// - /// This method will help to determine is the date only instance is in between range or not. - /// - /// - /// - /// - /// - /// - public static bool IsInBetween(this DateOnly dateOnly, DateOnly startDate, DateOnly endDate) - { - if (startDate > endDate) - throw new ArgumentOutOfRangeException(nameof(startDate) + " cannot be greater than " + nameof(endDate)); + if (startDate > endDate) + UtilityVerseException.Throw(nameof(startDate) + " cannot be greater than " + nameof(endDate)); - return dateOnly >= startDate && dateOnly <= endDate; - } + return dt >= startDate && dt <= endDate; + } + /// + /// This method will extract dateonly information from the datetime object. + /// + /// + /// + /// int + /// + public static int ToIntDate(this DateOnly dateOnly, string format = "yyyyMMdd") + { + UtilityVerseException.ThrowIfNull(dateOnly); + UtilityVerseException.ThrowIfNullOrEmpty(format); + return int.TryParse(dateOnly.ToString(format), out var result) ? result : -1; + } - /// - /// This method will extract dateonly information from the datetime object. - /// - /// - /// - /// - public static int ToIntDate(this DateOnly? dateOnly, string format = "yyyyMMdd") - { - ArgumentNullException.ThrowIfNull(dateOnly); - return ToIntDate(dateOnly.Value, format); - } + /// + /// this method convert dateonly to a valid local datetime + /// + /// + /// DateTime + /// + public static DateTime ToDateTime(this DateOnly dateOnly) + { + UtilityVerseException.ThrowIfNull(dateOnly, nameof(dateOnly)); + return new DateTime(year: dateOnly.Year, month: dateOnly.Month, day: dateOnly.Day, hour: 0, minute: 0, second: 0, kind: DateTimeKind.Local); + } - /// - /// This method will extract dateonly information from the datetime object. - /// - /// - /// - /// - /// - public static int ToIntDate(this DateOnly dateOnly, string format = "yyyyMMdd") - { - if (string.IsNullOrEmpty(format) || string.IsNullOrWhiteSpace(format)) - throw new InvalidDataException(nameof(format) + " is null or empty"); - - return int.TryParse(dateOnly.ToString(format), out var result) ? result : -1; - } - - /// - /// this method convert dateonly to a valid local datetime - /// - /// - /// - public static DateTime? ToDateTime(this DateOnly dateOnly) - { - if (dateOnly == default) return default; - return new DateTime(dateOnly.Year, dateOnly.Month, dateOnly.Day, 0, 0, 0, DateTimeKind.Local); - } - - /// - /// this method will convert dateonly object into a valid utc datetime object - /// - /// - /// - public static DateTime? ToUtcDateTime(this DateOnly dateOnly) - { - if (dateOnly == default) return default; - return new DateTime(dateOnly.Year, dateOnly.Month, dateOnly.Day, 0, 0, 0, DateTimeKind.Utc); - } -} \ No newline at end of file + /// + /// this method will convert dateonly object into a valid utc datetime object + /// + /// + /// DateTime + /// + public static DateTime ToUtcDateTime(this DateOnly dateOnly) + { + UtilityVerseException.ThrowIfNull(dateOnly, nameof(dateOnly)); + return new DateTime(year: dateOnly.Year, month: dateOnly.Month, day: dateOnly.Day, hour: 0, minute: 0, second: 0, kind: DateTimeKind.Utc); + } +} +#endif \ No newline at end of file diff --git a/src/UtilityVerse/Extensions/DateTimeExtension.cs b/src/UtilityVerse/Extensions/DateTimeExtension.cs index f2b10e9..1ac3653 100644 --- a/src/UtilityVerse/Extensions/DateTimeExtension.cs +++ b/src/UtilityVerse/Extensions/DateTimeExtension.cs @@ -4,7 +4,9 @@ // --------------------------------------------------------------- +using System; using UtilityVerse.Contracts; +using UtilityVerse.Helpers; namespace UtilityVerse.Extensions; @@ -13,121 +15,84 @@ namespace UtilityVerse.Extensions; /// public static class DateTimeExtension { - /// - /// This method will help you determine if the target date time is within the given range (start and end) - /// - /// - /// - /// - /// - /// - public static bool IsInBetween(this DateTime? dt, DateTime? startDateTime, DateTime? endDateTime) - { - ArgumentNullException.ThrowIfNull(dt); - ArgumentNullException.ThrowIfNull(startDateTime, nameof(startDateTime)); - ArgumentNullException.ThrowIfNull(endDateTime, nameof(endDateTime)); + /// + /// This method will help you determine if the target date time is within the given range (start and end) + /// + /// + /// + /// + /// bool + /// + public static bool IsInBetween(this DateTime dt, DateTime startDateTime, DateTime endDateTime) + { + UtilityVerseException.ThrowIfNull(dt); + UtilityVerseException.ThrowIfNull(startDateTime); + UtilityVerseException.ThrowIfNull(endDateTime); - return IsInBetween(dt.Value, startDateTime.Value, endDateTime.Value); - } + if (endDateTime < startDateTime) + UtilityVerseException.Throw($"{nameof(endDateTime)} cannot be smaller than {nameof(startDateTime)}"); - /// - /// This method will help you determine if the target date time is within the given range (start and end) - /// - /// - /// - /// - /// - /// - public static bool IsInBetween(this DateTime dt, DateTime startDateTime, DateTime endDateTime) - { - if (endDateTime < startDateTime) - throw new ArgumentOutOfRangeException(nameof(endDateTime) + " cannot be smaller than " + nameof(startDateTime)); + return dt >= startDateTime && dt <= endDateTime; + } - return dt >= startDateTime && dt <= endDateTime; - } + /// + /// This method will convert date time object into UNIX time stamp + /// + /// + /// + /// long + /// + /// + public static long ToUnixTimeStamp(this DateTime dt, TimeEnum timeEnum) + { + UtilityVerseException.ThrowIfNull(dt); - /// - /// This method will convert date time object into UNIX time stamp - /// - /// - /// - /// - public static long ToUnixTimeStamp(this DateTime? dt, TimeEnum timeEnum) - { - ArgumentNullException.ThrowIfNull(dt); - return ToUnixTimeStamp(dt.Value, timeEnum); - } + return timeEnum switch + { + TimeEnum.MilliSecond => new DateTimeOffset(dt).ToUnixTimeMilliseconds(), + TimeEnum.Second => new DateTimeOffset(dt).ToUnixTimeSeconds(), + _ => throw new NotImplementedException() + }; + } - /// - /// This method will convert date time object into UNIX time stamp - /// - /// - /// - /// - public static long ToUnixTimeStamp(this DateTime dt, TimeEnum timeEnum) - { - var offset = new DateTimeOffset(dt); + /// + /// Convert datetime object into custom int format. If this method cannot parse the format it will return -1; + /// + /// new DateTime() like [6/10/2023 4:02:06 PM] + /// yyyyMMdd + /// int + /// + public static int ToIntDate(this DateTime dateTime, string format = "yyyyMMdd") + { + UtilityVerseException.ThrowIfNull(dateTime, nameof(dateTime)); + UtilityVerseException.ThrowIfNullOrEmpty(format, nameof(format)); + return int.TryParse(dateTime.ToString(format), out var result) ? result : -1; + } - return timeEnum switch - { - TimeEnum.MilliSecond => offset.ToUnixTimeMilliseconds(), - TimeEnum.Second => offset.ToUnixTimeSeconds(), - _ => throw new NotImplementedException() - }; - } + /// + /// This method will extract datetime information from the datetime object. + /// + /// + /// (int, int, int, int, int, int, int) + /// + public static (int year, int month, int day, int hour, int minute, int second, int milisecond) DestructFromDateTime( + this DateTime dateTime) + { + UtilityVerseException.ThrowIfNull(dateTime); + return (dateTime.Year, dateTime.Month, dateTime.Day, dateTime.Hour, dateTime.Minute, dateTime.Second, dateTime.Millisecond); + } - /// - /// Convert datetime object into custom int format. If this method cannot parse the format it will return -1; - /// - /// new DateTime() like [6/10/2023 4:02:06 PM] - /// yyyyMMdd - /// integer number (20230610) - public static int ToIntDate(this DateTime? dateTime, string format = "yyyyMMdd") - { - ArgumentNullException.ThrowIfNull(dateTime); - return ToIntDate(dateTime.Value, format); - } - - /// - /// Convert datetime object into custom int format. If this method cannot parse the format it will return -1; - /// - /// new DateTime() like [6/10/2023 4:02:06 PM] - /// yyyyMMdd - /// integer number (20230610) - /// - public static int ToIntDate(this DateTime dateTime, string format = "yyyyMMdd") - { - if (string.IsNullOrEmpty(format) || string.IsNullOrWhiteSpace(format)) - throw new InvalidDataException(nameof(format) + " is null or empty"); - - return int.TryParse(dateTime.ToString(format), out var result) ? result : -1; - } - - /// - /// This method will extract datetime information from the datetime object. - /// - /// - /// - /// - public static (int year, int month, int day, int hour, int minute, int second, int milisecond) DestructFromDateTime( - this DateTime? dateTime) - { - ArgumentNullException.ThrowIfNull(dateTime); - return (dateTime.Value.Year, dateTime.Value.Month, dateTime.Value.Day, dateTime.Value.Hour, - dateTime.Value.Minute, dateTime.Value.Second, dateTime.Value.Millisecond); - } - - /// - /// this method will convert your current datetime into utc formatted datetime - /// - /// - /// - /// - public static DateTime? ToUtcDateTime(this DateTime dateTime, bool includeTime = false) - { - if (dateTime == default) return default; - if (includeTime) - return new DateTime(dateTime.Year, dateTime.Month, dateTime.Day, dateTime.Hour, dateTime.Minute, dateTime.Second, DateTimeKind.Utc); - return new DateTime(dateTime.Year, dateTime.Month, dateTime.Day, 0, 0, 0, DateTimeKind.Utc); - } + /// + /// this method will convert your current datetime into utc formatted datetime + /// + /// + /// + /// DateTime + /// + public static DateTime ToUtcDateTime(this DateTime dateTime, bool includeTime = false) + { + UtilityVerseException.ThrowIfNull(dateTime); + if (includeTime) return new DateTime(year: dateTime.Year, month: dateTime.Month, day: dateTime.Day, hour: dateTime.Hour, minute: dateTime.Minute, second: dateTime.Second, kind: DateTimeKind.Utc); + return new DateTime(year: dateTime.Year, month: dateTime.Month, day: dateTime.Day, hour: 0, minute: 0, second: 0, kind: DateTimeKind.Utc); + } } \ No newline at end of file diff --git a/src/UtilityVerse/Extensions/DecimalExtension.cs b/src/UtilityVerse/Extensions/DecimalExtension.cs index a6b527f..d1f68f7 100644 --- a/src/UtilityVerse/Extensions/DecimalExtension.cs +++ b/src/UtilityVerse/Extensions/DecimalExtension.cs @@ -3,8 +3,7 @@ // FREE TO USE TO CONNECT THE WORLD // --------------------------------------------------------------- - -using System.Text; +using UtilityVerse.Helpers; namespace UtilityVerse.Extensions; @@ -13,28 +12,28 @@ namespace UtilityVerse.Extensions; /// public static class DecimalExtension { - /// - /// This extension method will convert any double array into string with separator. - /// - /// - /// - /// - public static string ToStr(this decimal[]? decimalArray, string? separator = null) - { - if (decimalArray is null || decimalArray.Length < 1) return string.Empty; - - var sb = new StringBuilder(); - - bool isSeparatorExist = !string.IsNullOrEmpty(separator); - - for (int index = 0; index < decimalArray.Length; index++) - { - sb.Append(decimalArray[index]); - - if (isSeparatorExist && index < decimalArray.Length - 1) - sb.Append(separator); - } - - return sb.ToString(); - } + /// + /// This extension method will convert any double array into string with separator. + /// + /// + /// + /// string + public static string ToStr(this decimal[] decimalArray, string separator = null) + { + if (decimalArray is null || decimalArray.Length < 1) UtilityVerseException.Throw("array is null"); + + var sb = new System.Text.StringBuilder(); + + bool isSeparatorExist = !string.IsNullOrEmpty(separator); + + for (int index = 0; index < decimalArray.Length; index++) + { + sb.Append(decimalArray[index]); + + if (isSeparatorExist && index < decimalArray.Length - 1) + sb.Append(separator); + } + + return sb.ToString(); + } } diff --git a/src/UtilityVerse/Extensions/DoubleExtension.cs b/src/UtilityVerse/Extensions/DoubleExtension.cs index 93f4491..8ae582b 100644 --- a/src/UtilityVerse/Extensions/DoubleExtension.cs +++ b/src/UtilityVerse/Extensions/DoubleExtension.cs @@ -3,9 +3,6 @@ // FREE TO USE TO CONNECT THE WORLD // --------------------------------------------------------------- - -using System.Text; - namespace UtilityVerse.Extensions; /// @@ -13,27 +10,27 @@ namespace UtilityVerse.Extensions; /// public static class DoubleExtension { - /// - /// This extension method will convert any double array into string with separator. - /// - /// - /// - /// - public static string ToStr(this double[]? doubleArray, string? separator = null) - { - if (doubleArray is null || doubleArray.Length < 1) return string.Empty; - var sb = new StringBuilder(); - - bool isSeparatorExist = !string.IsNullOrEmpty(separator); - - for (int index = 0; index < doubleArray.Length; index++) - { - sb.Append(doubleArray[index]); - - if (isSeparatorExist && index < doubleArray.Length - 1) - sb.Append(separator); - } - - return sb.ToString(); - } + /// + /// This extension method will convert any double array into string with separator. + /// + /// + /// + /// + public static string ToStr(this double[] doubleArray, string separator = null) + { + if (doubleArray is null || doubleArray.Length < 1) return string.Empty; + var sb = new System.Text.StringBuilder(); + + bool isSeparatorExist = !string.IsNullOrEmpty(separator); + + for (int index = 0; index < doubleArray.Length; index++) + { + sb.Append(doubleArray[index]); + + if (isSeparatorExist && index < doubleArray.Length - 1) + sb.Append(separator); + } + + return sb.ToString(); + } } diff --git a/src/UtilityVerse/Extensions/GenericExtension.cs b/src/UtilityVerse/Extensions/GenericExtension.cs index 88157ab..d8ceeb9 100644 --- a/src/UtilityVerse/Extensions/GenericExtension.cs +++ b/src/UtilityVerse/Extensions/GenericExtension.cs @@ -3,9 +3,8 @@ // FREE TO USE TO CONNECT THE WORLD // --------------------------------------------------------------- - -using System.Text; -using System.Text.Json; +using System; +using UtilityVerse.Helpers; namespace UtilityVerse.Extensions; @@ -14,15 +13,18 @@ namespace UtilityVerse.Extensions; /// public static class GenericExtension { - /// - /// This method will convert poco model into byte array. - /// - /// - /// - /// - public static byte[] ToByteArray(this T? instance) - { - ArgumentNullException.ThrowIfNull(instance); - return Encoding.UTF8.GetBytes(JsonSerializer.Serialize(instance)); - } +#if NETCOREAPP3_1_OR_GREATER + /// + /// This method will convert poco model into byte array. + /// + /// + /// + /// byte[] + /// + public static byte[] ToByteArray(this T instance) + { + UtilityVerseException.ThrowIfNull(instance); + return System.Text.Encoding.UTF8.GetBytes(System.Text.Json.JsonSerializer.Serialize(instance)); + } +#endif } diff --git a/src/UtilityVerse/Extensions/IntExtension.cs b/src/UtilityVerse/Extensions/IntExtension.cs index 572cefb..9bc0292 100644 --- a/src/UtilityVerse/Extensions/IntExtension.cs +++ b/src/UtilityVerse/Extensions/IntExtension.cs @@ -3,8 +3,8 @@ // FREE TO USE TO CONNECT THE WORLD // --------------------------------------------------------------- - -using System.Text; +using System; +using UtilityVerse.Helpers; namespace UtilityVerse.Extensions; @@ -13,66 +13,62 @@ namespace UtilityVerse.Extensions; /// public static class IntExtension { - /// - /// This method will help to determine that source number is in between or not. - /// - /// - /// - /// - /// - public static bool IsInBetween(this int? source, int? min, int? max) - { - ArgumentNullException.ThrowIfNull(source); - ArgumentNullException.ThrowIfNull(min, nameof(min)); - ArgumentNullException.ThrowIfNull(max, nameof(max)); + /// + /// This method will help to determine that source number is in between or not. + /// + /// + /// + /// + /// bool + /// + public static bool IsInBetween(this int? source, int? min, int? max) + { + UtilityVerseException.ThrowIfNull(source, nameof(source)); + UtilityVerseException.ThrowIfNull(min, nameof(min)); + UtilityVerseException.ThrowIfNull(max, nameof(max)); - return IsInBetween(source.Value, min.Value, max.Value); - } + return IsInBetween(source!.Value, min!.Value, max!.Value); + } - /// - /// This method will help to determine that source number is in between or not. - /// - /// - /// - /// - /// - public static bool IsInBetween(this int source, int min, int max) - { - if (min > max) - throw new ArgumentOutOfRangeException(nameof(min) + " cannot be greater than " + nameof(max)); + /// + /// This method will help to determine that source number is in between or not. + /// + /// + /// + /// + /// bool + /// + public static bool IsInBetween(this int source, int min, int max) + { + if (min > max) + UtilityVerseException.Throw(nameof(min) + " cannot be greater than " + nameof(max)); - return source >= min && source <= max; - } + return source >= min && source <= max; + } - /// - /// This method will convert 1 or 0 to boolean - /// - /// - /// - public static int ToInt(this bool value) => value ? 1 : 0; - /// - /// This extension method will convert any int array into string with separator. - /// - /// - /// - /// - public static string ToStr(this int[]? intArray, string? separator = null) - { - if (intArray is null || intArray.Length < 1) return string.Empty; + /// + /// This extension method will convert any int array into string with separator. + /// + /// + /// + /// + public static string ToStr(this int[] intArray, string separator = null) + { + if (intArray is null || intArray.Length < 1) return string.Empty; - var sb = new StringBuilder(); + var sb = new System.Text.StringBuilder(); - bool isSeparatorExist = !string.IsNullOrEmpty(separator); + bool isSeparatorExist = !string.IsNullOrEmpty(separator); - for (int index = 0; index < intArray.Length; index++) - { - sb.Append(intArray[index]); + for (int index = 0; index < intArray.Length; index++) + { + sb.Append(intArray[index]); - if (isSeparatorExist && index < intArray.Length - 1) - sb.Append(separator); - } + if (isSeparatorExist && index < intArray.Length - 1) + sb.Append(separator); + } - return sb.ToString(); - } + return sb.ToString(); + } } \ No newline at end of file diff --git a/src/UtilityVerse/Extensions/LongExtension.cs b/src/UtilityVerse/Extensions/LongExtension.cs index f3727c8..61c4b42 100644 --- a/src/UtilityVerse/Extensions/LongExtension.cs +++ b/src/UtilityVerse/Extensions/LongExtension.cs @@ -3,7 +3,9 @@ // FREE TO USE TO CONNECT THE WORLD // --------------------------------------------------------------- +using System; using UtilityVerse.Contracts; +using UtilityVerse.Helpers; namespace UtilityVerse.Extensions; @@ -20,7 +22,7 @@ public static class LongExtension /// public static DateTime ToDateTime(this long? timeStamp, TimeEnum timeEnum) { - ArgumentNullException.ThrowIfNull(timeStamp); + UtilityVerseException.ThrowIfNull(timeStamp); return ToDateTime(timeStamp.Value, timeEnum); } @@ -30,12 +32,12 @@ public static DateTime ToDateTime(this long? timeStamp, TimeEnum timeEnum) /// /// /// - /// + /// /// public static DateTime ToDateTime(this long timeStamp, TimeEnum timeEnum) { if (timeStamp < 1) - throw new ArgumentOutOfRangeException(nameof(timeStamp), "invalid time stamp value"); + UtilityVerseException.Throw($"{nameof(timeStamp)} invalid time stamp value"); return timeEnum switch { diff --git a/src/UtilityVerse/Extensions/ObjectExtension.cs b/src/UtilityVerse/Extensions/ObjectExtension.cs index 054633c..89809a1 100644 --- a/src/UtilityVerse/Extensions/ObjectExtension.cs +++ b/src/UtilityVerse/Extensions/ObjectExtension.cs @@ -3,8 +3,8 @@ // FREE TO USE TO CONNECT THE WORLD // --------------------------------------------------------------- -using System.Text; -using System.Text.Json; +using System; +using UtilityVerse.Helpers; namespace UtilityVerse.Extensions; @@ -13,14 +13,17 @@ namespace UtilityVerse.Extensions; /// public static class ObjectExtension { - /// - /// This method will convert any object into a byte array. - /// - /// - /// - public static byte[] ToByteArray(this object? obj) - { - ArgumentNullException.ThrowIfNull(obj); - return Encoding.UTF8.GetBytes(JsonSerializer.Serialize(obj)); - } +#if NETCOREAPP3_1_OR_GREATER + /// + /// This method will convert any object into a byte array. + /// + /// + /// byte[] + /// + public static byte[] ToByteArray(this object obj) + { + UtilityVerseException.ThrowIfNull(obj); + return System.Text.Encoding.UTF8.GetBytes(System.Text.Json.JsonSerializer.Serialize(obj)); + } +#endif } diff --git a/src/UtilityVerse/Extensions/PagedListExtension.cs b/src/UtilityVerse/Extensions/PagedListExtension.cs index 650faf3..e6c93b2 100644 --- a/src/UtilityVerse/Extensions/PagedListExtension.cs +++ b/src/UtilityVerse/Extensions/PagedListExtension.cs @@ -4,7 +4,10 @@ // --------------------------------------------------------------- +using System; +using System.Linq; using UtilityVerse.Contracts; +using UtilityVerse.Helpers; namespace UtilityVerse.Extensions; @@ -21,11 +24,11 @@ public static class PagedListExtension /// /// /// - /// + /// public static PagedList ToPagedList(this IQueryable queryable, int index, int pageSize) { if (index == default || pageSize == default) - throw new InvalidDataException($"{nameof(index)} or {nameof(pageSize)} params value is invalid"); + UtilityVerseException.Throw($"{nameof(index)} or {nameof(pageSize)} params value is invalid"); var pagedList = new PagedList(); try @@ -45,7 +48,7 @@ public static PagedList ToPagedList(this IQueryable queryable, int inde } - private static string ExtractExceptionError(Exception? exception) + private static string ExtractExceptionError(Exception exception) => exception is null ? string.Empty : $"Exception Message: {exception.Message}. Inner Exception: {exception.InnerException?.Message}"; diff --git a/src/UtilityVerse/Extensions/SpanExtension.cs b/src/UtilityVerse/Extensions/SpanExtension.cs index 6b9abc7..2e0fcb9 100644 --- a/src/UtilityVerse/Extensions/SpanExtension.cs +++ b/src/UtilityVerse/Extensions/SpanExtension.cs @@ -4,10 +4,12 @@ // --------------------------------------------------------------- -using System.Text.Json; +using System; namespace UtilityVerse.Extensions; +#if NETCOREAPP3_1_OR_GREATER + /// /// Span Extension /// @@ -18,5 +20,7 @@ public static class SpanExtension /// /// /// - public static object? ToObject(this ReadOnlySpan spanByteArr) => JsonSerializer.Deserialize(spanByteArr); + public static object ToObject(this ReadOnlySpan spanByteArr) => System.Text.Json.JsonSerializer.Deserialize(spanByteArr); } + +#endif \ No newline at end of file diff --git a/src/UtilityVerse/Extensions/StringExtension.cs b/src/UtilityVerse/Extensions/StringExtension.cs index d14c8b3..0438e21 100644 --- a/src/UtilityVerse/Extensions/StringExtension.cs +++ b/src/UtilityVerse/Extensions/StringExtension.cs @@ -4,6 +4,7 @@ // --------------------------------------------------------------- +using System; using System.Text; using System.Text.RegularExpressions; using UtilityVerse.Contracts; @@ -20,8 +21,8 @@ public static class StringExtension /// This method will convert any boolean string value to boolean /// /// "YES", "1", "true" - /// - public static bool ToBoolean(this string? value) + /// string + public static bool ToBoolean(this string value) { if (value.IsNullOrEmptyOrWhiteSpace()) return false; @@ -44,8 +45,8 @@ public static bool ToBoolean(this string? value) /// This extension method will help to validate if the string is null empty and whitespace or not. /// /// - /// - public static bool IsNullOrEmptyOrWhiteSpace(this string? val) + /// string + public static bool IsNullOrEmptyOrWhiteSpace(this string val) { if (val is null) return true; return string.IsNullOrEmpty(val) || string.IsNullOrWhiteSpace(val.Trim()); @@ -57,8 +58,8 @@ public static bool IsNullOrEmptyOrWhiteSpace(this string? val) /// /// /// - /// - public static string ToStr(this string[]? strs, string? separator = null) + /// string + public static string ToStr(this string[] strs, string separator = null) { if (strs is null || strs.Length < 1) return string.Empty; @@ -81,8 +82,8 @@ public static string ToStr(this string[]? strs, string? separator = null) /// this method will convert your file path based on the runtime /// /// - /// - public static string ConvertOsWisePath(this string? path) + /// string + public static string ConvertOsWisePath(this string path) { if (path.IsNullOrEmptyOrWhiteSpace()) return string.Empty; @@ -95,8 +96,8 @@ public static string ConvertOsWisePath(this string? path) /// /// /// - /// - public static string EncodeToBase64(this string? value, EncodingEnum encoding = EncodingEnum.UTF8) + /// string + public static string EncodeToBase64(this string value, EncodingEnum encoding = EncodingEnum.UTF8) { if (value.IsNullOrEmptyOrWhiteSpace()) return string.Empty; @@ -106,7 +107,9 @@ public static string EncodeToBase64(this string? value, EncodingEnum encoding = EncodingEnum.UTF32 => Convert.ToBase64String(Encoding.UTF32.GetBytes(value!)), EncodingEnum.Unicode => Convert.ToBase64String(Encoding.Unicode.GetBytes(value!)), EncodingEnum.Ascii => Convert.ToBase64String(Encoding.ASCII.GetBytes(value!)), +#if NETCOREAPP3_1_OR_GREATER EncodingEnum.Latin => Convert.ToBase64String(Encoding.Latin1.GetBytes(value!)), +#endif _ => string.Empty }; } @@ -117,7 +120,7 @@ public static string EncodeToBase64(this string? value, EncodingEnum encoding = /// /// /// - public static string DecodeFromBase64(this string? value, EncodingEnum encoding = EncodingEnum.UTF8) + public static string DecodeFromBase64(this string value, EncodingEnum encoding = EncodingEnum.UTF8) { if (value.IsNullOrEmptyOrWhiteSpace()) return string.Empty; @@ -127,7 +130,9 @@ public static string DecodeFromBase64(this string? value, EncodingEnum encoding EncodingEnum.UTF32 => Encoding.UTF32.GetString(Convert.FromBase64String(value!)), EncodingEnum.Unicode => Encoding.Unicode.GetString(Convert.FromBase64String(value!)), EncodingEnum.Ascii => Encoding.ASCII.GetString(Convert.FromBase64String(value!)), +#if NETCOREAPP3_1_OR_GREATER EncodingEnum.Latin => Encoding.Latin1.GetString(Convert.FromBase64String(value!)), +#endif _ => string.Empty }; } diff --git a/src/UtilityVerse/Helpers/UtilityVerseException.cs b/src/UtilityVerse/Helpers/UtilityVerseException.cs new file mode 100644 index 0000000..407810e --- /dev/null +++ b/src/UtilityVerse/Helpers/UtilityVerseException.cs @@ -0,0 +1,20 @@ +using System; + +namespace UtilityVerse.Helpers; + +internal class UtilityVerseException +{ + private static readonly string _value = "value"; + internal static void ThrowIfNullOrEmpty(string data, string paramName = null) + { + if (string.IsNullOrEmpty(data) || string.IsNullOrWhiteSpace(data)) + throw new Exception($"{(paramName is not null ? paramName : _value)} is null or empty"); + } + + internal static void ThrowIfNull(object data, string paramName = null) + { + if (data is null) throw new Exception($"{(paramName is not null ? paramName : _value)} is null"); + } + + internal static void Throw(string message) { throw new Exception(message); } +} diff --git a/src/UtilityVerse/Utility.Currency.cs b/src/UtilityVerse/Utility.Currency.cs index a8d2e87..4f419e3 100644 --- a/src/UtilityVerse/Utility.Currency.cs +++ b/src/UtilityVerse/Utility.Currency.cs @@ -12,7 +12,7 @@ public sealed partial class Utility /// This will convert dollar value to cent /// /// - /// + /// decimal public static decimal DollarToCent(decimal dollar) => dollar * 100; @@ -20,7 +20,7 @@ public sealed partial class Utility /// This will convert cent into dollar value /// /// - /// + /// decimal public static decimal CentToDollar(decimal cent) => cent / 100; } diff --git a/src/UtilityVerse/Utility.OS.cs b/src/UtilityVerse/Utility.OS.cs index 1049521..82c01b6 100644 --- a/src/UtilityVerse/Utility.OS.cs +++ b/src/UtilityVerse/Utility.OS.cs @@ -10,28 +10,34 @@ namespace UtilityVerse; public sealed partial class Utility { - /// - /// Is the running platform is windows?. - /// - public static bool IsItWindows => RuntimeInformation.IsOSPlatform(OSPlatform.Windows); - - /// - /// Is the app running on linux? - /// - public static bool IsItLinux => RuntimeInformation.IsOSPlatform(OSPlatform.Linux); - - /// - /// Is the app running on mac? - /// - public static bool IsItMac => RuntimeInformation.IsOSPlatform(OSPlatform.OSX); - - /// - /// Is the app is running on free bsd? - /// - public static bool IsBsd => RuntimeInformation.IsOSPlatform(OSPlatform.FreeBSD); - - /// - /// Framework runtime information. - /// - public static string FrameworkRuntime => RuntimeInformation.RuntimeIdentifier; + /// + /// Is the running platform is windows?. + /// + public static bool IsItWindows => RuntimeInformation.IsOSPlatform(OSPlatform.Windows); + + /// + /// Is the app running on Linux? + /// + public static bool IsItLinux => RuntimeInformation.IsOSPlatform(OSPlatform.Linux); + + /// + /// Is the app running on mac? + /// + public static bool IsItMac => RuntimeInformation.IsOSPlatform(OSPlatform.OSX); + +#if NETCOREAPP3_1_OR_GREATER + /// + /// Is the app is running on free bsd? + /// + public static bool IsBsd => RuntimeInformation.IsOSPlatform(OSPlatform.FreeBSD); + +#endif + +#if NETCOREAPP3_1_OR_GREATER + /// + /// Framework runtime information. + /// + public static string FrameworkRuntime => RuntimeInformation.RuntimeIdentifier; + +#endif } diff --git a/src/UtilityVerse/UtilityRetry.cs b/src/UtilityVerse/UtilityRetry.cs index db63728..d118aa8 100644 --- a/src/UtilityVerse/UtilityRetry.cs +++ b/src/UtilityVerse/UtilityRetry.cs @@ -4,82 +4,86 @@ // --------------------------------------------------------------- +using System; +using System.Threading.Tasks; using UtilityVerse.Contracts; +using UtilityVerse.Helpers; namespace UtilityVerse; public sealed partial class Utility { - /// - /// The method will retry on exception. - /// - /// - /// - /// - /// - public static T? AddRetry(Func? handler, RetryOption? retryOption = null) - { - ArgumentNullException.ThrowIfNull(handler, "Function is null"); - - retryOption ??= EvaluateOption(retryOption); - T? genericReturn = default; - return ExecuteRetryOperation(handler, retryOption, genericReturn) - .GetAwaiter().GetResult(); - } - - /// - /// Retry Operation Async Version. - /// This utility method will help you do retry on any method. It will retry on exception. - /// - /// - /// - /// - /// - public static Task AddRetryAsync(Func? handler, RetryOption? retryOption = null) - { - ArgumentNullException.ThrowIfNull(handler, "Function is null"); - - retryOption ??= EvaluateOption(retryOption); - T? genericReturn = default; - return ExecuteRetryOperation(handler, retryOption, genericReturn); - } - - #region Helper Private Methods - - private static Task ExecuteRetryOperation(Func handler, RetryOption? retryOption, T? genericReturn) - { - while (retryOption!.Count > 0) - { - try - { - genericReturn = handler(); - } - catch (Exception retryException) - { - Console.WriteLine($"Exception Occurred: - {retryException.Message}"); - - retryOption.Count--; - - Console.WriteLine($"Next Execution in: {retryOption.Delay!.Value.TotalSeconds} seconds"); - - Task.Delay(retryOption.Delay!.Value); - } - } - - return Task.FromResult(genericReturn); - } - - private static RetryOption EvaluateOption(RetryOption? retryOption = null) - { - retryOption ??= new RetryOption(); - - ArgumentNullException.ThrowIfNull(retryOption, nameof(RetryOption)); - - if (retryOption.Count is null || retryOption.Count < 1 || retryOption.Delay is null) - throw new ArgumentException($"Invalid {nameof(RetryOption)}"); - - return retryOption; - } - - #endregion + /// + /// The method will retry on exception. + /// + /// + /// + /// + /// + public static T AddRetry(Func handler, RetryOption retryOption = null) + { + UtilityVerseException.ThrowIfNull(data: handler, paramName: "Function"); + + retryOption ??= EvaluateOption(retryOption); + + T genericReturn = default; + return ExecuteRetryOperation(handler, retryOption, genericReturn).GetAwaiter().GetResult(); + } + + /// + /// Retry Operation Async Version. + /// This utility method will help you do retry on any method. It will retry on exception. + /// + /// + /// + /// + /// + public static Task AddRetryAsync(Func handler, RetryOption retryOption = null) + { + UtilityVerseException.ThrowIfNull(handler, "Function"); + + retryOption ??= EvaluateOption(retryOption); + + T genericReturn = default; + return ExecuteRetryOperation(handler, retryOption, genericReturn); + } + + #region Helper Private Methods + + private static Task ExecuteRetryOperation(Func handler, RetryOption retryOption, T genericReturn) + { + while (retryOption!.Count > 0) + { + try + { + genericReturn = handler(); + } + catch (Exception retryException) + { + Console.WriteLine($"Exception Occurred: - {retryException.Message}"); + + retryOption.Count--; + + Console.WriteLine($"Next Execution in: {retryOption.Delay!.TotalSeconds} seconds"); + + Task.Delay(retryOption.Delay!); + } + } + + return Task.FromResult(genericReturn); + } + + private static RetryOption EvaluateOption(RetryOption retryOption = null) + { + retryOption ??= new RetryOption(); + + UtilityVerseException.ThrowIfNull(data: retryOption, paramName: nameof(RetryOption)); + + if (retryOption.Count < 1) + UtilityVerseException.Throw($"Invalid {nameof(RetryOption)}"); + + return retryOption; + } + + #endregion } diff --git a/src/UtilityVerse/UtilityVerse.csproj b/src/UtilityVerse/UtilityVerse.csproj index 8d132be..737864a 100644 --- a/src/UtilityVerse/UtilityVerse.csproj +++ b/src/UtilityVerse/UtilityVerse.csproj @@ -1,52 +1,53 @@  - - net6.0 - enable - enable + + netstandard2.1; net6.0; net8.0 + Latest + disable + disable - MIT - true - UtilityVerse - 0.6 - utility, utils, C#, dotnet, c# utility, dotnet core - a curated list of utility methods that will help you, do your work at your best. - Pritom Purkayasta - https://github.com/purkayasta/TheUtilityVerse - https://www.nuget.org/packages/UtilityVerse/ - git - - Pritom Purkayasta - Copyright (c) Pritom Purkayasta All rights reserved. - FREE TO USE TO CONNECT THE WORLD - - true - TheUtilityVerse - utility-verse.png - nuget.md - True - snupkg - + MIT + true + UtilityVerse + 0.6 + utility, utils, C#, dotnet, c# utility, dotnet core + a curated list of utility methods that will help you, do your work at your best. + Pritom Purkayasta + https://github.com/purkayasta/TheUtilityVerse + https://www.nuget.org/packages/UtilityVerse/ + git + + Pritom Purkayasta + Copyright (c) Pritom Purkayasta All rights reserved. + FREE TO USE TO CONNECT THE WORLD + + true + TheUtilityVerse + utility-verse.png + nuget.md + True + snupkg + - - True - True - + + True + True + - - True - True - + + True + True + - - - True - \ - - - True - \ - - + + + True + \ + + + True + \ + + diff --git a/test/UtilityVerse.UnitTest/DateTimeExtensionTest.cs b/test/UtilityVerse.UnitTest/DateTimeExtensionTest.cs index e1a0cd9..3e40206 100644 --- a/test/UtilityVerse.UnitTest/DateTimeExtensionTest.cs +++ b/test/UtilityVerse.UnitTest/DateTimeExtensionTest.cs @@ -7,80 +7,32 @@ namespace UtilityVerse.UnitTest; public class DateTimeExtensionTest { - [Fact] - public void IsInBetween_ThrowsException_WhenSourceDateIsNull() - { - DateTime? dateTime = null; - Assert.Throws(() => dateTime.IsInBetween(null, null)); - } - - [Fact] - public void IsInBetween_ThrowsException_WhenFirstDateIsNull() - { - DateTime? dateTime = new DateTime(); - Assert.Throws(() => dateTime.IsInBetween(null, null)); - } - - [Fact] - public void IsInBetween_ThrowsException_WhenSecondDateIsNull() - { - DateTime? dateTime = new DateTime(); - Assert.Throws(() => dateTime.IsInBetween(new DateTime(), null)); - } - - [Fact] - public void IsInBetween_ThrowsException_WhenDateTimeIsNotRight() - { - DateTime? dateTime = DateTime.Now; - Assert.Throws(() => dateTime.IsInBetween(DateTime.Now, DateTime.UtcNow)); - } - [Fact] public void IsInBetween_ReturnsTrue_WhenDateTimeIsCorrect() { - DateTime? examDate = DateTime.Now; - DateTime? startDate = new DateTime(2020, 01, 01); - DateTime? endDate = new DateTime(2024, 01, 01); + DateTime examDate = DateTime.Now; + DateTime startDate = new DateTime(2020, 01, 01); + DateTime endDate = new DateTime(2030, 01, 01); Assert.True(examDate.IsInBetween(startDate, endDate)); } [Fact] public void IsInBetween_ReturnsFalse_WhenDataIsInvalid() { - DateTime? examDate = DateTime.Now; - DateTime? startDate = new DateTime(2020, 01, 01); - DateTime? endDate = new DateTime(2022, 01, 01); + DateTime examDate = DateTime.Now; + DateTime startDate = new DateTime(2020, 01, 01); + DateTime endDate = new DateTime(2022, 01, 01); Assert.False(examDate.IsInBetween(startDate, endDate)); } - [Fact] - public void ToUnixTimeStamp_ThrowsException_WhenNullIsGiven() - { - DateTime? dt = default; - Assert.Throws(() => dt.ToUnixTimeStamp(Contracts.TimeEnum.Second)); - } [Fact] public void ToUnixTimeStamp_ReturnValidIntTimeStamp_WhenValidDateIsProvided() { - DateTime? dt = new DateTime(1994, 08, 05, 00, 00, 01, DateTimeKind.Unspecified); + DateTime dt = new DateTime(1994, 08, 05, 00, 00, 01, DateTimeKind.Unspecified); Assert.Equal(776023201, dt.ToUnixTimeStamp(Contracts.TimeEnum.Second)); } - [Fact] - public void ToDateTime_ThrowsException_WhenInvalidLongNumberProvided() - { - long? longNumber = default; - Assert.Throws(() => longNumber.ToDateTime(Contracts.TimeEnum.MilliSecond)); - } - - [Fact] - public void ToDateTime_ThrowsException_WhenNumberIsBelowOne() - { - long? longNumber = 0; - Assert.Throws(() => longNumber.ToDateTime(Contracts.TimeEnum.Second)); - } - [Fact] public void ToDateTime_ReturnValidDateTime_WhenValidUnixEpochTimeIsProvided() { diff --git a/test/UtilityVerse.UnitTest/GenericExtensionTest.cs b/test/UtilityVerse.UnitTest/GenericExtensionTest.cs index fa9a57a..d740250 100644 --- a/test/UtilityVerse.UnitTest/GenericExtensionTest.cs +++ b/test/UtilityVerse.UnitTest/GenericExtensionTest.cs @@ -20,14 +20,14 @@ public void ToByteArray_ReturnsValidByteArray_WhenValidInputIsProvided() [Fact] public void To_ThrowException_WhenNullByteArrayIsGiven() { - byte[]? array = default; - Assert.Throws(() => array.To()); + byte[] array = default; + Assert.Throws(() => array.To()); } [Fact] public void ToByteArray_ThrowsException_WhenNullClassIsProvided() { - User? user = default; - Assert.Throws(() => user.ToByteArray()); + User user = default; + Assert.Throws(() => user.ToByteArray()); } } diff --git a/test/UtilityVerse.UnitTest/IntExtensionTest.cs b/test/UtilityVerse.UnitTest/IntExtensionTest.cs index 6e8d78a..16f0fea 100644 --- a/test/UtilityVerse.UnitTest/IntExtensionTest.cs +++ b/test/UtilityVerse.UnitTest/IntExtensionTest.cs @@ -7,45 +7,31 @@ namespace UtilityVerse.UnitTest; public class IntExtensionTest { - [Fact] - public void IsInBetween_ThrowException_IfSourceIsNull() - { - int? source = default; - Assert.Throws(() => source.IsInBetween(default, default)); - } - - [Fact] - public void IsInBetween_ThrowException_IfMinIsNull() - { - int? source = 0; - Assert.Throws(() => source.IsInBetween(default, default)); - } - [Fact] public void IsInBetween_ThrowException_IfMaxIsNull() { - int? source = 0; - Assert.Throws(() => source.IsInBetween(1, default)); + int source = 0; + Assert.Throws(() => source.IsInBetween(1, default)); } [Fact] public void IsInBetween_ThrowException_WhenMinIsGreaterThanMax() { - int? source = 0; - Assert.Throws(() => source.IsInBetween(1, 0)); + int source = 0; + Assert.Throws(() => source.IsInBetween(1, 0)); } [Fact] public void IsInBetween_ReturnFalse_WhenWrongDataIsProvided() { - int? source = 10; + int source = 10; Assert.False(source.IsInBetween(1, 9)); } [Fact] public void IsInBetween_ReturnTrue_WhenRightDataIsProvided() { - int? source = 100; + int source = 100; Assert.True(source.IsInBetween(10, 200)); } } diff --git a/test/UtilityVerse.UnitTest/StringExtensionTest.cs b/test/UtilityVerse.UnitTest/StringExtensionTest.cs index d9d1109..cb646b5 100644 --- a/test/UtilityVerse.UnitTest/StringExtensionTest.cs +++ b/test/UtilityVerse.UnitTest/StringExtensionTest.cs @@ -10,28 +10,28 @@ public class StringExtensionTest [Fact] public void IsNullEmptyOrWhiteSpace_ReturnTrue_WhenValueIsNull() { - string? value = default; + string value = default; Assert.True(value.IsNullOrEmptyOrWhiteSpace()); } [Fact] public void IsNullEmptyOrWhiteSpace_ReturnTrue_WhenValueIsEmpty() { - string? value = string.Empty; + string value = string.Empty; Assert.True(value.IsNullOrEmptyOrWhiteSpace()); } [Fact] public void IsNullEmptyOrWhiteSpace_ReturnTrue_WhenValueIsWhiteSpace() { - string? value = " "; + string value = " "; Assert.True(value.IsNullOrEmptyOrWhiteSpace()); } [Fact] public void AsString_ReturnEmpty_WhenArrayIsEmpty() { - string[] arr = Array.Empty(); + string[] arr = []; Assert.Equal(string.Empty, arr.ToStr()); } @@ -45,7 +45,7 @@ public void AsString_ReturnValid_WhenArrayIsValid() [Fact] public void AsString_ReturnValid_WhenArrayWithSeperatorIsValid() { - string[] arr = new[] { "Pritom", "Purkayasta" }; + string[] arr = ["Pritom", "Purkayasta"]; Assert.Equal("Pritom Purkayasta", arr.ToStr(" ")); } } diff --git a/test/UtilityVerse.UnitTest/UtilityVerse.UnitTest.csproj b/test/UtilityVerse.UnitTest/UtilityVerse.UnitTest.csproj index 96d442b..9c80242 100644 --- a/test/UtilityVerse.UnitTest/UtilityVerse.UnitTest.csproj +++ b/test/UtilityVerse.UnitTest/UtilityVerse.UnitTest.csproj @@ -1,29 +1,35 @@ - - net6.0 - enable - enable + + net8.0 + enable + disable - false - true - + false + true + - - - - - runtime; build; native; contentfiles; analyzers; buildtransitive - all - - - runtime; build; native; contentfiles; analyzers; buildtransitive - all - - + + + + + - - - + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + + + From 83b1fca8e0f0b86786756639cb0dd6ebe2cb5ae3 Mon Sep 17 00:00:00 2001 From: purkayasta Date: Sun, 7 Jan 2024 21:57:18 +0600 Subject: [PATCH 04/20] updated byte extension file --- src/UtilityVerse/Extensions/ByteExtension.cs | 60 +++++++++++++++---- .../Helpers/UtilityVerseException.cs | 20 ------- src/UtilityVerse/Helpers/Utils.cs | 10 ++++ 3 files changed, 59 insertions(+), 31 deletions(-) delete mode 100644 src/UtilityVerse/Helpers/UtilityVerseException.cs create mode 100644 src/UtilityVerse/Helpers/Utils.cs diff --git a/src/UtilityVerse/Extensions/ByteExtension.cs b/src/UtilityVerse/Extensions/ByteExtension.cs index d847701..9ed3b6f 100644 --- a/src/UtilityVerse/Extensions/ByteExtension.cs +++ b/src/UtilityVerse/Extensions/ByteExtension.cs @@ -4,7 +4,9 @@ // --------------------------------------------------------------- using System; +using System.Xml.Serialization; using UtilityVerse.Helpers; +using UtilityVerse.Shared; namespace UtilityVerse.Extensions; @@ -15,29 +17,65 @@ public static class ByteExtension { #if NETCOREAPP3_1_OR_GREATER /// - /// This method will convert to an object from byte array + /// This method will convert to an object from byte array (NOTE: Only Json is Supported) /// /// - /// object - public static object ToObject(this byte[] byteArr) + /// UtilityVerseResult + public static UtilityVerseResult ToObject(this byte[] byteArr) { - UtilityVerseException.ThrowIfNull(byteArr); - return System.Text.Json.JsonSerializer.Deserialize(byteArr); + if (byteArr is null || byteArr.Length < 1) return new UtilityVerseResult("byte array is invalid"); + try + { + return new UtilityVerseResult(System.Text.Json.JsonSerializer.Deserialize(byteArr)); + } + catch (System.Text.Json.JsonException jsonException) + { + return new UtilityVerseResult($"Json Exception: {jsonException}"); + } + catch (Exception unknownException) + { + return new UtilityVerseResult($"Unknown exception: {unknownException}"); + } } #endif #if NETCOREAPP3_1_OR_GREATER - /// - /// This method will convert byte array into desired model. + /// This method will convert byte array into desired model. (NOTE: Only Json is Supported) /// /// /// - /// T - public static T To(this byte[] byteArr) + /// UtilityVerseResult + public static UtilityVerseResult To(this byte[] byteArr) + { + if (byteArr is null || byteArr.Length < 1) return new UtilityVerseResult("Invalid Byte Array"); + + try + { + if (Utils.IsStruct(typeof(T))) + { + return new UtilityVerseResult(ConvertStructIntoT(byteArr)); + } + return new UtilityVerseResult(System.Text.Json.JsonSerializer.Deserialize(byteArr)); + } + catch (System.Text.Json.JsonException jsonException) + { + return new UtilityVerseResult($"Json Exception: {jsonException}"); + } + catch (Exception unknownException) + { + return new UtilityVerseResult($"Unknown Exception: {unknownException}"); + } + } + + private static T ConvertStructIntoT(byte[] arrBytes) { - UtilityVerseException.ThrowIfNull(byteArr); - return System.Text.Json.JsonSerializer.Deserialize(byteArr); + int size = arrBytes.Length; + IntPtr ptr = System.Runtime.InteropServices.Marshal.AllocHGlobal(size); + System.Runtime.InteropServices.Marshal.Copy(arrBytes, 0, ptr, size); + object obj = System.Runtime.InteropServices.Marshal.PtrToStructure(ptr, typeof(T)); + System.Runtime.InteropServices.Marshal.FreeHGlobal(ptr); + return (T)obj; } #endif } diff --git a/src/UtilityVerse/Helpers/UtilityVerseException.cs b/src/UtilityVerse/Helpers/UtilityVerseException.cs deleted file mode 100644 index 407810e..0000000 --- a/src/UtilityVerse/Helpers/UtilityVerseException.cs +++ /dev/null @@ -1,20 +0,0 @@ -using System; - -namespace UtilityVerse.Helpers; - -internal class UtilityVerseException -{ - private static readonly string _value = "value"; - internal static void ThrowIfNullOrEmpty(string data, string paramName = null) - { - if (string.IsNullOrEmpty(data) || string.IsNullOrWhiteSpace(data)) - throw new Exception($"{(paramName is not null ? paramName : _value)} is null or empty"); - } - - internal static void ThrowIfNull(object data, string paramName = null) - { - if (data is null) throw new Exception($"{(paramName is not null ? paramName : _value)} is null"); - } - - internal static void Throw(string message) { throw new Exception(message); } -} diff --git a/src/UtilityVerse/Helpers/Utils.cs b/src/UtilityVerse/Helpers/Utils.cs new file mode 100644 index 0000000..eab3eda --- /dev/null +++ b/src/UtilityVerse/Helpers/Utils.cs @@ -0,0 +1,10 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace UtilityVerse.Helpers; + +internal static class Utils +{ + internal static bool IsStruct(Type type) => type.IsValueType && !type.IsPrimitive && !type.IsEnum; +} From cceeab1f7e881d7667482fe969fd8697ac3bbe94 Mon Sep 17 00:00:00 2001 From: purkayasta Date: Sun, 7 Jan 2024 21:57:45 +0600 Subject: [PATCH 05/20] updated Utility to follow UtilituResult type --- .../UtilityVerse.Shared.csproj | 9 ++++ .../UtilityVerseException.cs | 20 +++++++++ src/UtilityVerse.Shared/UtilityVerseResult.cs | 19 ++++++++ src/UtilityVerse/Utility.Currency.cs | 45 +++++++++++++++++-- src/UtilityVerse/Utility.cs | 3 -- src/UtilityVerse/UtilityRetry.cs | 2 +- src/UtilityVerse/UtilityVerse.csproj | 1 + .../ByteExtensionTest.cs | 28 ++++++++++++ 8 files changed, 119 insertions(+), 8 deletions(-) create mode 100644 src/UtilityVerse.Shared/UtilityVerse.Shared.csproj create mode 100644 src/UtilityVerse.Shared/UtilityVerseException.cs create mode 100644 src/UtilityVerse.Shared/UtilityVerseResult.cs create mode 100644 test/UtilityVerse.UnitTest/ByteExtensionTest.cs diff --git a/src/UtilityVerse.Shared/UtilityVerse.Shared.csproj b/src/UtilityVerse.Shared/UtilityVerse.Shared.csproj new file mode 100644 index 0000000..7eb9569 --- /dev/null +++ b/src/UtilityVerse.Shared/UtilityVerse.Shared.csproj @@ -0,0 +1,9 @@ + + + + netstandard2.1 + Latest + disable + + + diff --git a/src/UtilityVerse.Shared/UtilityVerseException.cs b/src/UtilityVerse.Shared/UtilityVerseException.cs new file mode 100644 index 0000000..9b99a52 --- /dev/null +++ b/src/UtilityVerse.Shared/UtilityVerseException.cs @@ -0,0 +1,20 @@ +using System; + +namespace UtilityVerse.Shared; + +public sealed class UtilityVerseException +{ + private static readonly string _value = "value"; + public static void ThrowIfNullOrEmpty(string data, string paramName = null) + { + if (string.IsNullOrEmpty(data) || string.IsNullOrWhiteSpace(data)) + throw new Exception($"{(paramName is not null ? paramName : _value)} is null or empty"); + } + + public static void ThrowIfNull(object data, string paramName = null) + { + if (data is null) throw new Exception($"{(paramName is not null ? paramName : _value)} is null"); + } + + public static void Throw(string message) { throw new Exception(message); } +} diff --git a/src/UtilityVerse.Shared/UtilityVerseResult.cs b/src/UtilityVerse.Shared/UtilityVerseResult.cs new file mode 100644 index 0000000..668e52a --- /dev/null +++ b/src/UtilityVerse.Shared/UtilityVerseResult.cs @@ -0,0 +1,19 @@ +namespace UtilityVerse.Shared; + +public class UtilityVerseResult +{ + public T Result { get; set; } + public string Error { get; set; } = string.Empty; + public bool HasError => Error.Length > 0; + + public UtilityVerseResult(T result) => Result = result; + + public UtilityVerseResult(string error) => Error = error; + + public UtilityVerseResult(T result, string error) + { + Result = result; + Error = error; + } +} + diff --git a/src/UtilityVerse/Utility.Currency.cs b/src/UtilityVerse/Utility.Currency.cs index 4f419e3..f7942dc 100644 --- a/src/UtilityVerse/Utility.Currency.cs +++ b/src/UtilityVerse/Utility.Currency.cs @@ -3,6 +3,8 @@ // FREE TO USE TO CONNECT THE WORLD // --------------------------------------------------------------- +using System; +using UtilityVerse.Shared; namespace UtilityVerse; @@ -12,8 +14,26 @@ public sealed partial class Utility /// This will convert dollar value to cent /// /// - /// decimal - public static decimal DollarToCent(decimal dollar) => dollar * 100; + /// UtilityVerseResult + public static UtilityVerseResult DollarToCent(decimal dollar) + { + try + { + return new UtilityVerseResult(dollar * 100); + } + catch (ArgumentOutOfRangeException) + { + return new UtilityVerseResult("Argument Out Of Range"); + } + catch (ArithmeticException) + { + return new UtilityVerseResult("Math Exception"); + } + catch (Exception unknownException) + { + return new UtilityVerseResult($"Unknown exception: {unknownException}"); + } + } /// @@ -21,6 +41,23 @@ public sealed partial class Utility /// /// /// decimal - public static decimal CentToDollar(decimal cent) => cent / 100; - + public static UtilityVerseResult CentToDollar(decimal cent) + { + try + { + return new UtilityVerseResult(result: cent / 100); + } + catch (ArgumentOutOfRangeException) + { + return new UtilityVerseResult(error: "Out of Range Exception"); + } + catch (ArithmeticException) + { + return new UtilityVerseResult(error: "Arithmetic Exception Occurred"); + } + catch (Exception exception) + { + return new UtilityVerseResult(error: $"Unknow Exception: {exception}"); + } + } } diff --git a/src/UtilityVerse/Utility.cs b/src/UtilityVerse/Utility.cs index 3485918..d0beb6b 100644 --- a/src/UtilityVerse/Utility.cs +++ b/src/UtilityVerse/Utility.cs @@ -3,9 +3,6 @@ // FREE TO USE TO CONNECT THE WORLD // --------------------------------------------------------------- - -using System.Runtime.InteropServices; - namespace UtilityVerse; /// diff --git a/src/UtilityVerse/UtilityRetry.cs b/src/UtilityVerse/UtilityRetry.cs index d118aa8..e1c6a35 100644 --- a/src/UtilityVerse/UtilityRetry.cs +++ b/src/UtilityVerse/UtilityRetry.cs @@ -7,7 +7,7 @@ using System; using System.Threading.Tasks; using UtilityVerse.Contracts; -using UtilityVerse.Helpers; +using UtilityVerse.Shared; namespace UtilityVerse; diff --git a/src/UtilityVerse/UtilityVerse.csproj b/src/UtilityVerse/UtilityVerse.csproj index 737864a..6eb56ad 100644 --- a/src/UtilityVerse/UtilityVerse.csproj +++ b/src/UtilityVerse/UtilityVerse.csproj @@ -44,6 +44,7 @@ True \ + True \ diff --git a/test/UtilityVerse.UnitTest/ByteExtensionTest.cs b/test/UtilityVerse.UnitTest/ByteExtensionTest.cs new file mode 100644 index 0000000..b3c0f4a --- /dev/null +++ b/test/UtilityVerse.UnitTest/ByteExtensionTest.cs @@ -0,0 +1,28 @@ +using System.Text.Json; +using System.Text.Json.Nodes; + +namespace UtilityVerse.UnitTest; + +public class ByteExtensionTest +{ + [Fact] + public void ToObject_ThrowsJsonException_WhenInvalidByteArrayProvided() + { + byte[] bytes = [1, 2, 3, 4, 5]; + var result = ByteExtension.ToObject(bytes); + Assert.Throws(() => result); + } + + [Fact] + public void ToObject_ReturnProperObject_WhenValidJsonByteArrayIsProvided() + { + JsonObject jObject = new() + { + { "key1", "value1" }, + { "key2", "value2" } + }; + var byteArr = JsonSerializer.SerializeToUtf8Bytes(jObject); + var result = ByteExtension.ToObject(byteArr); + Assert.Equal(result.Result, jObject.AsObject()); + } +} From 413efc8bf84b1eff46053bf2d6e9fbbfa5e7d83d Mon Sep 17 00:00:00 2001 From: purkayasta Date: Sun, 7 Jan 2024 21:57:59 +0600 Subject: [PATCH 06/20] updated Solution --- UtilityVerse-Soln.sln | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/UtilityVerse-Soln.sln b/UtilityVerse-Soln.sln index ba2c3fb..8f7739c 100644 --- a/UtilityVerse-Soln.sln +++ b/UtilityVerse-Soln.sln @@ -24,6 +24,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "UtilityVerse.ASPNET", "src\ EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "UtilityVerse.UnitTest", "test\UtilityVerse.UnitTest\UtilityVerse.UnitTest.csproj", "{8E9B3E7F-4E9A-429E-B180-8FC9A0641008}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "UtilityVerse.Shared", "src\UtilityVerse.Shared\UtilityVerse.Shared.csproj", "{AEFA0EB9-2A91-4FA2-9476-378A6DB76270}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -42,6 +44,10 @@ Global {8E9B3E7F-4E9A-429E-B180-8FC9A0641008}.Debug|Any CPU.Build.0 = Debug|Any CPU {8E9B3E7F-4E9A-429E-B180-8FC9A0641008}.Release|Any CPU.ActiveCfg = Release|Any CPU {8E9B3E7F-4E9A-429E-B180-8FC9A0641008}.Release|Any CPU.Build.0 = Release|Any CPU + {AEFA0EB9-2A91-4FA2-9476-378A6DB76270}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {AEFA0EB9-2A91-4FA2-9476-378A6DB76270}.Debug|Any CPU.Build.0 = Debug|Any CPU + {AEFA0EB9-2A91-4FA2-9476-378A6DB76270}.Release|Any CPU.ActiveCfg = Release|Any CPU + {AEFA0EB9-2A91-4FA2-9476-378A6DB76270}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -50,6 +56,7 @@ Global {256CD825-08FD-4531-8095-08833BABE1D3} = {4CA7E6FA-0DC4-4E66-BD2E-31A6447E6CD7} {D357B055-BFC5-4D26-B517-33F8ADB4B33E} = {4CA7E6FA-0DC4-4E66-BD2E-31A6447E6CD7} {8E9B3E7F-4E9A-429E-B180-8FC9A0641008} = {3D24A8E3-F6A7-4A0E-B22B-4DF324D85BB6} + {AEFA0EB9-2A91-4FA2-9476-378A6DB76270} = {4CA7E6FA-0DC4-4E66-BD2E-31A6447E6CD7} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {5D3B1F59-B642-4106-91C6-ADDA4D3BD4A5} From 2f0fd8b82a61edad6fb49af66639a879746954db Mon Sep 17 00:00:00 2001 From: purkayasta Date: Sun, 7 Jan 2024 22:14:46 +0600 Subject: [PATCH 07/20] updated Character and Object Extensions --- .../Extensions/CharacterExtension.cs | 32 ++++++++++------ .../Extensions/GenericExtension.cs | 37 ++++++++++++++++--- .../Extensions/ObjectExtension.cs | 36 +++++++++++++++--- 3 files changed, 84 insertions(+), 21 deletions(-) diff --git a/src/UtilityVerse/Extensions/CharacterExtension.cs b/src/UtilityVerse/Extensions/CharacterExtension.cs index aeb0c70..fa6c6a4 100644 --- a/src/UtilityVerse/Extensions/CharacterExtension.cs +++ b/src/UtilityVerse/Extensions/CharacterExtension.cs @@ -3,6 +3,8 @@ // FREE TO USE TO CONNECT THE WORLD // --------------------------------------------------------------- +using UtilityVerse.Shared; + namespace UtilityVerse.Extensions; /// @@ -16,22 +18,30 @@ public static class CharacterExtension /// /// /// string - public static string ToStr(this char[] characterArray, string separator = null) + public static UtilityVerseResult ToStr(this char[] characterArray, string separator = null) { - if (characterArray is null || characterArray.Length < 1) return string.Empty; + if (characterArray is null || characterArray.Length < 1) + return new UtilityVerseResult("Invalid Character Array is given"); - var sb = new System.Text.StringBuilder(); + try + { + var sb = new System.Text.StringBuilder(); - bool isSeparatorExist = !string.IsNullOrEmpty(separator); + bool isSeparatorExist = !string.IsNullOrEmpty(separator); - for (int index = 0; index < characterArray.Length; index++) - { - sb.Append(characterArray[index]); + for (int index = 0; index < characterArray.Length; index++) + { + sb.Append(characterArray[index]); - if (isSeparatorExist && index < characterArray.Length - 1) - sb.Append(separator); - } + if (isSeparatorExist && index < characterArray.Length - 1) + sb.Append(separator); + } - return sb.ToString(); + return new UtilityVerseResult(sb.ToString()); + } + catch (System.Exception unknownException) + { + return new UtilityVerseResult($"Unknown exception: {unknownException}"); + } } } diff --git a/src/UtilityVerse/Extensions/GenericExtension.cs b/src/UtilityVerse/Extensions/GenericExtension.cs index d8ceeb9..b139468 100644 --- a/src/UtilityVerse/Extensions/GenericExtension.cs +++ b/src/UtilityVerse/Extensions/GenericExtension.cs @@ -5,6 +5,7 @@ using System; using UtilityVerse.Helpers; +using UtilityVerse.Shared; namespace UtilityVerse.Extensions; @@ -19,12 +20,38 @@ public static class GenericExtension /// /// /// - /// byte[] - /// - public static byte[] ToByteArray(this T instance) + /// UtilityVerseResult + public static UtilityVerseResult ToByteArray(this T instance) { - UtilityVerseException.ThrowIfNull(instance); - return System.Text.Encoding.UTF8.GetBytes(System.Text.Json.JsonSerializer.Serialize(instance)); + if (instance is null) return new UtilityVerseResult("instance is null or invalid"); + + try + { + if (Utils.IsStruct(instance.GetType())) + { + return new UtilityVerseResult(ConvertStructIntoByteArray(instance)); + } + return new UtilityVerseResult(System.Text.Json.JsonSerializer.SerializeToUtf8Bytes(instance)); + } + catch (System.Text.Json.JsonException jsonException) + { + return new UtilityVerseResult($"Json Exception: {jsonException.Message}"); + } + catch (Exception unknownException) + { + return new UtilityVerseResult($"unknown exception: {unknownException}"); + } + } + + private static byte[] ConvertStructIntoByteArray(object obj) + { + int size = System.Runtime.InteropServices.Marshal.SizeOf(obj); + byte[] arr = new byte[size]; + IntPtr ptr = System.Runtime.InteropServices.Marshal.AllocHGlobal(size); + System.Runtime.InteropServices.Marshal.StructureToPtr(obj, ptr, true); + System.Runtime.InteropServices.Marshal.Copy(ptr, arr, 0, size); + System.Runtime.InteropServices.Marshal.FreeHGlobal(ptr); + return arr; } #endif } diff --git a/src/UtilityVerse/Extensions/ObjectExtension.cs b/src/UtilityVerse/Extensions/ObjectExtension.cs index 89809a1..466d99d 100644 --- a/src/UtilityVerse/Extensions/ObjectExtension.cs +++ b/src/UtilityVerse/Extensions/ObjectExtension.cs @@ -5,6 +5,7 @@ using System; using UtilityVerse.Helpers; +using UtilityVerse.Shared; namespace UtilityVerse.Extensions; @@ -15,15 +16,40 @@ public static class ObjectExtension { #if NETCOREAPP3_1_OR_GREATER /// - /// This method will convert any object into a byte array. + /// This method will convert any object into a byte array. (NOTE: Only Json Is Supported) /// /// /// byte[] - /// - public static byte[] ToByteArray(this object obj) + public static UtilityVerseResult ToByteArray(this object obj) { - UtilityVerseException.ThrowIfNull(obj); - return System.Text.Encoding.UTF8.GetBytes(System.Text.Json.JsonSerializer.Serialize(obj)); + if (obj is null) return new UtilityVerseResult("Object is null or invalid"); + try + { + if (Utils.IsStruct(obj.GetType())) + { + return new UtilityVerseResult(ConvertStructObjectIntoByteArray(obj)); + } + return new UtilityVerseResult(System.Text.Json.JsonSerializer.SerializeToUtf8Bytes(obj)); + } + catch (System.Text.Json.JsonException) + { + return new UtilityVerseResult("Json Parsing Exception"); + } + catch (Exception unknownException) + { + return new UtilityVerseResult($"Unknown exception: {unknownException}"); + } + } + + private static byte[] ConvertStructObjectIntoByteArray(object obj) + { + int size = System.Runtime.InteropServices.Marshal.SizeOf(obj); + byte[] arr = new byte[size]; + IntPtr ptr = System.Runtime.InteropServices.Marshal.AllocHGlobal(size); + System.Runtime.InteropServices.Marshal.StructureToPtr(obj, ptr, true); + System.Runtime.InteropServices.Marshal.Copy(ptr, arr, 0, size); + System.Runtime.InteropServices.Marshal.FreeHGlobal(ptr); + return arr; } #endif } From 9865cca2adced6352ca14204f7ef175193d6198a Mon Sep 17 00:00:00 2001 From: purkayasta Date: Mon, 8 Jan 2024 01:44:15 +0600 Subject: [PATCH 08/20] removed exception and updated with a result type --- src/UtilityVerse/Contracts/PagedList.cs | 4 +- .../Extensions/DateOnlyExtension.cs | 24 ++---- .../Extensions/DateTimeExtension.cs | 43 ++++------ .../Extensions/DecimalExtension.cs | 10 +-- .../Extensions/DoubleExtension.cs | 12 +-- src/UtilityVerse/Extensions/IntExtension.cs | 35 +++------ src/UtilityVerse/Extensions/LongExtension.cs | 50 +++++------- .../Extensions/ObjectExtension.cs | 2 +- .../Extensions/PagedListExtension.cs | 78 ++++++++++--------- src/UtilityVerse/Extensions/SpanExtension.cs | 21 ++++- .../Extensions/StringExtension.cs | 1 - src/UtilityVerse/Helpers/Variables.cs | 9 +++ src/UtilityVerse/UtilityVerse.csproj | 2 +- src/UtilityVerse/breaking-changes.md | 5 ++ src/UtilityVerse/tasks.md | 4 + .../ByteExtensionTest.cs | 2 +- .../DateTimeExtensionTest.cs | 22 +++--- .../GenericExtensionTest.cs | 8 +- .../UtilityVerse.UnitTest/IntExtensionTest.cs | 8 +- 19 files changed, 164 insertions(+), 176 deletions(-) create mode 100644 src/UtilityVerse/breaking-changes.md create mode 100644 src/UtilityVerse/tasks.md diff --git a/src/UtilityVerse/Contracts/PagedList.cs b/src/UtilityVerse/Contracts/PagedList.cs index cf018be..2d0b2b4 100644 --- a/src/UtilityVerse/Contracts/PagedList.cs +++ b/src/UtilityVerse/Contracts/PagedList.cs @@ -18,12 +18,12 @@ public class PagedList /// /// If the instance has any error or not. If error found then it will true. /// - public bool IsValid { get; set; } + public bool IsValid => Errors.Length > 0; /// /// Number of results. /// - public int Count { get; set; } + public int Count => Results.Count(); /// /// The collection. diff --git a/src/UtilityVerse/Extensions/DateOnlyExtension.cs b/src/UtilityVerse/Extensions/DateOnlyExtension.cs index 224ec8a..7845634 100644 --- a/src/UtilityVerse/Extensions/DateOnlyExtension.cs +++ b/src/UtilityVerse/Extensions/DateOnlyExtension.cs @@ -4,7 +4,7 @@ // --------------------------------------------------------------- using System; -using UtilityVerse.Helpers; +using UtilityVerse.Shared; namespace UtilityVerse.Extensions; @@ -23,16 +23,12 @@ public static class DateOnlyExtension /// /// bool /// - public static bool IsInBetween(this DateOnly dt, DateOnly startDate, DateOnly endDate) + public static UtilityVerseResult IsInBetween(this DateOnly dt, DateOnly startDate, DateOnly endDate) { - UtilityVerseException.ThrowIfNull(dt); - UtilityVerseException.ThrowIfNull(startDate, nameof(startDate)); - UtilityVerseException.ThrowIfNull(endDate, nameof(endDate)); - if (startDate > endDate) - UtilityVerseException.Throw(nameof(startDate) + " cannot be greater than " + nameof(endDate)); + return new(nameof(startDate) + " cannot be greater than " + nameof(endDate)); - return dt >= startDate && dt <= endDate; + return new(dt >= startDate && dt <= endDate); } /// @@ -40,13 +36,11 @@ public static bool IsInBetween(this DateOnly dt, DateOnly startDate, DateOnly en /// /// /// - /// int - /// - public static int ToIntDate(this DateOnly dateOnly, string format = "yyyyMMdd") + /// UtilityVerseResult + public static UtilityVerseResult ToIntDate(this DateOnly dateOnly, string format = "yyyyMMdd") { - UtilityVerseException.ThrowIfNull(dateOnly); - UtilityVerseException.ThrowIfNullOrEmpty(format); - return int.TryParse(dateOnly.ToString(format), out var result) ? result : -1; + if (int.TryParse(dateOnly.ToString(format), out var result)) return new(result); + return new("int parsing failed"); } /// @@ -57,7 +51,6 @@ public static int ToIntDate(this DateOnly dateOnly, string format = "yyyyMMdd") /// public static DateTime ToDateTime(this DateOnly dateOnly) { - UtilityVerseException.ThrowIfNull(dateOnly, nameof(dateOnly)); return new DateTime(year: dateOnly.Year, month: dateOnly.Month, day: dateOnly.Day, hour: 0, minute: 0, second: 0, kind: DateTimeKind.Local); } @@ -69,7 +62,6 @@ public static DateTime ToDateTime(this DateOnly dateOnly) /// public static DateTime ToUtcDateTime(this DateOnly dateOnly) { - UtilityVerseException.ThrowIfNull(dateOnly, nameof(dateOnly)); return new DateTime(year: dateOnly.Year, month: dateOnly.Month, day: dateOnly.Day, hour: 0, minute: 0, second: 0, kind: DateTimeKind.Utc); } } diff --git a/src/UtilityVerse/Extensions/DateTimeExtension.cs b/src/UtilityVerse/Extensions/DateTimeExtension.cs index 1ac3653..7e4f686 100644 --- a/src/UtilityVerse/Extensions/DateTimeExtension.cs +++ b/src/UtilityVerse/Extensions/DateTimeExtension.cs @@ -7,6 +7,7 @@ using System; using UtilityVerse.Contracts; using UtilityVerse.Helpers; +using UtilityVerse.Shared; namespace UtilityVerse.Extensions; @@ -21,18 +22,13 @@ public static class DateTimeExtension /// /// /// - /// bool - /// - public static bool IsInBetween(this DateTime dt, DateTime startDateTime, DateTime endDateTime) + /// UtilityVerseResult + public static UtilityVerseResult IsInBetween(this DateTime dt, DateTime startDateTime, DateTime endDateTime) { - UtilityVerseException.ThrowIfNull(dt); - UtilityVerseException.ThrowIfNull(startDateTime); - UtilityVerseException.ThrowIfNull(endDateTime); - if (endDateTime < startDateTime) - UtilityVerseException.Throw($"{nameof(endDateTime)} cannot be smaller than {nameof(startDateTime)}"); + return new($"{nameof(endDateTime)} cannot be smaller than {nameof(startDateTime)}"); - return dt >= startDateTime && dt <= endDateTime; + return new(dt >= startDateTime && dt <= endDateTime); } /// @@ -40,18 +36,14 @@ public static bool IsInBetween(this DateTime dt, DateTime startDateTime, DateTim /// /// /// - /// long - /// - /// - public static long ToUnixTimeStamp(this DateTime dt, TimeEnum timeEnum) + /// UtilityVerseResult + public static UtilityVerseResult ToUnixTimeStamp(this DateTime dt, TimeEnum timeEnum) { - UtilityVerseException.ThrowIfNull(dt); - return timeEnum switch { - TimeEnum.MilliSecond => new DateTimeOffset(dt).ToUnixTimeMilliseconds(), - TimeEnum.Second => new DateTimeOffset(dt).ToUnixTimeSeconds(), - _ => throw new NotImplementedException() + TimeEnum.MilliSecond => new(new DateTimeOffset(dt).ToUnixTimeMilliseconds()), + TimeEnum.Second => new(new DateTimeOffset(dt).ToUnixTimeSeconds()), + _ => new(StaticMessages.ContactDeveloperError) }; } @@ -60,13 +52,12 @@ public static long ToUnixTimeStamp(this DateTime dt, TimeEnum timeEnum) /// /// new DateTime() like [6/10/2023 4:02:06 PM] /// yyyyMMdd - /// int - /// - public static int ToIntDate(this DateTime dateTime, string format = "yyyyMMdd") + /// UtilityVerseResult + public static UtilityVerseResult ToIntDate(this DateTime dateTime, string format = "yyyyMMdd") { - UtilityVerseException.ThrowIfNull(dateTime, nameof(dateTime)); - UtilityVerseException.ThrowIfNullOrEmpty(format, nameof(format)); - return int.TryParse(dateTime.ToString(format), out var result) ? result : -1; + if (format.IsNullOrEmptyOrWhiteSpace()) return new(string.Format(StaticMessages.NullOrEmptyErrorWithParam, format)); + if (int.TryParse(dateTime.ToString(format), out int result)) return new(result); + return new("int parsing failed"); } /// @@ -74,11 +65,9 @@ public static int ToIntDate(this DateTime dateTime, string format = "yyyyMMdd") /// /// /// (int, int, int, int, int, int, int) - /// public static (int year, int month, int day, int hour, int minute, int second, int milisecond) DestructFromDateTime( this DateTime dateTime) { - UtilityVerseException.ThrowIfNull(dateTime); return (dateTime.Year, dateTime.Month, dateTime.Day, dateTime.Hour, dateTime.Minute, dateTime.Second, dateTime.Millisecond); } @@ -88,10 +77,8 @@ public static (int year, int month, int day, int hour, int minute, int second, i /// /// /// DateTime - /// public static DateTime ToUtcDateTime(this DateTime dateTime, bool includeTime = false) { - UtilityVerseException.ThrowIfNull(dateTime); if (includeTime) return new DateTime(year: dateTime.Year, month: dateTime.Month, day: dateTime.Day, hour: dateTime.Hour, minute: dateTime.Minute, second: dateTime.Second, kind: DateTimeKind.Utc); return new DateTime(year: dateTime.Year, month: dateTime.Month, day: dateTime.Day, hour: 0, minute: 0, second: 0, kind: DateTimeKind.Utc); } diff --git a/src/UtilityVerse/Extensions/DecimalExtension.cs b/src/UtilityVerse/Extensions/DecimalExtension.cs index d1f68f7..b8afe4a 100644 --- a/src/UtilityVerse/Extensions/DecimalExtension.cs +++ b/src/UtilityVerse/Extensions/DecimalExtension.cs @@ -3,7 +3,7 @@ // FREE TO USE TO CONNECT THE WORLD // --------------------------------------------------------------- -using UtilityVerse.Helpers; +using UtilityVerse.Shared; namespace UtilityVerse.Extensions; @@ -17,10 +17,10 @@ public static class DecimalExtension /// /// /// - /// string - public static string ToStr(this decimal[] decimalArray, string separator = null) + /// UtilityVerseResult + public static UtilityVerseResult ToStr(this decimal[] decimalArray, string separator = null) { - if (decimalArray is null || decimalArray.Length < 1) UtilityVerseException.Throw("array is null"); + if (decimalArray is null || decimalArray.Length < 1) UtilityVerseException.Throw("Decimal Array is null or empty"); var sb = new System.Text.StringBuilder(); @@ -34,6 +34,6 @@ public static string ToStr(this decimal[] decimalArray, string separator = null) sb.Append(separator); } - return sb.ToString(); + return new UtilityVerseResult(sb.ToString()); } } diff --git a/src/UtilityVerse/Extensions/DoubleExtension.cs b/src/UtilityVerse/Extensions/DoubleExtension.cs index 8ae582b..5bb08f8 100644 --- a/src/UtilityVerse/Extensions/DoubleExtension.cs +++ b/src/UtilityVerse/Extensions/DoubleExtension.cs @@ -3,6 +3,8 @@ // FREE TO USE TO CONNECT THE WORLD // --------------------------------------------------------------- +using UtilityVerse.Shared; + namespace UtilityVerse.Extensions; /// @@ -15,12 +17,12 @@ public static class DoubleExtension /// /// /// - /// - public static string ToStr(this double[] doubleArray, string separator = null) + /// UtilityVerseResult + public static UtilityVerseResult ToStr(this double[] doubleArray, string separator = null) { - if (doubleArray is null || doubleArray.Length < 1) return string.Empty; - var sb = new System.Text.StringBuilder(); + if (doubleArray is null || doubleArray.Length < 1) return new UtilityVerseResult("Array is null or empty"); + var sb = new System.Text.StringBuilder(); bool isSeparatorExist = !string.IsNullOrEmpty(separator); for (int index = 0; index < doubleArray.Length; index++) @@ -31,6 +33,6 @@ public static string ToStr(this double[] doubleArray, string separator = null) sb.Append(separator); } - return sb.ToString(); + return new UtilityVerseResult(sb.ToString()); } } diff --git a/src/UtilityVerse/Extensions/IntExtension.cs b/src/UtilityVerse/Extensions/IntExtension.cs index 9bc0292..03b7666 100644 --- a/src/UtilityVerse/Extensions/IntExtension.cs +++ b/src/UtilityVerse/Extensions/IntExtension.cs @@ -5,6 +5,7 @@ using System; using UtilityVerse.Helpers; +using UtilityVerse.Shared; namespace UtilityVerse.Extensions; @@ -19,31 +20,13 @@ public static class IntExtension /// /// /// - /// bool - /// - public static bool IsInBetween(this int? source, int? min, int? max) - { - UtilityVerseException.ThrowIfNull(source, nameof(source)); - UtilityVerseException.ThrowIfNull(min, nameof(min)); - UtilityVerseException.ThrowIfNull(max, nameof(max)); - - return IsInBetween(source!.Value, min!.Value, max!.Value); - } - - /// - /// This method will help to determine that source number is in between or not. - /// - /// - /// - /// - /// bool - /// - public static bool IsInBetween(this int source, int min, int max) + /// UtilityVerseResult + public static UtilityVerseResult IsInBetween(this int source, int min, int max) { if (min > max) - UtilityVerseException.Throw(nameof(min) + " cannot be greater than " + nameof(max)); + return new UtilityVerseResult(nameof(min) + " cannot be greater than " + nameof(max)); - return source >= min && source <= max; + return new UtilityVerseResult(source >= min && source <= max); } @@ -52,10 +35,10 @@ public static bool IsInBetween(this int source, int min, int max) /// /// /// - /// - public static string ToStr(this int[] intArray, string separator = null) + /// UtilityVerseResult + public static UtilityVerseResult ToStr(this int[] intArray, string separator = null) { - if (intArray is null || intArray.Length < 1) return string.Empty; + if (intArray is null || intArray.Length < 1) return new UtilityVerseResult("int Array is empty"); var sb = new System.Text.StringBuilder(); @@ -69,6 +52,6 @@ public static string ToStr(this int[] intArray, string separator = null) sb.Append(separator); } - return sb.ToString(); + return new UtilityVerseResult(sb.ToString()); } } \ No newline at end of file diff --git a/src/UtilityVerse/Extensions/LongExtension.cs b/src/UtilityVerse/Extensions/LongExtension.cs index 61c4b42..8aac828 100644 --- a/src/UtilityVerse/Extensions/LongExtension.cs +++ b/src/UtilityVerse/Extensions/LongExtension.cs @@ -6,6 +6,7 @@ using System; using UtilityVerse.Contracts; using UtilityVerse.Helpers; +using UtilityVerse.Shared; namespace UtilityVerse.Extensions; @@ -14,36 +15,23 @@ namespace UtilityVerse.Extensions; /// public static class LongExtension { - /// - /// This method will convert any valid time stamp into a DateTime object - /// - /// - /// - /// - public static DateTime ToDateTime(this long? timeStamp, TimeEnum timeEnum) - { - UtilityVerseException.ThrowIfNull(timeStamp); - return ToDateTime(timeStamp.Value, timeEnum); - } + /// + /// This method will convert any valid time stamp into a DateTime object + /// + /// + /// + /// UtilityVerseResult + /// + public static UtilityVerseResult ToDateTime(this long timeStamp, TimeEnum timerEnum) + { + if (timeStamp < 1) + return new UtilityVerseResult($"{nameof(timeStamp)} invalid time stamp value"); - /// - /// This method will convert any valid time stamp into a DateTime object - /// - /// - /// - /// - /// - /// - public static DateTime ToDateTime(this long timeStamp, TimeEnum timeEnum) - { - if (timeStamp < 1) - UtilityVerseException.Throw($"{nameof(timeStamp)} invalid time stamp value"); - - return timeEnum switch - { - TimeEnum.MilliSecond => DateTimeOffset.FromUnixTimeMilliseconds(timeStamp).DateTime, - TimeEnum.Second => DateTimeOffset.FromUnixTimeSeconds(timeStamp).DateTime, - _ => throw new NotImplementedException() - }; - } + return timerEnum switch + { + TimeEnum.MilliSecond => new UtilityVerseResult(DateTimeOffset.FromUnixTimeMilliseconds(timeStamp).DateTime), + TimeEnum.Second => new UtilityVerseResult(DateTimeOffset.FromUnixTimeSeconds(timeStamp).DateTime), + _ => throw new NotImplementedException() + }; + } } diff --git a/src/UtilityVerse/Extensions/ObjectExtension.cs b/src/UtilityVerse/Extensions/ObjectExtension.cs index 466d99d..157da12 100644 --- a/src/UtilityVerse/Extensions/ObjectExtension.cs +++ b/src/UtilityVerse/Extensions/ObjectExtension.cs @@ -19,7 +19,7 @@ public static class ObjectExtension /// This method will convert any object into a byte array. (NOTE: Only Json Is Supported) /// /// - /// byte[] + /// UtilityVerseResult public static UtilityVerseResult ToByteArray(this object obj) { if (obj is null) return new UtilityVerseResult("Object is null or invalid"); diff --git a/src/UtilityVerse/Extensions/PagedListExtension.cs b/src/UtilityVerse/Extensions/PagedListExtension.cs index e6c93b2..658a952 100644 --- a/src/UtilityVerse/Extensions/PagedListExtension.cs +++ b/src/UtilityVerse/Extensions/PagedListExtension.cs @@ -3,11 +3,9 @@ // FREE TO USE TO CONNECT THE WORLD // --------------------------------------------------------------- - using System; using System.Linq; using UtilityVerse.Contracts; -using UtilityVerse.Helpers; namespace UtilityVerse.Extensions; @@ -16,40 +14,44 @@ namespace UtilityVerse.Extensions; /// public static class PagedListExtension { - /// - /// This will convert your list into a paged list. (Pagination helper). - /// - /// - /// - /// - /// - /// - /// - public static PagedList ToPagedList(this IQueryable queryable, int index, int pageSize) - { - if (index == default || pageSize == default) - UtilityVerseException.Throw($"{nameof(index)} or {nameof(pageSize)} params value is invalid"); - - var pagedList = new PagedList(); - try - { - var result = queryable.Take(pageSize).Skip(index * pageSize).ToList(); - pagedList.Results = result; - pagedList.Count = result.Count; - pagedList.IsValid = true; - } - catch (Exception exception) - { - pagedList.IsValid = false; - pagedList.Errors = ExtractExceptionError(exception); - } - - return pagedList; - } - - - private static string ExtractExceptionError(Exception exception) - => exception is null - ? string.Empty - : $"Exception Message: {exception.Message}. Inner Exception: {exception.InnerException?.Message}"; + /// + /// This will convert your list into a paged list. (Pagination helper). + /// + /// + /// + /// + /// + /// PagedList + public static PagedList ToPagedList(this IQueryable queryable, int index, int pageSize) + { + if (index == default || pageSize == default) + { + return new PagedList + { + Errors = $"{nameof(index)} or {nameof(pageSize)} params value is invalid" + }; + } + + var pagedList = new PagedList(); + try + { + var result = queryable.Take(pageSize).Skip(index * pageSize).ToList(); + pagedList.Results = result; + } + catch (Exception exception) + { + pagedList.Errors = ExtractExceptionError(exception); + } + + return pagedList; + } + + private static string ExtractExceptionError(Exception exception) + { + if (exception is null) + { + return string.Empty; + } + return $"Exception Message: {exception.Message}. Inner Exception: {exception.InnerException?.Message}"; + } } \ No newline at end of file diff --git a/src/UtilityVerse/Extensions/SpanExtension.cs b/src/UtilityVerse/Extensions/SpanExtension.cs index 2e0fcb9..557696d 100644 --- a/src/UtilityVerse/Extensions/SpanExtension.cs +++ b/src/UtilityVerse/Extensions/SpanExtension.cs @@ -5,6 +5,7 @@ using System; +using UtilityVerse.Shared; namespace UtilityVerse.Extensions; @@ -19,8 +20,24 @@ public static class SpanExtension /// This method will convert an readonly span byte array into an object. /// /// - /// - public static object ToObject(this ReadOnlySpan spanByteArr) => System.Text.Json.JsonSerializer.Deserialize(spanByteArr); + /// UtilityVerseResult + public static UtilityVerseResult ToObject(this ReadOnlySpan spanByteArr) + { + if (spanByteArr.Length < 1) return new UtilityVerseResult("Byte Array is invalid"); + + try + { + return new UtilityVerseResult(System.Text.Json.JsonSerializer.Deserialize(spanByteArr)); + } + catch (System.Text.Json.JsonException) + { + return new UtilityVerseResult("Json parsing error"); + } + catch (Exception unknownException) + { + return new UtilityVerseResult($"Unknown exception: {unknownException}"); + } + } } #endif \ No newline at end of file diff --git a/src/UtilityVerse/Extensions/StringExtension.cs b/src/UtilityVerse/Extensions/StringExtension.cs index 0438e21..2f95dc4 100644 --- a/src/UtilityVerse/Extensions/StringExtension.cs +++ b/src/UtilityVerse/Extensions/StringExtension.cs @@ -52,7 +52,6 @@ public static bool IsNullOrEmptyOrWhiteSpace(this string val) return string.IsNullOrEmpty(val) || string.IsNullOrWhiteSpace(val.Trim()); } - /// /// This extension method will convert any string array into string with separator. /// diff --git a/src/UtilityVerse/Helpers/Variables.cs b/src/UtilityVerse/Helpers/Variables.cs index b5f2da0..ee7e3b3 100644 --- a/src/UtilityVerse/Helpers/Variables.cs +++ b/src/UtilityVerse/Helpers/Variables.cs @@ -5,4 +5,13 @@ internal static class Variables internal const string IpAddressRegex = @"\b(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\b"; internal const string EmailAddressRegex = @"^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$"; internal const string InvalidCharacterRegex = @"[^a-z0-9\s-]"; +} + + + +internal class StaticMessages +{ + internal const string ContactDeveloperError = "Error Occurred, Contact Developer!"; + internal const string NullOrEmptyError = "Field value is null or empty"; + internal const string NullOrEmptyErrorWithParam = "{} value is null or empty"; } \ No newline at end of file diff --git a/src/UtilityVerse/UtilityVerse.csproj b/src/UtilityVerse/UtilityVerse.csproj index 6eb56ad..0cb4672 100644 --- a/src/UtilityVerse/UtilityVerse.csproj +++ b/src/UtilityVerse/UtilityVerse.csproj @@ -9,7 +9,7 @@ MIT true UtilityVerse - 0.6 + 1.0 utility, utils, C#, dotnet, c# utility, dotnet core a curated list of utility methods that will help you, do your work at your best. Pritom Purkayasta diff --git a/src/UtilityVerse/breaking-changes.md b/src/UtilityVerse/breaking-changes.md new file mode 100644 index 0000000..f561dd3 --- /dev/null +++ b/src/UtilityVerse/breaking-changes.md @@ -0,0 +1,5 @@ +# Breaking Changes + +- [x] Upgraded to .net8.0 and .netstandard2.1 +- [x] Instead of throwing exception, returning a data type where errors are given in a text so that developer can log it and handle their own way. +- [x] Remove Nullability. \ No newline at end of file diff --git a/src/UtilityVerse/tasks.md b/src/UtilityVerse/tasks.md new file mode 100644 index 0000000..3aef837 --- /dev/null +++ b/src/UtilityVerse/tasks.md @@ -0,0 +1,4 @@ +# Tasks + +- [ ] Rewrite the unit test and test only the core logic part. +- [ ] Upload into the nuget. \ No newline at end of file diff --git a/test/UtilityVerse.UnitTest/ByteExtensionTest.cs b/test/UtilityVerse.UnitTest/ByteExtensionTest.cs index b3c0f4a..f725e55 100644 --- a/test/UtilityVerse.UnitTest/ByteExtensionTest.cs +++ b/test/UtilityVerse.UnitTest/ByteExtensionTest.cs @@ -22,7 +22,7 @@ public void ToObject_ReturnProperObject_WhenValidJsonByteArrayIsProvided() { "key2", "value2" } }; var byteArr = JsonSerializer.SerializeToUtf8Bytes(jObject); - var result = ByteExtension.ToObject(byteArr); + Shared.UtilityVerseResult result = ByteExtension.ToObject(byteArr); Assert.Equal(result.Result, jObject.AsObject()); } } diff --git a/test/UtilityVerse.UnitTest/DateTimeExtensionTest.cs b/test/UtilityVerse.UnitTest/DateTimeExtensionTest.cs index 3e40206..1327daf 100644 --- a/test/UtilityVerse.UnitTest/DateTimeExtensionTest.cs +++ b/test/UtilityVerse.UnitTest/DateTimeExtensionTest.cs @@ -13,7 +13,7 @@ public void IsInBetween_ReturnsTrue_WhenDateTimeIsCorrect() DateTime examDate = DateTime.Now; DateTime startDate = new DateTime(2020, 01, 01); DateTime endDate = new DateTime(2030, 01, 01); - Assert.True(examDate.IsInBetween(startDate, endDate)); + Assert.True(examDate.IsInBetween(startDate, endDate).Result); } [Fact] @@ -22,7 +22,7 @@ public void IsInBetween_ReturnsFalse_WhenDataIsInvalid() DateTime examDate = DateTime.Now; DateTime startDate = new DateTime(2020, 01, 01); DateTime endDate = new DateTime(2022, 01, 01); - Assert.False(examDate.IsInBetween(startDate, endDate)); + Assert.False(examDate.IsInBetween(startDate, endDate).Result); } @@ -30,31 +30,31 @@ public void IsInBetween_ReturnsFalse_WhenDataIsInvalid() public void ToUnixTimeStamp_ReturnValidIntTimeStamp_WhenValidDateIsProvided() { DateTime dt = new DateTime(1994, 08, 05, 00, 00, 01, DateTimeKind.Unspecified); - Assert.Equal(776023201, dt.ToUnixTimeStamp(Contracts.TimeEnum.Second)); + Assert.Equal(776023201, dt.ToUnixTimeStamp(Contracts.TimeEnum.Second).Result); } [Fact] public void ToDateTime_ReturnValidDateTime_WhenValidUnixEpochTimeIsProvided() { var newYear = new DateTime(2024, 01, 01, 0, 0, 0, DateTimeKind.Utc); - var unixEpochTime = newYear.ToUnixTimeStamp(Contracts.TimeEnum.Second); - Assert.Equal(unixEpochTime.ToDateTime(Contracts.TimeEnum.Second), newYear); + var unixEpochTime = newYear.ToUnixTimeStamp(Contracts.TimeEnum.Second).Result; + Assert.Equal(unixEpochTime.ToDateTime(Contracts.TimeEnum.Second).Result, newYear); } [Fact] public void ToDateTime_ReturnValidDateTime_WhenValidUnixEpochMillisecondTimeIsProvided() { var newYear = new DateTime(2024, 01, 01, 0, 0, 0, DateTimeKind.Utc); - var unixEpochTime = newYear.ToUnixTimeStamp(Contracts.TimeEnum.MilliSecond); - Assert.Equal(unixEpochTime.ToDateTime(Contracts.TimeEnum.MilliSecond), newYear); + var unixEpochTime = newYear.ToUnixTimeStamp(Contracts.TimeEnum.MilliSecond).Result; + Assert.Equal(unixEpochTime.ToDateTime(Contracts.TimeEnum.MilliSecond).Result, newYear); } [Fact] public void ToDateTime_ReturnDoesNotMatch_WhenMillisecondUnixTimeIsGiven_ButSecondUnixTimeIsExpected() { var newYear = new DateTime(2024, 01, 01, 0, 0, 0, DateTimeKind.Utc); - var unixEpochTime = newYear.ToUnixTimeStamp(Contracts.TimeEnum.Second); - Assert.NotEqual(unixEpochTime.ToDateTime(Contracts.TimeEnum.MilliSecond), newYear); + var unixEpochTime = newYear.ToUnixTimeStamp(Contracts.TimeEnum.Second).Result; + Assert.NotEqual(unixEpochTime.ToDateTime(Contracts.TimeEnum.MilliSecond).Result, newYear); } [Theory] @@ -63,7 +63,7 @@ public void ToDateTime_ReturnDoesNotMatch_WhenMillisecondUnixTimeIsGiven_ButSeco public void ToIntDate_ReturnValidInt_WhenValidDateTimeIsProvided(string dt, int dtInt) { var dateTime = DateTime.Parse(dt); - Assert.Equal(dateTime.ToIntDate(), dtInt); + Assert.Equal(dateTime.ToIntDate().Result, dtInt); } [Theory] @@ -72,6 +72,6 @@ public void ToIntDate_ReturnValidInt_WhenCustomFormatIsGiven(string dt, string f { var datetime = DateTime.Parse(dt); var result = datetime.ToIntDate(format); - Assert.Equal(result, dtInt); + Assert.Equal(result.Result, dtInt); } } \ No newline at end of file diff --git a/test/UtilityVerse.UnitTest/GenericExtensionTest.cs b/test/UtilityVerse.UnitTest/GenericExtensionTest.cs index d740250..78babcc 100644 --- a/test/UtilityVerse.UnitTest/GenericExtensionTest.cs +++ b/test/UtilityVerse.UnitTest/GenericExtensionTest.cs @@ -13,21 +13,21 @@ internal record User(string Name); public void ToByteArray_ReturnsValidByteArray_WhenValidInputIsProvided() { var user = new User("PRITOM PURKAYASTA"); - var expected = user.ToByteArray(); - Assert.Equal(expected.To(), user); + var expected = user.ToByteArray().Result; + Assert.Equal(expected.To().Result, user); } [Fact] public void To_ThrowException_WhenNullByteArrayIsGiven() { byte[] array = default; - Assert.Throws(() => array.To()); + Assert.Throws(() => array.To().Result); } [Fact] public void ToByteArray_ThrowsException_WhenNullClassIsProvided() { User user = default; - Assert.Throws(() => user.ToByteArray()); + Assert.Throws(() => user.ToByteArray().Result); } } diff --git a/test/UtilityVerse.UnitTest/IntExtensionTest.cs b/test/UtilityVerse.UnitTest/IntExtensionTest.cs index 16f0fea..bf7c6da 100644 --- a/test/UtilityVerse.UnitTest/IntExtensionTest.cs +++ b/test/UtilityVerse.UnitTest/IntExtensionTest.cs @@ -11,27 +11,27 @@ public class IntExtensionTest public void IsInBetween_ThrowException_IfMaxIsNull() { int source = 0; - Assert.Throws(() => source.IsInBetween(1, default)); + Assert.Throws(() => source.IsInBetween(1, default).Result); } [Fact] public void IsInBetween_ThrowException_WhenMinIsGreaterThanMax() { int source = 0; - Assert.Throws(() => source.IsInBetween(1, 0)); + Assert.Throws(() => source.IsInBetween(1, 0).Result); } [Fact] public void IsInBetween_ReturnFalse_WhenWrongDataIsProvided() { int source = 10; - Assert.False(source.IsInBetween(1, 9)); + Assert.False(source.IsInBetween(1, 9).Result); } [Fact] public void IsInBetween_ReturnTrue_WhenRightDataIsProvided() { int source = 100; - Assert.True(source.IsInBetween(10, 200)); + Assert.True(source.IsInBetween(10, 200).Result); } } From bbde226a99904c910931c4a00729ca620e82751f Mon Sep 17 00:00:00 2001 From: purkayasta Date: Mon, 8 Jan 2024 01:46:31 +0600 Subject: [PATCH 09/20] added files into the solution --- TODO.md | 3 +++ UtilityVerse-Soln.sln | 3 ++- src/UtilityVerse/tasks.md | 4 ---- 3 files changed, 5 insertions(+), 5 deletions(-) delete mode 100644 src/UtilityVerse/tasks.md diff --git a/TODO.md b/TODO.md index 8fd3094..12e47d7 100644 --- a/TODO.md +++ b/TODO.md @@ -6,3 +6,6 @@ - [ ] Mapper - [ ] Object Mapper Attribute with Source Code Generation. + +- [ ] Rewrite the unit test and test only the core logic part. +- [ ] Upload into the nuget. \ No newline at end of file diff --git a/UtilityVerse-Soln.sln b/UtilityVerse-Soln.sln index 8f7739c..26c1736 100644 --- a/UtilityVerse-Soln.sln +++ b/UtilityVerse-Soln.sln @@ -13,6 +13,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "__solution-items__", "__sol ProjectSection(SolutionItems) = preProject .gitattributes = .gitattributes .gitignore = .gitignore + src\UtilityVerse\breaking-changes.md = src\UtilityVerse\breaking-changes.md CONTRIBUTING.md = CONTRIBUTING.md LICENSE.txt = LICENSE.txt README.md = README.md @@ -24,7 +25,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "UtilityVerse.ASPNET", "src\ EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "UtilityVerse.UnitTest", "test\UtilityVerse.UnitTest\UtilityVerse.UnitTest.csproj", "{8E9B3E7F-4E9A-429E-B180-8FC9A0641008}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "UtilityVerse.Shared", "src\UtilityVerse.Shared\UtilityVerse.Shared.csproj", "{AEFA0EB9-2A91-4FA2-9476-378A6DB76270}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "UtilityVerse.Shared", "src\UtilityVerse.Shared\UtilityVerse.Shared.csproj", "{AEFA0EB9-2A91-4FA2-9476-378A6DB76270}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution diff --git a/src/UtilityVerse/tasks.md b/src/UtilityVerse/tasks.md deleted file mode 100644 index 3aef837..0000000 --- a/src/UtilityVerse/tasks.md +++ /dev/null @@ -1,4 +0,0 @@ -# Tasks - -- [ ] Rewrite the unit test and test only the core logic part. -- [ ] Upload into the nuget. \ No newline at end of file From bca05b6017197076c8e52c69c35bfe9a9bff3457 Mon Sep 17 00:00:00 2001 From: purkayasta Date: Sat, 28 Jun 2025 02:28:55 +0600 Subject: [PATCH 10/20] Added Copy --- .gitignore | 3 +- README.md | 1 + UtilityVerse-Soln.sln | 14 ++ src/UtilityVerse.Copy/Assets/copy.png | Bin 0 -> 42320 bytes src/UtilityVerse.Copy/Attributes/DeepCopy.cs | 9 + .../Attributes/ShallowCopy.cs | 9 + src/UtilityVerse.Copy/BENCHMARK.md | 6 + src/UtilityVerse.Copy/CONTRIBUTE.md | 45 ++++ src/UtilityVerse.Copy/CopyGenerator.cs | 81 +++++++ src/UtilityVerse.Copy/Extensions.cs | 9 + .../Generators/DeepCopyGenerator.cs | 198 ++++++++++++++++++ .../Generators/ShallowCopyGenerator.cs | 140 +++++++++++++ src/UtilityVerse.Copy/Helper.cs | 79 +++++++ src/UtilityVerse.Copy/Interfaces/IDeepCopy.cs | 6 + .../Interfaces/IShallowCopy.cs | 7 + src/UtilityVerse.Copy/README.md | 96 +++++++++ .../UtilityVerse.Copy.csproj | 63 ++++++ src/UtilityVerse.Copy/nuget.md | 88 ++++++++ test/UtilityVerse.Copy.Test/HelloWorld.cs | 33 +++ test/UtilityVerse.Copy.Test/Program.cs | 67 ++++++ .../UtilityVerse.Copy.Test.csproj | 15 ++ test/UtilityVerse.UnitTest/Usings.cs | 4 +- 22 files changed, 970 insertions(+), 3 deletions(-) create mode 100644 src/UtilityVerse.Copy/Assets/copy.png create mode 100644 src/UtilityVerse.Copy/Attributes/DeepCopy.cs create mode 100644 src/UtilityVerse.Copy/Attributes/ShallowCopy.cs create mode 100644 src/UtilityVerse.Copy/BENCHMARK.md create mode 100644 src/UtilityVerse.Copy/CONTRIBUTE.md create mode 100644 src/UtilityVerse.Copy/CopyGenerator.cs create mode 100644 src/UtilityVerse.Copy/Extensions.cs create mode 100644 src/UtilityVerse.Copy/Generators/DeepCopyGenerator.cs create mode 100644 src/UtilityVerse.Copy/Generators/ShallowCopyGenerator.cs create mode 100644 src/UtilityVerse.Copy/Helper.cs create mode 100644 src/UtilityVerse.Copy/Interfaces/IDeepCopy.cs create mode 100644 src/UtilityVerse.Copy/Interfaces/IShallowCopy.cs create mode 100644 src/UtilityVerse.Copy/README.md create mode 100644 src/UtilityVerse.Copy/UtilityVerse.Copy.csproj create mode 100644 src/UtilityVerse.Copy/nuget.md create mode 100644 test/UtilityVerse.Copy.Test/HelloWorld.cs create mode 100644 test/UtilityVerse.Copy.Test/Program.cs create mode 100644 test/UtilityVerse.Copy.Test/UtilityVerse.Copy.Test.csproj diff --git a/.gitignore b/.gitignore index 52d9dd1..ea0ad30 100644 --- a/.gitignore +++ b/.gitignore @@ -9,7 +9,8 @@ *.user *.userosscache *.sln.docstates - +.DS_Store +.idea/ # User-specific files (MonoDevelop/Xamarin Studio) *.userprefs diff --git a/README.md b/README.md index 0cfb988..b6ab50c 100644 --- a/README.md +++ b/README.md @@ -11,6 +11,7 @@ There are multiple packages in this universe. Use whatever suits you. 1. [UtilityVerse](https://www.nuget.org/packages/UtilityVerse/) - No dependency. Just the core utilities. 2. [UtilityVerse.ASPNET](https://www.nuget.org/packages/UtilityVerse.ASPNET/) - This is based on aspnet core. +3. [UtilityVerse.Copy]() - Roslyn based source generator to create shallow copy and deep copy for your objects. To Invoke this library, you may want to use ```Utility``` class. diff --git a/UtilityVerse-Soln.sln b/UtilityVerse-Soln.sln index 26c1736..7fd8430 100644 --- a/UtilityVerse-Soln.sln +++ b/UtilityVerse-Soln.sln @@ -27,6 +27,10 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "UtilityVerse.UnitTest", "te EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "UtilityVerse.Shared", "src\UtilityVerse.Shared\UtilityVerse.Shared.csproj", "{AEFA0EB9-2A91-4FA2-9476-378A6DB76270}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "UtilityVerse.Copy", "src\UtilityVerse.Copy\UtilityVerse.Copy.csproj", "{ACA65175-DE7A-4904-A071-E6C35F184D53}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "UtilityVerse.Copy.Test", "test\UtilityVerse.Copy.Test\UtilityVerse.Copy.Test.csproj", "{E41314B2-F762-4CAF-A9B6-5A85F5C63DF3}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -49,6 +53,14 @@ Global {AEFA0EB9-2A91-4FA2-9476-378A6DB76270}.Debug|Any CPU.Build.0 = Debug|Any CPU {AEFA0EB9-2A91-4FA2-9476-378A6DB76270}.Release|Any CPU.ActiveCfg = Release|Any CPU {AEFA0EB9-2A91-4FA2-9476-378A6DB76270}.Release|Any CPU.Build.0 = Release|Any CPU + {ACA65175-DE7A-4904-A071-E6C35F184D53}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {ACA65175-DE7A-4904-A071-E6C35F184D53}.Debug|Any CPU.Build.0 = Debug|Any CPU + {ACA65175-DE7A-4904-A071-E6C35F184D53}.Release|Any CPU.ActiveCfg = Release|Any CPU + {ACA65175-DE7A-4904-A071-E6C35F184D53}.Release|Any CPU.Build.0 = Release|Any CPU + {E41314B2-F762-4CAF-A9B6-5A85F5C63DF3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E41314B2-F762-4CAF-A9B6-5A85F5C63DF3}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E41314B2-F762-4CAF-A9B6-5A85F5C63DF3}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E41314B2-F762-4CAF-A9B6-5A85F5C63DF3}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -58,6 +70,8 @@ Global {D357B055-BFC5-4D26-B517-33F8ADB4B33E} = {4CA7E6FA-0DC4-4E66-BD2E-31A6447E6CD7} {8E9B3E7F-4E9A-429E-B180-8FC9A0641008} = {3D24A8E3-F6A7-4A0E-B22B-4DF324D85BB6} {AEFA0EB9-2A91-4FA2-9476-378A6DB76270} = {4CA7E6FA-0DC4-4E66-BD2E-31A6447E6CD7} + {ACA65175-DE7A-4904-A071-E6C35F184D53} = {4CA7E6FA-0DC4-4E66-BD2E-31A6447E6CD7} + {E41314B2-F762-4CAF-A9B6-5A85F5C63DF3} = {3D24A8E3-F6A7-4A0E-B22B-4DF324D85BB6} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {5D3B1F59-B642-4106-91C6-ADDA4D3BD4A5} diff --git a/src/UtilityVerse.Copy/Assets/copy.png b/src/UtilityVerse.Copy/Assets/copy.png new file mode 100644 index 0000000000000000000000000000000000000000..31de9a51b7cd0c5ab959ae458c6cf55ffc9052fc GIT binary patch literal 42320 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 JPDHLkV1gY+i5~y} literal 0 HcmV?d00001 diff --git a/src/UtilityVerse.Copy/Attributes/DeepCopy.cs b/src/UtilityVerse.Copy/Attributes/DeepCopy.cs new file mode 100644 index 0000000..643d634 --- /dev/null +++ b/src/UtilityVerse.Copy/Attributes/DeepCopy.cs @@ -0,0 +1,9 @@ +/// +/// Author: Pritom Purkayasta +/// + +using System; +namespace UtilityVerse.Copy.Attributes; + +[AttributeUsage(AttributeTargets.Class, Inherited = false)] +public sealed class DeepCopy : Attribute; \ No newline at end of file diff --git a/src/UtilityVerse.Copy/Attributes/ShallowCopy.cs b/src/UtilityVerse.Copy/Attributes/ShallowCopy.cs new file mode 100644 index 0000000..a702626 --- /dev/null +++ b/src/UtilityVerse.Copy/Attributes/ShallowCopy.cs @@ -0,0 +1,9 @@ +/// +/// Author: Pritom Purkayasta +/// + +using System; +namespace UtilityVerse.Copy.Attributes; + +[AttributeUsage(AttributeTargets.Class, Inherited = false)] +public sealed class ShallowCopy : Attribute; \ No newline at end of file diff --git a/src/UtilityVerse.Copy/BENCHMARK.md b/src/UtilityVerse.Copy/BENCHMARK.md new file mode 100644 index 0000000..75d843c --- /dev/null +++ b/src/UtilityVerse.Copy/BENCHMARK.md @@ -0,0 +1,6 @@ + +# Benchmark + + +## Surprise!!! +This is work in progress! \ No newline at end of file diff --git a/src/UtilityVerse.Copy/CONTRIBUTE.md b/src/UtilityVerse.Copy/CONTRIBUTE.md new file mode 100644 index 0000000..e2f9559 --- /dev/null +++ b/src/UtilityVerse.Copy/CONTRIBUTE.md @@ -0,0 +1,45 @@ +🙌 Contributing + +We welcome contributions! To ensure a smooth process, please follow these steps: + +- Open an Issue First +- Before starting work on any feature or fix, please open an issue describing the problem or proposal. This helps avoid duplicate work and aligns with the project's goals. + +## Fork and Clone +Fork the repository to your own GitHub account and clone it locally: +```bash +git clone https://github.com/your-username/copy.git +cd copy +``` + +Create a Branch +Use a descriptive branch name based on the issue or feature: + +```bash +git checkout -b feature/deepcopy-support-for-records +``` + +Add Use Cases and Tests + +Include clear and relevant use cases that your change supports. + +Add unit tests and integration tests to cover new behavior. + +Ensure that all existing tests pass (dotnet test). + +Commit and Push +Push your changes to your fork: + +```bash +git push origin feature/deepcopy-support-for-records +``` + +Submit a Pull Request +Create a pull request to the main branch of the upstream repository. Clearly describe: + +- What the change does +- Related issue(s) +- Any limitations or follow-up work + +Review and Iterate +Be responsive to feedback and ready to make improvements. We're happy to collaborate! diff --git a/src/UtilityVerse.Copy/CopyGenerator.cs b/src/UtilityVerse.Copy/CopyGenerator.cs new file mode 100644 index 0000000..822a59d --- /dev/null +++ b/src/UtilityVerse.Copy/CopyGenerator.cs @@ -0,0 +1,81 @@ +/// +/// Author: Pritom Purkayasta +/// + +using System.Linq; +using System.Text; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Text; +using UtilityVerse.Copy.Generators; + +namespace UtilityVerse.Copy; + +[Generator] +public class CopyGenerator : IIncrementalGenerator +{ + public void Initialize(IncrementalGeneratorInitializationContext context) + { + var typeDeclarations = context.SyntaxProvider + .CreateSyntaxProvider( + predicate: (s, _) => s is TypeDeclarationSyntax, + transform: (ctx, _) => GetSemanticTarget(ctx)) + .Where(static m => m is not null) + .Select<(INamedTypeSymbol Symbol, CopyMode Mode)?, (INamedTypeSymbol Symbol, CopyMode Mode)>(static (m, _) => m!.Value) + .Collect(); + + context.RegisterSourceOutput(typeDeclarations, (spc, types) => + { + foreach (var group in types.GroupBy(x => (x.Symbol, x.Mode))) + { + var (symbol, mode) = group.Key; + var source = mode switch + { + CopyMode.Deep => GenerateDeepCopy(symbol), + CopyMode.Shallow => GenerateShallowCopy(symbol), + _ => null + }; + + if (string.IsNullOrWhiteSpace(source)) continue; + + var hintName = $"{symbol.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat).Replace("global::", "").Replace(".", "_")}_{mode}Copy.g.cs"; + spc.AddSource(hintName, SourceText.From(source!, Encoding.UTF8)); + } + }); + } + + private static (INamedTypeSymbol Symbol, CopyMode Mode)? GetSemanticTarget(GeneratorSyntaxContext context) + { + if (context.Node is not TypeDeclarationSyntax typeDecl) + return null; + + if (!typeDecl.Modifiers.Any(m => m.IsKind(SyntaxKind.PartialKeyword))) + return null; + + var symbol = ModelExtensions.GetDeclaredSymbol(context.SemanticModel, typeDecl) as INamedTypeSymbol; + if (symbol is not { DeclaredAccessibility: Accessibility.Public }) + return null; + + var hasDeep = symbol.GetAttributes().Any(a => a.AttributeClass?.ToDisplayString() == "UtilityVerse.Copy.Attributes.DeepCopy") || + symbol.AllInterfaces.Any(i => i.ToDisplayString() == "UtilityVerse.Copy.Interfaces.IDeepCopy"); + + var hasShallow = symbol.GetAttributes().Any(a => a.AttributeClass?.ToDisplayString() == "UtilityVerse.Copy.Attributes.ShallowCopy") || + symbol.AllInterfaces.Any(i => i.ToDisplayString() == "UtilityVerse.Copy.Interfaces.IShallowCopy"); + + if (hasDeep) return (symbol, CopyMode.Deep); + if (hasShallow) return (symbol, CopyMode.Shallow); + + return null; + } + + private static string GenerateDeepCopy(INamedTypeSymbol typeSymbol) => DeepCopyGenerator.Generate(typeSymbol); + + private static string? GenerateShallowCopy(INamedTypeSymbol typeSymbol) => ShallowCopyGenerator.Generate(typeSymbol); + + enum CopyMode + { + Deep, + Shallow + } +} \ No newline at end of file diff --git a/src/UtilityVerse.Copy/Extensions.cs b/src/UtilityVerse.Copy/Extensions.cs new file mode 100644 index 0000000..6f841cc --- /dev/null +++ b/src/UtilityVerse.Copy/Extensions.cs @@ -0,0 +1,9 @@ +using System.Collections.Generic; +using System.Linq; + +namespace UtilityVerse.Copy; + +public static class Extensions +{ + +} \ No newline at end of file diff --git a/src/UtilityVerse.Copy/Generators/DeepCopyGenerator.cs b/src/UtilityVerse.Copy/Generators/DeepCopyGenerator.cs new file mode 100644 index 0000000..c60022c --- /dev/null +++ b/src/UtilityVerse.Copy/Generators/DeepCopyGenerator.cs @@ -0,0 +1,198 @@ +using System.Linq; +using System.Text; +using Microsoft.CodeAnalysis; + +namespace UtilityVerse.Copy.Generators; + +public static class DeepCopyGenerator +{ + public static string Generate(INamedTypeSymbol typeSymbol) + { + if (typeSymbol.IsAbstract) return string.Empty; + + var isGlobalNamespace = typeSymbol.ContainingNamespace.IsGlobalNamespace; + var namespaceName = isGlobalNamespace ? null : typeSymbol.ContainingNamespace.ToDisplayString(); + var typeName = typeSymbol.Name; + + var typeKind = typeSymbol switch + { + { TypeKind: TypeKind.Class, IsRecord: true } => "record", + { TypeKind: TypeKind.Struct, IsRecord: true } => "record struct", + { TypeKind: TypeKind.Class } => "class", + { TypeKind: TypeKind.Struct } => "struct", + _ => null + }; + + if (typeKind == null) + return string.Empty; + + var sb = new StringBuilder(); + + sb.AppendLine(""" + + // + // This code was generated by Copy. + // Author: Pritom Purkayasta + // DO NOT modify this file manually. Changes may be overwritten. + // This file contains auto-generated DeepCopy() implementations. + // + + using System; + using System.Linq; + using System.Collections.Generic; + + """); + + if (!string.IsNullOrEmpty(namespaceName)) + { + sb.AppendLine($"namespace {namespaceName}"); + sb.AppendLine("{"); + } + + sb.AppendLine($" public partial {typeKind} {typeName}"); + sb.AppendLine(" {"); + sb.AppendLine(" [System.CodeDom.Compiler.GeneratedCode(\"DeepCopyGenerator\", \"1.0\")]"); + sb.AppendLine($" public {typeName} DeepCopy()"); + sb.AppendLine(" {"); + + var hasParameterlessCtor = typeSymbol.InstanceConstructors.Any(c => c.Parameters.Length == 0); + + if (!hasParameterlessCtor) + { + var ctor = typeSymbol.InstanceConstructors + .OrderByDescending(c => c.Parameters.Length) + .FirstOrDefault(); + + if (ctor != null) + { + var args = ctor.Parameters.Select(p => + { + var prop = typeSymbol.GetMembers().OfType() + .FirstOrDefault(m => m.Name.Equals(p.Name, System.StringComparison.OrdinalIgnoreCase)); + + if (prop == null) + return $"default({p.Type.ToDisplayString()})"; + + return Helper.IsTrulyPrimitive(prop.Type) + ? $"this.{prop.Name}" + : $"this.{prop.Name}?.DeepCopy()"; + }); + + sb.AppendLine($" return new {typeName}({string.Join(", ", args)});"); + } + else + { + // No constructor found - return empty string (no code generated) + if (!string.IsNullOrEmpty(namespaceName)) + sb.AppendLine(" }"); + sb.AppendLine(" }"); + if (!string.IsNullOrEmpty(namespaceName)) + sb.AppendLine("}"); + return string.Empty; + } + } + else + { + sb.AppendLine($@" return new {typeName} + {{"); + + foreach (var member in typeSymbol.GetMembers().OfType()) + { + if (member.IsReadOnly || member.SetMethod is null) + continue; + + var propType = member.Type; + string assignment; + + if (Helper.IsTrulyPrimitive(propType)) + { + assignment = $"this.{member.Name}"; + } + else switch (propType) + { + case IArrayTypeSymbol arrayType: + { + var elementType = arrayType.ElementType; + assignment = Helper.IsTrulyPrimitive(elementType) + ? $"this.{member.Name}?.ToArray()" + : $"this.{member.Name}?.Select(x => x?.DeepCopy()).ToArray()"; + break; + } + case INamedTypeSymbol { IsGenericType: true } named: + { + var original = named.OriginalDefinition.ToDisplayString(); + var typeArgs = named.TypeArguments; + + if (Helper.IsGenericCollection(original, out var collectionKind)) + { + var elementType = typeArgs[0]; + var cloneExpr = Helper.IsTrulyPrimitive(elementType) + ? "x" + : "x?.DeepCopy()"; + + if (collectionKind == "HashSet") + { + assignment = + $"this.{member.Name} != null ? new HashSet<{elementType.ToDisplayString()}>(this.{member.Name}.Select(x => {cloneExpr}).ToList()) : null"; + } + else if (collectionKind == "Dictionary") + { + var keyType = typeArgs[0]; + var valueType = typeArgs[1]; + + var keyCopy = Helper.IsTrulyPrimitive(keyType) ? "x.Key" : "x.Key?.DeepCopy()"; + var valueCopy = Helper.IsTrulyPrimitive(valueType) ? "x.Value" : "x.Value?.DeepCopy()"; + + assignment = $"this.{member.Name}?.ToDictionary(x => {keyCopy}, x => {valueCopy})"; + } + else + { + // For List, IEnumerable, Collection, ReadOnlyCollection, etc. + assignment = $"this.{member.Name}?.Select(x => {cloneExpr}).ToList()"; + } + } + else if (original == "System.Collections.Generic.Dictionary") + { + var keyType = typeArgs[0]; + var valueType = typeArgs[1]; + + assignment = $"this.{member.Name}?.ToDictionary(" + + $"x => {(Helper.IsTrulyPrimitive(keyType) ? "x.Key" : "x.Key?.DeepCopy()")}, " + + $"x => {(Helper.IsTrulyPrimitive(valueType) ? "x.Value" : "x.Value?.DeepCopy()")})"; + } + else if (original.StartsWith("System.Tuple") || original.StartsWith("System.ValueTuple")) + { + var tupleArgs = typeArgs.Select((arg, i) => + Helper.IsTrulyPrimitive(arg) ? $"x.Item{i + 1}" : $"x.Item{i + 1}?.DeepCopy()"); + + assignment = $"this.{member.Name} is {{ }} x ? new {propType.ToDisplayString()}({string.Join(", ", tupleArgs)}) : default"; + } + else + { + assignment = $"this.{member.Name}?.DeepCopy()"; + } + + break; + } + default: + assignment = $"this.{member.Name}?.DeepCopy()"; + break; + } + + sb.AppendLine($" {member.Name} = {assignment},"); + } + + sb.AppendLine(@" };"); + } + + sb.AppendLine(" }"); + sb.AppendLine(" }"); + + if (!string.IsNullOrEmpty(namespaceName)) + { + sb.AppendLine("}"); + } + + return sb.ToString(); + } +} \ No newline at end of file diff --git a/src/UtilityVerse.Copy/Generators/ShallowCopyGenerator.cs b/src/UtilityVerse.Copy/Generators/ShallowCopyGenerator.cs new file mode 100644 index 0000000..6682760 --- /dev/null +++ b/src/UtilityVerse.Copy/Generators/ShallowCopyGenerator.cs @@ -0,0 +1,140 @@ +using System.Linq; +using System.Text; +using Microsoft.CodeAnalysis; + +namespace UtilityVerse.Copy.Generators; + +public static class ShallowCopyGenerator +{ + public static string? Generate(INamedTypeSymbol typeSymbol) + { + if (typeSymbol.IsAbstract) + return null; + + var isGlobalNamespace = typeSymbol.ContainingNamespace.IsGlobalNamespace; + var namespaceName = isGlobalNamespace ? null : typeSymbol.ContainingNamespace.ToDisplayString(); + var typeName = typeSymbol.Name; + + var typeKind = typeSymbol switch + { + { TypeKind: TypeKind.Class, IsRecord: true } => "record", + { TypeKind: TypeKind.Struct, IsRecord: true } => "record struct", + { TypeKind: TypeKind.Class } => "class", + { TypeKind: TypeKind.Struct } => "struct", + _ => null + }; + + if (typeKind == null) + return null; + + var sb = new StringBuilder(); + + sb.AppendLine(""" + + // + // This code was generated by Copy. + // Author: Pritom Purkayasta + // DO NOT modify this file manually. Changes may be overwritten. + // This file contains auto-generated ShallowCopy() implementations. + // + + using System; + using System.Linq; + using System.Collections.Generic; + using System.Collections.ObjectModel; + using System.Collections.Immutable; + using System.Collections.Concurrent; + using System.Collections.Frozen; + + """); + + if (!string.IsNullOrEmpty(namespaceName)) + { + sb.AppendLine($"namespace {namespaceName}"); + sb.AppendLine("{"); + } + + sb.AppendLine($" public partial {typeKind} {typeName}"); + sb.AppendLine(" {"); + sb.AppendLine(" [System.CodeDom.Compiler.GeneratedCode(\"ShallowCopyGenerator\", \"1.0\")]"); + sb.AppendLine($" public {typeName} ShallowCopy()"); + sb.AppendLine(" {"); + + if (typeSymbol.TypeKind == TypeKind.Class || typeSymbol.IsRecord) + { + sb.AppendLine($" var copy = ({typeName})this.MemberwiseClone();"); + + foreach (var member in typeSymbol.GetMembers().OfType()) + { + if (member.IsReadOnly || member.SetMethod is null) + continue; + + var propType = member.Type; + if (Helper.IsPrimitive(propType)) continue; + + string? assignment = null; + + switch (propType) + { + case IArrayTypeSymbol: + assignment = $"this.{member.Name} != null ? ({member.Type.ToDisplayString()})this.{member.Name}.Clone() : null"; + break; + + case INamedTypeSymbol named when named.IsGenericType: + { + var original = named.OriginalDefinition.ConstructUnboundGenericType().ToDisplayString(); + + if (Helper.IsGenericCollection(original, out var kind)) + { + var itemType = named.TypeArguments.FirstOrDefault()?.ToDisplayString() ?? "var"; + assignment = kind switch + { + "List" or "Enumerable" => + $"this.{member.Name}?.ToList()", + + "HashSet" => + $"this.{member.Name} != null ? new HashSet<{itemType}>(this.{member.Name}) : null", + + "Dictionary" => + $"this.{member.Name}?.ToDictionary(x => x.Key, x => x.Value)", + + _ => null + }; + } + else if (original.StartsWith("System.Tuple")) + { + assignment = $"this.{member.Name}"; + } + + break; + } + + case INamedTypeSymbol named when named.ToDisplayString().StartsWith("System.ValueTuple"): + assignment = $"this.{member.Name}"; + break; + } + + if (assignment != null) + { + sb.AppendLine($" copy.{member.Name} = {assignment};"); + } + } + + sb.AppendLine(" return copy;"); + } + else + { + sb.AppendLine(" return this;"); + } + + sb.AppendLine(" }"); + sb.AppendLine(" }"); + + if (!string.IsNullOrEmpty(namespaceName)) + { + sb.AppendLine("}"); + } + + return sb.ToString(); + } +} \ No newline at end of file diff --git a/src/UtilityVerse.Copy/Helper.cs b/src/UtilityVerse.Copy/Helper.cs new file mode 100644 index 0000000..543322f --- /dev/null +++ b/src/UtilityVerse.Copy/Helper.cs @@ -0,0 +1,79 @@ +/// +/// Author: Pritom Purkayasta +/// + +using Microsoft.CodeAnalysis; + +namespace UtilityVerse.Copy; + +internal static class Helper +{ + internal static bool IsPrimitive(ITypeSymbol symbol) => + symbol.IsValueType || symbol.SpecialType == SpecialType.System_String; + + internal static bool IsTrulyPrimitive(ITypeSymbol symbol) + { + return symbol.SpecialType switch + { + SpecialType.System_Boolean or + SpecialType.System_Byte or + SpecialType.System_SByte or + SpecialType.System_Int16 or + SpecialType.System_UInt16 or + SpecialType.System_Int32 or + SpecialType.System_UInt32 or + SpecialType.System_Int64 or + SpecialType.System_UInt64 or + SpecialType.System_Single or + SpecialType.System_Double or + SpecialType.System_Char or + SpecialType.System_String => true, + _ => false + }; + } + + internal static bool IsGenericCollection(string? originalDef, out string? kind) + { + kind = originalDef switch + { + // List-like collections + "System.Collections.Generic.List" => "List", + "System.Collections.Generic.IList" => "List", + "System.Collections.ObjectModel.Collection" => "List", + "System.Collections.ObjectModel.ReadOnlyCollection" => "List", + "System.Collections.ObjectModel.ObservableCollection" => "List", + "System.ComponentModel.BindingList" => "List", + "System.Collections.Immutable.ImmutableList" => "List", + "System.Collections.Generic.ReadOnlyList" => "List", // custom/unofficial + + // Enumerable-like collections + "System.Collections.Generic.IEnumerable" => "Enumerable", + "System.Collections.Generic.ICollection" => "Enumerable", + "System.Collections.Generic.IReadOnlyCollection" => "Enumerable", + "System.Collections.Generic.IReadOnlyList" => "Enumerable", + + // Hash-based collections + "System.Collections.Generic.HashSet" => "HashSet", + + // Dictionary-like collections + "System.Collections.Generic.Dictionary" => "Dictionary", + "System.Collections.Frozen.FrozenDictionary" => "Dictionary", + "System.Collections.Concurrent.ConcurrentDictionary" => "Dictionary", + + // Immutable + "System.Collections.Immutable.ImmutableArray" => "List", + "System.Collections.Immutable.ImmutableSortedSet" => "HashSet", + "System.Collections.Immutable.ImmutableHashSet" => "HashSet", + "System.Collections.Immutable.ImmutableDictionary" => "Dictionary", + "System.Collections.Immutable.ImmutableSortedDictionary" => "Dictionary", + + // Queue / Stack + "System.Collections.Generic.Queue" => "Queue", + "System.Collections.Generic.Stack" => "Stack", + + _ => null + }; + + return kind != null; + } +} \ No newline at end of file diff --git a/src/UtilityVerse.Copy/Interfaces/IDeepCopy.cs b/src/UtilityVerse.Copy/Interfaces/IDeepCopy.cs new file mode 100644 index 0000000..c18cfaa --- /dev/null +++ b/src/UtilityVerse.Copy/Interfaces/IDeepCopy.cs @@ -0,0 +1,6 @@ +/// +/// Author: Pritom Purkayasta +/// + +namespace UtilityVerse.Copy.Interfaces; +public interface IDeepCopy; \ No newline at end of file diff --git a/src/UtilityVerse.Copy/Interfaces/IShallowCopy.cs b/src/UtilityVerse.Copy/Interfaces/IShallowCopy.cs new file mode 100644 index 0000000..ffab419 --- /dev/null +++ b/src/UtilityVerse.Copy/Interfaces/IShallowCopy.cs @@ -0,0 +1,7 @@ +/// +/// Author: Pritom Purkayasta +/// + +namespace UtilityVerse.Copy.Interfaces; + +public interface IShallowCopy; \ No newline at end of file diff --git a/src/UtilityVerse.Copy/README.md b/src/UtilityVerse.Copy/README.md new file mode 100644 index 0000000..4a585b0 --- /dev/null +++ b/src/UtilityVerse.Copy/README.md @@ -0,0 +1,96 @@ +# Copy - Source Generator for DeepCopy and ShallowCopy in C# + +🚀 **Copy** is a Roslyn-based source generator that reduces boilerplate by automatically generating `DeepCopy` and `ShallowCopy` methods for your models. Just use an attribute or implement a marker interface, and the generator handles the rest! + + +--- + +## ✨ Features + +- ✅ Automatically generates `DeepCopy()` and/or `ShallowCopy()` methods +- ✅ Clean, zero-runtime dependency — generation happens at compile-time +- ✅ Supports both `[Attribute]` and `interface`-based opt-in mechanisms +- ✅ Great for DTOs, ViewModels, and data layer objects + +--- + +## 📦 Installation + +Add the package to your project using NuGet: + +```bash +dotnet add package Copy +``` + + +🛠️ How It Works + +> MAKE SURE, your models/pocos/dtos are `partial`. Without this, it won't work. + +### Option 1: Use Attributes + +Annotate your model class with `[ShallowCopy]` or `[DeepCopy]`: +```c# +using Copy; + +[ShallowCopy] +public partial class Person +{ + public string Name { get; set; } + public int Age { get; set; } +} + +``` + +This generates a ShallowCopy() method: +```c# +public Person ShallowCopy() +{ + return new Person + { + Name = this.Name, + Age = this.Age + }; +} +``` +### Option 2: Implement Marker Interfaces + +Instead of using attributes, implement one of the marker interfaces: + +```c# +public partial class Person : IShallowCopy +{ + public string Name { get; set; } + public int Age { get; set; } +} +``` +This will have the same result: a ShallowCopy() method will be generated at compile-time. + +🤿 Deep Copy Support + +If your model contains reference-type properties or collections and you want a true deep copy, use `[DeepCopy]` or implement `IDeepCopy`: +```c# +[DeepCopy] +public partial class Order +{ + public Customer Customer { get; set; } + public List Items { get; set; } +} +``` +The generator will recursively clone reference types where possible. + +✅ Generated Code Is: + + Partial – Safe to regenerate on build + + Readable – Emitted to the intermediate folder for inspection + + Non-intrusive – Won’t modify your original source + + +📄 [License](LICENSE) +This project is licensed under the MIT License. + +🙌 [Contribution](CONTRIBUTE.md) See here! + +🏎️ [Benchmark](BENCHMARK.md) - See here! \ No newline at end of file diff --git a/src/UtilityVerse.Copy/UtilityVerse.Copy.csproj b/src/UtilityVerse.Copy/UtilityVerse.Copy.csproj new file mode 100644 index 0000000..84770ed --- /dev/null +++ b/src/UtilityVerse.Copy/UtilityVerse.Copy.csproj @@ -0,0 +1,63 @@ + + + + netstandard2.0 + latest + enable + + + + Analyzer + true + true + true + + + + Copy + UtilityVerse.Copy + 1.0 + Pritom Purkayasta + A Roslyn source generator for generating shallow copy and deep copy for class, + records and structs + + + sourcegenerator;roslyn;codegeneration;csharp;shallowcopy;deepcopy;shallowclone;deepclone;clone;shallow;deep;dotnet + + MIT + https://github.com/purkayasta/TheUtilityVerse + git + https://github.com/purkayasta/TheUtilityVerse + false + true + snupkg + + Pritom Purkayasta + Copyright (c) Pritom Purkayasta All rights reserved. + FREE TO USE TO CONNECT THE WORLD + + copy.png + nuget.md + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + True + \ + + + True + \ + + + + + diff --git a/src/UtilityVerse.Copy/nuget.md b/src/UtilityVerse.Copy/nuget.md new file mode 100644 index 0000000..093cc7c --- /dev/null +++ b/src/UtilityVerse.Copy/nuget.md @@ -0,0 +1,88 @@ +# Copy - Source Generator for DeepCopy and ShallowCopy in C# + +🚀 **Copy** is a Roslyn-based source generator that reduces boilerplate by automatically generating `DeepCopy` and `ShallowCopy` methods for your models. Just use an attribute or implement a marker interface, and the generator handles the rest! + + +--- + +## ✨ Features + +- ✅ Automatically generates `DeepCopy()` and/or `ShallowCopy()` methods +- ✅ Clean, zero-runtime dependency — generation happens at compile-time +- ✅ Supports both `[Attribute]` and `interface`-based opt-in mechanisms +- ✅ Great for DTOs, ViewModels, and data layer objects + +--- + +## 📦 Installation + +Add the package to your project using NuGet: + +```bash +dotnet add package Copy +``` + + +🛠️ How It Works + +> MAKE SURE, your models/pocos/dtos are `partial`. Without this, it won't work. + +### Option 1: Use Attributes + +Annotate your model class with `[ShallowCopy]` or `[DeepCopy]`: +```c# +using Copy; + +[ShallowCopy] +public partial class Person +{ + public string Name { get; set; } + public int Age { get; set; } +} + +``` + +This generates a ShallowCopy() method: +```c# +public Person ShallowCopy() +{ + return new Person + { + Name = this.Name, + Age = this.Age + }; +} +``` +### Option 2: Implement Marker Interfaces + +Instead of using attributes, implement one of the marker interfaces: + +```c# +public partial class Person : IShallowCopy +{ + public string Name { get; set; } + public int Age { get; set; } +} +``` +This will have the same result: a ShallowCopy() method will be generated at compile-time. + +🤿 Deep Copy Support + +If your model contains reference-type properties or collections and you want a true deep copy, use `[DeepCopy]` or implement `IDeepCopy`: +```c# +[DeepCopy] +public partial class Order +{ + public Customer Customer { get; set; } + public List Items { get; set; } +} +``` +The generator will recursively clone reference types where possible. + +✅ Generated Code Is: + + Partial – Safe to regenerate on build + + Readable – Emitted to the intermediate folder for inspection + + Non-intrusive – Won’t modify your original source diff --git a/test/UtilityVerse.Copy.Test/HelloWorld.cs b/test/UtilityVerse.Copy.Test/HelloWorld.cs new file mode 100644 index 0000000..91577ac --- /dev/null +++ b/test/UtilityVerse.Copy.Test/HelloWorld.cs @@ -0,0 +1,33 @@ +using UtilityVerse.Copy.Attributes; + +[DeepCopy] +public partial class HelloWorld +{ + public int Id { get; set; } + public string Name { get; set; } + public IEnumerable
      Addresses { get; set; } + + public HelloWorld Clone() + { + return new HelloWorld + { + Id = Id, + Name = Name, + Addresses = Addresses.Select(x=> x.Clone()).ToList() + }; + } +} + +[DeepCopy] +public partial class Address +{ + public string? StreetNumber { get; set; } + + public Address Clone() + { + return new Address + { + StreetNumber = StreetNumber + }; + } +} \ No newline at end of file diff --git a/test/UtilityVerse.Copy.Test/Program.cs b/test/UtilityVerse.Copy.Test/Program.cs new file mode 100644 index 0000000..9d644ae --- /dev/null +++ b/test/UtilityVerse.Copy.Test/Program.cs @@ -0,0 +1,67 @@ +Console.WriteLine("Starting...."); + +ManualTesting(); + +Console.WriteLine(" ===== "); + +AutomatedTesting(); + +Console.WriteLine("Ending...."); + + +static void AutomatedTesting() +{ + var originalObject = new HelloWorld + { + Id = 1, + Name = "Pritom", + Addresses = new[] { new Address() { StreetNumber = "12" } } + }; + + var clonedObject = originalObject.DeepCopy(); + Console.WriteLine("Before Cloning..."); + + Console.WriteLine( + $"OriginalObject Id: {originalObject.Id} Name: {originalObject.Name} Addresses: {originalObject.Addresses.First().StreetNumber}"); + Console.WriteLine( + $"ClonedObject Id: {clonedObject.Id} Name: {clonedObject.Name} Addresses: {clonedObject.Addresses.First().StreetNumber}"); + + + originalObject.Id = 2; + originalObject.Name = "Purkayasta"; + originalObject.Addresses.First().StreetNumber = "123"; + + + Console.WriteLine("After Cloning..."); + Console.WriteLine( + $"OriginalObject Id: {originalObject.Id} Name: {originalObject.Name} Addresses: {originalObject.Addresses.First().StreetNumber}"); + Console.WriteLine( + $"ClonedObject Id: {clonedObject.Id} Name: {clonedObject.Name} Addresses: {clonedObject.Addresses.First().StreetNumber}"); +} + +static void ManualTesting() +{ + var originalObject = new HelloWorld() + { + Id = 1, + Name = "Pritom", + Addresses = new[] { new Address() { StreetNumber = "12" } } + }; + var clonedObject = originalObject.Clone(); + Console.WriteLine("Before Cloning..."); + Console.WriteLine( + $"OriginalObject Id: {originalObject.Id} Name: {originalObject.Name} Addresses: {originalObject.Addresses.First().StreetNumber}"); + Console.WriteLine( + $"ClonedObject Id: {clonedObject.Id} Name: {clonedObject.Name} Addresses: {clonedObject.Addresses.First().StreetNumber}"); + + + originalObject.Id = 2; + originalObject.Name = "Purkayasta"; + originalObject.Addresses.First().StreetNumber = "123"; + + Console.WriteLine("After Cloning..."); + Console.WriteLine( + $"OriginalObject Id: {originalObject.Id} Name: {originalObject.Name} Addresses: {originalObject.Addresses.First().StreetNumber}"); + Console.WriteLine( + $"ClonedObject Id: {clonedObject.Id} Name: {clonedObject.Name} Addresses: {clonedObject.Addresses.First().StreetNumber}"); +} \ No newline at end of file diff --git a/test/UtilityVerse.Copy.Test/UtilityVerse.Copy.Test.csproj b/test/UtilityVerse.Copy.Test/UtilityVerse.Copy.Test.csproj new file mode 100644 index 0000000..e214f0d --- /dev/null +++ b/test/UtilityVerse.Copy.Test/UtilityVerse.Copy.Test.csproj @@ -0,0 +1,15 @@ + + + + Exe + net9.0 + enable + enable + + + + + + + + diff --git a/test/UtilityVerse.UnitTest/Usings.cs b/test/UtilityVerse.UnitTest/Usings.cs index 5eb883f..502a8d8 100644 --- a/test/UtilityVerse.UnitTest/Usings.cs +++ b/test/UtilityVerse.UnitTest/Usings.cs @@ -1,2 +1,2 @@ -global using Xunit; -global using UtilityVerse.Extensions; \ No newline at end of file +global using UtilityVerse.Extensions; +global using Xunit; \ No newline at end of file From 6afc849ff7cfe8bce36d3c6d19a8326dde58060d Mon Sep 17 00:00:00 2001 From: purkayasta Date: Sat, 28 Jun 2025 02:43:21 +0600 Subject: [PATCH 11/20] Updated Readme.md --- README.md | 102 ++---------- src/UtilityVerse.Copy/README.md | 148 +++++++++++++----- .../UtilityVerse.Copy.csproj | 2 +- 3 files changed, 130 insertions(+), 122 deletions(-) diff --git a/README.md b/README.md index b6ab50c..91cba86 100644 --- a/README.md +++ b/README.md @@ -1,99 +1,31 @@ -# The UtilityVerse 🧰 - *(Multi verse of utility methods) is a library for all the helper methods that you might want to use.* +## The UtilityVerse 🧰 +*(Multi-verse of utility methods)* — a library full of helper methods you might want to use. ![Nuget](https://img.shields.io/github/repo-size/purkayasta/TheUtilityVerse?style=social) ![Nuget](https://img.shields.io/github/last-commit/purkayasta/TheUtilityVerse?style=flat-square) -> Give it a star if you like the project. 👏 🌠 🌟 +> ⭐️ Give it a star if you like the project! -> *Want to contribute? Here is the [guideline](./CONTRIBUTING.md)* +--- -There are multiple packages in this universe. Use whatever suits you. +## 📦 Packages in This Universe -1. [UtilityVerse](https://www.nuget.org/packages/UtilityVerse/) - No dependency. Just the core utilities. -2. [UtilityVerse.ASPNET](https://www.nuget.org/packages/UtilityVerse.ASPNET/) - This is based on aspnet core. -3. [UtilityVerse.Copy]() - Roslyn based source generator to create shallow copy and deep copy for your objects. +Use whatever suits your project: -To Invoke this library, you may want to use ```Utility``` class. +1. [**UtilityVerse**](https://www.nuget.org/packages/UtilityVerse/) + 🧰 No dependencies — includes core utility methods. -------------------------------- +2. [**UtilityVerse.ASPNET**](https://www.nuget.org/packages/UtilityVerse.ASPNET/) + 🌐 ASP.NET Core-specific helpers and extensions. -### Documentation Work in progress. (Experimental build) ⚒️ --- `cd documentation/docfx_project/` --- `docfx ./docfx.json --serve` +3. [**UtilityVerse.Copy**](https://www.nuget.org/packages/UtilityVerse.Copy/) + ✍️ A Roslyn-based source generator that automatically generates `ShallowCopy()` and `DeepCopy()` methods for your models. + → See full details here: [`UtilityVerse.Copy/README.md`](./src/UtilityVerse.Copy/README.md) -------------------------------- +--- -# [UtilityVerse](https://www.nuget.org/packages/UtilityVerse/) -![Nuget](https://img.shields.io/nuget/v/UtilityVerse) -![Nuget](https://img.shields.io/nuget/dt/UtilityVerse?style=plastic) +To use this utility library, you may want to start with the `Utility` class. +--- -## Methods: - -- [ ] Boolean Extensions - - [x] ToBoolean(string value). - -- [ ] Date Extensions - - [x] IsInBetween(). - - [x] ToUnixTimeStamp(). - - [x] ToIntDate(). - - [x] DestructFromDateTime(). - - [x] ToDateTime(). - - [x] ToUtcDateTime(). - -- [ ] Generic Extensions - - [x] ToByteArray(). - - [x] To(). - -- [ ] Integer Extensions - - [x] IsInBetween(). - - [x] ToInt(bool value). - - [x] ToStr() - -- [ ] Byte Extensions - - [x] ToObject(). - -- [ ] Object Extensions - - [x] ToByteArray(). - -- [ ] Character Extensions - - [x] ToStr(). - -- [ ] Decimal Extensions - - [x] ToStr(). - -- [ ] Double Extensions. - - [x] ToStr(). - -- [ ] Span Extensions. - - [x] ToObject(). - -- [ ] String Extensions - - [x] IsNullOrEmptyOrWhiteSpace(). - - [x] ToStr(). - - [x] EncodeToBase64(). - - [x] DecodeFromBase64(). - - [x] ConvertOsWisePath(). - - [x] ToSlugUrl(). - - [x] IsInIpAddress(). - - [x] IsValidEmail(). - -- [ ] Collection Extensions - - [x] IsNullOrEmpty(). - -- [ ] Features: - - [x] Retry Mechanism. - - [x] Dollar Cent Conversion. - - -# [UtilityVerse.ASPNET](https://www.nuget.org/packages/UtilityVerse.ASPNET/) -![Nuget](https://img.shields.io/nuget/v/UtilityVerse.ASPNET) -![Nuget](https://img.shields.io/nuget/dt/UtilityVerse.ASPNET?style=plastic) - - -## Features: -- [x] CreateHttpRequest() - - - -Made ❤ with C#. \ No newline at end of file +Made with ❤️ in C#. \ No newline at end of file diff --git a/src/UtilityVerse.Copy/README.md b/src/UtilityVerse.Copy/README.md index 4a585b0..5240774 100644 --- a/src/UtilityVerse.Copy/README.md +++ b/src/UtilityVerse.Copy/README.md @@ -1,37 +1,47 @@ -# Copy - Source Generator for DeepCopy and ShallowCopy in C# +--- -🚀 **Copy** is a Roslyn-based source generator that reduces boilerplate by automatically generating `DeepCopy` and `ShallowCopy` methods for your models. Just use an attribute or implement a marker interface, and the generator handles the rest! +# 📦 UtilityVerse.Copy — Source Generator for DeepCopy and ShallowCopy in C\# +🚀 **UtilityVerse.Copy** is a Roslyn-based source generator that automatically creates `DeepCopy()` and `ShallowCopy()` methods for your models. Eliminate repetitive boilerplate and enjoy clean, maintainable code with zero runtime dependencies. --- ## ✨ Features -- ✅ Automatically generates `DeepCopy()` and/or `ShallowCopy()` methods -- ✅ Clean, zero-runtime dependency — generation happens at compile-time -- ✅ Supports both `[Attribute]` and `interface`-based opt-in mechanisms -- ✅ Great for DTOs, ViewModels, and data layer objects +* ✅ Automatically generates `DeepCopy()` and/or `ShallowCopy()` methods at compile-time +* ✅ No runtime overhead — it's all generated in the background by Roslyn +* ✅ Supports both `[Attribute]`-based and `interface`-based opt-in mechanisms +* ✅ Works great for DTOs, ViewModels, and plain C# objects +* ✅ Smart support for common collection types, arrays, tuples, and more + +--- + + +# > Give it a star if you like the project. 👏 🌠 🌟 + --- ## 📦 Installation -Add the package to your project using NuGet: +Install the NuGet package: ```bash -dotnet add package Copy +dotnet add package UtilityVerse.Copy ``` +--- + +## 🛠️ How It Works -🛠️ How It Works +> **Important**: Your classes must be marked `partial` for the generator to emit code successfully. -> MAKE SURE, your models/pocos/dtos are `partial`. Without this, it won't work. +### ✅ Option 1: Use Attributes -### Option 1: Use Attributes +Apply `[ShallowCopy]` or `[DeepCopy]` to your class or struct: -Annotate your model class with `[ShallowCopy]` or `[DeepCopy]`: -```c# -using Copy; +```csharp +using UtilityVerse.Copy; [ShallowCopy] public partial class Person @@ -39,11 +49,11 @@ public partial class Person public string Name { get; set; } public int Age { get; set; } } - ``` -This generates a ShallowCopy() method: -```c# +This generates a `ShallowCopy()` method at compile-time: + +```csharp public Person ShallowCopy() { return new Person @@ -53,44 +63,110 @@ public Person ShallowCopy() }; } ``` -### Option 2: Implement Marker Interfaces -Instead of using attributes, implement one of the marker interfaces: +For deep copy: + +```csharp +[DeepCopy] +public partial class Order +{ + public Customer Customer { get; set; } + public List Items { get; set; } +} +``` + +Generates a deep recursive `DeepCopy()` method that copies nested references and collections. + +--- + +### ✅ Option 2: Use Marker Interfaces + +Prefer no attributes? Just implement the marker interfaces: -```c# +```csharp public partial class Person : IShallowCopy { public string Name { get; set; } public int Age { get; set; } } ``` -This will have the same result: a ShallowCopy() method will be generated at compile-time. -🤿 Deep Copy Support - -If your model contains reference-type properties or collections and you want a true deep copy, use `[DeepCopy]` or implement `IDeepCopy`: -```c# -[DeepCopy] -public partial class Order +```csharp +public partial class Order : IDeepCopy { public Customer Customer { get; set; } public List Items { get; set; } } ``` -The generator will recursively clone reference types where possible. -✅ Generated Code Is: +The generator will handle the rest automatically! + +--- + +## 📚 Supported Types + +* ✅ Primitives and strings +* ✅ Arrays +* ✅ Tuples and `ValueTuple` +* ✅ Generic collections: `List`, `IEnumerable`, `ICollection`, `IReadOnlyList`, `HashSet`, etc. +* ✅ Dictionary-like collections: `Dictionary`, `ConcurrentDictionary`, `FrozenDictionary`, etc. +* ✅ Record types, classes, structs (as long as they are `partial`) +* ✅ Nested properties recursively copied in `DeepCopy()` + +--- + +## 🧪 Sample Output + +Given this class: + +```csharp +[DeepCopy] +public partial class User +{ + public string Name { get; set; } + public List
      Addresses { get; set; } +} +``` + +Generated deep copy: + +```csharp +public User DeepCopy() +{ + return new User + { + Name = this.Name, + Addresses = this.Addresses?.Select(x => x?.DeepCopy()).ToList() + }; +} +``` + +--- + +## 🔒 Safe and Reliable - Partial – Safe to regenerate on build +* 💡 **Partial**: Won’t overwrite your code — generated code lives alongside your class +* 🧾 **Readable**: Generated files are emitted to the intermediate folder (obj) +* ⚙️ **Non-Intrusive**: No reflection, no extra dependencies - Readable – Emitted to the intermediate folder for inspection +--- - Non-intrusive – Won’t modify your original source +## 📄 License +This project is licensed under the [MIT License](LICENSE). -📄 [License](LICENSE) -This project is licensed under the MIT License. +--- -🙌 [Contribution](CONTRIBUTE.md) See here! +## 🤝 Contributing + +We welcome contributions! Please see the [CONTRIBUTE.md](CONTRIBUTE.md) file for guidelines. + +--- + +## 🏎️ Benchmark + +See real-world performance metrics in [BENCHMARK.md](BENCHMARK.md) + +--- -🏎️ [Benchmark](BENCHMARK.md) - See here! \ No newline at end of file +Let me know if you’d like to auto-generate this README as part of your build pipeline or package metadata! \ No newline at end of file diff --git a/src/UtilityVerse.Copy/UtilityVerse.Copy.csproj b/src/UtilityVerse.Copy/UtilityVerse.Copy.csproj index 84770ed..3ac4bb2 100644 --- a/src/UtilityVerse.Copy/UtilityVerse.Copy.csproj +++ b/src/UtilityVerse.Copy/UtilityVerse.Copy.csproj @@ -16,7 +16,7 @@ Copy UtilityVerse.Copy - 1.0 + 0.1.0 Pritom Purkayasta A Roslyn source generator for generating shallow copy and deep copy for class, records and structs From c515a3912ed27959dd569841ded620ffec9385b9 Mon Sep 17 00:00:00 2001 From: purkayasta Date: Sun, 29 Jun 2025 14:27:49 +0600 Subject: [PATCH 12/20] Added Inherited Property Support in the Copy --- src/UtilityVerse.Copy/Attributes/DeepCopy.cs | 4 +- .../Attributes/ShallowCopy.cs | 4 +- src/UtilityVerse.Copy/CopyGenerator.cs | 6 +- src/UtilityVerse.Copy/Extensions.cs | 9 -- .../Generators/DeepCopyGenerator.cs | 138 +++++++++--------- .../Generators/ShallowCopyGenerator.cs | 8 +- src/UtilityVerse.Copy/Helper.cs | 24 ++- src/UtilityVerse.Copy/Interfaces/IDeepCopy.cs | 4 +- .../Interfaces/IShallowCopy.cs | 5 +- src/UtilityVerse.Copy/README.md | 6 +- .../UtilityVerse.Copy.csproj | 25 ++-- src/UtilityVerse.Copy/nuget.md | 115 +++++++++++---- test/UtilityVerse.Copy.Test/HelloWorld.cs | 9 +- test/UtilityVerse.Copy.Test/Program.cs | 7 +- .../UtilityVerse.Copy.Test.csproj | 10 +- 15 files changed, 233 insertions(+), 141 deletions(-) delete mode 100644 src/UtilityVerse.Copy/Extensions.cs diff --git a/src/UtilityVerse.Copy/Attributes/DeepCopy.cs b/src/UtilityVerse.Copy/Attributes/DeepCopy.cs index 643d634..7892506 100644 --- a/src/UtilityVerse.Copy/Attributes/DeepCopy.cs +++ b/src/UtilityVerse.Copy/Attributes/DeepCopy.cs @@ -1,9 +1,11 @@ /// /// Author: Pritom Purkayasta +// Copyright (c) Pritom Purkayasta All rights reserved. +// FREE TO USE TO CONNECT THE WORLD /// using System; -namespace UtilityVerse.Copy.Attributes; +namespace UtilityVerse.Copy; [AttributeUsage(AttributeTargets.Class, Inherited = false)] public sealed class DeepCopy : Attribute; \ No newline at end of file diff --git a/src/UtilityVerse.Copy/Attributes/ShallowCopy.cs b/src/UtilityVerse.Copy/Attributes/ShallowCopy.cs index a702626..a3a58ca 100644 --- a/src/UtilityVerse.Copy/Attributes/ShallowCopy.cs +++ b/src/UtilityVerse.Copy/Attributes/ShallowCopy.cs @@ -1,9 +1,11 @@ /// /// Author: Pritom Purkayasta +// Copyright (c) Pritom Purkayasta All rights reserved. +// FREE TO USE TO CONNECT THE WORLD /// using System; -namespace UtilityVerse.Copy.Attributes; +namespace UtilityVerse.Copy; [AttributeUsage(AttributeTargets.Class, Inherited = false)] public sealed class ShallowCopy : Attribute; \ No newline at end of file diff --git a/src/UtilityVerse.Copy/CopyGenerator.cs b/src/UtilityVerse.Copy/CopyGenerator.cs index 822a59d..44d5882 100644 --- a/src/UtilityVerse.Copy/CopyGenerator.cs +++ b/src/UtilityVerse.Copy/CopyGenerator.cs @@ -1,5 +1,7 @@ /// /// Author: Pritom Purkayasta +// Copyright (c) Pritom Purkayasta All rights reserved. +// FREE TO USE TO CONNECT THE WORLD /// using System.Linq; @@ -13,7 +15,7 @@ namespace UtilityVerse.Copy; [Generator] -public class CopyGenerator : IIncrementalGenerator +public sealed class CopyGenerator : IIncrementalGenerator { public void Initialize(IncrementalGeneratorInitializationContext context) { @@ -22,7 +24,7 @@ public void Initialize(IncrementalGeneratorInitializationContext context) predicate: (s, _) => s is TypeDeclarationSyntax, transform: (ctx, _) => GetSemanticTarget(ctx)) .Where(static m => m is not null) - .Select<(INamedTypeSymbol Symbol, CopyMode Mode)?, (INamedTypeSymbol Symbol, CopyMode Mode)>(static (m, _) => m!.Value) + .Select(static (m, _) => m!.Value) .Collect(); context.RegisterSourceOutput(typeDeclarations, (spc, types) => diff --git a/src/UtilityVerse.Copy/Extensions.cs b/src/UtilityVerse.Copy/Extensions.cs deleted file mode 100644 index 6f841cc..0000000 --- a/src/UtilityVerse.Copy/Extensions.cs +++ /dev/null @@ -1,9 +0,0 @@ -using System.Collections.Generic; -using System.Linq; - -namespace UtilityVerse.Copy; - -public static class Extensions -{ - -} \ No newline at end of file diff --git a/src/UtilityVerse.Copy/Generators/DeepCopyGenerator.cs b/src/UtilityVerse.Copy/Generators/DeepCopyGenerator.cs index c60022c..7b08e8d 100644 --- a/src/UtilityVerse.Copy/Generators/DeepCopyGenerator.cs +++ b/src/UtilityVerse.Copy/Generators/DeepCopyGenerator.cs @@ -1,12 +1,18 @@ +/// +/// Author: Pritom Purkayasta +// Copyright (c) Pritom Purkayasta All rights reserved. +// FREE TO USE TO CONNECT THE WORLD +/// + using System.Linq; using System.Text; using Microsoft.CodeAnalysis; namespace UtilityVerse.Copy.Generators; -public static class DeepCopyGenerator +internal static class DeepCopyGenerator { - public static string Generate(INamedTypeSymbol typeSymbol) + internal static string Generate(INamedTypeSymbol typeSymbol) { if (typeSymbol.IsAbstract) return string.Empty; @@ -67,7 +73,7 @@ public static string Generate(INamedTypeSymbol typeSymbol) { var args = ctor.Parameters.Select(p => { - var prop = typeSymbol.GetMembers().OfType() + var prop = Helper.GetAllProperties(typeSymbol) .FirstOrDefault(m => m.Name.Equals(p.Name, System.StringComparison.OrdinalIgnoreCase)); if (prop == null) @@ -96,7 +102,7 @@ public static string Generate(INamedTypeSymbol typeSymbol) sb.AppendLine($@" return new {typeName} {{"); - foreach (var member in typeSymbol.GetMembers().OfType()) + foreach (var member in Helper.GetAllProperties(typeSymbol)) { if (member.IsReadOnly || member.SetMethod is null) continue; @@ -109,75 +115,75 @@ public static string Generate(INamedTypeSymbol typeSymbol) assignment = $"this.{member.Name}"; } else switch (propType) - { - case IArrayTypeSymbol arrayType: - { - var elementType = arrayType.ElementType; - assignment = Helper.IsTrulyPrimitive(elementType) - ? $"this.{member.Name}?.ToArray()" - : $"this.{member.Name}?.Select(x => x?.DeepCopy()).ToArray()"; - break; - } - case INamedTypeSymbol { IsGenericType: true } named: { - var original = named.OriginalDefinition.ToDisplayString(); - var typeArgs = named.TypeArguments; - - if (Helper.IsGenericCollection(original, out var collectionKind)) - { - var elementType = typeArgs[0]; - var cloneExpr = Helper.IsTrulyPrimitive(elementType) - ? "x" - : "x?.DeepCopy()"; - - if (collectionKind == "HashSet") + case IArrayTypeSymbol arrayType: { - assignment = - $"this.{member.Name} != null ? new HashSet<{elementType.ToDisplayString()}>(this.{member.Name}.Select(x => {cloneExpr}).ToList()) : null"; + var elementType = arrayType.ElementType; + assignment = Helper.IsTrulyPrimitive(elementType) + ? $"this.{member.Name}?.ToArray()" + : $"this.{member.Name}?.Select(x => x?.DeepCopy()).ToArray()"; + break; } - else if (collectionKind == "Dictionary") + case INamedTypeSymbol { IsGenericType: true } named: { - var keyType = typeArgs[0]; - var valueType = typeArgs[1]; - - var keyCopy = Helper.IsTrulyPrimitive(keyType) ? "x.Key" : "x.Key?.DeepCopy()"; - var valueCopy = Helper.IsTrulyPrimitive(valueType) ? "x.Value" : "x.Value?.DeepCopy()"; - - assignment = $"this.{member.Name}?.ToDictionary(x => {keyCopy}, x => {valueCopy})"; - } - else - { - // For List, IEnumerable, Collection, ReadOnlyCollection, etc. - assignment = $"this.{member.Name}?.Select(x => {cloneExpr}).ToList()"; + var original = named.OriginalDefinition.ToDisplayString(); + var typeArgs = named.TypeArguments; + + if (Helper.IsGenericCollection(original, out var collectionKind)) + { + var elementType = typeArgs[0]; + var cloneExpr = Helper.IsTrulyPrimitive(elementType) + ? "x" + : "x?.DeepCopy()"; + + if (collectionKind == "HashSet") + { + assignment = + $"this.{member.Name} != null ? new HashSet<{elementType.ToDisplayString()}>(this.{member.Name}.Select(x => {cloneExpr}).ToList()) : null"; + } + else if (collectionKind == "Dictionary") + { + var keyType = typeArgs[0]; + var valueType = typeArgs[1]; + + var keyCopy = Helper.IsTrulyPrimitive(keyType) ? "x.Key" : "x.Key?.DeepCopy()"; + var valueCopy = Helper.IsTrulyPrimitive(valueType) ? "x.Value" : "x.Value?.DeepCopy()"; + + assignment = $"this.{member.Name}?.ToDictionary(x => {keyCopy}, x => {valueCopy})"; + } + else + { + // For List, IEnumerable, Collection, ReadOnlyCollection, etc. + assignment = $"this.{member.Name}?.Select(x => {cloneExpr}).ToList()"; + } + } + else if (original == "System.Collections.Generic.Dictionary") + { + var keyType = typeArgs[0]; + var valueType = typeArgs[1]; + + assignment = $"this.{member.Name}?.ToDictionary(" + + $"x => {(Helper.IsTrulyPrimitive(keyType) ? "x.Key" : "x.Key?.DeepCopy()")}, " + + $"x => {(Helper.IsTrulyPrimitive(valueType) ? "x.Value" : "x.Value?.DeepCopy()")})"; + } + else if (original.StartsWith("System.Tuple") || original.StartsWith("System.ValueTuple")) + { + var tupleArgs = typeArgs.Select((arg, i) => + Helper.IsTrulyPrimitive(arg) ? $"x.Item{i + 1}" : $"x.Item{i + 1}?.DeepCopy()"); + + assignment = $"this.{member.Name} is {{ }} x ? new {propType.ToDisplayString()}({string.Join(", ", tupleArgs)}) : default"; + } + else + { + assignment = $"this.{member.Name}?.DeepCopy()"; + } + + break; } - } - else if (original == "System.Collections.Generic.Dictionary") - { - var keyType = typeArgs[0]; - var valueType = typeArgs[1]; - - assignment = $"this.{member.Name}?.ToDictionary(" + - $"x => {(Helper.IsTrulyPrimitive(keyType) ? "x.Key" : "x.Key?.DeepCopy()")}, " + - $"x => {(Helper.IsTrulyPrimitive(valueType) ? "x.Value" : "x.Value?.DeepCopy()")})"; - } - else if (original.StartsWith("System.Tuple") || original.StartsWith("System.ValueTuple")) - { - var tupleArgs = typeArgs.Select((arg, i) => - Helper.IsTrulyPrimitive(arg) ? $"x.Item{i + 1}" : $"x.Item{i + 1}?.DeepCopy()"); - - assignment = $"this.{member.Name} is {{ }} x ? new {propType.ToDisplayString()}({string.Join(", ", tupleArgs)}) : default"; - } - else - { + default: assignment = $"this.{member.Name}?.DeepCopy()"; - } - - break; + break; } - default: - assignment = $"this.{member.Name}?.DeepCopy()"; - break; - } sb.AppendLine($" {member.Name} = {assignment},"); } diff --git a/src/UtilityVerse.Copy/Generators/ShallowCopyGenerator.cs b/src/UtilityVerse.Copy/Generators/ShallowCopyGenerator.cs index 6682760..3d764b3 100644 --- a/src/UtilityVerse.Copy/Generators/ShallowCopyGenerator.cs +++ b/src/UtilityVerse.Copy/Generators/ShallowCopyGenerator.cs @@ -1,3 +1,9 @@ +/// +/// Author: Pritom Purkayasta +// Copyright (c) Pritom Purkayasta All rights reserved. +// FREE TO USE TO CONNECT THE WORLD +/// + using System.Linq; using System.Text; using Microsoft.CodeAnalysis; @@ -64,7 +70,7 @@ public static class ShallowCopyGenerator { sb.AppendLine($" var copy = ({typeName})this.MemberwiseClone();"); - foreach (var member in typeSymbol.GetMembers().OfType()) + foreach (var member in Helper.GetAllProperties(typeSymbol)) { if (member.IsReadOnly || member.SetMethod is null) continue; diff --git a/src/UtilityVerse.Copy/Helper.cs b/src/UtilityVerse.Copy/Helper.cs index 543322f..0209aba 100644 --- a/src/UtilityVerse.Copy/Helper.cs +++ b/src/UtilityVerse.Copy/Helper.cs @@ -1,7 +1,10 @@ /// /// Author: Pritom Purkayasta +// Copyright (c) Pritom Purkayasta All rights reserved. +// FREE TO USE TO CONNECT THE WORLD /// +using System.Collections.Generic; using Microsoft.CodeAnalysis; namespace UtilityVerse.Copy; @@ -10,7 +13,7 @@ internal static class Helper { internal static bool IsPrimitive(ITypeSymbol symbol) => symbol.IsValueType || symbol.SpecialType == SpecialType.System_String; - + internal static bool IsTrulyPrimitive(ITypeSymbol symbol) { return symbol.SpecialType switch @@ -45,7 +48,7 @@ internal static bool IsGenericCollection(string? originalDef, out string? kind) "System.ComponentModel.BindingList" => "List", "System.Collections.Immutable.ImmutableList" => "List", "System.Collections.Generic.ReadOnlyList" => "List", // custom/unofficial - + // Enumerable-like collections "System.Collections.Generic.IEnumerable" => "Enumerable", "System.Collections.Generic.ICollection" => "Enumerable", @@ -59,7 +62,7 @@ internal static bool IsGenericCollection(string? originalDef, out string? kind) "System.Collections.Generic.Dictionary" => "Dictionary", "System.Collections.Frozen.FrozenDictionary" => "Dictionary", "System.Collections.Concurrent.ConcurrentDictionary" => "Dictionary", - + // Immutable "System.Collections.Immutable.ImmutableArray" => "List", "System.Collections.Immutable.ImmutableSortedSet" => "HashSet", @@ -76,4 +79,19 @@ internal static bool IsGenericCollection(string? originalDef, out string? kind) return kind != null; } + + internal static IEnumerable GetAllProperties(INamedTypeSymbol? type) + { + while (type != null && type.SpecialType != SpecialType.System_Object) + { + foreach (var member in type.GetMembers().OfType()) + { + if (!member.IsReadOnly && member.SetMethod != null) + yield return member; + } + + type = type.BaseType; + } + } + } \ No newline at end of file diff --git a/src/UtilityVerse.Copy/Interfaces/IDeepCopy.cs b/src/UtilityVerse.Copy/Interfaces/IDeepCopy.cs index c18cfaa..7d86f2f 100644 --- a/src/UtilityVerse.Copy/Interfaces/IDeepCopy.cs +++ b/src/UtilityVerse.Copy/Interfaces/IDeepCopy.cs @@ -1,6 +1,8 @@ /// /// Author: Pritom Purkayasta +// Copyright (c) Pritom Purkayasta All rights reserved. +// FREE TO USE TO CONNECT THE WORLD /// -namespace UtilityVerse.Copy.Interfaces; +namespace UtilityVerse.Copy; public interface IDeepCopy; \ No newline at end of file diff --git a/src/UtilityVerse.Copy/Interfaces/IShallowCopy.cs b/src/UtilityVerse.Copy/Interfaces/IShallowCopy.cs index ffab419..9f363e5 100644 --- a/src/UtilityVerse.Copy/Interfaces/IShallowCopy.cs +++ b/src/UtilityVerse.Copy/Interfaces/IShallowCopy.cs @@ -1,7 +1,8 @@ /// /// Author: Pritom Purkayasta +// Copyright (c) Pritom Purkayasta All rights reserved. +// FREE TO USE TO CONNECT THE WORLD /// -namespace UtilityVerse.Copy.Interfaces; - +namespace UtilityVerse.Copy; public interface IShallowCopy; \ No newline at end of file diff --git a/src/UtilityVerse.Copy/README.md b/src/UtilityVerse.Copy/README.md index 5240774..7b2e7a4 100644 --- a/src/UtilityVerse.Copy/README.md +++ b/src/UtilityVerse.Copy/README.md @@ -17,7 +17,7 @@ --- -# > Give it a star if you like the project. 👏 🌠 🌟 +## > Give it a star if you like the project. 👏 🌠 🌟 --- @@ -167,6 +167,4 @@ We welcome contributions! Please see the [CONTRIBUTE.md](CONTRIBUTE.md) file for See real-world performance metrics in [BENCHMARK.md](BENCHMARK.md) ---- - -Let me know if you’d like to auto-generate this README as part of your build pipeline or package metadata! \ No newline at end of file +--- \ No newline at end of file diff --git a/src/UtilityVerse.Copy/UtilityVerse.Copy.csproj b/src/UtilityVerse.Copy/UtilityVerse.Copy.csproj index 3ac4bb2..0e317e3 100644 --- a/src/UtilityVerse.Copy/UtilityVerse.Copy.csproj +++ b/src/UtilityVerse.Copy/UtilityVerse.Copy.csproj @@ -5,18 +5,18 @@ latest enable - + Analyzer true true true - + Copy UtilityVerse.Copy - 0.1.0 + 0.1.1 Pritom Purkayasta A Roslyn source generator for generating shallow copy and deep copy for class, records and structs @@ -41,13 +41,18 @@ - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + - + True @@ -60,4 +65,4 @@ - + \ No newline at end of file diff --git a/src/UtilityVerse.Copy/nuget.md b/src/UtilityVerse.Copy/nuget.md index 093cc7c..910123d 100644 --- a/src/UtilityVerse.Copy/nuget.md +++ b/src/UtilityVerse.Copy/nuget.md @@ -1,37 +1,42 @@ -# Copy - Source Generator for DeepCopy and ShallowCopy in C# +--- -🚀 **Copy** is a Roslyn-based source generator that reduces boilerplate by automatically generating `DeepCopy` and `ShallowCopy` methods for your models. Just use an attribute or implement a marker interface, and the generator handles the rest! +# 📦 UtilityVerse.Copy — Source Generator for DeepCopy and ShallowCopy in C\# +🚀 **UtilityVerse.Copy** is a Roslyn-based source generator that automatically creates `DeepCopy()` and `ShallowCopy()` methods for your models. Eliminate repetitive boilerplate and enjoy clean, maintainable code with zero runtime dependencies. --- ## ✨ Features -- ✅ Automatically generates `DeepCopy()` and/or `ShallowCopy()` methods -- ✅ Clean, zero-runtime dependency — generation happens at compile-time -- ✅ Supports both `[Attribute]` and `interface`-based opt-in mechanisms -- ✅ Great for DTOs, ViewModels, and data layer objects +* ✅ Automatically generates `DeepCopy()` and/or `ShallowCopy()` methods at compile-time +* ✅ No runtime overhead — it's all generated in the background by Roslyn +* ✅ Supports both `[Attribute]`-based and `interface`-based opt-in mechanisms +* ✅ Works great for DTOs, ViewModels, and plain C# objects +* ✅ Smart support for common collection types, arrays, tuples, and more --- + ## 📦 Installation -Add the package to your project using NuGet: +Install the NuGet package: ```bash -dotnet add package Copy +dotnet add package UtilityVerse.Copy ``` +--- + +## 🛠️ How It Works -🛠️ How It Works +> **Important**: Your classes must be marked `partial` for the generator to emit code successfully. -> MAKE SURE, your models/pocos/dtos are `partial`. Without this, it won't work. +### ✅ Option 1: Use Attributes -### Option 1: Use Attributes +Apply `[ShallowCopy]` or `[DeepCopy]` to your class or struct: -Annotate your model class with `[ShallowCopy]` or `[DeepCopy]`: -```c# -using Copy; +```csharp +using UtilityVerse.Copy; [ShallowCopy] public partial class Person @@ -39,11 +44,11 @@ public partial class Person public string Name { get; set; } public int Age { get; set; } } - ``` -This generates a ShallowCopy() method: -```c# +This generates a `ShallowCopy()` method at compile-time: + +```csharp public Person ShallowCopy() { return new Person @@ -53,36 +58,82 @@ public Person ShallowCopy() }; } ``` -### Option 2: Implement Marker Interfaces -Instead of using attributes, implement one of the marker interfaces: +For deep copy: -```c# +```csharp +[DeepCopy] +public partial class Order +{ + public Customer Customer { get; set; } + public List Items { get; set; } +} +``` + +Generates a deep recursive `DeepCopy()` method that copies nested references and collections. + +--- + +### ✅ Option 2: Use Marker Interfaces + +Prefer no attributes? Just implement the marker interfaces: + +```csharp public partial class Person : IShallowCopy { public string Name { get; set; } public int Age { get; set; } } ``` -This will have the same result: a ShallowCopy() method will be generated at compile-time. -🤿 Deep Copy Support - -If your model contains reference-type properties or collections and you want a true deep copy, use `[DeepCopy]` or implement `IDeepCopy`: -```c# -[DeepCopy] -public partial class Order +```csharp +public partial class Order : IDeepCopy { public Customer Customer { get; set; } public List Items { get; set; } } ``` -The generator will recursively clone reference types where possible. -✅ Generated Code Is: +The generator will handle the rest automatically! + +--- + +## 📚 Supported Types + +* ✅ Primitives and strings +* ✅ Arrays +* ✅ Tuples and `ValueTuple` +* ✅ Generic collections: `List`, `IEnumerable`, `ICollection`, `IReadOnlyList`, `HashSet`, etc. +* ✅ Dictionary-like collections: `Dictionary`, `ConcurrentDictionary`, `FrozenDictionary`, etc. +* ✅ Record types, classes, structs (as long as they are `partial`) +* ✅ Nested properties recursively copied in `DeepCopy()` - Partial – Safe to regenerate on build +--- - Readable – Emitted to the intermediate folder for inspection +## 🧪 Sample Output + +Given this class: + +```csharp +[DeepCopy] +public partial class User +{ + public string Name { get; set; } + public List
      Addresses { get; set; } +} +``` + +Generated deep copy: + +```csharp +public User DeepCopy() +{ + return new User + { + Name = this.Name, + Addresses = this.Addresses?.Select(x => x?.DeepCopy()).ToList() + }; +} +``` - Non-intrusive – Won’t modify your original source +--- \ No newline at end of file diff --git a/test/UtilityVerse.Copy.Test/HelloWorld.cs b/test/UtilityVerse.Copy.Test/HelloWorld.cs index 91577ac..8f1f2f5 100644 --- a/test/UtilityVerse.Copy.Test/HelloWorld.cs +++ b/test/UtilityVerse.Copy.Test/HelloWorld.cs @@ -1,11 +1,11 @@ -using UtilityVerse.Copy.Attributes; +namespace UtilityVerse.Copy.Test; [DeepCopy] public partial class HelloWorld { public int Id { get; set; } - public string Name { get; set; } - public IEnumerable
      Addresses { get; set; } + public string? Name { get; set; } + public IEnumerable
      Addresses { get; set; } = []; public HelloWorld Clone() { @@ -13,11 +13,12 @@ public HelloWorld Clone() { Id = Id, Name = Name, - Addresses = Addresses.Select(x=> x.Clone()).ToList() + Addresses = Addresses.Select(x => x.Clone()).ToList() }; } } + [DeepCopy] public partial class Address { diff --git a/test/UtilityVerse.Copy.Test/Program.cs b/test/UtilityVerse.Copy.Test/Program.cs index 9d644ae..54af99a 100644 --- a/test/UtilityVerse.Copy.Test/Program.cs +++ b/test/UtilityVerse.Copy.Test/Program.cs @@ -1,4 +1,7 @@ -Console.WriteLine("Starting...."); +using UtilityVerse.Copy.Test; +using UtilityVerse.Copy; + +Console.WriteLine("Starting...."); ManualTesting(); @@ -18,7 +21,7 @@ static void AutomatedTesting() Addresses = new[] { new Address() { StreetNumber = "12" } } }; - var clonedObject = originalObject.DeepCopy(); + var clonedObject = originalObject; Console.WriteLine("Before Cloning..."); Console.WriteLine( diff --git a/test/UtilityVerse.Copy.Test/UtilityVerse.Copy.Test.csproj b/test/UtilityVerse.Copy.Test/UtilityVerse.Copy.Test.csproj index e214f0d..919d4fe 100644 --- a/test/UtilityVerse.Copy.Test/UtilityVerse.Copy.Test.csproj +++ b/test/UtilityVerse.Copy.Test/UtilityVerse.Copy.Test.csproj @@ -8,8 +8,12 @@ - - + + - + + + \ No newline at end of file From 5b697d2879209d656f70a9b7bca739cab53c7ad8 Mon Sep 17 00:00:00 2001 From: purkayasta Date: Sun, 29 Jun 2025 16:08:22 +0600 Subject: [PATCH 13/20] Copy: Fixed generator issue in Copy --- src/UtilityVerse.Copy/CopyGenerator.cs | 8 ++-- src/UtilityVerse.Copy/README.md | 40 +++++++++------- .../UtilityVerse.Copy.csproj | 4 +- src/UtilityVerse.Copy/UtilityVerse.Copy.sln | 24 ++++++++++ src/UtilityVerse.Copy/nuget.md | 47 ++++++++++++++----- 5 files changed, 87 insertions(+), 36 deletions(-) create mode 100644 src/UtilityVerse.Copy/UtilityVerse.Copy.sln diff --git a/src/UtilityVerse.Copy/CopyGenerator.cs b/src/UtilityVerse.Copy/CopyGenerator.cs index 44d5882..70ca80a 100644 --- a/src/UtilityVerse.Copy/CopyGenerator.cs +++ b/src/UtilityVerse.Copy/CopyGenerator.cs @@ -59,11 +59,11 @@ private static (INamedTypeSymbol Symbol, CopyMode Mode)? GetSemanticTarget(Gener if (symbol is not { DeclaredAccessibility: Accessibility.Public }) return null; - var hasDeep = symbol.GetAttributes().Any(a => a.AttributeClass?.ToDisplayString() == "UtilityVerse.Copy.Attributes.DeepCopy") || - symbol.AllInterfaces.Any(i => i.ToDisplayString() == "UtilityVerse.Copy.Interfaces.IDeepCopy"); + var hasDeep = symbol.GetAttributes().Any(a => a.AttributeClass?.ToDisplayString() == "UtilityVerse.Copy.DeepCopy") || + symbol.AllInterfaces.Any(i => i.ToDisplayString() == "UtilityVerse.Copy.IDeepCopy"); - var hasShallow = symbol.GetAttributes().Any(a => a.AttributeClass?.ToDisplayString() == "UtilityVerse.Copy.Attributes.ShallowCopy") || - symbol.AllInterfaces.Any(i => i.ToDisplayString() == "UtilityVerse.Copy.Interfaces.IShallowCopy"); + var hasShallow = symbol.GetAttributes().Any(a => a.AttributeClass?.ToDisplayString() == "UtilityVerse.Copy.ShallowCopy") || + symbol.AllInterfaces.Any(i => i.ToDisplayString() == "UtilityVerse.Copy.IShallowCopy"); if (hasDeep) return (symbol, CopyMode.Deep); if (hasShallow) return (symbol, CopyMode.Shallow); diff --git a/src/UtilityVerse.Copy/README.md b/src/UtilityVerse.Copy/README.md index 7b2e7a4..c3c24e7 100644 --- a/src/UtilityVerse.Copy/README.md +++ b/src/UtilityVerse.Copy/README.md @@ -12,7 +12,7 @@ * ✅ No runtime overhead — it's all generated in the background by Roslyn * ✅ Supports both `[Attribute]`-based and `interface`-based opt-in mechanisms * ✅ Works great for DTOs, ViewModels, and plain C# objects -* ✅ Smart support for common collection types, arrays, tuples, and more +* ✅ Support for common collection types, arrays, tuples, and more --- @@ -56,27 +56,35 @@ This generates a `ShallowCopy()` method at compile-time: ```csharp public Person ShallowCopy() { - return new Person - { - Name = this.Name, - Age = this.Age - }; + return (Person)this.MemberWiseClone(); } ``` For deep copy: ```csharp +using UtilityVerse.Copy; [DeepCopy] -public partial class Order +public partial class Person { - public Customer Customer { get; set; } - public List Items { get; set; } + public string Name { get; set; } + public int Age { get; set; } } ``` Generates a deep recursive `DeepCopy()` method that copies nested references and collections. +```csharp +public Person DeepCopy() +{ + return new Person + { + Name = this.Name, + Age = this.Age + }; +} +``` + --- ### ✅ Option 2: Use Marker Interfaces @@ -84,6 +92,7 @@ Generates a deep recursive `DeepCopy()` method that copies nested references and Prefer no attributes? Just implement the marker interfaces: ```csharp +using UtilityVerse.Copy; public partial class Person : IShallowCopy { public string Name { get; set; } @@ -92,10 +101,11 @@ public partial class Person : IShallowCopy ``` ```csharp -public partial class Order : IDeepCopy +using UtilityVerse.Copy; +public partial class Person : IDeepCopy { - public Customer Customer { get; set; } - public List Items { get; set; } + public string Name { get; set; } + public int Age { get; set; } } ``` @@ -161,10 +171,4 @@ This project is licensed under the [MIT License](LICENSE). We welcome contributions! Please see the [CONTRIBUTE.md](CONTRIBUTE.md) file for guidelines. ---- - -## 🏎️ Benchmark - -See real-world performance metrics in [BENCHMARK.md](BENCHMARK.md) - --- \ No newline at end of file diff --git a/src/UtilityVerse.Copy/UtilityVerse.Copy.csproj b/src/UtilityVerse.Copy/UtilityVerse.Copy.csproj index 0e317e3..12c128f 100644 --- a/src/UtilityVerse.Copy/UtilityVerse.Copy.csproj +++ b/src/UtilityVerse.Copy/UtilityVerse.Copy.csproj @@ -16,7 +16,7 @@ Copy UtilityVerse.Copy - 0.1.1 + 0.2.1 Pritom Purkayasta A Roslyn source generator for generating shallow copy and deep copy for class, records and structs @@ -50,7 +50,7 @@ - + diff --git a/src/UtilityVerse.Copy/UtilityVerse.Copy.sln b/src/UtilityVerse.Copy/UtilityVerse.Copy.sln new file mode 100644 index 0000000..5f63515 --- /dev/null +++ b/src/UtilityVerse.Copy/UtilityVerse.Copy.sln @@ -0,0 +1,24 @@ +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.5.2.0 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "UtilityVerse.Copy", "UtilityVerse.Copy.csproj", "{381427B0-86D7-C4BF-ADDE-F56BD66B638B}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {381427B0-86D7-C4BF-ADDE-F56BD66B638B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {381427B0-86D7-C4BF-ADDE-F56BD66B638B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {381427B0-86D7-C4BF-ADDE-F56BD66B638B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {381427B0-86D7-C4BF-ADDE-F56BD66B638B}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {54E540C6-9E0C-4569-B917-30077F35B028} + EndGlobalSection +EndGlobal diff --git a/src/UtilityVerse.Copy/nuget.md b/src/UtilityVerse.Copy/nuget.md index 910123d..21e42f2 100644 --- a/src/UtilityVerse.Copy/nuget.md +++ b/src/UtilityVerse.Copy/nuget.md @@ -12,11 +12,16 @@ * ✅ No runtime overhead — it's all generated in the background by Roslyn * ✅ Supports both `[Attribute]`-based and `interface`-based opt-in mechanisms * ✅ Works great for DTOs, ViewModels, and plain C# objects -* ✅ Smart support for common collection types, arrays, tuples, and more +* ✅ Support for common collection types, arrays, tuples, and more --- +## > Give it a star if you like the project. 👏 🌠 🌟 + + +--- + ## 📦 Installation Install the NuGet package: @@ -51,27 +56,35 @@ This generates a `ShallowCopy()` method at compile-time: ```csharp public Person ShallowCopy() { - return new Person - { - Name = this.Name, - Age = this.Age - }; + return (Person)this.MemberWiseClone(); } ``` For deep copy: ```csharp +using UtilityVerse.Copy; [DeepCopy] -public partial class Order +public partial class Person { - public Customer Customer { get; set; } - public List Items { get; set; } + public string Name { get; set; } + public int Age { get; set; } } ``` Generates a deep recursive `DeepCopy()` method that copies nested references and collections. +```csharp +public Person DeepCopy() +{ + return new Person + { + Name = this.Name, + Age = this.Age + }; +} +``` + --- ### ✅ Option 2: Use Marker Interfaces @@ -79,6 +92,7 @@ Generates a deep recursive `DeepCopy()` method that copies nested references and Prefer no attributes? Just implement the marker interfaces: ```csharp +using UtilityVerse.Copy; public partial class Person : IShallowCopy { public string Name { get; set; } @@ -87,10 +101,11 @@ public partial class Person : IShallowCopy ``` ```csharp -public partial class Order : IDeepCopy +using UtilityVerse.Copy; +public partial class Person : IDeepCopy { - public Customer Customer { get; set; } - public List Items { get; set; } + public string Name { get; set; } + public int Age { get; set; } } ``` @@ -136,4 +151,12 @@ public User DeepCopy() } ``` +--- + +## 🔒 Safe and Reliable + +* 💡 **Partial**: Won’t overwrite your code — generated code lives alongside your class +* 🧾 **Readable**: Generated files are emitted to the intermediate folder (obj) +* ⚙️ **Non-Intrusive**: No reflection, no extra dependencies + --- \ No newline at end of file From bcb28ec5ef0a7ec7b2abb3a99e484c9d510472d0 Mon Sep 17 00:00:00 2001 From: purkayasta Date: Sun, 29 Jun 2025 16:28:12 +0600 Subject: [PATCH 14/20] Updated Readme.md --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 91cba86..ac36288 100644 --- a/README.md +++ b/README.md @@ -12,13 +12,13 @@ Use whatever suits your project: -1. [**UtilityVerse**](https://www.nuget.org/packages/UtilityVerse/) +1. [**UtilityVerse**](https://www.nuget.org/packages/UtilityVerse/) -> ![Nuget](https://img.shields.io/nuget/v/UtilityVerse) ![Nuget](https://img.shields.io/nuget/dt/UtilityVerse?style=plastic) 🧰 No dependencies — includes core utility methods. -2. [**UtilityVerse.ASPNET**](https://www.nuget.org/packages/UtilityVerse.ASPNET/) +2. [**UtilityVerse.ASPNET**](https://www.nuget.org/packages/UtilityVerse.ASPNET/) -> ![Nuget](https://img.shields.io/nuget/v/UtilityVerse.ASPNET) ![Nuget](https://img.shields.io/nuget/dt/UtilityVerse.ASPNET?style=plastic) 🌐 ASP.NET Core-specific helpers and extensions. -3. [**UtilityVerse.Copy**](https://www.nuget.org/packages/UtilityVerse.Copy/) +3. [**UtilityVerse.Copy**](https://www.nuget.org/packages/UtilityVerse.Copy/) -> ![Nuget](https://img.shields.io/nuget/v/UtilityVerse.Copy) ![Nuget](https://img.shields.io/nuget/dt/UtilityVerse.Copy?style=plastic) ✍️ A Roslyn-based source generator that automatically generates `ShallowCopy()` and `DeepCopy()` methods for your models. → See full details here: [`UtilityVerse.Copy/README.md`](./src/UtilityVerse.Copy/README.md) From 0c901564f44acd5b4700d7149887c5c36673bf26 Mon Sep 17 00:00:00 2001 From: purkayasta Date: Wed, 16 Jul 2025 02:10:36 +0600 Subject: [PATCH 15/20] Added enum support and Analyzer warnings --- .../AnalyzerReleases.Shipped.md | 9 ++ .../AnalyzerReleases.Unshipped.md | 0 .../Analyzers/MissingPartialErrorAnalyzer.cs | 56 ++++++++ .../Analyzers/NestedTypeErrorAnalyzer.cs | 122 ++++++++++++++++++ src/UtilityVerse.Copy/CopyGenerator.cs | 7 +- .../Generators/DeepCopyGenerator.cs | 27 ++-- .../Generators/ShallowCopyGenerator.cs | 54 ++++---- src/UtilityVerse.Copy/Helper.cs | 22 ++++ .../UtilityVerse.Copy.csproj | 13 +- test/UtilityVerse.Copy.Test/HelloWorld.cs | 1 - 10 files changed, 260 insertions(+), 51 deletions(-) create mode 100644 src/UtilityVerse.Copy/AnalyzerReleases.Shipped.md create mode 100644 src/UtilityVerse.Copy/AnalyzerReleases.Unshipped.md create mode 100644 src/UtilityVerse.Copy/Analyzers/MissingPartialErrorAnalyzer.cs create mode 100644 src/UtilityVerse.Copy/Analyzers/NestedTypeErrorAnalyzer.cs diff --git a/src/UtilityVerse.Copy/AnalyzerReleases.Shipped.md b/src/UtilityVerse.Copy/AnalyzerReleases.Shipped.md new file mode 100644 index 0000000..9c59d23 --- /dev/null +++ b/src/UtilityVerse.Copy/AnalyzerReleases.Shipped.md @@ -0,0 +1,9 @@ +## Release 0.3.0 + +### New Rules + +Rule ID | Category | Severity | Notes +--------|------------------|----------|-------------------- +UV001 | UtilityVerse.Copy | Error | MissingPartialErrorAnalyzer +UV002 | UtilityVerse.Copy | Error | NestedTypeErrorAnalyzer (DeepCopy) +UV003 | UtilityVerse.Copy | Error | NestedTypeErrorAnalyzer (ShallowCopy) diff --git a/src/UtilityVerse.Copy/AnalyzerReleases.Unshipped.md b/src/UtilityVerse.Copy/AnalyzerReleases.Unshipped.md new file mode 100644 index 0000000..e69de29 diff --git a/src/UtilityVerse.Copy/Analyzers/MissingPartialErrorAnalyzer.cs b/src/UtilityVerse.Copy/Analyzers/MissingPartialErrorAnalyzer.cs new file mode 100644 index 0000000..3161723 --- /dev/null +++ b/src/UtilityVerse.Copy/Analyzers/MissingPartialErrorAnalyzer.cs @@ -0,0 +1,56 @@ +using System.Collections.Immutable; +using System.Linq; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Diagnostics; + +namespace UtilityVerse.Copy.Analyzers; + +[DiagnosticAnalyzer(LanguageNames.CSharp)] +public class MissingPartialErrorAnalyzer : DiagnosticAnalyzer +{ + private static readonly DiagnosticDescriptor MissingPartialModifierRule = new( + id: "UV001", + title: "Type must be marked as partial for DeepCopy/ShallowCopy generation", + messageFormat: "Type '{0}' must be marked as 'partial' to support DeepCopy/ShallowCopy generation", + category: "UtilityVerse.Copy", + DiagnosticSeverity.Error, + isEnabledByDefault: true, + description: "UtilityVerse.Copy requires all target types to be marked with 'partial' so the DeepCopy() or ShallowCopy() method can be injected." + ); + + public override ImmutableArray SupportedDiagnostics => [MissingPartialModifierRule]; + + public override void Initialize(AnalysisContext context) + { + // Configure analysis for syntax trees + context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None); + context.EnableConcurrentExecution(); + + context.RegisterSyntaxNodeAction(AnalyzeTypeDeclaration, SyntaxKind.ClassDeclaration, SyntaxKind.StructDeclaration, SyntaxKind.RecordDeclaration, SyntaxKind.RecordStructDeclaration); + } + + private static void AnalyzeTypeDeclaration(SyntaxNodeAnalysisContext context) + { + var typeDecl = (TypeDeclarationSyntax)context.Node; + + // Skip if already marked partial + if (typeDecl.Modifiers.Any(m => m.IsKind(SyntaxKind.PartialKeyword))) + return; + + // Skip if not user-defined (e.g., external/metadata references) + var symbol = context.SemanticModel.GetDeclaredSymbol(typeDecl); + if (symbol is null || symbol.DeclaringSyntaxReferences.Length == 0) + return; + + // Optionally: check if it's used for DeepCopy somehow + // Example: skip types with [GeneratedCode] or [CompilerGenerated] + if (symbol.GetAttributes().Any(attr => + attr.AttributeClass?.ToDisplayString() is "System.CodeDom.Compiler.GeneratedCodeAttribute" or "System.Runtime.CompilerServices.CompilerGeneratedAttribute")) + return; + + var diagnostic = Diagnostic.Create(MissingPartialModifierRule, typeDecl.Identifier.GetLocation(), symbol.Name); + context.ReportDiagnostic(diagnostic); + } +} \ No newline at end of file diff --git a/src/UtilityVerse.Copy/Analyzers/NestedTypeErrorAnalyzer.cs b/src/UtilityVerse.Copy/Analyzers/NestedTypeErrorAnalyzer.cs new file mode 100644 index 0000000..1c3a407 --- /dev/null +++ b/src/UtilityVerse.Copy/Analyzers/NestedTypeErrorAnalyzer.cs @@ -0,0 +1,122 @@ +using System.Collections.Immutable; +using System.Linq; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Diagnostics; + +namespace UtilityVerse.Copy.Analyzers; + +[DiagnosticAnalyzer(LanguageNames.CSharp)] +public class NestedTypeErrorAnalyzer : DiagnosticAnalyzer +{ + private static readonly DiagnosticDescriptor DeepRule = new( + id: "UV002", + title: "Nested type missing DeepCopy attribute", + messageFormat: "Property '{0}' of type '{1}' requires nested type '{2}' to have DeepCopy attribute or implement IDeepCopy", + category: "UtilityVerse.Copy", + defaultSeverity: DiagnosticSeverity.Error, + isEnabledByDefault: true); + + private static readonly DiagnosticDescriptor ShallowRule = new DiagnosticDescriptor( + id: "UV003", + title: "Nested type missing ShallowCopy attribute", + messageFormat: "Property '{0}' of type '{1}' requires nested type '{2}' to have ShallowCopy attribute or implement IShallowCopy", + category: "UtilityVerse.Copy", + defaultSeverity: DiagnosticSeverity.Error, + isEnabledByDefault: true); + + public override ImmutableArray SupportedDiagnostics => + [DeepRule, ShallowRule]; + + public override void Initialize(AnalysisContext context) + { + context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None); + context.EnableConcurrentExecution(); + + context.RegisterSymbolAction(AnalyzeNamedType, SymbolKind.NamedType); + } + + private void AnalyzeNamedType(SymbolAnalysisContext context) + { + var namedType = (INamedTypeSymbol)context.Symbol; + + // Only analyze public partial types (assuming generator requires partial) + if (namedType.DeclaredAccessibility != Accessibility.Public) + return; + + if (!namedType.DeclaringSyntaxReferences.Any(syntaxRef => + syntaxRef.GetSyntax() is TypeDeclarationSyntax tds && + tds.Modifiers.Any(m => m.IsKind(SyntaxKind.PartialKeyword)))) + return; + + // Check if type opts into DeepCopy or ShallowCopy + bool hasDeepCopy = Helper.HasDeepCopyOptIn(namedType); + bool hasShallowCopy = Helper.HasShallowCopyOptIn(namedType); + + if (!hasDeepCopy && !hasShallowCopy) + return; // type itself not opted in - no need to check nested types + + // Get all properties including inherited + var properties = namedType.GetMembers().OfType().Where(p => !p.IsStatic); + + foreach (var prop in properties) + { + if (prop.Type is not INamedTypeSymbol propType) + { + continue; + } + // Skip primitives and enums + if (Helper.IsTrulyPrimitive(propType)) + continue; + + if (propType.IsGenericType) + { + foreach (var typeArg in propType.TypeArguments.OfType()) + { + if (Helper.IsTrulyPrimitive(typeArg)) + continue; + + if (hasDeepCopy && !Helper.HasDeepCopyOptIn(typeArg)) + { + var diagnostic = Diagnostic.Create( + DeepRule, + prop.Locations.FirstOrDefault(), + prop.Name, namedType.Name, typeArg.Name); + context.ReportDiagnostic(diagnostic); + } + + if (hasShallowCopy && !Helper.HasShallowCopyOptIn(typeArg)) + { + var diagnostic = Diagnostic.Create( + ShallowRule, + prop.Locations.FirstOrDefault(), + prop.Name, namedType.Name, typeArg.Name); + context.ReportDiagnostic(diagnostic); + } + } + } + else + { + // Handle non-generic nested types + if (hasDeepCopy && !Helper.HasDeepCopyOptIn(propType)) + { + var diagnostic = Diagnostic.Create( + DeepRule, + prop.Locations.FirstOrDefault(), + prop.Name, namedType.Name, propType.Name); + context.ReportDiagnostic(diagnostic); + } + + if (hasShallowCopy && !Helper.HasShallowCopyOptIn(propType)) + { + var diagnostic = Diagnostic.Create( + ShallowRule, + prop.Locations.FirstOrDefault(), + prop.Name, namedType.Name, propType.Name); + context.ReportDiagnostic(diagnostic); + } + } + } + } +} \ No newline at end of file diff --git a/src/UtilityVerse.Copy/CopyGenerator.cs b/src/UtilityVerse.Copy/CopyGenerator.cs index 70ca80a..50121f6 100644 --- a/src/UtilityVerse.Copy/CopyGenerator.cs +++ b/src/UtilityVerse.Copy/CopyGenerator.cs @@ -7,7 +7,6 @@ using System.Linq; using System.Text; using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Text; using UtilityVerse.Copy.Generators; @@ -52,8 +51,8 @@ private static (INamedTypeSymbol Symbol, CopyMode Mode)? GetSemanticTarget(Gener if (context.Node is not TypeDeclarationSyntax typeDecl) return null; - if (!typeDecl.Modifiers.Any(m => m.IsKind(SyntaxKind.PartialKeyword))) - return null; + // if (!typeDecl.Modifiers.Any(m => m.IsKind(SyntaxKind.PartialKeyword))) + // return null; var symbol = ModelExtensions.GetDeclaredSymbol(context.SemanticModel, typeDecl) as INamedTypeSymbol; if (symbol is not { DeclaredAccessibility: Accessibility.Public }) @@ -71,7 +70,7 @@ private static (INamedTypeSymbol Symbol, CopyMode Mode)? GetSemanticTarget(Gener return null; } - private static string GenerateDeepCopy(INamedTypeSymbol typeSymbol) => DeepCopyGenerator.Generate(typeSymbol); + private static string? GenerateDeepCopy(INamedTypeSymbol typeSymbol) => DeepCopyGenerator.Generate(typeSymbol); private static string? GenerateShallowCopy(INamedTypeSymbol typeSymbol) => ShallowCopyGenerator.Generate(typeSymbol); diff --git a/src/UtilityVerse.Copy/Generators/DeepCopyGenerator.cs b/src/UtilityVerse.Copy/Generators/DeepCopyGenerator.cs index 7b08e8d..0677cc8 100644 --- a/src/UtilityVerse.Copy/Generators/DeepCopyGenerator.cs +++ b/src/UtilityVerse.Copy/Generators/DeepCopyGenerator.cs @@ -12,9 +12,10 @@ namespace UtilityVerse.Copy.Generators; internal static class DeepCopyGenerator { - internal static string Generate(INamedTypeSymbol typeSymbol) + internal static string? Generate(INamedTypeSymbol typeSymbol) { if (typeSymbol.IsAbstract) return string.Empty; + if (!Helper.IsPartial(typeSymbol)) return string.Empty; var isGlobalNamespace = typeSymbol.ContainingNamespace.IsGlobalNamespace; var namespaceName = isGlobalNamespace ? null : typeSymbol.ContainingNamespace.ToDisplayString(); @@ -36,16 +37,20 @@ internal static string Generate(INamedTypeSymbol typeSymbol) sb.AppendLine(""" - // - // This code was generated by Copy. - // Author: Pritom Purkayasta - // DO NOT modify this file manually. Changes may be overwritten. - // This file contains auto-generated DeepCopy() implementations. - // - - using System; - using System.Linq; - using System.Collections.Generic; + // + // This code was generated by Copy. + // Author: Pritom Purkayasta + // DO NOT modify this file manually. Changes may be overwritten. + // This file contains auto-generated DeepCopy() implementations. + // + + using System; + using System.Linq; + using System.Collections.Generic; + using System.Collections.ObjectModel; + using System.Collections.Immutable; + using System.Collections.Concurrent; + using System.Collections.Frozen; """); diff --git a/src/UtilityVerse.Copy/Generators/ShallowCopyGenerator.cs b/src/UtilityVerse.Copy/Generators/ShallowCopyGenerator.cs index 3d764b3..f3c1da9 100644 --- a/src/UtilityVerse.Copy/Generators/ShallowCopyGenerator.cs +++ b/src/UtilityVerse.Copy/Generators/ShallowCopyGenerator.cs @@ -10,12 +10,12 @@ namespace UtilityVerse.Copy.Generators; -public static class ShallowCopyGenerator +internal static class ShallowCopyGenerator { - public static string? Generate(INamedTypeSymbol typeSymbol) + internal static string? Generate(INamedTypeSymbol typeSymbol) { - if (typeSymbol.IsAbstract) - return null; + if (typeSymbol.IsAbstract) return string.Empty; + if (!Helper.IsPartial(typeSymbol)) return string.Empty; var isGlobalNamespace = typeSymbol.ContainingNamespace.IsGlobalNamespace; var namespaceName = isGlobalNamespace ? null : typeSymbol.ContainingNamespace.ToDisplayString(); @@ -87,34 +87,34 @@ public static class ShallowCopyGenerator break; case INamedTypeSymbol named when named.IsGenericType: - { - var original = named.OriginalDefinition.ConstructUnboundGenericType().ToDisplayString(); - - if (Helper.IsGenericCollection(original, out var kind)) { - var itemType = named.TypeArguments.FirstOrDefault()?.ToDisplayString() ?? "var"; - assignment = kind switch - { - "List" or "Enumerable" => - $"this.{member.Name}?.ToList()", + var original = named.OriginalDefinition.ConstructUnboundGenericType().ToDisplayString(); - "HashSet" => - $"this.{member.Name} != null ? new HashSet<{itemType}>(this.{member.Name}) : null", - - "Dictionary" => - $"this.{member.Name}?.ToDictionary(x => x.Key, x => x.Value)", + if (Helper.IsGenericCollection(original, out var kind)) + { + var itemType = named.TypeArguments.FirstOrDefault()?.ToDisplayString() ?? "var"; + assignment = kind switch + { + "List" or "Enumerable" => + $"this.{member.Name}?.ToList()", + + "HashSet" => + $"this.{member.Name} != null ? new HashSet<{itemType}>(this.{member.Name}) : null", + + "Dictionary" => + $"this.{member.Name}?.ToDictionary(x => x.Key, x => x.Value)", + + _ => null + }; + } + else if (original.StartsWith("System.Tuple")) + { + assignment = $"this.{member.Name}"; + } - _ => null - }; - } - else if (original.StartsWith("System.Tuple")) - { - assignment = $"this.{member.Name}"; + break; } - break; - } - case INamedTypeSymbol named when named.ToDisplayString().StartsWith("System.ValueTuple"): assignment = $"this.{member.Name}"; break; diff --git a/src/UtilityVerse.Copy/Helper.cs b/src/UtilityVerse.Copy/Helper.cs index 0209aba..7a0a838 100644 --- a/src/UtilityVerse.Copy/Helper.cs +++ b/src/UtilityVerse.Copy/Helper.cs @@ -5,7 +5,10 @@ /// using System.Collections.Generic; +using System.Linq; using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; namespace UtilityVerse.Copy; @@ -30,6 +33,7 @@ SpecialType.System_UInt64 or SpecialType.System_Single or SpecialType.System_Double or SpecialType.System_Char or + SpecialType.System_Enum or SpecialType.System_String => true, _ => false }; @@ -94,4 +98,22 @@ internal static IEnumerable GetAllProperties(INamedTypeSymbol? } } + internal static bool IsPartial(INamedTypeSymbol typeSymbol) + { + return typeSymbol.DeclaringSyntaxReferences.Any(syntaxRef => + syntaxRef.GetSyntax() is TypeDeclarationSyntax syntax && + syntax.Modifiers.Any(m => m.IsKind(SyntaxKind.PartialKeyword))); + } + + internal static bool HasDeepCopyOptIn(INamedTypeSymbol typeSymbol) + { + return typeSymbol.GetAttributes().Any(a => a.AttributeClass?.ToDisplayString() == "UtilityVerse.Copy.DeepCopy") || + typeSymbol.AllInterfaces.Any(i => i.ToDisplayString() == "UtilityVerse.Copy.IDeepCopy"); + } + + internal static bool HasShallowCopyOptIn(INamedTypeSymbol typeSymbol) + { + return typeSymbol.GetAttributes().Any(a => a.AttributeClass?.ToDisplayString() == "UtilityVerse.Copy.ShallowCopy") || + typeSymbol.AllInterfaces.Any(i => i.ToDisplayString() == "UtilityVerse.Copy.IShallowCopy"); + } } \ No newline at end of file diff --git a/src/UtilityVerse.Copy/UtilityVerse.Copy.csproj b/src/UtilityVerse.Copy/UtilityVerse.Copy.csproj index 12c128f..3e5aee4 100644 --- a/src/UtilityVerse.Copy/UtilityVerse.Copy.csproj +++ b/src/UtilityVerse.Copy/UtilityVerse.Copy.csproj @@ -1,5 +1,4 @@  - netstandard2.0 latest @@ -10,13 +9,13 @@ Analyzer true true - true + true Copy UtilityVerse.Copy - 0.2.1 + 0.3.0 Pritom Purkayasta A Roslyn source generator for generating shallow copy and deep copy for class, records and structs @@ -45,12 +44,12 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive - + + - - + @@ -63,6 +62,4 @@ \ - - \ No newline at end of file diff --git a/test/UtilityVerse.Copy.Test/HelloWorld.cs b/test/UtilityVerse.Copy.Test/HelloWorld.cs index 8f1f2f5..9caaae1 100644 --- a/test/UtilityVerse.Copy.Test/HelloWorld.cs +++ b/test/UtilityVerse.Copy.Test/HelloWorld.cs @@ -18,7 +18,6 @@ public HelloWorld Clone() } } - [DeepCopy] public partial class Address { From dd97efb465c2aa1ce7975da4ef78ad426fd61aab Mon Sep 17 00:00:00 2001 From: purkayasta Date: Wed, 16 Jul 2025 02:32:01 +0600 Subject: [PATCH 16/20] Updated the csproj file --- src/UtilityVerse/UtilityVerse.csproj | 28 ++++++---------------------- 1 file changed, 6 insertions(+), 22 deletions(-) diff --git a/src/UtilityVerse/UtilityVerse.csproj b/src/UtilityVerse/UtilityVerse.csproj index 83e0d23..4ab4649 100644 --- a/src/UtilityVerse/UtilityVerse.csproj +++ b/src/UtilityVerse/UtilityVerse.csproj @@ -10,7 +10,8 @@ UtilityVerse 1.0 utility, utils, C#, dotnet, c# utility, dotnet core - a curated list of utility methods that will help you, do your work at your best. + a curated list of utility methods that will help you, do your work at your + best. Pritom Purkayasta https://github.com/purkayasta/TheUtilityVerse https://www.nuget.org/packages/UtilityVerse/ @@ -27,12 +28,14 @@ True snupkg + MIT true UtilityVerse 1.0 utility, utils, C#, dotnet, c# utility, dotnet core - a curated list of utility methods that will help you, do your work at your best. + a curated list of utility methods that will help you, do your work at your + best. Pritom Purkayasta https://github.com/purkayasta/TheUtilityVerse https://www.nuget.org/packages/UtilityVerse/ @@ -50,19 +53,11 @@ snupkg - - True - True - True True - - True - True - True True @@ -79,16 +74,5 @@ \ - - - True - \ - - - - True - \ - - - + \ No newline at end of file From ea25fe30fa9479747fe0c17d4d0684ffcff47bc1 Mon Sep 17 00:00:00 2001 From: purkayasta Date: Wed, 16 Jul 2025 02:35:52 +0600 Subject: [PATCH 17/20] Updated Yml file --- .github/workflows/dotnet.yml | 4 +-- .../ByteExtensionTest.cs | 28 ------------------- 2 files changed, 2 insertions(+), 30 deletions(-) delete mode 100644 test/UtilityVerse.UnitTest/ByteExtensionTest.cs diff --git a/.github/workflows/dotnet.yml b/.github/workflows/dotnet.yml index 4245ab0..a5ca465 100644 --- a/.github/workflows/dotnet.yml +++ b/.github/workflows/dotnet.yml @@ -5,9 +5,9 @@ name: .NET on: push: - branches: [ "master", "development" ] + branches: [ "development" ] pull_request: - branches: [ "master", "development" ] + branches: [ "master" ] jobs: build: diff --git a/test/UtilityVerse.UnitTest/ByteExtensionTest.cs b/test/UtilityVerse.UnitTest/ByteExtensionTest.cs deleted file mode 100644 index f725e55..0000000 --- a/test/UtilityVerse.UnitTest/ByteExtensionTest.cs +++ /dev/null @@ -1,28 +0,0 @@ -using System.Text.Json; -using System.Text.Json.Nodes; - -namespace UtilityVerse.UnitTest; - -public class ByteExtensionTest -{ - [Fact] - public void ToObject_ThrowsJsonException_WhenInvalidByteArrayProvided() - { - byte[] bytes = [1, 2, 3, 4, 5]; - var result = ByteExtension.ToObject(bytes); - Assert.Throws(() => result); - } - - [Fact] - public void ToObject_ReturnProperObject_WhenValidJsonByteArrayIsProvided() - { - JsonObject jObject = new() - { - { "key1", "value1" }, - { "key2", "value2" } - }; - var byteArr = JsonSerializer.SerializeToUtf8Bytes(jObject); - Shared.UtilityVerseResult result = ByteExtension.ToObject(byteArr); - Assert.Equal(result.Result, jObject.AsObject()); - } -} From 767871a7a6a2b276dd6b4ee8722963940525f113 Mon Sep 17 00:00:00 2001 From: purkayasta Date: Sat, 19 Jul 2025 13:21:27 +0600 Subject: [PATCH 18/20] COPY: Updated The validation error analyzer --- TODO.md | 4 - .../UtilityVerse.ASPNET.csproj | 6 +- .../Analyzers/MissingPartialErrorAnalyzer.cs | 56 ------ .../Analyzers/NestedTypeErrorAnalyzer.cs | 122 ------------ src/UtilityVerse.Copy/Analyzers/Rules.cs | 30 +++ .../Analyzers/ValidationAnalyzer.cs | 133 +++++++++++++ src/UtilityVerse.Copy/Helper.cs | 59 +++--- src/UtilityVerse.Copy/README.md | 185 +++++++++--------- .../UtilityVerse.Copy.csproj | 2 +- src/UtilityVerse.Copy/UtilityVerse.Copy.sln | 24 --- src/UtilityVerse/breaking-changes.md | 5 +- .../UtilityVerse.UnitTest.csproj | 8 +- 12 files changed, 298 insertions(+), 336 deletions(-) delete mode 100644 src/UtilityVerse.Copy/Analyzers/MissingPartialErrorAnalyzer.cs delete mode 100644 src/UtilityVerse.Copy/Analyzers/NestedTypeErrorAnalyzer.cs create mode 100644 src/UtilityVerse.Copy/Analyzers/Rules.cs create mode 100644 src/UtilityVerse.Copy/Analyzers/ValidationAnalyzer.cs delete mode 100644 src/UtilityVerse.Copy/UtilityVerse.Copy.sln diff --git a/TODO.md b/TODO.md index 12e47d7..15f06ee 100644 --- a/TODO.md +++ b/TODO.md @@ -5,7 +5,3 @@ - [ ] Enum To String Converter function. - [ ] Mapper - [ ] Object Mapper Attribute with Source Code Generation. - - -- [ ] Rewrite the unit test and test only the core logic part. -- [ ] Upload into the nuget. \ No newline at end of file diff --git a/src/UtilityVerse.ASPNET/UtilityVerse.ASPNET.csproj b/src/UtilityVerse.ASPNET/UtilityVerse.ASPNET.csproj index bcfb76b..f16982c 100644 --- a/src/UtilityVerse.ASPNET/UtilityVerse.ASPNET.csproj +++ b/src/UtilityVerse.ASPNET/UtilityVerse.ASPNET.csproj @@ -1,7 +1,7 @@  - netstandard2.1; net6.0; net8.0 + netstandard2.1; net6.0; net8.0; net9.0 Latest enable enable @@ -52,8 +52,8 @@ - - + + diff --git a/src/UtilityVerse.Copy/Analyzers/MissingPartialErrorAnalyzer.cs b/src/UtilityVerse.Copy/Analyzers/MissingPartialErrorAnalyzer.cs deleted file mode 100644 index 3161723..0000000 --- a/src/UtilityVerse.Copy/Analyzers/MissingPartialErrorAnalyzer.cs +++ /dev/null @@ -1,56 +0,0 @@ -using System.Collections.Immutable; -using System.Linq; -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.CSharp; -using Microsoft.CodeAnalysis.CSharp.Syntax; -using Microsoft.CodeAnalysis.Diagnostics; - -namespace UtilityVerse.Copy.Analyzers; - -[DiagnosticAnalyzer(LanguageNames.CSharp)] -public class MissingPartialErrorAnalyzer : DiagnosticAnalyzer -{ - private static readonly DiagnosticDescriptor MissingPartialModifierRule = new( - id: "UV001", - title: "Type must be marked as partial for DeepCopy/ShallowCopy generation", - messageFormat: "Type '{0}' must be marked as 'partial' to support DeepCopy/ShallowCopy generation", - category: "UtilityVerse.Copy", - DiagnosticSeverity.Error, - isEnabledByDefault: true, - description: "UtilityVerse.Copy requires all target types to be marked with 'partial' so the DeepCopy() or ShallowCopy() method can be injected." - ); - - public override ImmutableArray SupportedDiagnostics => [MissingPartialModifierRule]; - - public override void Initialize(AnalysisContext context) - { - // Configure analysis for syntax trees - context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None); - context.EnableConcurrentExecution(); - - context.RegisterSyntaxNodeAction(AnalyzeTypeDeclaration, SyntaxKind.ClassDeclaration, SyntaxKind.StructDeclaration, SyntaxKind.RecordDeclaration, SyntaxKind.RecordStructDeclaration); - } - - private static void AnalyzeTypeDeclaration(SyntaxNodeAnalysisContext context) - { - var typeDecl = (TypeDeclarationSyntax)context.Node; - - // Skip if already marked partial - if (typeDecl.Modifiers.Any(m => m.IsKind(SyntaxKind.PartialKeyword))) - return; - - // Skip if not user-defined (e.g., external/metadata references) - var symbol = context.SemanticModel.GetDeclaredSymbol(typeDecl); - if (symbol is null || symbol.DeclaringSyntaxReferences.Length == 0) - return; - - // Optionally: check if it's used for DeepCopy somehow - // Example: skip types with [GeneratedCode] or [CompilerGenerated] - if (symbol.GetAttributes().Any(attr => - attr.AttributeClass?.ToDisplayString() is "System.CodeDom.Compiler.GeneratedCodeAttribute" or "System.Runtime.CompilerServices.CompilerGeneratedAttribute")) - return; - - var diagnostic = Diagnostic.Create(MissingPartialModifierRule, typeDecl.Identifier.GetLocation(), symbol.Name); - context.ReportDiagnostic(diagnostic); - } -} \ No newline at end of file diff --git a/src/UtilityVerse.Copy/Analyzers/NestedTypeErrorAnalyzer.cs b/src/UtilityVerse.Copy/Analyzers/NestedTypeErrorAnalyzer.cs deleted file mode 100644 index 1c3a407..0000000 --- a/src/UtilityVerse.Copy/Analyzers/NestedTypeErrorAnalyzer.cs +++ /dev/null @@ -1,122 +0,0 @@ -using System.Collections.Immutable; -using System.Linq; -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.CSharp; -using Microsoft.CodeAnalysis.CSharp.Syntax; -using Microsoft.CodeAnalysis.Diagnostics; - -namespace UtilityVerse.Copy.Analyzers; - -[DiagnosticAnalyzer(LanguageNames.CSharp)] -public class NestedTypeErrorAnalyzer : DiagnosticAnalyzer -{ - private static readonly DiagnosticDescriptor DeepRule = new( - id: "UV002", - title: "Nested type missing DeepCopy attribute", - messageFormat: "Property '{0}' of type '{1}' requires nested type '{2}' to have DeepCopy attribute or implement IDeepCopy", - category: "UtilityVerse.Copy", - defaultSeverity: DiagnosticSeverity.Error, - isEnabledByDefault: true); - - private static readonly DiagnosticDescriptor ShallowRule = new DiagnosticDescriptor( - id: "UV003", - title: "Nested type missing ShallowCopy attribute", - messageFormat: "Property '{0}' of type '{1}' requires nested type '{2}' to have ShallowCopy attribute or implement IShallowCopy", - category: "UtilityVerse.Copy", - defaultSeverity: DiagnosticSeverity.Error, - isEnabledByDefault: true); - - public override ImmutableArray SupportedDiagnostics => - [DeepRule, ShallowRule]; - - public override void Initialize(AnalysisContext context) - { - context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None); - context.EnableConcurrentExecution(); - - context.RegisterSymbolAction(AnalyzeNamedType, SymbolKind.NamedType); - } - - private void AnalyzeNamedType(SymbolAnalysisContext context) - { - var namedType = (INamedTypeSymbol)context.Symbol; - - // Only analyze public partial types (assuming generator requires partial) - if (namedType.DeclaredAccessibility != Accessibility.Public) - return; - - if (!namedType.DeclaringSyntaxReferences.Any(syntaxRef => - syntaxRef.GetSyntax() is TypeDeclarationSyntax tds && - tds.Modifiers.Any(m => m.IsKind(SyntaxKind.PartialKeyword)))) - return; - - // Check if type opts into DeepCopy or ShallowCopy - bool hasDeepCopy = Helper.HasDeepCopyOptIn(namedType); - bool hasShallowCopy = Helper.HasShallowCopyOptIn(namedType); - - if (!hasDeepCopy && !hasShallowCopy) - return; // type itself not opted in - no need to check nested types - - // Get all properties including inherited - var properties = namedType.GetMembers().OfType().Where(p => !p.IsStatic); - - foreach (var prop in properties) - { - if (prop.Type is not INamedTypeSymbol propType) - { - continue; - } - // Skip primitives and enums - if (Helper.IsTrulyPrimitive(propType)) - continue; - - if (propType.IsGenericType) - { - foreach (var typeArg in propType.TypeArguments.OfType()) - { - if (Helper.IsTrulyPrimitive(typeArg)) - continue; - - if (hasDeepCopy && !Helper.HasDeepCopyOptIn(typeArg)) - { - var diagnostic = Diagnostic.Create( - DeepRule, - prop.Locations.FirstOrDefault(), - prop.Name, namedType.Name, typeArg.Name); - context.ReportDiagnostic(diagnostic); - } - - if (hasShallowCopy && !Helper.HasShallowCopyOptIn(typeArg)) - { - var diagnostic = Diagnostic.Create( - ShallowRule, - prop.Locations.FirstOrDefault(), - prop.Name, namedType.Name, typeArg.Name); - context.ReportDiagnostic(diagnostic); - } - } - } - else - { - // Handle non-generic nested types - if (hasDeepCopy && !Helper.HasDeepCopyOptIn(propType)) - { - var diagnostic = Diagnostic.Create( - DeepRule, - prop.Locations.FirstOrDefault(), - prop.Name, namedType.Name, propType.Name); - context.ReportDiagnostic(diagnostic); - } - - if (hasShallowCopy && !Helper.HasShallowCopyOptIn(propType)) - { - var diagnostic = Diagnostic.Create( - ShallowRule, - prop.Locations.FirstOrDefault(), - prop.Name, namedType.Name, propType.Name); - context.ReportDiagnostic(diagnostic); - } - } - } - } -} \ No newline at end of file diff --git a/src/UtilityVerse.Copy/Analyzers/Rules.cs b/src/UtilityVerse.Copy/Analyzers/Rules.cs new file mode 100644 index 0000000..5bb93a9 --- /dev/null +++ b/src/UtilityVerse.Copy/Analyzers/Rules.cs @@ -0,0 +1,30 @@ +using Microsoft.CodeAnalysis; + +namespace UtilityVerse.Copy.Analyzers; + +internal static class Rules +{ + internal static readonly DiagnosticDescriptor MissingPartialRule = new( + id: "UV001", + title: "Type must be marked as partial", + messageFormat: "Type '{0}' must be marked as 'partial' to support DeepCopy/ShallowCopy generation", + category: "UtilityVerse.Copy", + DiagnosticSeverity.Error, + isEnabledByDefault: true); + + internal static readonly DiagnosticDescriptor DeepRule = new( + id: "UV002", + title: "Nested type missing DeepCopy attribute", + messageFormat: "Property '{0}' of type '{1}' requires nested type '{2}' to have DeepCopy attribute or implement IDeepCopy", + category: "UtilityVerse.Copy", + DiagnosticSeverity.Error, + isEnabledByDefault: true); + + internal static readonly DiagnosticDescriptor ShallowRule = new( + id: "UV003", + title: "Nested type missing ShallowCopy attribute", + messageFormat: "Property '{0}' of type '{1}' requires nested type '{2}' to have ShallowCopy attribute or implement IShallowCopy", + category: "UtilityVerse.Copy", + DiagnosticSeverity.Error, + isEnabledByDefault: true); +} \ No newline at end of file diff --git a/src/UtilityVerse.Copy/Analyzers/ValidationAnalyzer.cs b/src/UtilityVerse.Copy/Analyzers/ValidationAnalyzer.cs new file mode 100644 index 0000000..6207a06 --- /dev/null +++ b/src/UtilityVerse.Copy/Analyzers/ValidationAnalyzer.cs @@ -0,0 +1,133 @@ +using System.Collections.Immutable; +using System.Linq; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Diagnostics; + +namespace UtilityVerse.Copy.Analyzers; + +[DiagnosticAnalyzer(LanguageNames.CSharp)] +public class ValidationAnalyzer : DiagnosticAnalyzer +{ + public override ImmutableArray SupportedDiagnostics => + [Rules.MissingPartialRule, Rules.DeepRule, Rules.ShallowRule]; + + public override void Initialize(AnalysisContext context) + { + context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None); + context.EnableConcurrentExecution(); + + context.RegisterCompilationStartAction(compilationContext => + { + compilationContext.RegisterSymbolAction(AnalyzeNamedType, SymbolKind.NamedType); + }); + } + + private static void AnalyzeNamedType(SymbolAnalysisContext context) + { + if (context.Symbol is not INamedTypeSymbol namedType) + return; + + if (namedType.TypeKind is not (TypeKind.Class or TypeKind.Struct)) + return; + + if (namedType.GetAttributes().Any(attr => + attr.AttributeClass?.ToDisplayString() is + "System.CodeDom.Compiler.GeneratedCodeAttribute" or + "System.Runtime.CompilerServices.CompilerGeneratedAttribute")) + return; + + var syntaxRef = namedType.DeclaringSyntaxReferences.FirstOrDefault(); + if (syntaxRef?.GetSyntax() is not TypeDeclarationSyntax typeSyntax) + return; + + var hasDeepCopy = Helper.HasDeepCopyOptIn(namedType); + var hasShallowCopy = Helper.HasShallowCopyOptIn(namedType); + + if ((hasDeepCopy || hasShallowCopy) && !Helper.IsPartial(namedType)) + { + var diagnostic = Diagnostic.Create(Rules.MissingPartialRule, typeSyntax.Identifier.GetLocation(), namedType.Name); + context.ReportDiagnostic(diagnostic); + } + + if (!hasDeepCopy && !hasShallowCopy) + return; + + foreach (var prop in Helper.GetAllProperties(namedType).Where(p => !p.IsStatic)) + { + AnalyzeTypeRecursive( + context, + prop.Type, + prop.Locations.FirstOrDefault() ?? typeSyntax.Identifier.GetLocation(), + prop.Name, + namedType, + hasDeepCopy, + hasShallowCopy + ); + } + } + + private static void AnalyzeTypeRecursive( + SymbolAnalysisContext context, + ITypeSymbol type, + Location diagnosticLocation, + string propertyName, + INamedTypeSymbol parentType, + bool requireDeepCopy, + bool requireShallowCopy) + { + if (type is not INamedTypeSymbol namedType) + return; + + var unwrapped = UnwrapNullable(namedType); + + if (Helper.IsTrulyPrimitive(unwrapped)) + return; + + if (Helper.IsGenericCollection(unwrapped.OriginalDefinition.ToDisplayString(), out _)) + { + foreach (var typeArg in unwrapped.TypeArguments.OfType()) + { + AnalyzeTypeRecursive(context, typeArg, diagnosticLocation, propertyName, parentType, requireDeepCopy, requireShallowCopy); + } + return; + } + + if (requireDeepCopy && !Helper.HasDeepCopyOptIn(unwrapped)) + { + var diag = Diagnostic.Create(Rules.DeepRule, diagnosticLocation, + propertyName, parentType.Name, unwrapped.Name); + context.ReportDiagnostic(diag); + } + + if (requireShallowCopy && !Helper.HasShallowCopyOptIn(unwrapped)) + { + var diag = Diagnostic.Create(Rules.ShallowRule, diagnosticLocation, + propertyName, parentType.Name, unwrapped.Name); + context.ReportDiagnostic(diag); + } + } + + private static INamedTypeSymbol UnwrapNullable(INamedTypeSymbol type) + { + return type.OriginalDefinition.SpecialType == SpecialType.System_Nullable_T && + type.TypeArguments.FirstOrDefault() is INamedTypeSymbol underlying + ? underlying + : type; + } + + private static bool IsInsideCopyEnabledPartialParent(INamedTypeSymbol? symbol) + { + while (symbol is not null) + { + if (!Helper.IsPartial(symbol)) + return false; + + if (Helper.HasDeepCopyOptIn(symbol) || Helper.HasShallowCopyOptIn(symbol)) + return true; + + symbol = symbol.ContainingType; + } + return false; + } +} diff --git a/src/UtilityVerse.Copy/Helper.cs b/src/UtilityVerse.Copy/Helper.cs index 7a0a838..83ce90d 100644 --- a/src/UtilityVerse.Copy/Helper.cs +++ b/src/UtilityVerse.Copy/Helper.cs @@ -1,9 +1,3 @@ -/// -/// Author: Pritom Purkayasta -// Copyright (c) Pritom Purkayasta All rights reserved. -// FREE TO USE TO CONNECT THE WORLD -/// - using System.Collections.Generic; using System.Linq; using Microsoft.CodeAnalysis; @@ -22,19 +16,19 @@ internal static bool IsTrulyPrimitive(ITypeSymbol symbol) return symbol.SpecialType switch { SpecialType.System_Boolean or - SpecialType.System_Byte or - SpecialType.System_SByte or - SpecialType.System_Int16 or - SpecialType.System_UInt16 or - SpecialType.System_Int32 or - SpecialType.System_UInt32 or - SpecialType.System_Int64 or - SpecialType.System_UInt64 or - SpecialType.System_Single or - SpecialType.System_Double or - SpecialType.System_Char or - SpecialType.System_Enum or - SpecialType.System_String => true, + SpecialType.System_Byte or + SpecialType.System_SByte or + SpecialType.System_Int16 or + SpecialType.System_UInt16 or + SpecialType.System_Int32 or + SpecialType.System_UInt32 or + SpecialType.System_Int64 or + SpecialType.System_UInt64 or + SpecialType.System_Single or + SpecialType.System_Double or + SpecialType.System_Char or + SpecialType.System_Enum or + SpecialType.System_String => true, _ => false }; } @@ -43,7 +37,6 @@ internal static bool IsGenericCollection(string? originalDef, out string? kind) { kind = originalDef switch { - // List-like collections "System.Collections.Generic.List" => "List", "System.Collections.Generic.IList" => "List", "System.Collections.ObjectModel.Collection" => "List", @@ -51,30 +44,25 @@ internal static bool IsGenericCollection(string? originalDef, out string? kind) "System.Collections.ObjectModel.ObservableCollection" => "List", "System.ComponentModel.BindingList" => "List", "System.Collections.Immutable.ImmutableList" => "List", - "System.Collections.Generic.ReadOnlyList" => "List", // custom/unofficial + "System.Collections.Generic.ReadOnlyList" => "List", - // Enumerable-like collections "System.Collections.Generic.IEnumerable" => "Enumerable", "System.Collections.Generic.ICollection" => "Enumerable", "System.Collections.Generic.IReadOnlyCollection" => "Enumerable", "System.Collections.Generic.IReadOnlyList" => "Enumerable", - // Hash-based collections "System.Collections.Generic.HashSet" => "HashSet", - // Dictionary-like collections "System.Collections.Generic.Dictionary" => "Dictionary", "System.Collections.Frozen.FrozenDictionary" => "Dictionary", "System.Collections.Concurrent.ConcurrentDictionary" => "Dictionary", - // Immutable "System.Collections.Immutable.ImmutableArray" => "List", "System.Collections.Immutable.ImmutableSortedSet" => "HashSet", "System.Collections.Immutable.ImmutableHashSet" => "HashSet", "System.Collections.Immutable.ImmutableDictionary" => "Dictionary", "System.Collections.Immutable.ImmutableSortedDictionary" => "Dictionary", - // Queue / Stack "System.Collections.Generic.Queue" => "Queue", "System.Collections.Generic.Stack" => "Stack", @@ -107,13 +95,22 @@ internal static bool IsPartial(INamedTypeSymbol typeSymbol) internal static bool HasDeepCopyOptIn(INamedTypeSymbol typeSymbol) { - return typeSymbol.GetAttributes().Any(a => a.AttributeClass?.ToDisplayString() == "UtilityVerse.Copy.DeepCopy") || - typeSymbol.AllInterfaces.Any(i => i.ToDisplayString() == "UtilityVerse.Copy.IDeepCopy"); + return typeSymbol.GetAttributes().Any(a => + SymbolEquals(a.AttributeClass, "UtilityVerse.Copy.DeepCopy")) || + typeSymbol.AllInterfaces.Any(i => + SymbolEquals(i, "UtilityVerse.Copy.IDeepCopy")); } internal static bool HasShallowCopyOptIn(INamedTypeSymbol typeSymbol) { - return typeSymbol.GetAttributes().Any(a => a.AttributeClass?.ToDisplayString() == "UtilityVerse.Copy.ShallowCopy") || - typeSymbol.AllInterfaces.Any(i => i.ToDisplayString() == "UtilityVerse.Copy.IShallowCopy"); + return typeSymbol.GetAttributes().Any(a => + SymbolEquals(a.AttributeClass, "UtilityVerse.Copy.ShallowCopy")) || + typeSymbol.AllInterfaces.Any(i => + SymbolEquals(i, "UtilityVerse.Copy.IShallowCopy")); + } + + private static bool SymbolEquals(ISymbol? symbol, string fullyQualifiedMetadataName) + { + return symbol?.ToDisplayString() == fullyQualifiedMetadataName; } -} \ No newline at end of file +} diff --git a/src/UtilityVerse.Copy/README.md b/src/UtilityVerse.Copy/README.md index c3c24e7..b9fb7b2 100644 --- a/src/UtilityVerse.Copy/README.md +++ b/src/UtilityVerse.Copy/README.md @@ -1,174 +1,183 @@ + --- # 📦 UtilityVerse.Copy — Source Generator for DeepCopy and ShallowCopy in C\# -🚀 **UtilityVerse.Copy** is a Roslyn-based source generator that automatically creates `DeepCopy()` and `ShallowCopy()` methods for your models. Eliminate repetitive boilerplate and enjoy clean, maintainable code with zero runtime dependencies. - +![Nuget](https://img.shields.io/nuget/v/UtilityVerse.Copy) ![Nuget](https://img.shields.io/nuget/dt/UtilityVerse.Copy?style=plastic) --- -## ✨ Features +## 📖 What is UtilityVerse.Copy? -* ✅ Automatically generates `DeepCopy()` and/or `ShallowCopy()` methods at compile-time -* ✅ No runtime overhead — it's all generated in the background by Roslyn -* ✅ Supports both `[Attribute]`-based and `interface`-based opt-in mechanisms -* ✅ Works great for DTOs, ViewModels, and plain C# objects -* ✅ Support for common collection types, arrays, tuples, and more +**UtilityVerse.Copy** is a Roslyn-based source generator for C#. It automatically creates `DeepCopy()` and `ShallowCopy()` methods for your classes and structs at compile-time. With zero runtime dependencies, your copy logic is safe, efficient, and maintainable — without writing repetitive code. --- +## ❓ Why Use It? -## > Give it a star if you like the project. 👏 🌠 🌟 +* Copying objects (deep or shallow) usually requires verbose, error-prone boilerplate. +* UtilityVerse.Copy removes that burden by generating fully-typed copy methods at compile-time using Roslyn. +* No runtime reflection, no magic — just pure, reliable, compiled code. +* Perfect for: + * DTOs + * ViewModels + * Configuration models + * Game entities + * Domain models needing clean clone operations --- -## 📦 Installation +## ✨ Features -Install the NuGet package: +* ✅ Auto-generates `DeepCopy()` and/or `ShallowCopy()` methods +* ✅ Pure compile-time code generation (no runtime dependencies) +* ✅ Supports both `[Attributes]` and `Marker Interfaces` +* ✅ Handles: -```bash -dotnet add package UtilityVerse.Copy -``` + * Common collections (List, HashSet, Dictionary, etc.) + * Arrays, Tuples, ValueTuples + * Primitive types, strings, and nested objects + * Records, classes, and structs (as long as they are `partial`) +* ✅ Safe — generated code is separate and non-intrusive --- -## 🛠️ How It Works +## 🔑 API — What You Need to Use -> **Important**: Your classes must be marked `partial` for the generator to emit code successfully. +### 1️⃣ Attributes -### ✅ Option 1: Use Attributes +Decorate your classes/structs using: -Apply `[ShallowCopy]` or `[DeepCopy]` to your class or struct: +* `[DeepCopy]` — to generate a recursive deep copy method. +* `[ShallowCopy]` — to generate a simple shallow copy. + +Example: ```csharp using UtilityVerse.Copy; -[ShallowCopy] -public partial class Person +[DeepCopy] +public partial class Product { public string Name { get; set; } - public int Age { get; set; } + public decimal Price { get; set; } } ``` -This generates a `ShallowCopy()` method at compile-time: +--- -```csharp -public Person ShallowCopy() -{ - return (Person)this.MemberWiseClone(); -} -``` +### 2️⃣ Marker Interfaces -For deep copy: +If you prefer code-first opt-in: + +* Implement `IDeepCopy` for deep copy generation. +* Implement `IShallowCopy` for shallow copy generation. + +Example: ```csharp using UtilityVerse.Copy; -[DeepCopy] -public partial class Person + +public partial class Product : IDeepCopy { public string Name { get; set; } - public int Age { get; set; } + public decimal Price { get; set; } } ``` -Generates a deep recursive `DeepCopy()` method that copies nested references and collections. +--- -```csharp -public Person DeepCopy() -{ - return new Person - { - Name = this.Name, - Age = this.Age - }; -} -``` +> **Important:** +> Your class or struct **must be marked `partial`** for the source generator to work. --- -### ✅ Option 2: Use Marker Interfaces +## 🚀 Tutorial — How to Use -Prefer no attributes? Just implement the marker interfaces: +### Step 1️⃣ — Install via NuGet + +```bash +dotnet add package UtilityVerse.Copy +``` + +--- + +### Step 2️⃣ — Add `[DeepCopy]` or `[ShallowCopy]` ```csharp using UtilityVerse.Copy; -public partial class Person : IShallowCopy + +[DeepCopy] +public partial class User { - public string Name { get; set; } - public int Age { get; set; } + public int Id { get; set; } + public string? Name { get; set; } + public List
      Addresses { get; set; } = []; } -``` -```csharp -using UtilityVerse.Copy; -public partial class Person : IDeepCopy +[DeepCopy] +public partial class Address { - public string Name { get; set; } - public int Age { get; set; } + public string? StreetNumber { get; set; } } ``` -The generator will handle the rest automatically! - --- -## 📚 Supported Types +### Step 3️⃣ — Generated Code (Example) -* ✅ Primitives and strings -* ✅ Arrays -* ✅ Tuples and `ValueTuple` -* ✅ Generic collections: `List`, `IEnumerable`, `ICollection`, `IReadOnlyList`, `HashSet`, etc. -* ✅ Dictionary-like collections: `Dictionary`, `ConcurrentDictionary`, `FrozenDictionary`, etc. -* ✅ Record types, classes, structs (as long as they are `partial`) -* ✅ Nested properties recursively copied in `DeepCopy()` - ---- - -## 🧪 Sample Output - -Given this class: +`DeepCopy()` for `User` will be automatically generated: ```csharp -[DeepCopy] -public partial class User +public User DeepCopy() { - public string Name { get; set; } - public List
      Addresses { get; set; } + return new User + { + Id = this.Id, + Name = this.Name, + Addresses = this.Addresses?.Select(x => x?.DeepCopy()).ToList() + }; } ``` -Generated deep copy: +Similarly for `Address`: ```csharp -public User DeepCopy() +public Address DeepCopy() { - return new User + return new Address { - Name = this.Name, - Addresses = this.Addresses?.Select(x => x?.DeepCopy()).ToList() + StreetNumber = this.StreetNumber }; } ``` --- -## 🔒 Safe and Reliable +### Step 4️⃣ — Use It! -* 💡 **Partial**: Won’t overwrite your code — generated code lives alongside your class -* 🧾 **Readable**: Generated files are emitted to the intermediate folder (obj) -* ⚙️ **Non-Intrusive**: No reflection, no extra dependencies +```csharp +var userClone = existingUser.DeepCopy(); +``` --- -## 📄 License +## 📚 Supported Types -This project is licensed under the [MIT License](LICENSE). +* ✅ Primitives (`int`, `string`, `bool`, etc.) +* ✅ Arrays +* ✅ Tuples & ValueTuples +* ✅ Generic collections (`List`, `Dictionary`, etc.) +* ✅ Records, classes, structs (`partial` required) +* ✅ Deep recursive copying of nested properties --- -## 🤝 Contributing -We welcome contributions! Please see the [CONTRIBUTE.md](CONTRIBUTE.md) file for guidelines. +## 📣 Like It? Star It! ⭐ + +If you find UtilityVerse.Copy useful, give the repository a ⭐ and help others discover it! + +--- ---- \ No newline at end of file +Let me know if you'd like this structured as a template file or want me to generate sample output code files directly. diff --git a/src/UtilityVerse.Copy/UtilityVerse.Copy.csproj b/src/UtilityVerse.Copy/UtilityVerse.Copy.csproj index 3e5aee4..ad62653 100644 --- a/src/UtilityVerse.Copy/UtilityVerse.Copy.csproj +++ b/src/UtilityVerse.Copy/UtilityVerse.Copy.csproj @@ -15,7 +15,7 @@ Copy UtilityVerse.Copy - 0.3.0 + 0.4.0 Pritom Purkayasta A Roslyn source generator for generating shallow copy and deep copy for class, records and structs diff --git a/src/UtilityVerse.Copy/UtilityVerse.Copy.sln b/src/UtilityVerse.Copy/UtilityVerse.Copy.sln deleted file mode 100644 index 5f63515..0000000 --- a/src/UtilityVerse.Copy/UtilityVerse.Copy.sln +++ /dev/null @@ -1,24 +0,0 @@ -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 17 -VisualStudioVersion = 17.5.2.0 -MinimumVisualStudioVersion = 10.0.40219.1 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "UtilityVerse.Copy", "UtilityVerse.Copy.csproj", "{381427B0-86D7-C4BF-ADDE-F56BD66B638B}" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Release|Any CPU = Release|Any CPU - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {381427B0-86D7-C4BF-ADDE-F56BD66B638B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {381427B0-86D7-C4BF-ADDE-F56BD66B638B}.Debug|Any CPU.Build.0 = Debug|Any CPU - {381427B0-86D7-C4BF-ADDE-F56BD66B638B}.Release|Any CPU.ActiveCfg = Release|Any CPU - {381427B0-86D7-C4BF-ADDE-F56BD66B638B}.Release|Any CPU.Build.0 = Release|Any CPU - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection - GlobalSection(ExtensibilityGlobals) = postSolution - SolutionGuid = {54E540C6-9E0C-4569-B917-30077F35B028} - EndGlobalSection -EndGlobal diff --git a/src/UtilityVerse/breaking-changes.md b/src/UtilityVerse/breaking-changes.md index f561dd3..bd1d520 100644 --- a/src/UtilityVerse/breaking-changes.md +++ b/src/UtilityVerse/breaking-changes.md @@ -1,5 +1,4 @@ # Breaking Changes -- [x] Upgraded to .net8.0 and .netstandard2.1 -- [x] Instead of throwing exception, returning a data type where errors are given in a text so that developer can log it and handle their own way. -- [x] Remove Nullability. \ No newline at end of file +- [x] Upgraded to .net9.0 and .netstandard2.1 +- [x] Instead of throwing exception, returning a data type where errors are given in a text so that developer can log it and handle their own way. \ No newline at end of file diff --git a/test/UtilityVerse.UnitTest/UtilityVerse.UnitTest.csproj b/test/UtilityVerse.UnitTest/UtilityVerse.UnitTest.csproj index d863d7b..6bc15f5 100644 --- a/test/UtilityVerse.UnitTest/UtilityVerse.UnitTest.csproj +++ b/test/UtilityVerse.UnitTest/UtilityVerse.UnitTest.csproj @@ -9,13 +9,13 @@ - - - + + + runtime; build; native; contentfiles; analyzers; buildtransitive all - + runtime; build; native; contentfiles; analyzers; buildtransitive all From af499379983240b05d928860a663e2df37241fe5 Mon Sep 17 00:00:00 2001 From: purkayasta Date: Sat, 19 Jul 2025 13:26:32 +0600 Subject: [PATCH 19/20] COPY: Updated nuget.md file --- src/UtilityVerse.Copy/nuget.md | 143 +-------------------------------- 1 file changed, 1 insertion(+), 142 deletions(-) diff --git a/src/UtilityVerse.Copy/nuget.md b/src/UtilityVerse.Copy/nuget.md index 21e42f2..e84e0bb 100644 --- a/src/UtilityVerse.Copy/nuget.md +++ b/src/UtilityVerse.Copy/nuget.md @@ -17,146 +17,5 @@ --- -## > Give it a star if you like the project. 👏 🌠 🌟 - ---- - -## 📦 Installation - -Install the NuGet package: - -```bash -dotnet add package UtilityVerse.Copy -``` - ---- - -## 🛠️ How It Works - -> **Important**: Your classes must be marked `partial` for the generator to emit code successfully. - -### ✅ Option 1: Use Attributes - -Apply `[ShallowCopy]` or `[DeepCopy]` to your class or struct: - -```csharp -using UtilityVerse.Copy; - -[ShallowCopy] -public partial class Person -{ - public string Name { get; set; } - public int Age { get; set; } -} -``` - -This generates a `ShallowCopy()` method at compile-time: - -```csharp -public Person ShallowCopy() -{ - return (Person)this.MemberWiseClone(); -} -``` - -For deep copy: - -```csharp -using UtilityVerse.Copy; -[DeepCopy] -public partial class Person -{ - public string Name { get; set; } - public int Age { get; set; } -} -``` - -Generates a deep recursive `DeepCopy()` method that copies nested references and collections. - -```csharp -public Person DeepCopy() -{ - return new Person - { - Name = this.Name, - Age = this.Age - }; -} -``` - ---- - -### ✅ Option 2: Use Marker Interfaces - -Prefer no attributes? Just implement the marker interfaces: - -```csharp -using UtilityVerse.Copy; -public partial class Person : IShallowCopy -{ - public string Name { get; set; } - public int Age { get; set; } -} -``` - -```csharp -using UtilityVerse.Copy; -public partial class Person : IDeepCopy -{ - public string Name { get; set; } - public int Age { get; set; } -} -``` - -The generator will handle the rest automatically! - ---- - -## 📚 Supported Types - -* ✅ Primitives and strings -* ✅ Arrays -* ✅ Tuples and `ValueTuple` -* ✅ Generic collections: `List`, `IEnumerable`, `ICollection`, `IReadOnlyList`, `HashSet`, etc. -* ✅ Dictionary-like collections: `Dictionary`, `ConcurrentDictionary`, `FrozenDictionary`, etc. -* ✅ Record types, classes, structs (as long as they are `partial`) -* ✅ Nested properties recursively copied in `DeepCopy()` - ---- - -## 🧪 Sample Output - -Given this class: - -```csharp -[DeepCopy] -public partial class User -{ - public string Name { get; set; } - public List
      Addresses { get; set; } -} -``` - -Generated deep copy: - -```csharp -public User DeepCopy() -{ - return new User - { - Name = this.Name, - Addresses = this.Addresses?.Select(x => x?.DeepCopy()).ToList() - }; -} -``` - ---- - -## 🔒 Safe and Reliable - -* 💡 **Partial**: Won’t overwrite your code — generated code lives alongside your class -* 🧾 **Readable**: Generated files are emitted to the intermediate folder (obj) -* ⚙️ **Non-Intrusive**: No reflection, no extra dependencies - ---- \ No newline at end of file +> Code is fully opensource and more details can be found here at: [Github](https://github.com/purkayasta/TheUtilityVerse/blob/development/src/UtilityVerse.Copy/README.md) \ No newline at end of file From 2b3fe8e10584d2434dbddbd64a352f4d063a1660 Mon Sep 17 00:00:00 2001 From: purkayasta Date: Sat, 19 Jul 2025 13:27:45 +0600 Subject: [PATCH 20/20] Updated the MIT license file --- LICENSE.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/LICENSE.txt b/LICENSE.txt index c85c401..12c11b7 100644 --- a/LICENSE.txt +++ b/LICENSE.txt @@ -1,6 +1,6 @@ MIT License -Copyright (c) [2023] [Pritom Purkayasta] +Copyright (c) [2025] [Pritom Purkayasta] Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal