diff --git a/README.md b/README.md
index 126aff9..822851c 100644
--- a/README.md
+++ b/README.md
@@ -7,6 +7,7 @@ github.com/shaunrd0/klips/
├── ansible # Ansible roles, playbooks, and examples
├── blockchain # Blockchain related project templates and examples
├── cpp # C++ programs, datastructures, and other examples
+├── dotnet # .NET projects and examples
├── figlet # Figlet fonts I like :)
├── javascript # Javascript projects and examples
├── python # Python scripts or tools I've made
diff --git a/dotnet/README.md b/dotnet/README.md
new file mode 100644
index 0000000..8430435
--- /dev/null
+++ b/dotnet/README.md
@@ -0,0 +1,11 @@
+# Dotnet
+
+```bash
+shaunrd0/klips/dotnet/
+├── sitemap # Custom library to generate sitemaps
+├── testing # General .NET practice
+└── README.md
+```
+
+All of these projects were created with the `dotnet` CLI on Linux (Kubuntu 20.04).
+They have not been tested on any other platform.
diff --git a/dotnet/sitemap/.gitignore b/dotnet/sitemap/.gitignore
new file mode 100644
index 0000000..a72f3dd
--- /dev/null
+++ b/dotnet/sitemap/.gitignore
@@ -0,0 +1,454 @@
+## Ignore Visual Studio temporary files, build results, and
+## files generated by popular Visual Studio add-ons.
+##
+## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
+
+# User-specific files
+*.rsuser
+*.suo
+*.user
+*.userosscache
+*.sln.docstates
+
+# User-specific files (MonoDevelop/Xamarin Studio)
+*.userprefs
+
+# Mono auto generated files
+mono_crash.*
+
+# Build results
+[Dd]ebug/
+[Dd]ebugPublic/
+[Rr]elease/
+[Rr]eleases/
+x64/
+x86/
+[Ww][Ii][Nn]32/
+[Aa][Rr][Mm]/
+[Aa][Rr][Mm]64/
+bld/
+[Bb]in/
+[Oo]bj/
+[Ll]og/
+[Ll]ogs/
+
+# Visual Studio 2015/2017 cache/options directory
+.vs/
+# Uncomment if you have tasks that create the project's static files in wwwroot
+#wwwroot/
+
+# Visual Studio 2017 auto generated files
+Generated\ Files/
+
+# MSTest test Results
+[Tt]est[Rr]esult*/
+[Bb]uild[Ll]og.*
+
+# NUnit
+*.VisualState.xml
+TestResult.xml
+nunit-*.xml
+
+# Build Results of an ATL Project
+[Dd]ebugPS/
+[Rr]eleasePS/
+dlldata.c
+
+# Benchmark Results
+BenchmarkDotNet.Artifacts/
+
+# .NET
+project.lock.json
+project.fragment.lock.json
+artifacts/
+
+# Tye
+.tye/
+
+# ASP.NET Scaffolding
+ScaffoldingReadMe.txt
+
+# StyleCop
+StyleCopReport.xml
+
+# Files built by Visual Studio
+*_i.c
+*_p.c
+*_h.h
+*.ilk
+*.meta
+*.obj
+*.iobj
+*.pch
+*.pdb
+*.ipdb
+*.pgc
+*.pgd
+*.rsp
+*.sbr
+*.tlb
+*.tli
+*.tlh
+*.tmp
+*.tmp_proj
+*_wpftmp.csproj
+*.log
+*.vspscc
+*.vssscc
+.builds
+*.pidb
+*.svclog
+*.scc
+
+# Chutzpah Test files
+_Chutzpah*
+
+# Visual C++ cache files
+ipch/
+*.aps
+*.ncb
+*.opendb
+*.opensdf
+*.sdf
+*.cachefile
+*.VC.db
+*.VC.VC.opendb
+
+# Visual Studio profiler
+*.psess
+*.vsp
+*.vspx
+*.sap
+
+# Visual Studio Trace Files
+*.e2e
+
+# TFS 2012 Local Workspace
+$tf/
+
+# Guidance Automation Toolkit
+*.gpState
+
+# ReSharper is a .NET coding add-in
+_ReSharper*/
+*.[Rr]e[Ss]harper
+*.DotSettings.user
+
+# TeamCity is a build add-in
+_TeamCity*
+
+# DotCover is a Code Coverage Tool
+*.dotCover
+
+# AxoCover is a Code Coverage Tool
+.axoCover/*
+!.axoCover/settings.json
+
+# Coverlet is a free, cross platform Code Coverage Tool
+coverage*.json
+coverage*.xml
+coverage*.info
+
+# Visual Studio code coverage results
+*.coverage
+*.coveragexml
+
+# NCrunch
+_NCrunch_*
+.*crunch*.local.xml
+nCrunchTemp_*
+
+# MightyMoose
+*.mm.*
+AutoTest.Net/
+
+# Web workbench (sass)
+.sass-cache/
+
+# Installshield output folder
+[Ee]xpress/
+
+# DocProject is a documentation generator add-in
+DocProject/buildhelp/
+DocProject/Help/*.HxT
+DocProject/Help/*.HxC
+DocProject/Help/*.hhc
+DocProject/Help/*.hhk
+DocProject/Help/*.hhp
+DocProject/Help/Html2
+DocProject/Help/html
+
+# Click-Once directory
+publish/
+
+# Publish Web Output
+*.[Pp]ublish.xml
+*.azurePubxml
+# Note: Comment the next line if you want to checkin your web deploy settings,
+# but database connection strings (with potential passwords) will be unencrypted
+*.pubxml
+*.publishproj
+
+# Microsoft Azure Web App publish settings. Comment the next line if you want to
+# checkin your Azure Web App publish settings, but sensitive information contained
+# in these scripts will be unencrypted
+PublishScripts/
+
+# NuGet Packages
+*.nupkg
+# NuGet Symbol Packages
+*.snupkg
+# The packages folder can be ignored because of Package Restore
+**/[Pp]ackages/*
+# except build/, which is used as an MSBuild target.
+!**/[Pp]ackages/build/
+# Uncomment if necessary however generally it will be regenerated when needed
+#!**/[Pp]ackages/repositories.config
+# NuGet v3's project.json files produces more ignorable files
+*.nuget.props
+*.nuget.targets
+
+# Microsoft Azure Build Output
+csx/
+*.build.csdef
+
+# Microsoft Azure Emulator
+ecf/
+rcf/
+
+# Windows Store app package directories and files
+AppPackages/
+BundleArtifacts/
+Package.StoreAssociation.xml
+_pkginfo.txt
+*.appx
+*.appxbundle
+*.appxupload
+
+# Visual Studio cache files
+# files ending in .cache can be ignored
+*.[Cc]ache
+# but keep track of directories ending in .cache
+!?*.[Cc]ache/
+
+# Others
+ClientBin/
+~$*
+*~
+*.dbmdl
+*.dbproj.schemaview
+*.jfm
+*.pfx
+*.publishsettings
+orleans.codegen.cs
+
+# Including strong name files can present a security risk
+# (https://github.com/github/gitignore/pull/2483#issue-259490424)
+#*.snk
+
+# Since there are multiple workflows, uncomment next line to ignore bower_components
+# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
+#bower_components/
+
+# RIA/Silverlight projects
+Generated_Code/
+
+# Backup & report files from converting an old project file
+# to a newer Visual Studio version. Backup files are not needed,
+# because we have git ;-)
+_UpgradeReport_Files/
+Backup*/
+UpgradeLog*.XML
+UpgradeLog*.htm
+ServiceFabricBackup/
+*.rptproj.bak
+
+# SQL Server files
+*.mdf
+*.ldf
+*.ndf
+
+# Business Intelligence projects
+*.rdl.data
+*.bim.layout
+*.bim_*.settings
+*.rptproj.rsuser
+*- [Bb]ackup.rdl
+*- [Bb]ackup ([0-9]).rdl
+*- [Bb]ackup ([0-9][0-9]).rdl
+
+# Microsoft Fakes
+FakesAssemblies/
+
+# GhostDoc plugin setting file
+*.GhostDoc.xml
+
+# Node.js Tools for Visual Studio
+.ntvs_analysis.dat
+node_modules/
+
+# Visual Studio 6 build log
+*.plg
+
+# Visual Studio 6 workspace options file
+*.opt
+
+# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
+*.vbw
+
+# Visual Studio LightSwitch build output
+**/*.HTMLClient/GeneratedArtifacts
+**/*.DesktopClient/GeneratedArtifacts
+**/*.DesktopClient/ModelManifest.xml
+**/*.Server/GeneratedArtifacts
+**/*.Server/ModelManifest.xml
+_Pvt_Extensions
+
+# Paket dependency manager
+.paket/paket.exe
+paket-files/
+
+# FAKE - F# Make
+.fake/
+
+# CodeRush personal settings
+.cr/personal
+
+# Python Tools for Visual Studio (PTVS)
+__pycache__/
+*.pyc
+
+# Cake - Uncomment if you are using it
+# tools/**
+# !tools/packages.config
+
+# Tabs Studio
+*.tss
+
+# Telerik's JustMock configuration file
+*.jmconfig
+
+# BizTalk build output
+*.btp.cs
+*.btm.cs
+*.odx.cs
+*.xsd.cs
+
+# OpenCover UI analysis results
+OpenCover/
+
+# Azure Stream Analytics local run output
+ASALocalRun/
+
+# MSBuild Binary and Structured Log
+*.binlog
+
+# NVidia Nsight GPU debugger configuration file
+*.nvuser
+
+# MFractors (Xamarin productivity tool) working folder
+.mfractor/
+
+# Local History for Visual Studio
+.localhistory/
+
+# BeatPulse healthcheck temp database
+healthchecksdb
+
+# Backup folder for Package Reference Convert tool in Visual Studio 2017
+MigrationBackup/
+
+# Ionide (cross platform F# VS Code tools) working folder
+.ionide/
+
+# Fody - auto-generated XML schema
+FodyWeavers.xsd
+
+##
+## Visual studio for Mac
+##
+
+
+# globs
+Makefile.in
+*.userprefs
+*.usertasks
+config.make
+config.status
+aclocal.m4
+install-sh
+autom4te.cache/
+*.tar.gz
+tarballs/
+test-results/
+
+# Mac bundle stuff
+*.dmg
+*.app
+
+# content below from: https://github.com/github/gitignore/blob/master/Global/macOS.gitignore
+# General
+.DS_Store
+.AppleDouble
+.LSOverride
+
+# Icon must end with two \r
+Icon
+
+
+# Thumbnails
+._*
+
+# Files that might appear in the root of a volume
+.DocumentRevisions-V100
+.fseventsd
+.Spotlight-V100
+.TemporaryItems
+.Trashes
+.VolumeIcon.icns
+.com.apple.timemachine.donotpresent
+
+# Directories potentially created on remote AFP share
+.AppleDB
+.AppleDesktop
+Network Trash Folder
+Temporary Items
+.apdisk
+
+# content below from: https://github.com/github/gitignore/blob/master/Global/Windows.gitignore
+# Windows thumbnail cache files
+Thumbs.db
+ehthumbs.db
+ehthumbs_vista.db
+
+# Dump file
+*.stackdump
+
+# Folder config file
+[Dd]esktop.ini
+
+# Recycle Bin used on file shares
+$RECYCLE.BIN/
+
+# Windows Installer files
+*.cab
+*.msi
+*.msix
+*.msm
+*.msp
+
+# Windows shortcuts
+*.lnk
+
+# JetBrains Rider
+.idea/
+*.sln.iml
+
+##
+## Visual Studio Code
+##
+.vscode/*
+!.vscode/settings.json
+!.vscode/tasks.json
+!.vscode/launch.json
+!.vscode/extensions.json
diff --git a/dotnet/sitemap/ConsoleApp/ConsoleApp.csproj b/dotnet/sitemap/ConsoleApp/ConsoleApp.csproj
new file mode 100644
index 0000000..43373a9
--- /dev/null
+++ b/dotnet/sitemap/ConsoleApp/ConsoleApp.csproj
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+
+ Exe
+ net6.0
+ enable
+ enable
+
+
+
diff --git a/dotnet/sitemap/ConsoleApp/Program.cs b/dotnet/sitemap/ConsoleApp/Program.cs
new file mode 100644
index 0000000..78826c1
--- /dev/null
+++ b/dotnet/sitemap/ConsoleApp/Program.cs
@@ -0,0 +1,12 @@
+using SiteMapLibrary;
+
+// Create an XmlManager to use for generating our sitemap; Provide a file path (and optional Xml settings; See ctor)
+var mgr = new XmlManager("/home/kapper/Code/klips/dotnet/sitemap/ConsoleApp/TestFiles/sitemap.xml");
+// If we want to output the sitemap to the console, instead of saving to a file
+// var mgr = new XmlManager("Console.Out");
+
+// Provide a base URL to start crawling, an XmlManager, and a Regex pattern to use for matching URLs while crawling
+using SiteMap siteMap = new SiteMap("https://knoats.com", mgr,
+ new("(http?s://knoats.com(?!.*/dist/|.*/settings/|.*/register/|.*/login/|.*/uploads/|.*/export/|.*/search?).*?(?=\"))"));
+// Start crawling; When this returns, we have visited all found URLs and wrote them to our sitemap
+await siteMap.Crawl();
diff --git a/dotnet/sitemap/ConsoleApp/TestFiles/robots.txt b/dotnet/sitemap/ConsoleApp/TestFiles/robots.txt
new file mode 100644
index 0000000..bfea538
--- /dev/null
+++ b/dotnet/sitemap/ConsoleApp/TestFiles/robots.txt
@@ -0,0 +1,9 @@
+User-agent: *
+Disallow:
+Disallow: /dist/
+Disallow: /settings/
+Disallow: /register/
+Disallow: /login/
+Disallow: /uploads/
+Disallow: /export/
+Disallow: /search?
diff --git a/dotnet/sitemap/ConsoleApp/TestFiles/sitemap.xml b/dotnet/sitemap/ConsoleApp/TestFiles/sitemap.xml
new file mode 100644
index 0000000..c3a101a
--- /dev/null
+++ b/dotnet/sitemap/ConsoleApp/TestFiles/sitemap.xml
@@ -0,0 +1,975 @@
+
+
+
+ https://knoats.com
+ 2022-5-4
+ daily
+ 0.5
+
+
+ https://knoats.com/tags
+ 2022-5-4
+ daily
+ 0.5
+
+
+ https://knoats.com/books
+ 2022-5-4
+ daily
+ 0.5
+
+
+ https://knoats.com/login
+ 2022-5-4
+ daily
+ 0.5
+
+
+ https://knoats.com/shelves
+ 2022-5-4
+ daily
+ 0.5
+
+
+ https://knoats.com/books/c
+ 2022-5-4
+ daily
+ 0.5
+
+
+ https://knoats.com/register
+ 2022-5-4
+ daily
+ 0.5
+
+
+ https://knoats.com/books/pi
+ 2022-5-4
+ daily
+ 0.5
+
+
+ https://knoats.com/books/vim
+ 2022-5-4
+ daily
+ 0.5
+
+
+ https://knoats.com/books/git
+ 2022-5-4
+ daily
+ 0.5
+
+
+ https://knoats.com/books/docker
+ 2022-5-4
+ daily
+ 0.5
+
+
+ https://knoats.com/books/ansible
+ 2022-5-4
+ daily
+ 0.5
+
+
+ https://knoats.com/books/security
+ 2022-5-4
+ daily
+ 0.5
+
+
+ https://knoats.com/password/email
+ 2022-5-4
+ daily
+ 0.5
+
+
+ https://knoats.com/books/bookstack
+ 2022-5-4
+ daily
+ 0.5
+
+
+ https://knoats.com/user/shaun-reed
+ 2022-5-4
+ daily
+ 0.5
+
+
+ https://knoats.com/books/c?shelf=2
+ 2022-5-4
+ daily
+ 0.5
+
+
+ https://knoats.com/books/javascript
+ 2022-5-4
+ daily
+ 0.5
+
+
+ https://knoats.com/books/blockchain
+ 2022-5-4
+ daily
+ 0.5
+
+
+ https://knoats.com/books/networking
+ 2022-5-4
+ daily
+ 0.5
+
+
+ https://knoats.com/books/linux-admin
+ 2022-5-4
+ daily
+ 0.5
+
+
+ https://knoats.com/books/vim?shelf=1
+ 2022-5-4
+ daily
+ 0.5
+
+
+ https://knoats.com/books/vim?shelf=2
+ 2022-5-4
+ daily
+ 0.5
+
+
+ https://knoats.com/books/git?shelf=2
+ 2022-5-4
+ daily
+ 0.5
+
+
+ https://knoats.com/shelves/containers
+ 2022-5-4
+ daily
+ 0.5
+
+
+ https://knoats.com/books/c/page/basics
+ 2022-5-4
+ daily
+ 0.5
+
+
+ https://knoats.com/shelves/programming
+ 2022-5-4
+ daily
+ 0.5
+
+
+ https://knoats.com/books/docker?shelf=3
+ 2022-5-4
+ daily
+ 0.5
+
+
+ https://knoats.com/books/docker?shelf=1
+ 2022-5-4
+ daily
+ 0.5
+
+
+ https://knoats.com/books/c/page/classes
+ 2022-5-4
+ daily
+ 0.5
+
+
+ https://knoats.com/books/vim/page/notes
+ 2022-5-4
+ daily
+ 0.5
+
+
+ https://knoats.com/books/ansible?shelf=1
+ 2022-5-4
+ daily
+ 0.5
+
+
+ https://knoats.com/books/git/page/basics
+ 2022-5-4
+ daily
+ 0.5
+
+
+ https://knoats.com/books/game-development
+ 2022-5-4
+ daily
+ 0.5
+
+
+ https://knoats.com/pages/recently-updated
+ 2022-5-4
+ daily
+ 0.5
+
+
+ https://knoats.com/books/security?shelf=1
+ 2022-5-4
+ daily
+ 0.5
+
+
+ https://knoats.com/books/bookstack?shelf=3
+ 2022-5-4
+ daily
+ 0.5
+
+
+ https://knoats.com/books/git/chapter/usage
+ 2022-5-4
+ daily
+ 0.5
+
+
+ https://knoats.com/books/networking?shelf=3
+ 2022-5-4
+ daily
+ 0.5
+
+
+ https://knoats.com/books/networking?shelf=1
+ 2022-5-4
+ daily
+ 0.5
+
+
+ https://knoats.com/books/docker/page/gitlab
+ 2022-5-4
+ daily
+ 0.5
+
+
+ https://knoats.com/books/docker/page/shlink
+ 2022-5-4
+ daily
+ 0.5
+
+
+ https://knoats.com/books/linux-admin?shelf=3
+ 2022-5-4
+ daily
+ 0.5
+
+
+ https://knoats.com/books/linux-admin?shelf=1
+ 2022-5-4
+ daily
+ 0.5
+
+
+ https://knoats.com/books/git/page/submodules
+ 2022-5-4
+ daily
+ 0.5
+
+
+ https://knoats.com/books/linux-admin/page/i3
+ 2022-5-4
+ daily
+ 0.5
+
+
+ https://knoats.com/books/networking/page/dns
+ 2022-5-4
+ daily
+ 0.5
+
+
+ https://knoats.com/books/docker/page/heimdall
+ 2022-5-4
+ daily
+ 0.5
+
+
+ https://knoats.com/books/pi/page/installation
+ 2022-5-4
+ daily
+ 0.5
+
+
+ https://knoats.com/books/c-s68/page/dotnet-cli
+ 2022-5-4
+ daily
+ 0.5
+
+
+ https://knoats.com/books/c/page/multithreading
+ 2022-5-4
+ daily
+ 0.5
+
+
+ https://knoats.com/books/linux-admin/page/grub
+ 2022-5-4
+ daily
+ 0.5
+
+
+ https://knoats.com/books/javascript/page/webgl
+ 2022-5-4
+ daily
+ 0.5
+
+
+ https://knoats.com/books/linux-admin/page/arch
+ 2022-5-4
+ daily
+ 0.5
+
+
+ https://knoats.com/books/networking/page/tcpip
+ 2022-5-4
+ daily
+ 0.5
+
+
+ https://knoats.com/books/networking/page/nginx
+ 2022-5-4
+ daily
+ 0.5
+
+
+ https://knoats.com/link/30#bkmrk-configure-ssl
+ 2022-5-4
+ daily
+ 0.5
+
+
+ https://knoats.com/books/security/page/fail2ban
+ 2022-5-4
+ daily
+ 0.5
+
+
+ https://knoats.com/books/networking/page/apache
+ 2022-5-4
+ daily
+ 0.5
+
+
+ https://knoats.com/books/docker/page/dockerfile
+ 2022-5-4
+ daily
+ 0.5
+
+
+ https://knoats.com/books/git/page/authentication
+ 2022-5-4
+ daily
+ 0.5
+
+
+ https://knoats.com/books/linux-admin/page/debian
+ 2022-5-4
+ daily
+ 0.5
+
+
+ https://knoats.com/books/networking/page/tcp-udp
+ 2022-5-4
+ daily
+ 0.5
+
+
+ https://knoats.com/pages/recently-updated?page=2
+ 2022-5-4
+ daily
+ 0.5
+
+
+ https://knoats.com/pages/recently-updated?page=3
+ 2022-5-4
+ daily
+ 0.5
+
+
+ https://knoats.com/pages/recently-updated?page=4
+ 2022-5-4
+ daily
+ 0.5
+
+
+ https://knoats.com/pages/recently-updated?page=5
+ 2022-5-4
+ daily
+ 0.5
+
+
+ https://knoats.com/books/pi/chapter/magic-mirror
+ 2022-5-4
+ daily
+ 0.5
+
+
+ https://knoats.com/pages/recently-updated?page=1
+ 2022-5-4
+ daily
+ 0.5
+
+
+ https://knoats.com/books/self-hosted-applications
+ 2022-5-4
+ daily
+ 0.5
+
+
+ https://knoats.com/books/blockchain/page/solidity
+ 2022-5-4
+ daily
+ 0.5
+
+
+ https://knoats.com/books/c/page/building-projects
+ 2022-5-4
+ daily
+ 0.5
+
+
+ https://knoats.com/books/vim/page/configuring-vim
+ 2022-5-4
+ daily
+ 0.5
+
+
+ https://knoats.com/books/linux-admin/chapter/bash
+ 2022-5-4
+ daily
+ 0.5
+
+
+ https://knoats.com/books/linux-admin/page/crontab
+ 2022-5-4
+ daily
+ 0.5
+
+
+ https://knoats.com/books/linux-admin/page/yakuake
+ 2022-5-4
+ daily
+ 0.5
+
+
+ https://knoats.com/books/bookstack/chapter/knoats
+ 2022-5-4
+ daily
+ 0.5
+
+
+ https://knoats.com/books/linux-admin/page/examples
+ 2022-5-4
+ daily
+ 0.5
+
+
+ https://knoats.com/books/linux-admin/page/xps-9310
+ 2022-5-4
+ daily
+ 0.5
+
+
+ https://knoats.com/books/linux-admin/page/wireless
+ 2022-5-4
+ daily
+ 0.5
+
+
+ https://knoats.com/books/security/page/ossec-rules
+ 2022-5-4
+ daily
+ 0.5
+
+
+ https://knoats.com/books/networking/page/osi-model
+ 2022-5-4
+ daily
+ 0.5
+
+
+ https://knoats.com/books/pi/chapter/backup-scripts
+ 2022-5-4
+ daily
+ 0.5
+
+
+ https://knoats.com/books/linux-admin/page/tunneling
+ 2022-5-4
+ daily
+ 0.5
+
+
+ https://knoats.com/books/networking/page/subnetting
+ 2022-5-4
+ daily
+ 0.5
+
+
+ https://knoats.com/books/linux-admin/page/bluetooth
+ 2022-5-4
+ daily
+ 0.5
+
+
+ https://knoats.com/books/ansible/page/creating-roles
+ 2022-5-4
+ daily
+ 0.5
+
+
+ https://knoats.com/books/linux-admin/page/virtualbox
+ 2022-5-4
+ daily
+ 0.5
+
+
+ https://knoats.com/books/security/chapter/monitoring
+ 2022-5-4
+ daily
+ 0.5
+
+
+ https://knoats.com/books/networking/chapter/protocols
+ 2022-5-4
+ daily
+ 0.5
+
+
+ https://knoats.com/books/pi/page/magic-mirror-modules
+ 2022-5-4
+ daily
+ 0.5
+
+
+ https://knoats.com/books/git/page/software-development
+ 2022-5-4
+ daily
+ 0.5
+
+
+ https://knoats.com/books/linux-admin/page/boot-process
+ 2022-5-4
+ daily
+ 0.5
+
+
+ https://knoats.com/shelves/linux-server-administration
+ 2022-5-4
+ daily
+ 0.5
+
+
+ https://knoats.com/books/game-development/page/prefabs
+ 2022-5-4
+ daily
+ 0.5
+
+
+ https://knoats.com/books/networking/page/proxy-servers
+ 2022-5-4
+ daily
+ 0.5
+
+
+ https://knoats.com/books/bookstack/chapter/development
+ 2022-5-4
+ daily
+ 0.5
+
+
+ https://knoats.com/books/game-development/chapter/unity
+ 2022-5-4
+ daily
+ 0.5
+
+
+ https://knoats.com/books/linux-admin/page/bash-profiles
+ 2022-5-4
+ daily
+ 0.5
+
+
+ https://knoats.com/books/linux-admin/page/configure-ftp
+ 2022-5-4
+ daily
+ 0.5
+
+
+ https://knoats.com/books/linux-admin/chapter/interfaces
+ 2022-5-4
+ daily
+ 0.5
+
+
+ https://knoats.com/books/linux-admin/page/audio-devices
+ 2022-5-4
+ daily
+ 0.5
+
+
+ https://knoats.com/books/security/page/server-checklist
+ 2022-5-4
+ daily
+ 0.5
+
+
+ https://knoats.com/books/security/page/devsec-baselines
+ 2022-5-4
+ daily
+ 0.5
+
+
+ https://knoats.com/books/networking/chapter/web-servers
+ 2022-5-4
+ daily
+ 0.5
+
+
+ https://knoats.com/books/bookstack/chapter/installation
+ 2022-5-4
+ daily
+ 0.5
+
+
+ https://knoats.com/books/game-development/page/scripting
+ 2022-5-4
+ daily
+ 0.5
+
+
+ https://knoats.com/books/game-development/page/shortcuts
+ 2022-5-4
+ daily
+ 0.5
+
+
+ https://knoats.com/books/ansible/page/creating-playbooks
+ 2022-5-4
+ daily
+ 0.5
+
+
+ https://knoats.com/books/linux-admin/page/system-sensors
+ 2022-5-4
+ daily
+ 0.5
+
+
+ https://knoats.com/books/self-hosted-applications?shelf=3
+ 2022-5-4
+ daily
+ 0.5
+
+
+ https://knoats.com/books/linux-admin/page/swap-allocation
+ 2022-5-4
+ daily
+ 0.5
+
+
+ https://knoats.com/books/linux-admin/page/getting-started
+ 2022-5-4
+ daily
+ 0.5
+
+
+ https://knoats.com/books/linux-admin/chapter/system-admin
+ 2022-5-4
+ daily
+ 0.5
+
+
+ https://knoats.com/books/linux-admin/page/server-hostname
+ 2022-5-4
+ daily
+ 0.5
+
+
+ https://knoats.com/books/linux-admin/page/disk-management
+ 2022-5-4
+ daily
+ 0.5
+
+
+ https://knoats.com/books/pi/page/staging-configs-to-a-usb
+ 2022-5-4
+ daily
+ 0.5
+
+
+ https://knoats.com/books/game-development/page/linux-setup
+ 2022-5-4
+ daily
+ 0.5
+
+
+ https://knoats.com/books/git/page/pushing-merging-branches
+ 2022-5-4
+ daily
+ 0.5
+
+
+ https://knoats.com/books/linux-admin/page/systemd-services
+ 2022-5-4
+ daily
+ 0.5
+
+
+ https://knoats.com/books/linux-admin/chapter/distributions
+ 2022-5-4
+ daily
+ 0.5
+
+
+ https://knoats.com/books/linux-admin/chapter/customization
+ 2022-5-4
+ daily
+ 0.5
+
+
+ https://knoats.com/books/linux-admin/page/installing-fonts
+ 2022-5-4
+ daily
+ 0.5
+
+
+ https://knoats.com/books/security/page/virtualbox-networks
+ 2022-5-4
+ daily
+ 0.5
+
+
+ https://knoats.com/books/security/page/ossec-ubuntu-server
+ 2022-5-4
+ daily
+ 0.5
+
+
+ https://knoats.com/books/security/chapter/server-hardening
+ 2022-5-4
+ daily
+ 0.5
+
+
+ https://knoats.com/books/linux-admin/page/kernel-management
+ 2022-5-4
+ daily
+ 0.5
+
+
+ https://knoats.com/books/ansible/page/managing-remote-hosts
+ 2022-5-4
+ daily
+ 0.5
+
+
+ https://knoats.com/books/linux-admin/page/configure-postfix
+ 2022-5-4
+ daily
+ 0.5
+
+
+ https://knoats.com/books/self-hosted-applications/page/hexo
+ 2022-5-4
+ daily
+ 0.5
+
+
+ https://knoats.com/books/linux-admin/page/mount-google-drive
+ 2022-5-4
+ daily
+ 0.5
+
+
+ https://knoats.com/books/self-hosted-applications/page/gitea
+ 2022-5-4
+ daily
+ 0.5
+
+
+ https://knoats.com/books/linux-admin/page/enabling-google-2fa
+ 2022-5-4
+ daily
+ 0.5
+
+
+ https://knoats.com/books/linux-admin/page/user-administration
+ 2022-5-4
+ daily
+ 0.5
+
+
+ https://knoats.com/books/linux-admin/page/unattended-upgrades
+ 2022-5-4
+ daily
+ 0.5
+
+
+ https://knoats.com/books/self-hosted-applications/page/jekyll
+ 2022-5-4
+ daily
+ 0.5
+
+
+ https://knoats.com/books/bookstack/page/welcome-to-knoats-432
+ 2022-5-4
+ daily
+ 0.5
+
+
+ https://knoats.com/books/linux-admin/page/linux-on-chromebooks
+ 2022-5-4
+ daily
+ 0.5
+
+
+ https://knoats.com/books/game-development/page/post-processing
+ 2022-5-4
+ daily
+ 0.5
+
+
+ https://knoats.com/books/linux-admin/chapter/ssh-configuration
+ 2022-5-4
+ daily
+ 0.5
+
+
+ https://knoats.com/books/bookstack/page/exploring-the-database
+ 2022-5-4
+ daily
+ 0.5
+
+
+ https://knoats.com/books/game-development/page/project-settings
+ 2022-5-4
+ daily
+ 0.5
+
+
+ https://knoats.com/books/game-development/page/new-input-system
+ 2022-5-4
+ daily
+ 0.5
+
+
+ https://knoats.com/books/bookstack/page/bookstack-configuration
+ 2022-5-4
+ daily
+ 0.5
+
+
+ https://knoats.com/books/docker/chapter/docker-compose-services
+ 2022-5-4
+ daily
+ 0.5
+
+
+ https://knoats.com/books/game-development/chapter/unreal-engine-4
+ 2022-5-4
+ daily
+ 0.5
+
+
+ https://knoats.com/books/networking/page/certbot-ssl-certificates
+ 2022-5-4
+ daily
+ 0.5
+
+
+ https://knoats.com/books/linux-admin/page/yubikey-ssh-authentication
+ 2022-5-4
+ daily
+ 0.5
+
+
+ https://knoats.com/books/self-hosted-applications/page/read-the-docs
+ 2022-5-4
+ daily
+ 0.5
+
+
+ https://knoats.com/books/bookstack/page/backup-bookstack-using-docker
+ 2022-5-4
+ daily
+ 0.5
+
+
+ https://knoats.com/books/game-development/page/gameplay-ability-system
+ 2022-5-4
+ daily
+ 0.5
+
+
+ https://knoats.com/books/linux-admin/page/synchronizing-time-using-ntp
+ 2022-5-4
+ daily
+ 0.5
+
+
+ https://knoats.com/books/bookstack/page/bookstack-using-docker-compose
+ 2022-5-4
+ daily
+ 0.5
+
+
+ https://knoats.com/books/self-hosted-applications/chapter/url-shortners
+ 2022-5-4
+ daily
+ 0.5
+
+
+ https://knoats.com/books/bookstack/page/updating-bookstack-using-docker
+ 2022-5-4
+ daily
+ 0.5
+
+
+ https://knoats.com/books/linux-admin/page/configuring-sshd-authentication
+ 2022-5-4
+ daily
+ 0.5
+
+
+ https://knoats.com/books/self-hosted-applications/chapter/site-generators
+ 2022-5-4
+ daily
+ 0.5
+
+
+ https://knoats.com/books/vim/page/configuring-vim#bkmrk-unicode.vim-plugin
+ 2022-5-4
+ daily
+ 0.5
+
+
+ https://knoats.com/books/game-development/page/retarget-skeleton-animations
+ 2022-5-4
+ daily
+ 0.5
+
+
+ https://knoats.com/books/self-hosted-applications/page/mame-web-application
+ 2022-5-4
+ daily
+ 0.5
+
+
+ https://knoats.com/books/linux-admin/page/configuring-multi-boot-filesystems
+ 2022-5-4
+ daily
+ 0.5
+
+
+ https://knoats.com/books/self-hosted-applications/chapter/documentation-generators
+ 2022-5-4
+ daily
+ 0.5
+
+
\ No newline at end of file
diff --git a/dotnet/sitemap/README.md b/dotnet/sitemap/README.md
new file mode 100644
index 0000000..6f7bd64
--- /dev/null
+++ b/dotnet/sitemap/README.md
@@ -0,0 +1,45 @@
+
+Sitemap generator I created while learning some C#.
+Example of using the library is in `ConsoleApp/Program.cs`, files used for testing are in `ConsoleApp/TestFiles/`
+`ConsoleApp/TestFiles/sitemap.xml` currently contains the sitemap for my website.
+If we run the console application with a different URL that targets this same file, the file will be overwritten with the new sitemap.
+There is no need to delete or recreate files manually.
+
+I plan to check for a `robots.txt` while generating sitemaps to prevent crawling pages that aren't useful.
+For now there is no use for a `robots.txt`, the `SiteMap.Crawl()` function visits the URL provided to the `SiteMap` constructor.
+Regex is used to check the visited page and match URLs with the same base domain, the URLS found are logged for the crawler to visit.
+Each time we finish collecting URLS on a page, we move to the next URL in the queue and repeat this process.
+Once we finish crawling all URLs, an XML sitemap is generated where the URLs are sorted by their length.
+
+I used [sitemaps.org - XML Format](https://www.sitemaps.org/protocol.html) to determine the proper formatting for the sitemap.
+For now, since the web application I used for testing does not respond with [Last-Modified](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Last-Modified) in the HTTP header, the last modified time is set to the date the sitemap was generated.
+The `priority` fields are all set to the default value indicated on sitemaps.org, which is `0.5`.
+This is to avoid confusing crawlers with a huge list of 'top-priority' pages to crawl.
+All `changefreq` fields of the sitemap are marked as `daily`.
+
+The primary motivation for this project was learning about unmanaged resources in C#, and trying out the [Dispose Pattern](https://docs.microsoft.com/en-us/dotnet/standard/garbage-collection/implementing-dispose?redirectedfrom=MSDN#implement-the-dispose-pattern) for myself.
+If someone reading this were to find a problem with the way I handled disposing of the `HttpClient` in the `SiteMap` class, feel free to let me know :) Creating an issue, PR, or sending an email is all acceptable.
+
+### Future plans
+* Parse `robots.txt` to avoid crawling pages that are not desired
+* Test the generator with an application that serves `LastModified` date; Use it if available
+* Set `priority` in a more useful way, or allow some form of customization of the way this is handled.
+* Set `changefreq` in a more useful way, or allow some form of customization of the way this is handled.
+* Generate a regex pattern to match, if one is not provided
+
+For now, the general use of this library is seen in the example below.
+
+```C#
+using SiteMapLibrary;
+
+// Create an XmlManager to use for generating our sitemap; Provide a file path (and optional Xml settings; See ctor)
+var mgr = new XmlManager("/home/kapper/Code/klips/dotnet/sitemap/ConsoleApp/TestFiles/sitemap.xml");
+// If we want to output the sitemap to the console, instead of saving to a file
+// var mgr = new XmlManager("Console.Out");
+
+// Provide a base URL to start crawling, an XmlManager, and a Regex pattern to use for matching URLs while crawling
+using SiteMap siteMap = new SiteMap("https://knoats.com", mgr,
+ new("(http?s://knoats.com(?!.*/dist/|.*/settings/|.*/register/|.*/login/|.*/uploads/|.*/export/|.*/search?).*?(?=\"))"));
+// Start crawling; When this returns, we have visited all found URLs and wrote them to our sitemap
+await siteMap.Crawl();
+```
diff --git a/dotnet/sitemap/SiteMapLibrary/SiteMap.cs b/dotnet/sitemap/SiteMapLibrary/SiteMap.cs
new file mode 100644
index 0000000..839f633
--- /dev/null
+++ b/dotnet/sitemap/SiteMapLibrary/SiteMap.cs
@@ -0,0 +1,114 @@
+using System.Text.RegularExpressions;
+
+namespace SiteMapLibrary;
+
+public class SiteMap : IDisposable
+{
+ private HttpClient _client;
+ private HashSet _foundUrls;
+ private HashSet _visitedUrls;
+ private Queue _visitQueue;
+ private bool _disposed = false;
+ private XmlManager XmlManager { get; set; }
+ public string? Url { get; private set; }
+ public Regex Regexp { get; set; }
+
+ public SiteMap(string url, string savepath, Regex pattern)
+ {
+ Url = url;
+ _client = new HttpClient();
+ _foundUrls = new HashSet();
+ _visitedUrls = new HashSet();
+ _visitQueue = new Queue();
+ Regexp = pattern;
+ XmlManager = new XmlManager(savepath);
+ }
+
+ public SiteMap(string url, XmlManager mgr, Regex pattern)
+ {
+ _client = new HttpClient();
+ _foundUrls = new HashSet();
+ _visitedUrls = new HashSet();
+ _visitQueue = new Queue();
+ Regexp = pattern;
+ Url = url;
+ XmlManager = mgr;
+ }
+
+ public async Task Crawl()
+ {
+ while (Url != null)
+ {
+ _visitedUrls.Add(Url);
+ using var content = await _client.GetAsync(Url);
+ if (!content.IsSuccessStatusCode)
+ {
+ Console.WriteLine($"{content.StatusCode} on url: {Url}");
+ NextUrl();
+ continue;
+ }
+
+ var m = Regexp.Match(await content.Content.ReadAsStringAsync());
+ while (m.Success)
+ {
+ foreach (Group group in m.Groups)
+ {
+ if (_foundUrls.Add(group.Value))
+ {
+ Console.WriteLine(group.Value);
+ // Console.WriteLine(content.Content.Headers.LastModified);
+ if (!_visitedUrls.Contains(group.Value) && !_visitQueue.Contains(group.Value))
+ {
+ _visitQueue.Enqueue(group.Value);
+ }
+ }
+ }
+
+ m = m.NextMatch();
+ }
+
+ NextUrl();
+ content.Dispose();
+ }
+ WriteXml();
+ }
+
+ private void WriteXml()
+ {
+ List urls = new List(_visitedUrls.OrderBy(k => k.Length).ToArray());
+ foreach (string url in urls)
+ {
+ XmlManager.AddUrl(url);
+ }
+ XmlManager.Save();
+ }
+
+ private void NextUrl()
+ {
+ if (_visitQueue.Count == 0)
+ {
+ Url = null;
+ return;
+ }
+ Url = _visitQueue.Dequeue();
+ }
+
+ public void Dispose()
+ {
+ Dispose(true);
+ GC.SuppressFinalize(this);
+ }
+
+ public void Dispose(bool disposing)
+ {
+ if (!_disposed)
+ {
+ if (disposing)
+ {
+ _client.Dispose();
+ }
+
+ _disposed = true;
+ }
+ }
+}
diff --git a/dotnet/sitemap/SiteMapLibrary/SiteMapLibrary.csproj b/dotnet/sitemap/SiteMapLibrary/SiteMapLibrary.csproj
new file mode 100644
index 0000000..06e82b0
--- /dev/null
+++ b/dotnet/sitemap/SiteMapLibrary/SiteMapLibrary.csproj
@@ -0,0 +1,10 @@
+
+
+
+ net6.0
+ enable
+ enable
+ SiteMap
+
+
+
diff --git a/dotnet/sitemap/SiteMapLibrary/XmlManager.cs b/dotnet/sitemap/SiteMapLibrary/XmlManager.cs
new file mode 100644
index 0000000..300dd97
--- /dev/null
+++ b/dotnet/sitemap/SiteMapLibrary/XmlManager.cs
@@ -0,0 +1,59 @@
+using System.Xml;
+
+namespace SiteMapLibrary;
+
+public class XmlManager
+{
+ private XmlDocument XmlDocument { get; set; }
+ private XmlDeclaration XmlDeclaration { get; set; }
+ private XmlElement XmlUrlset { get; set; }
+ private string Path { get; set; }
+
+ public XmlManager(string path,
+ string version="1.0", string encoding="utf-8", string standalone="")
+ {
+ XmlDocument = new XmlDocument();
+ XmlDeclaration = XmlDocument.CreateXmlDeclaration(version, encoding, standalone);
+ XmlDocument.AppendChild(XmlDeclaration);
+ XmlUrlset = XmlDocument.CreateElement("urlset");
+ XmlDocument.AppendChild(XmlUrlset);
+ Path = path;
+ }
+
+ ~XmlManager()
+ {
+ Save();
+ }
+
+ public void AddUrl(string url)
+ {
+ XmlElement newUrl = XmlDocument.CreateElement("url");
+ XmlUrlset.AppendChild(newUrl);
+ XmlElement newLoc = XmlDocument.CreateElement("loc");
+ newLoc.InnerText = url;
+ newUrl.AppendChild(newLoc);
+ var lastmod = XmlDocument.CreateElement("lastmod");
+ lastmod.InnerText = DateTime.Now.Year.ToString()
+ + '-' + DateTime.Now.Month.ToString()
+ + '-' + DateTime.Now.Day;
+ newUrl.AppendChild(lastmod);
+ var changeFreq = XmlDocument.CreateElement("changefreq");
+ changeFreq.InnerText = "daily";
+ newUrl.AppendChild(changeFreq);
+ var priority = XmlDocument.CreateElement("priority");
+ priority.InnerText = "0.5";
+ newUrl.AppendChild(priority);
+ }
+
+ public void Save()
+ {
+ if (Path == "Console.Out")
+ {
+ XmlDocument.Save(Console.Out);
+ }
+ else
+ {
+ XmlDocument.Save(Path);
+ }
+ }
+}
diff --git a/dotnet/sitemap/sitemap.sln b/dotnet/sitemap/sitemap.sln
new file mode 100644
index 0000000..8221e29
--- /dev/null
+++ b/dotnet/sitemap/sitemap.sln
@@ -0,0 +1,28 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 16
+VisualStudioVersion = 16.0.30114.105
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SiteMapLibrary", "SiteMapLibrary\SiteMapLibrary.csproj", "{0BDEDA94-7BBF-4F82-9A14-131C4F8A3330}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ConsoleApp", "ConsoleApp\ConsoleApp.csproj", "{600802AC-C872-4115-BFE6-DA2AE7138F9C}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Release|Any CPU = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {0BDEDA94-7BBF-4F82-9A14-131C4F8A3330}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {0BDEDA94-7BBF-4F82-9A14-131C4F8A3330}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {0BDEDA94-7BBF-4F82-9A14-131C4F8A3330}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {0BDEDA94-7BBF-4F82-9A14-131C4F8A3330}.Release|Any CPU.Build.0 = Release|Any CPU
+ {600802AC-C872-4115-BFE6-DA2AE7138F9C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {600802AC-C872-4115-BFE6-DA2AE7138F9C}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {600802AC-C872-4115-BFE6-DA2AE7138F9C}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {600802AC-C872-4115-BFE6-DA2AE7138F9C}.Release|Any CPU.Build.0 = Release|Any CPU
+ EndGlobalSection
+EndGlobal
diff --git a/dotnet/testing/.gitignore b/dotnet/testing/.gitignore
new file mode 100644
index 0000000..a72f3dd
--- /dev/null
+++ b/dotnet/testing/.gitignore
@@ -0,0 +1,454 @@
+## Ignore Visual Studio temporary files, build results, and
+## files generated by popular Visual Studio add-ons.
+##
+## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
+
+# User-specific files
+*.rsuser
+*.suo
+*.user
+*.userosscache
+*.sln.docstates
+
+# User-specific files (MonoDevelop/Xamarin Studio)
+*.userprefs
+
+# Mono auto generated files
+mono_crash.*
+
+# Build results
+[Dd]ebug/
+[Dd]ebugPublic/
+[Rr]elease/
+[Rr]eleases/
+x64/
+x86/
+[Ww][Ii][Nn]32/
+[Aa][Rr][Mm]/
+[Aa][Rr][Mm]64/
+bld/
+[Bb]in/
+[Oo]bj/
+[Ll]og/
+[Ll]ogs/
+
+# Visual Studio 2015/2017 cache/options directory
+.vs/
+# Uncomment if you have tasks that create the project's static files in wwwroot
+#wwwroot/
+
+# Visual Studio 2017 auto generated files
+Generated\ Files/
+
+# MSTest test Results
+[Tt]est[Rr]esult*/
+[Bb]uild[Ll]og.*
+
+# NUnit
+*.VisualState.xml
+TestResult.xml
+nunit-*.xml
+
+# Build Results of an ATL Project
+[Dd]ebugPS/
+[Rr]eleasePS/
+dlldata.c
+
+# Benchmark Results
+BenchmarkDotNet.Artifacts/
+
+# .NET
+project.lock.json
+project.fragment.lock.json
+artifacts/
+
+# Tye
+.tye/
+
+# ASP.NET Scaffolding
+ScaffoldingReadMe.txt
+
+# StyleCop
+StyleCopReport.xml
+
+# Files built by Visual Studio
+*_i.c
+*_p.c
+*_h.h
+*.ilk
+*.meta
+*.obj
+*.iobj
+*.pch
+*.pdb
+*.ipdb
+*.pgc
+*.pgd
+*.rsp
+*.sbr
+*.tlb
+*.tli
+*.tlh
+*.tmp
+*.tmp_proj
+*_wpftmp.csproj
+*.log
+*.vspscc
+*.vssscc
+.builds
+*.pidb
+*.svclog
+*.scc
+
+# Chutzpah Test files
+_Chutzpah*
+
+# Visual C++ cache files
+ipch/
+*.aps
+*.ncb
+*.opendb
+*.opensdf
+*.sdf
+*.cachefile
+*.VC.db
+*.VC.VC.opendb
+
+# Visual Studio profiler
+*.psess
+*.vsp
+*.vspx
+*.sap
+
+# Visual Studio Trace Files
+*.e2e
+
+# TFS 2012 Local Workspace
+$tf/
+
+# Guidance Automation Toolkit
+*.gpState
+
+# ReSharper is a .NET coding add-in
+_ReSharper*/
+*.[Rr]e[Ss]harper
+*.DotSettings.user
+
+# TeamCity is a build add-in
+_TeamCity*
+
+# DotCover is a Code Coverage Tool
+*.dotCover
+
+# AxoCover is a Code Coverage Tool
+.axoCover/*
+!.axoCover/settings.json
+
+# Coverlet is a free, cross platform Code Coverage Tool
+coverage*.json
+coverage*.xml
+coverage*.info
+
+# Visual Studio code coverage results
+*.coverage
+*.coveragexml
+
+# NCrunch
+_NCrunch_*
+.*crunch*.local.xml
+nCrunchTemp_*
+
+# MightyMoose
+*.mm.*
+AutoTest.Net/
+
+# Web workbench (sass)
+.sass-cache/
+
+# Installshield output folder
+[Ee]xpress/
+
+# DocProject is a documentation generator add-in
+DocProject/buildhelp/
+DocProject/Help/*.HxT
+DocProject/Help/*.HxC
+DocProject/Help/*.hhc
+DocProject/Help/*.hhk
+DocProject/Help/*.hhp
+DocProject/Help/Html2
+DocProject/Help/html
+
+# Click-Once directory
+publish/
+
+# Publish Web Output
+*.[Pp]ublish.xml
+*.azurePubxml
+# Note: Comment the next line if you want to checkin your web deploy settings,
+# but database connection strings (with potential passwords) will be unencrypted
+*.pubxml
+*.publishproj
+
+# Microsoft Azure Web App publish settings. Comment the next line if you want to
+# checkin your Azure Web App publish settings, but sensitive information contained
+# in these scripts will be unencrypted
+PublishScripts/
+
+# NuGet Packages
+*.nupkg
+# NuGet Symbol Packages
+*.snupkg
+# The packages folder can be ignored because of Package Restore
+**/[Pp]ackages/*
+# except build/, which is used as an MSBuild target.
+!**/[Pp]ackages/build/
+# Uncomment if necessary however generally it will be regenerated when needed
+#!**/[Pp]ackages/repositories.config
+# NuGet v3's project.json files produces more ignorable files
+*.nuget.props
+*.nuget.targets
+
+# Microsoft Azure Build Output
+csx/
+*.build.csdef
+
+# Microsoft Azure Emulator
+ecf/
+rcf/
+
+# Windows Store app package directories and files
+AppPackages/
+BundleArtifacts/
+Package.StoreAssociation.xml
+_pkginfo.txt
+*.appx
+*.appxbundle
+*.appxupload
+
+# Visual Studio cache files
+# files ending in .cache can be ignored
+*.[Cc]ache
+# but keep track of directories ending in .cache
+!?*.[Cc]ache/
+
+# Others
+ClientBin/
+~$*
+*~
+*.dbmdl
+*.dbproj.schemaview
+*.jfm
+*.pfx
+*.publishsettings
+orleans.codegen.cs
+
+# Including strong name files can present a security risk
+# (https://github.com/github/gitignore/pull/2483#issue-259490424)
+#*.snk
+
+# Since there are multiple workflows, uncomment next line to ignore bower_components
+# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
+#bower_components/
+
+# RIA/Silverlight projects
+Generated_Code/
+
+# Backup & report files from converting an old project file
+# to a newer Visual Studio version. Backup files are not needed,
+# because we have git ;-)
+_UpgradeReport_Files/
+Backup*/
+UpgradeLog*.XML
+UpgradeLog*.htm
+ServiceFabricBackup/
+*.rptproj.bak
+
+# SQL Server files
+*.mdf
+*.ldf
+*.ndf
+
+# Business Intelligence projects
+*.rdl.data
+*.bim.layout
+*.bim_*.settings
+*.rptproj.rsuser
+*- [Bb]ackup.rdl
+*- [Bb]ackup ([0-9]).rdl
+*- [Bb]ackup ([0-9][0-9]).rdl
+
+# Microsoft Fakes
+FakesAssemblies/
+
+# GhostDoc plugin setting file
+*.GhostDoc.xml
+
+# Node.js Tools for Visual Studio
+.ntvs_analysis.dat
+node_modules/
+
+# Visual Studio 6 build log
+*.plg
+
+# Visual Studio 6 workspace options file
+*.opt
+
+# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
+*.vbw
+
+# Visual Studio LightSwitch build output
+**/*.HTMLClient/GeneratedArtifacts
+**/*.DesktopClient/GeneratedArtifacts
+**/*.DesktopClient/ModelManifest.xml
+**/*.Server/GeneratedArtifacts
+**/*.Server/ModelManifest.xml
+_Pvt_Extensions
+
+# Paket dependency manager
+.paket/paket.exe
+paket-files/
+
+# FAKE - F# Make
+.fake/
+
+# CodeRush personal settings
+.cr/personal
+
+# Python Tools for Visual Studio (PTVS)
+__pycache__/
+*.pyc
+
+# Cake - Uncomment if you are using it
+# tools/**
+# !tools/packages.config
+
+# Tabs Studio
+*.tss
+
+# Telerik's JustMock configuration file
+*.jmconfig
+
+# BizTalk build output
+*.btp.cs
+*.btm.cs
+*.odx.cs
+*.xsd.cs
+
+# OpenCover UI analysis results
+OpenCover/
+
+# Azure Stream Analytics local run output
+ASALocalRun/
+
+# MSBuild Binary and Structured Log
+*.binlog
+
+# NVidia Nsight GPU debugger configuration file
+*.nvuser
+
+# MFractors (Xamarin productivity tool) working folder
+.mfractor/
+
+# Local History for Visual Studio
+.localhistory/
+
+# BeatPulse healthcheck temp database
+healthchecksdb
+
+# Backup folder for Package Reference Convert tool in Visual Studio 2017
+MigrationBackup/
+
+# Ionide (cross platform F# VS Code tools) working folder
+.ionide/
+
+# Fody - auto-generated XML schema
+FodyWeavers.xsd
+
+##
+## Visual studio for Mac
+##
+
+
+# globs
+Makefile.in
+*.userprefs
+*.usertasks
+config.make
+config.status
+aclocal.m4
+install-sh
+autom4te.cache/
+*.tar.gz
+tarballs/
+test-results/
+
+# Mac bundle stuff
+*.dmg
+*.app
+
+# content below from: https://github.com/github/gitignore/blob/master/Global/macOS.gitignore
+# General
+.DS_Store
+.AppleDouble
+.LSOverride
+
+# Icon must end with two \r
+Icon
+
+
+# Thumbnails
+._*
+
+# Files that might appear in the root of a volume
+.DocumentRevisions-V100
+.fseventsd
+.Spotlight-V100
+.TemporaryItems
+.Trashes
+.VolumeIcon.icns
+.com.apple.timemachine.donotpresent
+
+# Directories potentially created on remote AFP share
+.AppleDB
+.AppleDesktop
+Network Trash Folder
+Temporary Items
+.apdisk
+
+# content below from: https://github.com/github/gitignore/blob/master/Global/Windows.gitignore
+# Windows thumbnail cache files
+Thumbs.db
+ehthumbs.db
+ehthumbs_vista.db
+
+# Dump file
+*.stackdump
+
+# Folder config file
+[Dd]esktop.ini
+
+# Recycle Bin used on file shares
+$RECYCLE.BIN/
+
+# Windows Installer files
+*.cab
+*.msi
+*.msix
+*.msm
+*.msp
+
+# Windows shortcuts
+*.lnk
+
+# JetBrains Rider
+.idea/
+*.sln.iml
+
+##
+## Visual Studio Code
+##
+.vscode/*
+!.vscode/settings.json
+!.vscode/tasks.json
+!.vscode/launch.json
+!.vscode/extensions.json
diff --git a/dotnet/testing/KlipsConsole/KlipsConsole.csproj b/dotnet/testing/KlipsConsole/KlipsConsole.csproj
new file mode 100644
index 0000000..529a638
--- /dev/null
+++ b/dotnet/testing/KlipsConsole/KlipsConsole.csproj
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+ Exe
+ net6.0
+ enable
+ enable
+
+
+
diff --git a/dotnet/testing/KlipsConsole/Program.cs b/dotnet/testing/KlipsConsole/Program.cs
new file mode 100644
index 0000000..e0d0a49
--- /dev/null
+++ b/dotnet/testing/KlipsConsole/Program.cs
@@ -0,0 +1,14 @@
+using KlipsLibrary;
+
+Klips.SayHello();
+
+int[] arr = { 2, 4, 6, 2, 1, 44, 10 };
+Klips.Print(arr);
+
+Console.WriteLine();
+HashSet set = new HashSet(){ "hi", "my", "name", "is", "shaun" };
+Klips.Print(set);
+Console.WriteLine();
+
+// Test other C# containers
+Klips.TestContainers();
diff --git a/dotnet/testing/KlipsLibrary.Test/KlipsLibrary.Test.csproj b/dotnet/testing/KlipsLibrary.Test/KlipsLibrary.Test.csproj
new file mode 100644
index 0000000..b559b32
--- /dev/null
+++ b/dotnet/testing/KlipsLibrary.Test/KlipsLibrary.Test.csproj
@@ -0,0 +1,27 @@
+
+
+
+ net6.0
+ enable
+
+ false
+
+
+
+
+
+
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+ all
+
+
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+ all
+
+
+
+
+
+
+
+
diff --git a/dotnet/testing/KlipsLibrary.Test/UnitTest1.cs b/dotnet/testing/KlipsLibrary.Test/UnitTest1.cs
new file mode 100644
index 0000000..d7c92a4
--- /dev/null
+++ b/dotnet/testing/KlipsLibrary.Test/UnitTest1.cs
@@ -0,0 +1,22 @@
+using Xunit;
+
+namespace KlipsLibrary.Test;
+
+public class UnitTest1
+{
+ [Fact]
+ public void DogTest()
+ {
+ var dog = new Dog("Buford", "Woof");
+ Assert.Equal("Buford", dog.Name);
+ dog.Speak();
+ }
+
+ [Fact]
+ public void HumanTest()
+ {
+ var human = new Human("Shaun", "Hi");
+ Assert.Equal("Shaun", human.Name);
+ human.Speak();
+ }
+}
\ No newline at end of file
diff --git a/dotnet/testing/KlipsLibrary/Animal.cs b/dotnet/testing/KlipsLibrary/Animal.cs
new file mode 100644
index 0000000..a31bdb2
--- /dev/null
+++ b/dotnet/testing/KlipsLibrary/Animal.cs
@@ -0,0 +1,70 @@
+using System.Collections;
+using KlipsLibrary;
+namespace KlipsLibrary;
+public class Solution {
+ public int NumUniqueEmails(string[] emails)
+ {
+ HashSet sent = new();
+ foreach (var email in emails)
+ {
+ var domain = email.Substring(email.IndexOf("@"), email.Length);
+ var to = email.Substring(0, email.IndexOf("@"));
+ if (to.Contains(".")) to = to.Replace(".", "");
+ to = to.Remove(to.IndexOf("+"));
+ Console.Write("{0} at {1}", to, domain);
+ sent.Add(to + "@" + domain);
+ }
+
+ return sent.Count;
+ }
+}
+public abstract class Animal
+{
+ public Animal(string n, string p)
+ {
+ this.Name = n;
+ this.Phrase = p;
+ }
+
+ public abstract void Speak();
+
+ private string name;
+ public string Name { get; set; }
+ private string phrase;
+ public string Phrase { get; set; }
+}
+
+public class Dog : Animal
+{
+ public Dog(string n, string p) : base(n, p) { }
+
+ public override void Speak()
+ {
+ Console.WriteLine("{0} (Dog): {1}", Name, Phrase);
+ }
+}
+
+public class Human : Animal
+{
+ public Human(string n, string p) : base(n, p) { }
+
+ public override void Speak()
+ {
+ Console.WriteLine("{0} (Human): {1}", Name, Phrase);
+ }
+}
+
+public class Teacher : Human, IComparable, ICloneable
+{
+ public Teacher(string n, string p) : base(n, p) { }
+
+ public int CompareTo(object? obj)
+ {
+ throw new NotImplementedException();
+ }
+
+ public object Clone()
+ {
+ throw new NotImplementedException();
+ }
+}
diff --git a/dotnet/testing/KlipsLibrary/Bag.cs b/dotnet/testing/KlipsLibrary/Bag.cs
new file mode 100644
index 0000000..e54a5c4
--- /dev/null
+++ b/dotnet/testing/KlipsLibrary/Bag.cs
@@ -0,0 +1,74 @@
+namespace KlipsLibrary;
+
+public struct Item : IEquatable-
+{
+ public Item(double v, int q, string name)
+ {
+ Value = v;
+ Qty = q;
+ Name = name;
+ }
+
+ public Item(double v, string name)
+ {
+ Value = v;
+ Qty = 1;
+ Name = name;
+ }
+
+ public string Name { get; set; }
+ public double Value { get; set; }
+ public int Qty { get; set; }
+
+ public static bool operator ==(Item a, Item b)
+ {
+ if (((object)a) == null || ((object)b) == null) return Object.Equals(a, b);
+ return a.Equals(b);
+ }
+
+ public static bool operator !=(Item a, Item b)
+ {
+ return !(a == b);
+ }
+
+ public bool Equals(Item other)
+ {
+ return Name == other.Name && Value == other.Value;
+ }
+
+ public override bool Equals(object? obj)
+ {
+ return obj is Item other && Equals(other);
+ }
+
+ public override int GetHashCode()
+ {
+ return HashCode.Combine(Name, Value);
+ }
+}
+
+public class Bag
+{
+ public Bag()
+ {
+ contents = new List
- ();
+ maxCarry = 10;
+ }
+ private List
- contents;
+ private int maxCarry;
+
+ public bool AddItem(Item i)
+ {
+ if (contents.Count >= maxCarry) return false;
+ contents.Add(i);
+ return true;
+ }
+
+ public Item? TakeItem(Item i)
+ {
+ var found = contents.Find((Item inBag) => inBag == i);
+ if (found == default(Item)) return null;
+ contents.Remove(found);
+ return found;
+ }
+}
diff --git a/dotnet/testing/KlipsLibrary/Fruit.cs b/dotnet/testing/KlipsLibrary/Fruit.cs
new file mode 100644
index 0000000..8851334
--- /dev/null
+++ b/dotnet/testing/KlipsLibrary/Fruit.cs
@@ -0,0 +1,19 @@
+using System.Collections.ObjectModel;
+
+namespace KlipsLibrary;
+
+public class Fruit
+{
+ public Fruit()
+ {
+ Name = "Default";
+ }
+
+ public Fruit(string name)
+ {
+ Name = name;
+ }
+ public string Name { get; set; }
+}
+
+public class Fruits : Collection { }
diff --git a/dotnet/testing/KlipsLibrary/InitOrder.cs b/dotnet/testing/KlipsLibrary/InitOrder.cs
new file mode 100644
index 0000000..581a9fb
--- /dev/null
+++ b/dotnet/testing/KlipsLibrary/InitOrder.cs
@@ -0,0 +1,75 @@
+namespace KlipsLibrary;
+
+public class A
+{
+ public A()
+ {
+ Console.WriteLine("A default constructor was called");
+ Val = 0;
+ }
+ public A(int v)
+ {
+ Console.WriteLine("A parameterized constructor was called");
+ Val = v;
+ }
+ private int val;
+ public int Val
+ {
+ // No setter; We can only initialize on construction
+ init
+ {
+ Console.WriteLine($"A.val was initialized: {val}");
+ val = value;
+ }
+ }
+}
+
+public class B : A
+{
+ public B()
+ {
+ Console.WriteLine("B default constructor was called");
+ BVal = 0;
+ }
+ public B(int bv)
+ {
+ Console.WriteLine("B parameterized constructor was called");
+ BVal = bv;
+ }
+
+ private int bVal;
+
+ public int BVal
+ {
+ init
+ {
+ bVal = value;
+ Console.WriteLine($"B.bVal was initialized: {bVal}");
+ }
+ }
+}
+
+public class C : B
+{
+ public C()
+ {
+ Console.WriteLine("C default constructor was called");
+ CVal = 0;
+ }
+ public C(int cv)
+ {
+ Console.WriteLine("C parameterized constructor was called");
+ CVal = cv;
+ }
+
+ private int cVal;
+
+ public int CVal
+ {
+ init
+ {
+ cVal = value;
+ Console.WriteLine($"C.cVal was initialized: {cVal}");
+ }
+ }
+}
\ No newline at end of file
diff --git a/dotnet/testing/KlipsLibrary/Klips.cs b/dotnet/testing/KlipsLibrary/Klips.cs
new file mode 100644
index 0000000..be83a4d
--- /dev/null
+++ b/dotnet/testing/KlipsLibrary/Klips.cs
@@ -0,0 +1,281 @@
+using System.Collections;
+using System.Linq.Expressions;
+
+namespace KlipsLibrary;
+
+public class Klips
+{
+ public static void SayHello()
+ {
+ Console.WriteLine("Hello, C# library!");
+ }
+
+ public static void TestInput()
+ {
+ string formattingString = "Captured {0} input: {1}\n";
+
+ Console.Write("\nInput a character, then press enter: ");
+ int ascii = Console.Read();
+ char ch = Convert.ToChar(ascii);
+ Console.Write(formattingString, "character", ch);
+ Console.ReadLine(); // Discard any left over input
+
+ Console.Write("\nPress a key: ");
+ ConsoleKeyInfo key = Console.ReadKey();
+ Console.Write("\n" + formattingString, "key", key.KeyChar);
+
+ Console.Write("\nEnter a line: ");
+ string? line = Console.ReadLine();
+ Console.Write(formattingString, "line", line);
+ }
+
+ public static void PrintEnum(IEnumerable obj)
+ {
+ foreach (var i in obj)
+ {
+ Console.Write("{0}, ", i);
+ }
+ }
+
+ public static void PrintInfo(IEnumerable c)
+ {
+ Console.WriteLine("Type: {0}", c.GetType().ToString());
+ }
+
+ public static void Print(IEnumerable obj)
+ {
+ Console.WriteLine();
+ PrintInfo(obj);
+ PrintEnum(obj);
+ }
+
+ // Doesn't work because we can't resolve passed object name
+ // + Limited to parameter object name; ref causes errors
+ public static void PrintName(T obj)
+ {
+ Expression> expression = () => obj!;
+ string name = ((expression.Body as MemberExpression)!).Member.Name;
+ Console.WriteLine("{0}", name);
+ }
+
+ public static void MakeGarbage(int count)
+ {
+ for (int i = 0; i < count; i++)
+ {
+ var v = new Version();
+ }
+ }
+
+ public static void TestContainers()
+ {
+ int[] test = { 1, 2, 3, 4, 5 };
+ Klips.Print(test);
+
+ var arr = new int[5];
+ for (int i = 1; i < 6; i++)
+ {
+ arr[i-1] = i;
+ }
+ Klips.Print(arr);
+
+ var list = new List();
+ for (int i = 1; i < 6; i++)
+ {
+ list.Add(i);
+ }
+ Klips.Print(list);
+
+ list = list.Concat(arr).ToList();
+ Klips.Print(list);
+
+ list.Sort();
+ Klips.Print(list);
+
+ var dict = new Dictionary()
+ {
+ [4] = "four",
+ [2] = "two",
+ [1] = "one",
+ [3] = "three",
+ [5] = "five",
+ };
+ Klips.Print(dict);
+ for (int i = 1; i <= 5; i++)
+ {
+ Console.Write("\n{0}", dict[i]);
+ }
+
+ var sortedDict = new SortedDictionary(dict);
+ Klips.Print(sortedDict);
+
+ var hashset = new HashSet{3, 1, 4, 2};
+ Klips.Print(hashset);
+
+ var sortedSet = new SortedSet(hashset);
+ Klips.Print(sortedSet);
+
+ var sortedList = new SortedList(sortedDict);
+ Console.WriteLine("Type: {0}", sortedList.GetType().ToString());
+ foreach (DictionaryEntry item in sortedList)
+ {
+ Console.WriteLine("Key: {0} Value: {1}", item.Key.ToString(), item.Value);
+ }
+ // Klips.Print(sortedList);
+
+ var q = new Queue(dict.Values);
+ Klips.Print(q);
+
+ var llist = new LinkedList(dict.Keys);
+ Klips.Print(llist);
+
+ }
+
+ public static void TestGC()
+ {
+ Klips.MakeGarbage(1000000);
+ // GC.Collect();
+ Console.Write("\nHeap memory: {0}\nAllocated heap memory: {1}",
+ GC.GetGCMemoryInfo().HeapSizeBytes.ToString(), GC.GetTotalMemory(false).ToString());
+ for (int i = 0; i < 3; i++)
+ {
+ Console.Write("\nGeneration {0} collection count: {1}", i, GC.CollectionCount(i).ToString());
+ }
+ }
+
+ public static void TestStrings()
+ {
+ Console.Write("\n\nWhat time is it?\n{0}", DateTime.Now.ToString());
+
+ var lit = @"
+ hi
+ how
+ ""are"" you? \this\is\a\literal
+ ";
+ Console.WriteLine(lit);
+
+ string[] @foreach = {@"\this\is\new\a\test\n", "Not verbatim\nBut still literal"};
+ foreach (string s in @foreach)
+ {
+ Console.WriteLine(@s);
+ }
+
+ string a = "This is my string!";
+ Console.WriteLine($"This is my rifle; {a, 30}");
+ Console.WriteLine($"This is {{my}} rifle; {a, -30}");
+ var b = $"This {{is}} my rifle; {a}";
+ Console.WriteLine(b);
+ Console.WriteLine($"Conditional formatting result: {(b.Length == 0 ? "Empty" : "Not empty")}");
+ var pi = Math.PI;
+ Console.WriteLine($"{pi:F3}, {pi:F10}, {DateTime.Now:d}, {DateTime.Now:f}, {DateTime.Now.ToLocalTime():h:mm:ss tt zz}");
+
+ string fmt = "This is pi: {0}\nThis is the date: {1}\nThis is also pi: {0:F6}";
+ Console.WriteLine(fmt, Math.PI, DateTime.Now);
+ }
+
+ public static void TestLambdas()
+ {
+ // Both of these lambdas are of the same type; Func where int is the value returned
+ var getLen = (string s) => s.Length;
+ Func funcLen = (string s) => s.Length;
+ Console.WriteLine("Length: {0}", getLen("Hello").ToString());
+ Console.WriteLine("Length: {0}", funcLen("Hello").ToString());
+
+ var isEqual = (string a, string b) => a == b;
+ Console.WriteLine(isEqual("Test", "Test"));
+ Func funcIsEqual = (string a, string b) => a == b;
+ Console.WriteLine(funcIsEqual("Test", "Test"));
+
+ // These two lamdas are both of type Action, as they do not return a result
+ var statement = (string s) =>
+ {
+ var arr = s.ToCharArray();
+ Array.Reverse(arr);
+ Console.WriteLine($"\"{s}\" reversed: {new string(arr)}");
+ };
+ Action actionReverse = (string s) =>
+ {
+ var arr = s.ToCharArray();
+ Array.Reverse(arr);
+ Console.WriteLine($"\"{s}\" reversed: {new string(arr)}");
+ };
+
+ // This lamda is a Func as it take a string parameter and returns a string as a result
+ Func revString = (string s) =>
+ {
+ var revArr = s.ToCharArray();
+ Array.Reverse(revArr);
+ return new string(revArr);
+ };
+ string testS = "Racecar";
+ Console.WriteLine($"{testS} reversed: {revString(testS)}");
+
+ statement("Test");
+ }
+
+ public static void TestShape()
+ {
+ var shape = new Square();
+ shape.Print();
+ var shapeRef = shape;
+ shapeRef.Height = 20;
+ shape.Print();
+
+ var box = new List();
+ box.Add(new Shape(5, 5));
+ box.Add(new Square());
+ box.Last().Width = 8; // Access the last element we added to the List, set its width to 8
+ box.Add(new Cube());
+ box.Add(new Rectangle());
+ box.Add(new Rect());
+ // Use a lambda to find a Cube, get a reference to it; If we found a Cube, set its depth to 5
+ if (box.Find((Shape s) => s.GetType() == typeof(Cube)) is Cube cubeRef) cubeRef.Depth = 5;
+ foreach (var s in box) s.Print(); // Print all the Shapes
+
+ var cub = new Cube();
+ var sqr = cub as Square;
+ if (sqr != null) sqr.Print();
+
+ Console.WriteLine("Testing upcast");
+ var c = new Cube();
+ c.Print();
+ if (c is Square cubeSquare)
+ {
+ // Why is Shape's Print() not called?; cubeShape.Print calls Square.Print() instead
+ var cubeShape = cubeSquare as Shape;
+ if (cubeShape != null) cubeShape.Print();
+ }
+ }
+
+ public static void TestBag()
+ {
+ var bag = new Bag();
+ Item wrench = new Item(1.5, 1, "wrench"); // Create a wrench using Item ctor
+ var spanner = wrench; // Copy wrench to a new item
+ spanner.Name = "spanner";
+ spanner.Value = 5.0;
+ spanner.Qty = 2;
+ Item socket = new Item(2.5, 5, "socket"); // Create a new item using ctor
+ bag.AddItem(wrench);
+ bag.AddItem(spanner);
+ bag.AddItem(socket);
+ Item? bagSpanner = bag.TakeItem(spanner);
+ Console.WriteLine(Object.ReferenceEquals(bagSpanner, spanner));
+ Item? noSpanner = bag.TakeItem(spanner);
+ Console.WriteLine(noSpanner == null);
+ }
+
+ public static void TestFruits()
+ {
+ Fruits fruits = new Fruits() { new Fruit(), new Fruit("Apple"), new Fruit("Orange")};
+ foreach (Fruit f in fruits)
+ {
+ Console.WriteLine(f.Name);
+ }
+ }
+
+ public static void TestInitOrder()
+ {
+ var aClass = new C(5) {CVal = 10};
+ }
+
+}
\ No newline at end of file
diff --git a/dotnet/testing/KlipsLibrary/KlipsLibrary.csproj b/dotnet/testing/KlipsLibrary/KlipsLibrary.csproj
new file mode 100644
index 0000000..bafd05b
--- /dev/null
+++ b/dotnet/testing/KlipsLibrary/KlipsLibrary.csproj
@@ -0,0 +1,9 @@
+
+
+
+ net6.0
+ enable
+ enable
+
+
+
diff --git a/dotnet/testing/KlipsLibrary/Shape.cs b/dotnet/testing/KlipsLibrary/Shape.cs
new file mode 100644
index 0000000..e71aadf
--- /dev/null
+++ b/dotnet/testing/KlipsLibrary/Shape.cs
@@ -0,0 +1,98 @@
+namespace KlipsLibrary;
+
+public class Shape
+{
+ public Shape(int y, int x)
+ {
+ Y = y;
+ X = x;
+ Name = this.ToString();
+ }
+
+ public Shape()
+ {
+ X = 0;
+ Y = 0;
+ Name = this.ToString();
+ }
+
+ // Shape position; Private setter, public getter
+ public int X { get; private set; }
+ public int Y { get; private set; }
+ public string Name { get; private set; }
+
+ // Auto-implemented properties may use a default value initializer
+ public virtual int Width { get; set; } = 1;
+ public virtual int Height { get; set; } = 2;
+
+ public virtual void Print()
+ {
+ Console.WriteLine($"{Name} WxH is {Width}x{Height} at position ({X},{Y})");
+ }
+}
+
+public class Square : Shape
+{
+ // Set default value on encapsulated value for non auto-implemented properties
+ private int width = 10;
+ private int height = 10;
+
+ // We can override properties just as we can functions
+ // + Height and Width properties can no longer set default values
+ public override int Height
+ {
+ // Can use expressions for getters / setters
+ get => height;
+ set => width = height = value;
+ }
+
+ public override int Width
+ {
+ get => width;
+ set
+ {
+ // Same setter as Height, just within a block of statements
+ width = value;
+ height = value;
+ }
+ }
+
+ public override void Print()
+ {
+ Console.WriteLine("Printing Square info...");
+ base.Print(); // Will use Square's getter / setter to print private int width, height
+ }
+}
+
+public class Cube : Square
+{
+ // Add new properties or encapsulated values as needed
+ private int depth = 10;
+ public int Depth
+ {
+ get => depth;
+ set => depth = value;
+ }
+ // Implement a `new` Print() function which acts as a new stand-alone implementaton
+ public new void Print()
+ {
+ Console.WriteLine("Printing Cube info...");
+ Console.WriteLine($"{Name} WxHxD is {Width}x{Height}x{Depth} at position ({X},{Y})");
+ }
+}
+
+public class Rectangle : Shape
+{
+ // Classes that inherit from Rectangle can not override Print
+ public sealed override void Print()
+ {
+ Console.WriteLine("Printing sealed Rectangle info...");
+ base.Print();
+ }
+}
+
+public class Rect : Rectangle
+{
+ // Rect can't override Print(), since its base class declared it as `sealed`
+}
+
diff --git a/dotnet/testing/README.md b/dotnet/testing/README.md
new file mode 100644
index 0000000..c34973f
--- /dev/null
+++ b/dotnet/testing/README.md
@@ -0,0 +1,5 @@
+
+This is a testing project I created while first learning C#.
+There's no objective or goal, aside from playing with different features of C# and NuGet packages.
+Essentially, this is a scrap project that I code in while learning new C# things.
+Since this is just for learning, I won't bother explaining how to use things here; It's very possible this entire project could disappear one day.
diff --git a/dotnet/testing/dotnet-klips.sln b/dotnet/testing/dotnet-klips.sln
new file mode 100644
index 0000000..4fe9063
--- /dev/null
+++ b/dotnet/testing/dotnet-klips.sln
@@ -0,0 +1,34 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 16
+VisualStudioVersion = 16.0.30114.105
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "KlipsLibrary", "KlipsLibrary\KlipsLibrary.csproj", "{58CB87C5-7D98-43F9-8AD8-C73766C06E51}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "KlipsLibrary.Test", "KlipsLibrary.Test\KlipsLibrary.Test.csproj", "{3576A541-1B1E-42A1-A348-B0CAA3B5BC5A}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "KlipsConsole", "KlipsConsole\KlipsConsole.csproj", "{A1E88D77-4A17-47BF-BED4-CEB1821FB627}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Release|Any CPU = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {58CB87C5-7D98-43F9-8AD8-C73766C06E51}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {58CB87C5-7D98-43F9-8AD8-C73766C06E51}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {58CB87C5-7D98-43F9-8AD8-C73766C06E51}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {58CB87C5-7D98-43F9-8AD8-C73766C06E51}.Release|Any CPU.Build.0 = Release|Any CPU
+ {3576A541-1B1E-42A1-A348-B0CAA3B5BC5A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {3576A541-1B1E-42A1-A348-B0CAA3B5BC5A}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {3576A541-1B1E-42A1-A348-B0CAA3B5BC5A}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {3576A541-1B1E-42A1-A348-B0CAA3B5BC5A}.Release|Any CPU.Build.0 = Release|Any CPU
+ {A1E88D77-4A17-47BF-BED4-CEB1821FB627}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {A1E88D77-4A17-47BF-BED4-CEB1821FB627}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {A1E88D77-4A17-47BF-BED4-CEB1821FB627}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {A1E88D77-4A17-47BF-BED4-CEB1821FB627}.Release|Any CPU.Build.0 = Release|Any CPU
+ EndGlobalSection
+EndGlobal