refactor: bring external packages home and restructure

- Imported packages from separate repos:
  - github.com/Snider/config -> pkg/config
  - github.com/Snider/display -> pkg/display
  - github.com/Snider/help -> pkg/help
  - github.com/Snider/i18n -> pkg/i18n
  - github.com/Snider/updater -> pkg/updater
- Moved core code from root to pkg/core
- Flattened nested package structures
- Updated all import paths to github.com/Snider/Core/pkg/*
- Added Display interface to Core
- Updated go.work for workspace modules

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Snider 2026-01-15 15:30:43 +00:00
parent 60c0d4f6c7
commit 4e02d5bc97
239 changed files with 65949 additions and 63 deletions

View file

@ -1,9 +0,0 @@
package core
// ActionServiceStartup is a message sent when the application's services are starting up.
// This provides a hook for services to perform initialization tasks.
type ActionServiceStartup struct{}
// ActionServiceShutdown is a message sent when the application is shutting down.
// This allows services to perform cleanup tasks, such as saving state or closing resources.
type ActionServiceShutdown struct{}

View file

@ -2,10 +2,7 @@ module core-gui
go 1.25
require (
github.com/Snider/Core v0.0.0-20251024151010-ccfd407949f1
github.com/wailsapp/wails/v3 v3.0.0-alpha.41
)
require github.com/wailsapp/wails/v3 v3.0.0-alpha.41
replace github.com/Snider/Core => ../../

View file

@ -3,5 +3,11 @@ go 1.25
use (
.
./cmd/core-gui
cmd/examples/core-static-di
./cmd/examples/core-static-di
./pkg/config
./pkg/core
./pkg/display
./pkg/help
./pkg/i18n
./pkg/updater
)

View file

@ -6,6 +6,8 @@ atomicgo.dev/keyboard v0.2.9 h1:tOsIid3nlPLZ3lwgG8KZMp/SFmr7P0ssEN5JUsm78K8=
atomicgo.dev/keyboard v0.2.9/go.mod h1:BC4w9g00XkxH/f1HXhW2sXmJFOCWbKn9xrOunSFtExQ=
atomicgo.dev/schedule v0.1.0 h1:nTthAbhZS5YZmgYbb2+DH8uQIZcTlIrd4eYr3UQxEjs=
atomicgo.dev/schedule v0.1.0/go.mod h1:xeUa3oAkiuHYh8bKiQBRojqAMq3PXXbJujjb0hw8pEU=
cloud.google.com/go/compute/metadata v0.3.0 h1:Tz+eQXMEqDIKRsmY3cHTL6FVaynIjX2QxYC4trgAKZc=
cloud.google.com/go/compute/metadata v0.3.0/go.mod h1:zFmK7XCadkQkj6TtorcaGlCW1hT1fIilQDwofLpJ20k=
cyphar.com/go-pathrs v0.2.1 h1:9nx1vOgwVvX1mNBWDu93+vaceedpbsDqo+XuBGL40b8=
cyphar.com/go-pathrs v0.2.1/go.mod h1:y8f1EMG7r+hCuFf/rXsKqMJrJAUoADZGNh5/vZPKcGc=
dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk=
@ -16,6 +18,8 @@ github.com/AlekSi/pointer v1.2.0 h1:glcy/gc4h8HnG2Z3ZECSzZ1IX1x2JxRVuDzaJwQE0+w=
github.com/AlekSi/pointer v1.2.0/go.mod h1:gZGfd3dpW4vEc/UlyfKKi1roIqcCgwOIvb0tSNSBle0=
github.com/BurntSushi/toml v1.4.0 h1:kuoIxZQy2WRRk1pttg9asf+WVv6tWQuBNVmK8+nqPr0=
github.com/BurntSushi/toml v1.4.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
github.com/BurntSushi/toml v1.6.0 h1:dRaEfpa2VI55EwlIW72hMRHdWouJeRF7TPYhI+AUQjk=
github.com/BurntSushi/toml v1.6.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
github.com/DataDog/zstd v1.5.5 h1:oWf5W7GtOLgp6bciQYDmhHHjdhYkALu6S/5Ni9ZgSvQ=
github.com/DataDog/zstd v1.5.5/go.mod h1:g4AWEaM3yOg3HYfnJ3YIawPnVdXJh9QME85blwSAmyw=
github.com/Ladicle/tabwriter v1.0.0 h1:DZQqPvMumBDwVNElso13afjYLNp0Z7pHqHnu0r4t9Dg=
@ -108,8 +112,7 @@ github.com/containerd/console v1.0.4 h1:F2g4+oChYvBTsASRTz8NP6iIAi97J3TtSAsLbIFn
github.com/containerd/console v1.0.4/go.mod h1:YynlIjWYF8myEu6sdkwKIvGQq+cOckRm6So2avqoYAk=
github.com/cpuguy83/go-md2man/v2 v2.0.3 h1:qMCsGGgs+MAzDFyp9LpAe1Lqy/fY/qCovCm0qnXZOBM=
github.com/cpuguy83/go-md2man/v2 v2.0.6 h1:XJtiaUW6dEEqVuZiMTn1ldk455QWwEIsMIJlo5vtkx0=
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/creack/pty v1.1.9 h1:uDmaGzcdjhF4i/plgjmEsriH11Y0o7RKapEf/LDaM3w=
github.com/creack/pty v1.1.23 h1:4M6+isWdcStXEf15G/RbrMPOQj1dZ7HPZCGwE4kOeP0=
github.com/creack/pty v1.1.23/go.mod h1:08sCNb52WyoAwi2QDyzUCTgcvVFhUzewun7wtTfvcwE=
github.com/creack/pty v1.1.24/go.mod h1:08sCNb52WyoAwi2QDyzUCTgcvVFhUzewun7wtTfvcwE=
@ -140,6 +143,10 @@ github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJA
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-github/v39 v39.2.0 h1:rNNM311XtPOz5rDdsJXAp2o8F67X9FnROXTvto3aSnQ=
github.com/google/go-github/v39 v39.2.0/go.mod h1:C1s8C5aCC9L+JXIYpJM5GYytdX52vC1bLvHEF1IhBrE=
github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8=
github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU=
github.com/google/pprof v0.0.0-20240409012703-83162a5b38cd h1:gbpYu9NMq8jhDVbvlGkMFWCjLFlqqEZjEmObmhUy6Vo=
github.com/google/pprof v0.0.0-20240409012703-83162a5b38cd/go.mod h1:kf6iHlnVGwgKolg33glAes7Yg/8iWP8ukqeldJSO7jw=
github.com/google/renameio/v2 v2.0.0 h1:UifI23ZTGY8Tt29JbYFiuyIU3eX+RNFtUwefq9qAhxg=
@ -168,8 +175,6 @@ github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUq
github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg=
github.com/huandu/xstrings v1.5.0 h1:2ag3IFq9ZDANvthTwTiqSSZLjDc+BedvHPAp5tJy2TI=
github.com/huandu/xstrings v1.5.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/invopop/jsonschema v0.13.0 h1:KvpoAJWEjR3uD9Kbm2HWJmqsEaHt8lBUpd0qHcIi21E=
github.com/invopop/jsonschema v0.13.0/go.mod h1:ffZ5Km5SWWRAIN6wbDXItl95euhFz2uON45H2qjYt+0=
github.com/jackmordaunt/icns/v2 v2.2.7 h1:K/RbfvuzjmjVY5y4g+XENRs8ZZatwz4YnLHypa2KwQg=
@ -197,7 +202,6 @@ github.com/klauspost/cpuid/v2 v2.2.9 h1:66ze0taIn2H33fBvCkXuv9BmCwDfafmiIVpKV9kK
github.com/klauspost/cpuid/v2 v2.2.9/go.mod h1:rqkxqrZ1EhYM9G+hXH7YdowN5R5RGN6NK4QwQ3WMXF8=
github.com/klauspost/pgzip v1.2.6 h1:8RXeL5crjEUFnR2/Sn6GJNWtSQ3Dk8pq4CL3jvdDyjU=
github.com/klauspost/pgzip v1.2.6/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs=
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pty v1.1.1 h1:VkoXIwSboBpnk99O/KFauAEILuNHv5DVFKZMBN/gUgw=
github.com/leaanthony/clir v1.0.4/go.mod h1:k/RBkdkFl18xkkACMCLt09bhiZnrGORoxmomeMvDpE0=
github.com/leaanthony/clir v1.7.0 h1:xiAnhl7ryPwuH3ERwPWZp/pCHk8wTeiwuAOt6MiNyAw=
@ -227,6 +231,8 @@ github.com/mattn/go-zglob v0.0.6 h1:mP8RnmCgho4oaUYDIDn6GNxYk+qJGUs8fJLn+twYj2A=
github.com/mattn/go-zglob v0.0.6/go.mod h1:MxxjyoXXnMxfIpxTK2GAkw1w8glPsQILx3N5wrKakiY=
github.com/microcosm-cc/bluemonday v1.0.27 h1:MpEUotklkwCSLeH+Qdx1VJgNqLlpY2KXwXFM08ygZfk=
github.com/microcosm-cc/bluemonday v1.0.27/go.mod h1:jFi9vgW+H7c3V0lb6nR74Ib/DIB5OBs92Dimizgw2cA=
github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db h1:62I3jR2EmQ4l5rM/4FEfDWcRD+abF5XlKShorW5LRoQ=
github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db/go.mod h1:l0dey0ia/Uv7NcFFVbCLtqEBQbrT4OCwCSKTEv6enCw=
github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw=
github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s=
github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
@ -265,7 +271,6 @@ github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6
github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs=
github.com/pjbgf/sha1cd v0.3.2/go.mod h1:zQWigSxVmsHEZow5qaLtPYxpcKMMQpa09ixqBxuCS6A=
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e h1:aoZm08cpOy4WuID//EZDgcC4zIxODThtZNPirFr42+A=
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
github.com/pterm/pterm v0.12.27/go.mod h1:PhQ89w4i95rhgE+xedAoqous6K9X+r6aSOI2eFF7DZI=
github.com/pterm/pterm v0.12.29/go.mod h1:WI3qxgvoQFFGKGjGnJR849gU0TsEOvKn5Q8LlY1U7lg=
github.com/pterm/pterm v0.12.30/go.mod h1:MOqLIyMOgmTDz9yorcYbcw+HsgoZo3BQfg2wtl3HEFE=
@ -282,9 +287,7 @@ github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qq
github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rjeczalik/notify v0.9.3 h1:6rJAzHTGKXGj76sbRgDiDcYj/HniypXmSJo1SWakZeY=
github.com/rjeczalik/notify v0.9.3/go.mod h1:gF3zSOrafR9DQEWSE8TjfI9NkooDxbyT4UgRGKZA0lc=
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/sagikazarmark/locafero v0.4.0 h1:HApY1R9zGo4DBgr7dqsTH/JJxLTTsOt7u6keLGt6kNQ=
github.com/sagikazarmark/locafero v0.4.0/go.mod h1:Pe1W6UlPYUk/+wc/6KFhbORCfqzgYEpgQ3O5fPuL3H4=
github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE=
@ -296,6 +299,8 @@ github.com/sajari/fuzzy v1.0.0/go.mod h1:OjYR6KxoWOe9+dOlXeiCJd4dIbED4Oo8wpS89o0
github.com/samber/lo v1.49.1/go.mod h1:dO6KHFzUKXgP8LDhU0oI8d2hekjXnGOu0DB8Jecxd6o=
github.com/sassoftware/go-rpmutils v0.4.0 h1:ojND82NYBxgwrV+mX1CWsd5QJvvEZTKddtCdFLPWhpg=
github.com/sassoftware/go-rpmutils v0.4.0/go.mod h1:3goNWi7PGAT3/dlql2lv3+MSN5jNYPjT5mVcQcIsYzI=
github.com/schollz/progressbar/v3 v3.18.0 h1:uXdoHABRFmNIjUfte/Ex7WtuyVslrw2wVPQmCN62HpA=
github.com/schollz/progressbar/v3 v3.18.0/go.mod h1:IsO3lpbaGuzh8zIMzgY3+J8l4C8GjO0Y9S69eFvNsec=
github.com/sergi/go-diff v1.2.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4=
github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k=
@ -315,14 +320,9 @@ github.com/spf13/cast v1.7.1 h1:cuNEagBQEHWN1FnbGEjCXL2szYEXqfJPbP2HNUaca9Y=
github.com/spf13/cast v1.7.1/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo=
github.com/spf13/cobra v1.9.1 h1:CXSaggrXdbHK9CF+8ywj8Amf7PBRmPCOJugH954Nnlo=
github.com/spf13/cobra v1.9.1/go.mod h1:nDyEzZ8ogv936Cinf6g1RU9MRY64Ir93oCnqb9wxYW0=
github.com/spf13/cobra v1.10.1 h1:lJeBwCfmrnXthfAupyUTzJ/J4Nc1RsHC/mSRU2dll/s=
github.com/spf13/cobra v1.10.1/go.mod h1:7SmJGaTHFVBY0jW4NXGluQoLvhqFQM+6XSKD+P4XaB0=
github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o=
github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/pflag v1.0.9 h1:9exaQaMOCwffKiiiYk6/BndUBv+iRViNW+4lEMi0PvY=
github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk=
github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/viper v1.19.0 h1:RWq5SEjt8o25SROyN3z2OrDB9l7RPd3lwTWU8EcEdcI=
github.com/spf13/viper v1.19.0/go.mod h1:GQUN9bilAbhU/jgc1bKs99f/suXKeUMct8Adx5+Ntkg=
github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
@ -338,6 +338,8 @@ github.com/tc-hib/winres v0.3.1 h1:CwRjEGrKdbi5CvZ4ID+iyVhgyfatxFoizjPhzez9Io4=
github.com/tc-hib/winres v0.3.1/go.mod h1:C/JaNhH3KBvhNKVbvdlDWkbMDO9H4fKKDaN7/07SSuk=
github.com/ulikunitz/xz v0.5.12 h1:37Nm15o69RwBkXM0J6A5OlE67RZTfzUxTj8fB3dfcsc=
github.com/ulikunitz/xz v0.5.12/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
github.com/ulikunitz/xz v0.5.15 h1:9DNdB5s+SgV3bQ2ApL10xRc35ck0DuIX/isZvIk+ubY=
github.com/ulikunitz/xz v0.5.15/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
github.com/wailsapp/task/v3 v3.40.1-patched3 h1:i6O1WNdSur9CGaiMDIYGjsmj/qS4465zqv+WEs6sPRs=
github.com/wailsapp/task/v3 v3.40.1-patched3/go.mod h1:jIP48r8ftoSQNlxFP4+aEnkvGQqQXqCnRi/B7ROaecE=
github.com/wk8/go-ordered-map/v2 v2.1.8 h1:5h/BUHu93oj4gIdvHHHGsScSTMijfx5PeYkE/fJgbpc=
@ -363,7 +365,8 @@ go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE=
go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
go.uber.org/multierr v1.9.0 h1:7fIwc/ZtS0q++VgcfqFDxSBZVv/Xo49/SYnDFupUwlI=
go.uber.org/multierr v1.9.0/go.mod h1:X2jQV1h+kxSjClGpnseKVIxpmcjrj7MNnI0bnlfKTVQ=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.11.1-0.20230711161743-2e82bdd1719d/go.mod h1:xgJhtzW8F9jGdVFWZESrid1U1bjeNy4zgy5cRr/CIio=
golang.org/x/crypto v0.33.0/go.mod h1:bVdXmD7IV/4GdElGPozy6U7lWdRXA4qyRVGJV57uQ5M=
@ -382,8 +385,8 @@ golang.org/x/mod v0.21.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY=
golang.org/x/mod v0.24.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww=
golang.org/x/mod v0.28.0 h1:gQBtGhjxykdjY9YhZpSlZIsbnaE2+PgjfLWUQTnoZ1U=
golang.org/x/mod v0.28.0/go.mod h1:yfB/L0NOf/kmEbXjzCPOx1iK1fRutOydrCMsqRhEBxI=
golang.org/x/mod v0.29.0 h1:HV8lRxZC4l2cr3Zq1LvtOsi/ThTgWnUk/y64QSs8GwA=
golang.org/x/mod v0.29.0/go.mod h1:NyhrlYXJ2H4eJiRy/WDBO6HMqZQ6q9nk4JzS3NuCK+w=
golang.org/x/mod v0.30.0 h1:fDEXFVZ/fmCKProc/yAXXUijritrDzahmwwefnjoPFk=
golang.org/x/mod v0.30.0/go.mod h1:lAsf5O2EvJeSFMiBxXDki7sCgAxEUcZHXoXMKT4GJKc=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
@ -399,8 +402,9 @@ golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug=
golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
golang.org/x/sync v0.18.0 h1:kr88TuHDroi+UVf+0hZnirlk8o8T+4MrK6mr60WkH/I=
golang.org/x/sync v0.18.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
golang.org/x/sys v0.0.0-20180926160741-c2ed4eda69e7/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20211013075003-97ac67df715c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
@ -420,12 +424,13 @@ golang.org/x/telemetry v0.0.0-20250908211612-aef8a434d053 h1:dHQOQddU4YHS5gY33/6
golang.org/x/telemetry v0.0.0-20250908211612-aef8a434d053/go.mod h1:+nZKN+XVh4LCiA9DV3ywrzN4gumyCnKjau3NGb9SGoE=
golang.org/x/telemetry v0.0.0-20251008203120-078029d740a8 h1:LvzTn0GQhWuvKH/kVRS3R3bVAsdQWI7hvfLHGgh9+lU=
golang.org/x/telemetry v0.0.0-20251008203120-078029d740a8/go.mod h1:Pi4ztBfryZoJEkyFTI5/Ocsu2jXyDr6iSdgJiYE/uwE=
golang.org/x/telemetry v0.0.0-20251111182119-bc8e575c7b54 h1:E2/AqCUMZGgd73TQkxUMcMla25GB9i/5HOdLr+uH7Vo=
golang.org/x/telemetry v0.0.0-20251111182119-bc8e575c7b54/go.mod h1:hKdjCMrbv9skySur+Nek8Hd0uJ0GuxJIoIX2payrIdQ=
golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210615171337-6886f2dfbf5b/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/term v0.30.0/go.mod h1:NYYFdzHoI5wRh/h5tDMdMqCqPJZEuNqVR5xJLd/n67g=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
@ -443,6 +448,8 @@ golang.org/x/tools v0.37.0 h1:DVSRzp7FwePZW356yEAChSdNcQo6Nsp+fex1SUW09lE=
golang.org/x/tools v0.37.0/go.mod h1:MBN5QPQtLMHVdvsbtarmTNukZDdgwdwlO5qGacAzF0w=
golang.org/x/tools v0.38.0 h1:Hx2Xv8hISq8Lm16jvBZ2VQf+RLmbd7wVUsALibYI/IQ=
golang.org/x/tools v0.38.0/go.mod h1:yEsQ/d/YK8cjh0L6rZlY8tgtlKiBNTL14pGDJPJpYQs=
golang.org/x/tools v0.39.0 h1:ik4ho21kwuQln40uelmciQPp9SipgNDdrafrYA4TmQQ=
golang.org/x/tools v0.39.0/go.mod h1:JnefbkDPyD8UU2kI5fuf8ZX4/yUeh9W877ZeBONxUqQ=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7 h1:9zdDQZ7Thm29KFXgAX/+yaf3eVbP7djjWp/dXAppNCc=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI=
@ -450,7 +457,6 @@ google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHh
gopkg.in/yaml.v1 v1.0.0-20140924161607-9f9df34309c0 h1:POO/ycCATvegFmVuPpQzZFJ+pGZeX22Ufu6fibxDVjU=
gopkg.in/yaml.v1 v1.0.0-20140924161607-9f9df34309c0/go.mod h1:WDnlLJ4WF5VGsH/HVa3CI79GS0ol3YnhVnKP89i0kNg=
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
howett.net/plist v1.0.1 h1:37GdZ8tP09Q35o9ych3ehygcsL+HqKSwzctveSlarvM=

33
pkg/config/.github/workflows/ci.yml vendored Normal file
View file

@ -0,0 +1,33 @@
name: Go CI
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Set up Go
uses: actions/setup-go@v4
with:
go-version: '1.25'
- name: Install dependencies
run: |
sudo apt-get update
sudo apt-get install -y libgtk-3-dev libwebkit2gtk-4.1-dev
- name: Test
run: go test -v -coverprofile=coverage.out ./...
- name: Upload coverage to Codecov
uses: codecov/codecov-action@v3
with:
files: ./coverage.out
verbose: true

View file

@ -0,0 +1,24 @@
name: release
on:
push:
tags:
- 'v*'
jobs:
goreleaser:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v2
with:
fetch-depth: 0
- name: Set up Go
uses: actions/setup-go@v2
with:
go-version: 1.18
- name: Run GoReleaser
uses: goreleaser/goreleaser-action@v2
with:
version: latest
args: release --rm-dist
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

18
pkg/config/.gitignore vendored Normal file
View file

@ -0,0 +1,18 @@
# Go
*.exe
*.exe~
*.dll
*.so
*.dylib
*.test
*.out
*.prof
demo-cli
# Node
node_modules/
dist/
.DS_Store
npm-debug.log*
yarn-debug.log*
yarn-error.log*

View file

@ -0,0 +1,43 @@
# This is an example .goreleaser.yml file with some sensible defaults.
# Make sure to check the documentation at https://goreleaser.com
before:
hooks:
# You may remove this if you don't use go modules.
- go mod tidy
# you may remove this if you don't need go generate
- go generate ./...
builds:
- env:
- CGO_ENABLED=0
goos:
- linux
- windows
- darwin
archives:
- replacements:
darwin: Darwin
linux: Linux
windows: Windows
386: i386
amd64: x86_64
checksum:
name_template: 'checksums.txt'
snapshot:
name_template: "{{ incpatch .Version }}-next"
changelog:
sort: asc
filters:
exclude:
- '^docs:'
- '^test:'
brews:
- name: config
tap:
owner: Snider
name: homebrew-tap
commit_author:
name: goreleaserbot
email: goreleaser@carlosbecker.com
homepage: "https://github.com/Snider/config"
description: "Config module for the Core Framework"
license: "EUPL-1.2"

287
pkg/config/LICENSE Normal file
View file

@ -0,0 +1,287 @@
EUROPEAN UNION PUBLIC LICENCE v. 1.2
EUPL © the European Union 2007, 2016
This European Union Public Licence (the EUPL) applies to the Work (as defined
below) which is provided under the terms of this Licence. Any use of the Work,
other than as authorised under this Licence is prohibited (to the extent such
use is covered by a right of the copyright holder of the Work).
The Work is provided under the terms of this Licence when the Licensor (as
defined below) has placed the following notice immediately following the
copyright notice for the Work:
Licensed under the EUPL
or has expressed by any other means his willingness to license under the EUPL.
1. Definitions
In this Licence, the following terms have the following meaning:
- The Licence: this Licence.
- The Original Work: the work or software distributed or communicated by the
Licensor under this Licence, available as Source Code and also as Executable
Code as the case may be.
- Derivative Works: the works or software that could be created by the
Licensee, based upon the Original Work or modifications thereof. This Licence
does not define the extent of modification or dependence on the Original Work
required in order to classify a work as a Derivative Work; this extent is
determined by copyright law applicable in the country mentioned in Article 15.
- The Work: the Original Work or its Derivative Works.
- The Source Code: the human-readable form of the Work which is the most
convenient for people to study and modify.
- The Executable Code: any code which has generally been compiled and which is
meant to be interpreted by a computer as a program.
- The Licensor: the natural or legal person that distributes or communicates
the Work under the Licence.
- Contributor(s): any natural or legal person who modifies the Work under the
Licence, or otherwise contributes to the creation of a Derivative Work.
- The Licensee or You: any natural or legal person who makes any usage of
the Work under the terms of the Licence.
- Distribution or Communication: any act of selling, giving, lending,
renting, distributing, communicating, transmitting, or otherwise making
available, online or offline, copies of the Work or providing access to its
essential functionalities at the disposal of any other natural or legal
person.
2. Scope of the rights granted by the Licence
The Licensor hereby grants You a worldwide, royalty-free, non-exclusive,
sublicensable licence to do the following, for the duration of copyright vested
in the Original Work:
- use the Work in any circumstance and for all usage,
- reproduce the Work,
- modify the Work, and make Derivative Works based upon the Work,
- communicate to the public, including the right to make available or display
the Work or copies thereof to the public and perform publicly, as the case may
be, the Work,
- distribute the Work or copies thereof,
- lend and rent the Work or copies thereof,
- sublicense rights in the Work or copies thereof.
Those rights can be exercised on any media, supports and formats, whether now
known or later invented, as far as the applicable law permits so.
In the countries where moral rights apply, the Licensor waives his right to
exercise his moral right to the extent allowed by law in order to make effective
the licence of the economic rights here above listed.
The Licensor grants to the Licensee royalty-free, non-exclusive usage rights to
any patents held by the Licensor, to the extent necessary to make use of the
rights granted on the Work under this Licence.
3. Communication of the Source Code
The Licensor may provide the Work either in its Source Code form, or as
Executable Code. If the Work is provided as Executable Code, the Licensor
provides in addition a machine-readable copy of the Source Code of the Work
along with each copy of the Work that the Licensor distributes or indicates, in
a notice following the copyright notice attached to the Work, a repository where
the Source Code is easily and freely accessible for as long as the Licensor
continues to distribute or communicate the Work.
4. Limitations on copyright
Nothing in this Licence is intended to deprive the Licensee of the benefits from
any exception or limitation to the exclusive rights of the rights owners in the
Work, of the exhaustion of those rights or of other applicable limitations
thereto.
5. Obligations of the Licensee
The grant of the rights mentioned above is subject to some restrictions and
obligations imposed on the Licensee. Those obligations are the following:
Attribution right: The Licensee shall keep intact all copyright, patent or
trademarks notices and all notices that refer to the Licence and to the
disclaimer of warranties. The Licensee must include a copy of such notices and a
copy of the Licence with every copy of the Work he/she distributes or
communicates. The Licensee must cause any Derivative Work to carry prominent
notices stating that the Work has been modified and the date of modification.
Copyleft clause: If the Licensee distributes or communicates copies of the
Original Works or Derivative Works, this Distribution or Communication will be
done under the terms of this Licence or of a later version of this Licence
unless the Original Work is expressly distributed only under this version of the
Licence — for example by communicating EUPL v. 1.2 only. The Licensee
(becoming Licensor) cannot offer or impose any additional terms or conditions on
the Work or Derivative Work that alter or restrict the terms of the Licence.
Compatibility clause: If the Licensee Distributes or Communicates Derivative
Works or copies thereof based upon both the Work and another work licensed under
a Compatible Licence, this Distribution or Communication can be done under the
terms of this Compatible Licence. For the sake of this clause, Compatible
Licence refers to the licences listed in the appendix attached to this Licence.
Should the Licensee's obligations under the Compatible Licence conflict with
his/her obligations under this Licence, the obligations of the Compatible
Licence shall prevail.
Provision of Source Code: When distributing or communicating copies of the Work,
the Licensee will provide a machine-readable copy of the Source Code or indicate
a repository where this Source will be easily and freely available for as long
as the Licensee continues to distribute or communicate the Work.
Legal Protection: This Licence does not grant permission to use the trade names,
trademarks, service marks, or names of the Licensor, except as required for
reasonable and customary use in describing the origin of the Work and
reproducing the content of the copyright notice.
6. Chain of Authorship
The original Licensor warrants that the copyright in the Original Work granted
hereunder is owned by him/her or licensed to him/her and that he/she has the
power and authority to grant the Licence.
Each Contributor warrants that the copyright in the modifications he/she brings
to the Work are owned by him/her or licensed to him/her and that he/she has the
power and authority to grant the Licence.
Each time You accept the Licence, the original Licensor and subsequent
Contributors grant You a licence to their contributions to the Work, under the
terms of this Licence.
7. Disclaimer of Warranty
The Work is a work in progress, which is continuously improved by numerous
Contributors. It is not a finished work and may therefore contain defects or
bugs inherent to this type of development.
For the above reason, the Work is provided under the Licence on an as is basis
and without warranties of any kind concerning the Work, including without
limitation merchantability, fitness for a particular purpose, absence of defects
or errors, accuracy, non-infringement of intellectual property rights other than
copyright as stated in Article 6 of this Licence.
This disclaimer of warranty is an essential part of the Licence and a condition
for the grant of any rights to the Work.
8. Disclaimer of Liability
Except in the cases of wilful misconduct or damages directly caused to natural
persons, the Licensor will in no event be liable for any direct or indirect,
material or moral, damages of any kind, arising out of the Licence or of the use
of the Work, including without limitation, damages for loss of goodwill, work
stoppage, computer failure or malfunction, loss of data or any commercial
damage, even if the Licensor has been advised of the possibility of such damage.
However, the Licensor will be liable under statutory product liability laws as
far such laws apply to the Work.
9. Additional agreements
While distributing the Work, You may choose to conclude an additional agreement,
defining obligations or services consistent with this Licence. However, if
accepting obligations, You may act only on your own behalf and on your sole
responsibility, not on behalf of the original Licensor or any other Contributor,
and only if You agree to indemnify, defend, and hold each Contributor harmless
for any liability incurred by, or claims asserted against such Contributor by
the fact You have accepted any warranty or additional liability.
10. Acceptance of the Licence
The provisions of this Licence can be accepted by clicking on an icon I agree
placed under the bottom of a window displaying the text of this Licence or by
affirming consent in any other similar way, in accordance with the rules of
applicable law. Clicking on that icon indicates your clear and irrevocable
acceptance of this Licence and all of its terms and conditions.
Similarly, you irrevocably accept this Licence and all of its terms and
conditions by exercising any rights granted to You by Article 2 of this Licence,
such as the use of the Work, the creation by You of a Derivative Work or the
Distribution or Communication by You of the Work or copies thereof.
11. Information to the public
In case of any Distribution or Communication of the Work by means of electronic
communication by You (for example, by offering to download the Work from a
remote location) the distribution channel or media (for example, a website) must
at least provide to the public the information requested by the applicable law
regarding the Licensor, the Licence and the way it may be accessible, concluded,
stored and reproduced by the Licensee.
12. Termination of the Licence
The Licence and the rights granted hereunder will terminate automatically upon
any breach by the Licensee of the terms of the Licence.
Such a termination will not terminate the licences of any person who has
received the Work from the Licensee under the Licence, provided such persons
remain in full compliance with the Licence.
13. Miscellaneous
Without prejudice of Article 9 above, the Licence represents the complete
agreement between the Parties as to the Work.
If any provision of the Licence is invalid or unenforceable under applicable
law, this will not affect the validity or enforceability of the Licence as a
whole. Such provision will be construed or reformed so as necessary to make it
valid and enforceable.
The European Commission may publish other linguistic versions or new versions of
this Licence or updated versions of the Appendix, so far this is required and
reasonable, without reducing the scope of the rights granted by the Licence. New
versions of the Licence will be published with a unique version number.
All linguistic versions of this Licence, approved by the European Commission,
have identical value. Parties can take advantage of the linguistic version of
their choice.
14. Jurisdiction
Without prejudice to specific agreement between parties,
- any litigation resulting from the interpretation of this License, arising
between the European Union institutions, bodies, offices or agencies, as a
Licensor, and any Licensee, will be subject to the jurisdiction of the Court
of Justice of the European Union, as laid down in article 272 of the Treaty on
the Functioning of the European Union,
- any litigation arising between other parties and resulting from the
interpretation of this License, will be subject to the exclusive jurisdiction
of the competent court where the Licensor resides or conducts its primary
business.
15. Applicable Law
Without prejudice to specific agreement between parties,
- this Licence shall be governed by the law of the European Union Member State
where the Licensor has his seat, resides or has his registered office,
- this licence shall be governed by Belgian law if the Licensor has no seat,
residence or registered office inside a European Union Member State.
Appendix
Compatible Licences according to Article 5 EUPL are:
- GNU General Public License (GPL) v. 2, v. 3
- GNU Affero General Public License (AGPL) v. 3
- Open Software License (OSL) v. 2.1, v. 3.0
- Eclipse Public License (EPL) v. 1.0
- CeCILL v. 2.0, v. 2.1
- Mozilla Public Licence (MPL) v. 2
- GNU Lesser General Public Licence (LGPL) v. 2.1, v. 3
- Creative Commons Attribution-ShareAlike v. 3.0 Unported (CC BY-SA 3.0) for
works other than software
- European Union Public Licence (EUPL) v. 1.1, v. 1.2
- Québec Free and Open-Source Licence — Reciprocity (LiLiQ-R) or Strong
Reciprocity (LiLiQ-R+).
The European Commission may update this Appendix to later versions of the above
licences without producing a new version of the EUPL, as long as they provide
the rights granted in Article 2 of this Licence and protect the covered Source
Code from exclusive appropriation.
All other changes or additions to this Appendix require the production of a new
EUPL version.

166
pkg/config/README.md Normal file
View file

@ -0,0 +1,166 @@
# Config Module
[![Go CI](https://github.com/Snider/config/actions/workflows/ci.yml/badge.svg)](https://github.com/Snider/config/actions/workflows/ci.yml)
[![codecov](https://codecov.io/gh/Snider/config/branch/main/graph/badge.svg)](https://codecov.io/gh/Snider/config)
This repository is a config module for the Core Framework. It includes a Go backend, an Angular custom element, and a full release cycle configuration.
## Getting Started
1. **Clone the repository:**
```bash
git clone https://github.com/Snider/config.git
```
2. **Install the dependencies:**
```bash
cd config
go mod tidy
cd ui
npm install
```
3. **Run the development server:**
```bash
go run ./cmd/demo-cli serve
```
This will start the Go backend and serve the Angular custom element.
## Building the Custom Element
To build the Angular custom element, run the following command:
```bash
cd ui
npm run build
```
This will create a single JavaScript file in the `dist` directory that you can use in any HTML page.
## Usage
The `config` service provides a generic way to load and save configuration files in various formats. This is useful for other packages that need to persist their own configuration without being tied to a specific format.
### Supported Formats
* JSON (`.json`)
* YAML (`.yml`, `.yaml`)
* INI (`.ini`)
* XML (`.xml`)
### Saving Configuration
To save a set of key-value pairs, you can use the `SaveKeyValues` method. The format is determined by the file extension of the `key` you provide.
```go
package main
import (
"log"
"github.com/Snider/config/pkg/config"
)
func main() {
// Get a new config service instance
configSvc, err := config.New()
if err != nil {
log.Fatalf("Failed to create config service: %v", err)
}
// Example data to save
data := map[string]interface{}{
"setting1": "value1",
"enabled": true,
"retries": 3,
}
// Save as a JSON file
if err := configSvc.SaveKeyValues("my-app-settings.json", data); err != nil {
log.Fatalf("Failed to save JSON config: %v", err)
}
// Save as a YAML file
if err := configSvc.SaveKeyValues("my-app-settings.yaml", data); err != nil {
log.Fatalf("Failed to save YAML config: %v", err)
}
// For INI, keys are typically in `section.key` format
iniData := map[string]interface{}{
"general.setting1": "value1",
"general.enabled": true,
"network.retries": 3,
}
if err := configSvc.SaveKeyValues("my-app-settings.ini", iniData); err != nil {
log.Fatalf("Failed to save INI config: %v", err)
}
// Save as an XML file
if err := configSvc.SaveKeyValues("my-app-settings.xml", data); err != nil {
log.Fatalf("Failed to save XML config: %v", err)
}
}
```
### Loading Configuration
To load a configuration file, use the `LoadKeyValues` method. It will automatically parse the file based on its extension and return a `map[string]interface{}`.
```go
package main
import (
"fmt"
"log"
"github.com/Snider/config/pkg/config"
)
func main() {
// Get a new config service instance
configSvc, err := config.New()
if err != nil {
log.Fatalf("Failed to create config service: %v", err)
}
// Load a JSON file
jsonData, err := configSvc.LoadKeyValues("my-app-settings.json")
if err != nil {
log.Fatalf("Failed to load JSON config: %v", err)
}
fmt.Printf("Loaded from JSON: %v\n", jsonData)
// Note: Numbers from JSON are unmarshaled as float64
// Load a YAML file
yamlData, err := configSvc.LoadKeyValues("my-app-settings.yaml")
if err != nil {
log.Fatalf("Failed to load YAML config: %v", err)
}
fmt.Printf("Loaded from YAML: %v\n", yamlData)
// Note: Numbers from YAML without decimals are unmarshaled as int
// Load an INI file
iniData, err := configSvc.LoadKeyValues("my-app-settings.ini")
if err != nil {
log.Fatalf("Failed to load INI config: %v", err)
}
fmt.Printf("Loaded from INI: %v\n", iniData)
// Note: All values from INI are loaded as strings
// Load an XML file
xmlData, err := configSvc.LoadKeyValues("my-app-settings.xml")
if err != nil {
log.Fatalf("Failed to load XML config: %v", err)
}
fmt.Printf("Loaded from XML: %v\n", xmlData)
// Note: All values from XML are loaded as strings
}
```
## Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
## License
This project is licensed under the EUPL-1.2 License - see the [LICENSE](LICENSE) file for details.

328
pkg/config/config.go Normal file
View file

@ -0,0 +1,328 @@
// Package config provides a configuration management service that handles
// loading, saving, and accessing application settings. It supports both a
// main JSON configuration file and auxiliary data stored in various formats
// like YAML, INI, and XML. The service is designed to be extensible and
// can be used with static or dynamic dependency injection.
//
// The Service struct is the core of the package, providing methods to
// interact with the configuration. It manages file paths, default values,
// and the serialization/deserialization of data.
//
// Basic usage involves creating a new Service instance and then using its
// methods to get, set, and manage configuration data.
//
// Example:
//
// // Create a new config service.
// cfg, err := config.New()
// if err != nil {
// log.Fatalf("failed to create config service: %v", err)
// }
//
// // Set a new value.
// err = cfg.Set("language", "fr")
// if err != nil {
// log.Fatalf("failed to set config value: %v", err)
// }
//
// // Retrieve a value.
// var lang string
// err = cfg.Get("language", &lang)
// if err != nil {
// log.Fatalf("failed to get config value: %v", err)
// }
// fmt.Printf("Language: %s\n", lang)
package config
import (
"encoding/json"
"errors"
"fmt"
"os"
"path/filepath"
"reflect"
"strings"
"github.com/Snider/Core/pkg/core"
"github.com/adrg/xdg"
)
const appName = "lethean"
const configFileName = "config.json"
// Options holds configuration for the config service. This struct is provided
// for future extensibility and currently has no fields.
type Options struct{}
// Service provides access to the application's configuration.
// It handles loading, saving, and providing access to configuration values,
// abstracting away the details of file I/O and data serialization.
// The Service is designed to be a central point for all configuration-related
// operations within the application.
//
// The fields of the Service struct are automatically saved to and loaded from
// a JSON configuration file. The `json:"-"` tag on ServiceRuntime prevents
// it from being serialized.
type Service struct {
*core.ServiceRuntime[Options] `json:"-"`
// Persistent fields, saved to config.json.
ConfigPath string `json:"configPath,omitempty"`
UserHomeDir string `json:"userHomeDir,omitempty"`
RootDir string `json:"rootDir,omitempty"`
CacheDir string `json:"cacheDir,omitempty"`
ConfigDir string `json:"configDir,omitempty"`
DataDir string `json:"dataDir,omitempty"`
WorkspaceDir string `json:"workspaceDir,omitempty"`
DefaultRoute string `json:"default_route"`
Features []string `json:"features"`
Language string `json:"language"`
}
// createServiceInstance handles the setup of the configuration service. It
// resolves necessary paths, creates directories, and loads the configuration
// file if it exists. If the configuration file is not found, it creates a new
// one with default values. This function is not exported and is used internally
// by the New and Register constructors.
func createServiceInstance() (*Service, error) {
// --- Path and Directory Setup ---
homeDir, err := os.UserHomeDir()
if err != nil {
return nil, fmt.Errorf("could not resolve user home directory: %w", err)
}
userHomeDir := filepath.Join(homeDir, appName)
rootDir, err := xdg.DataFile(appName)
if err != nil {
return nil, fmt.Errorf("could not resolve data directory: %w", err)
}
cacheDir, err := xdg.CacheFile(appName)
if err != nil {
return nil, fmt.Errorf("could not resolve cache directory: %w", err)
}
s := &Service{
UserHomeDir: userHomeDir,
RootDir: rootDir,
CacheDir: cacheDir,
ConfigDir: filepath.Join(userHomeDir, "config"),
DataDir: filepath.Join(userHomeDir, "data"),
WorkspaceDir: filepath.Join(userHomeDir, "workspace"),
DefaultRoute: "/",
Features: []string{},
Language: "en",
}
s.ConfigPath = filepath.Join(s.ConfigDir, configFileName)
dirs := []string{s.RootDir, s.ConfigDir, s.DataDir, s.CacheDir, s.WorkspaceDir, s.UserHomeDir}
for _, dir := range dirs {
if err := os.MkdirAll(dir, os.ModePerm); err != nil {
return nil, fmt.Errorf("could not create directory %s: %w", dir, err)
}
}
// --- Load or Create Configuration ---
if data, err := os.ReadFile(s.ConfigPath); err == nil {
// Config file exists, load it.
if err := json.Unmarshal(data, s); err != nil {
return nil, fmt.Errorf("failed to unmarshal config: %w", err)
}
} else if os.IsNotExist(err) {
// Config file does not exist, create it with default values.
if err := s.Save(); err != nil {
return nil, fmt.Errorf("failed to create default config file: %w", err)
}
} else {
// Another error occurred reading the file.
return nil, fmt.Errorf("failed to read config file: %w", err)
}
return s, nil
}
// New creates a new instance of the configuration service. This constructor is
// intended for static dependency injection, where the service is created and
// managed manually. It initializes the service with default paths and values,
// and loads any existing configuration from disk.
//
// Example:
//
// cfg, err := config.New()
// if err != nil {
// log.Fatalf("Failed to initialize config: %v", err)
// }
// // Use cfg to access configuration settings.
func New() (*Service, error) {
return createServiceInstance()
}
// Register creates a new instance of the configuration service and registers it
// with the application's core. This constructor is intended for dynamic
// dependency injection, where services are managed by a central core component.
// It performs the same initialization as New, but also integrates the service
// with the provided core instance.
func Register(c *core.Core) (any, error) {
s, err := createServiceInstance()
if err != nil {
return nil, err
}
// Defensive check: createServiceInstance should not return nil service with nil error
if s == nil {
return nil, errors.New("config: createServiceInstance returned a nil service instance with no error")
}
s.ServiceRuntime = core.NewServiceRuntime(c, Options{})
return s, nil
}
// Save writes the current configuration to a JSON file. The location of the file
// is determined by the ConfigPath field of the Service struct. This method is
// typically called automatically by Set, but can be used to explicitly save
// changes.
//
// Example:
//
// err := cfg.Save()
// if err != nil {
// log.Printf("Error saving configuration: %v", err)
// }
func (s *Service) Save() error {
data, err := json.MarshalIndent(s, "", " ")
if err != nil {
return fmt.Errorf("failed to marshal config: %w", err)
}
if err := os.WriteFile(s.ConfigPath, data, 0644); err != nil {
return fmt.Errorf("failed to write config file: %w", err)
}
return nil
}
// Get retrieves a configuration value by its key. The key corresponds to the
// JSON tag of a field in the Service struct. The retrieved value is stored in
// the `out` parameter, which must be a non-nil pointer to a variable of the
// correct type.
//
// Example:
//
// var currentLanguage string
// err := cfg.Get("language", &currentLanguage)
// if err != nil {
// log.Printf("Could not retrieve language setting: %v", err)
// }
// fmt.Println("Current language is:", currentLanguage)
func (s *Service) Get(key string, out any) error {
val := reflect.ValueOf(s).Elem()
typ := val.Type()
for i := 0; i < val.NumField(); i++ {
field := typ.Field(i)
jsonTag := field.Tag.Get("json")
if jsonTag != "" && jsonTag != "-" {
jsonName := strings.Split(jsonTag, ",")[0]
if strings.EqualFold(jsonName, key) {
outVal := reflect.ValueOf(out)
if outVal.Kind() != reflect.Ptr || outVal.IsNil() {
return errors.New("output argument must be a non-nil pointer")
}
targetVal := outVal.Elem()
srcVal := val.Field(i)
if !srcVal.Type().AssignableTo(targetVal.Type()) {
return fmt.Errorf("cannot assign config value of type %s to output of type %s", srcVal.Type(), targetVal.Type())
}
targetVal.Set(srcVal)
return nil
}
}
}
return fmt.Errorf("key '%s' not found in config", key)
}
// SaveStruct saves an arbitrary struct to a JSON file in the config directory.
// This is useful for storing complex data that is not part of the main
// configuration. The `key` parameter is used as the filename (with a .json
// extension).
//
// Example:
//
// type UserPreferences struct {
// Theme string `json:"theme"`
// Notifications bool `json:"notifications"`
// }
// prefs := UserPreferences{Theme: "dark", Notifications: true}
// err := cfg.SaveStruct("user_prefs", prefs)
// if err != nil {
// log.Printf("Error saving user preferences: %v", err)
// }
func (s *Service) SaveStruct(key string, data interface{}) error {
filePath := filepath.Join(s.ConfigDir, key+".json")
jsonData, err := json.MarshalIndent(data, "", " ")
if err != nil {
return fmt.Errorf("failed to marshal struct for key '%s': %w", key, err)
}
return os.WriteFile(filePath, jsonData, 0644)
}
// LoadStruct loads an arbitrary struct from a JSON file in the config directory.
// The `key` parameter specifies the filename (without the .json extension). The
// loaded data is unmarshaled into the `data` parameter, which must be a
// non-nil pointer to a struct.
//
// Example:
//
// var prefs UserPreferences
// err := cfg.LoadStruct("user_prefs", &prefs)
// if err != nil {
// log.Printf("Error loading user preferences: %v", err)
// }
// fmt.Printf("User theme is: %s", prefs.Theme)
func (s *Service) LoadStruct(key string, data interface{}) error {
filePath := filepath.Join(s.ConfigDir, key+".json")
jsonData, err := os.ReadFile(filePath)
if err != nil {
if os.IsNotExist(err) {
return nil // Return nil if the file doesn't exist
}
return fmt.Errorf("failed to read struct file for key '%s': %w", key, err)
}
return json.Unmarshal(jsonData, data)
}
// Set updates a configuration value and saves the change to the configuration
// file. The key corresponds to the JSON tag of a field in the Service struct.
// The provided value `v` must be of a type that is assignable to the field.
//
// Example:
//
// err := cfg.Set("default_route", "/home")
// if err != nil {
// log.Printf("Failed to set default route: %v", err)
// }
func (s *Service) Set(key string, v any) error {
val := reflect.ValueOf(s).Elem()
typ := val.Type()
for i := 0; i < val.NumField(); i++ {
field := typ.Field(i)
jsonTag := field.Tag.Get("json")
if jsonTag != "" && jsonTag != "-" {
jsonName := strings.Split(jsonTag, ",")[0]
if strings.EqualFold(jsonName, key) {
fieldVal := val.Field(i)
if !fieldVal.CanSet() {
return fmt.Errorf("cannot set config field for key '%s'", key)
}
newVal := reflect.ValueOf(v)
if !newVal.Type().AssignableTo(fieldVal.Type()) {
return fmt.Errorf("type mismatch for key '%s': expected %s, got %s", key, fieldVal.Type(), newVal.Type())
}
fieldVal.Set(newVal)
return s.Save()
}
}
}
return fmt.Errorf("key '%s' not found in config", key)
}

349
pkg/config/config_test.go Normal file
View file

@ -0,0 +1,349 @@
package config
import (
"os"
"path/filepath"
"testing"
"github.com/Snider/Core/pkg/core"
)
// setupTestEnv creates a temporary home directory for testing and ensures a clean environment.
func setupTestEnv(t *testing.T) (string, func()) {
tempHomeDir, err := os.MkdirTemp("", "test_home_*")
if err != nil {
t.Fatalf("Failed to create temp home directory: %v", err)
}
oldHome := os.Getenv("HOME")
os.Setenv("HOME", tempHomeDir)
// Unset XDG vars to ensure HOME is used for path resolution, creating a hermetic test.
oldXdgData := os.Getenv("XDG_DATA_HOME")
oldXdgCache := os.Getenv("XDG_CACHE_HOME")
os.Unsetenv("XDG_DATA_HOME")
os.Unsetenv("XDG_CACHE_HOME")
cleanup := func() {
os.Setenv("HOME", oldHome)
os.Setenv("XDG_DATA_HOME", oldXdgData)
os.Setenv("XDG_CACHE_HOME", oldXdgCache)
os.RemoveAll(tempHomeDir)
}
return tempHomeDir, cleanup
}
// newTestCore creates a new, empty core instance for testing.
func newTestCore(t *testing.T) *core.Core {
c, err := core.New()
if err != nil {
t.Fatalf("core.New() failed: %v", err)
}
if c == nil {
t.Fatalf("core.New() returned a nil instance")
}
return c
}
func TestConfigServiceGood(t *testing.T) {
t.Run("New service creates default config", func(t *testing.T) {
_, cleanup := setupTestEnv(t)
defer cleanup()
serviceInstance, err := New()
if err != nil {
t.Fatalf("New() failed: %v", err)
}
// Check that the config file was created
if _, err := os.Stat(serviceInstance.ConfigPath); os.IsNotExist(err) {
t.Errorf("config.json was not created at %s", serviceInstance.ConfigPath)
}
// Check default values
if serviceInstance.Language != "en" {
t.Errorf("Expected default language 'en', got '%s'", serviceInstance.Language)
}
})
t.Run("New service loads existing config", func(t *testing.T) {
tempHomeDir, cleanup := setupTestEnv(t)
defer cleanup()
// Manually create a config file with non-default values
configDir := filepath.Join(tempHomeDir, appName, "config")
if err := os.MkdirAll(configDir, os.ModePerm); err != nil {
t.Fatalf("Failed to create test config dir: %v", err)
}
configPath := filepath.Join(configDir, configFileName)
customConfig := `{"language": "fr", "features": ["beta-testing"]}`
if err := os.WriteFile(configPath, []byte(customConfig), 0644); err != nil {
t.Fatalf("Failed to write custom config file: %v", err)
}
serviceInstance, err := New()
if err != nil {
t.Fatalf("New() failed while loading existing config: %v", err)
}
if serviceInstance.Language != "fr" {
t.Errorf("Expected language 'fr', got '%s'", serviceInstance.Language)
}
// A check for IsFeatureEnabled would require a proper core instance and service registration.
// This is a simplified check for now.
found := false
for _, f := range serviceInstance.Features {
if f == "beta-testing" {
found = true
break
}
}
if !found {
t.Errorf("Expected 'beta-testing' feature to be enabled")
}
})
t.Run("Set and Get", func(t *testing.T) {
_, cleanup := setupTestEnv(t)
defer cleanup()
s, err := New()
if err != nil {
t.Fatalf("New() failed: %v", err)
}
key := "language"
expectedValue := "de"
if err := s.Set(key, expectedValue); err != nil {
t.Fatalf("Set() failed: %v", err)
}
var actualValue string
if err := s.Get(key, &actualValue); err != nil {
t.Fatalf("Get() failed: %v", err)
}
if actualValue != expectedValue {
t.Errorf("Expected value '%s', got '%s'", expectedValue, actualValue)
}
})
t.Run("Save and Load Struct", func(t *testing.T) {
_, cleanup := setupTestEnv(t)
defer cleanup()
s, err := New()
if err != nil {
t.Fatalf("New() failed: %v", err)
}
type CustomConfig struct {
APIKey string `json:"apiKey"`
Timeout int `json:"timeout"`
}
key := "custom"
expectedConfig := CustomConfig{
APIKey: "12345",
Timeout: 30,
}
if err := s.SaveStruct(key, expectedConfig); err != nil {
t.Fatalf("SaveStruct() failed: %v", err)
}
var actualConfig CustomConfig
if err := s.LoadStruct(key, &actualConfig); err != nil {
t.Fatalf("LoadStruct() failed: %v", err)
}
if actualConfig.APIKey != expectedConfig.APIKey {
t.Errorf("Expected APIKey '%s', got '%s'", expectedConfig.APIKey, actualConfig.APIKey)
}
if actualConfig.Timeout != expectedConfig.Timeout {
t.Errorf("Expected Timeout '%d', got '%d'", expectedConfig.Timeout, actualConfig.Timeout)
}
})
}
func TestConfigServiceUgly(t *testing.T) {
t.Run("LoadStruct with nil value", func(t *testing.T) {
_, cleanup := setupTestEnv(t)
defer cleanup()
s, err := New()
if err != nil {
t.Fatalf("New() failed: %v", err)
}
key := "nil-value"
filePath := filepath.Join(s.ConfigDir, key+".json")
if err := os.WriteFile(filePath, []byte("null"), 0644); err != nil {
t.Fatalf("Failed to write nil value file: %v", err)
}
type CustomConfig struct {
APIKey string `json:"apiKey"`
Timeout int `json:"timeout"`
}
var actualConfig CustomConfig
err = s.LoadStruct(key, &actualConfig)
if err != nil {
t.Fatalf("LoadStruct() should not have failed with a nil value, but it did: %v", err)
}
})
t.Run("Concurrent access", func(t *testing.T) {
_, cleanup := setupTestEnv(t)
defer cleanup()
s, err := New()
if err != nil {
t.Fatalf("New() failed: %v", err)
}
// Run concurrent Set and Get operations
done := make(chan bool)
for i := 0; i < 10; i++ {
go func() {
s.Set("language", "en")
done <- true
}()
go func() {
var lang string
s.Get("language", &lang)
done <- true
}()
}
for i := 0; i < 20; i++ {
<-done
}
})
}
func TestConfigServiceBad(t *testing.T) {
t.Run("Load non-existent struct", func(t *testing.T) {
_, cleanup := setupTestEnv(t)
defer cleanup()
s, err := New()
if err != nil {
t.Fatalf("New() failed: %v", err)
}
type CustomConfig struct {
APIKey string `json:"apiKey"`
Timeout int `json:"timeout"`
}
var actualConfig CustomConfig
if err := s.LoadStruct("non-existent", &actualConfig); err != nil {
t.Fatalf("LoadStruct() failed: %v", err)
}
// Expect the struct to be zero-valued
if actualConfig.APIKey != "" {
t.Errorf("Expected empty APIKey, got '%s'", actualConfig.APIKey)
}
if actualConfig.Timeout != 0 {
t.Errorf("Expected zero Timeout, got '%d'", actualConfig.Timeout)
}
})
t.Run("Get non-existent key", func(t *testing.T) {
_, cleanup := setupTestEnv(t)
defer cleanup()
s, err := New()
if err != nil {
t.Fatalf("New() failed: %v", err)
}
var value string
err = s.Get("non-existent", &value)
if err == nil {
t.Errorf("Expected an error for non-existent key, but got nil")
}
})
t.Run("Set non-existent key", func(t *testing.T) {
_, cleanup := setupTestEnv(t)
defer cleanup()
s, err := New()
if err != nil {
t.Fatalf("New() failed: %v", err)
}
err = s.Set("non-existent", "value")
if err == nil {
t.Errorf("Expected an error for non-existent key, but got nil")
}
})
t.Run("SaveStruct with unmarshallable type", func(t *testing.T) {
_, cleanup := setupTestEnv(t)
defer cleanup()
s, err := New()
if err != nil {
t.Fatalf("New() failed: %v", err)
}
err = s.SaveStruct("test", make(chan int))
if err == nil {
t.Errorf("Expected an error for unmarshallable type, but got nil")
}
})
t.Run("LoadStruct with invalid JSON", func(t *testing.T) {
_, cleanup := setupTestEnv(t)
defer cleanup()
s, err := New()
if err != nil {
t.Fatalf("New() failed: %v", err)
}
key := "invalid"
filePath := filepath.Join(s.ConfigDir, key+".json")
if err := os.WriteFile(filePath, []byte("invalid json"), 0644); err != nil {
t.Fatalf("Failed to write invalid json file: %v", err)
}
type CustomConfig struct {
APIKey string `json:"apiKey"`
Timeout int `json:"timeout"`
}
var actualConfig CustomConfig
err = s.LoadStruct(key, &actualConfig)
if err == nil {
t.Errorf("Expected an error for invalid JSON, but got nil")
}
})
t.Run("New service with empty config file", func(t *testing.T) {
tempHomeDir, cleanup := setupTestEnv(t)
defer cleanup()
// Manually create an empty config file
configDir := filepath.Join(tempHomeDir, appName, "config")
if err := os.MkdirAll(configDir, os.ModePerm); err != nil {
t.Fatalf("Failed to create test config dir: %v", err)
}
configPath := filepath.Join(configDir, configFileName)
if err := os.WriteFile(configPath, []byte(""), 0644); err != nil {
t.Fatalf("Failed to write empty config file: %v", err)
}
_, err := New()
if err == nil {
t.Fatalf("New() should have failed with an empty config file, but it did not")
}
})
}

View file

@ -0,0 +1,46 @@
# Architecture Overview
This project implements a modular configuration system with a Go backend and an Angular frontend, orchestrated by a CLI runner.
## Components
The architecture consists of three main components:
1. **Backend Library (`pkg/config`)**
- **Responsibility**: Manages configuration loading, saving, and persistence.
- **Tech Stack**: Go.
- **Features**:
- Struct-based configuration with JSON persistence.
- Generic key-value storage supporting JSON, YAML, INI, and XML.
- XDG-compliant directory management.
- **Integration**: Can be used via static (`New`) or dynamic (`Register`) dependency injection.
2. **Frontend (`ui`)**
- **Responsibility**: Provides a user interface for the application.
- **Tech Stack**: Angular.
- **Deployment**: Built as a static asset (or Custom Element) served by the backend.
3. **CLI Runner (`cmd/demo-cli`)**
- **Responsibility**: Entry point for the application.
- **Tech Stack**: Go (`spf13/cobra`).
- **Functions**:
- `serve`: Starts a web server that hosts the compiled Angular frontend and API endpoints.
## Data Flow
1. **Initialization**: The CLI starts and initializes the `pkg/config` service to load settings from disk.
2. **Serving**: The CLI's `serve` command spins up an HTTP server.
3. **Interaction**: Users interact with the Angular UI in the browser.
4. **API Communication**: The UI communicates with the backend via REST API endpoints (e.g., `/api/v1/demo`).
5. **Persistence**: Configuration changes made via the backend service are persisted to the filesystem.
## Directory Structure
```
├── cmd/
│ └── demo-cli/ # CLI Application entry point
├── pkg/
│ └── config/ # Core configuration logic (Go)
├── ui/ # Frontend application (Angular)
└── docs/ # Project documentation
```

146
pkg/config/docs/backend.md Normal file
View file

@ -0,0 +1,146 @@
# Backend Documentation (`pkg/config`)
The `pkg/config` package provides a robust configuration management service for Go applications. It handles loading, saving, and accessing application settings, supporting both a main JSON configuration file and auxiliary data stored in various formats like YAML, INI, and XML.
## Core Concepts
### Service
The `Service` struct is the central point for all configuration-related operations. It manages:
- File paths and directories.
- Default values.
- Serialization and deserialization of data.
- Integration with the core application framework (if used).
The `Service` struct fields are automatically saved to and loaded from a JSON configuration file (`config.json`).
### Initialization
There are two ways to initialize the configuration service:
#### 1. Static Injection (`New`)
Use `config.New()` when you want to manage the service instance manually.
```go
cfg, err := config.New()
if err != nil {
log.Fatalf("Failed to initialize config: %v", err)
}
// Use cfg...
```
#### 2. Dynamic Injection (`Register`)
Use `config.Register(coreInstance)` when integrating with the `core` package for dynamic dependency injection.
```go
// Assuming 'c' is your *core.Core instance
svc, err := config.Register(c)
if err != nil {
// handle error
}
```
## Basic Configuration (Get/Set)
The service provides type-safe methods to get and set configuration values that correspond to the fields defined in the `Service` struct.
### Set a Value
```go
err := cfg.Set("language", "fr")
if err != nil {
log.Fatalf("failed to set config value: %v", err)
}
```
*Note: `Set` automatically persists the changes to the `config.json` file.*
### Get a Value
```go
var lang string
err := cfg.Get("language", &lang)
if err != nil {
log.Fatalf("failed to get config value: %v", err)
}
fmt.Printf("Language: %s\n", lang)
```
## Arbitrary Struct Persistence
You can save and load arbitrary Go structs to JSON files within the configuration directory using `SaveStruct` and `LoadStruct`. This is useful for complex data that doesn't fit into the main configuration schema.
### Saving a Struct
```go
type UserPreferences struct {
Theme string `json:"theme"`
Notifications bool `json:"notifications"`
}
prefs := UserPreferences{Theme: "dark", Notifications: true}
// Saves to <ConfigDir>/user_prefs.json
err := cfg.SaveStruct("user_prefs", prefs)
if err != nil {
log.Printf("Error saving user preferences: %v", err)
}
```
### Loading a Struct
```go
var prefs UserPreferences
err := cfg.LoadStruct("user_prefs", &prefs)
if err != nil {
log.Printf("Error loading user preferences: %v", err)
}
```
## Generic Key-Value Persistence
For more flexible data storage, the service supports generic key-value pairs in multiple file formats. The format is determined by the file extension.
### Supported Formats
- **JSON** (`.json`)
- **YAML** (`.yaml`, `.yml`)
- **INI** (`.ini`)
- **XML** (`.xml`)
### Saving Key-Values
```go
data := map[string]interface{}{
"host": "localhost",
"port": 8080,
}
// Save as YAML
if err := cfg.SaveKeyValues("database.yml", data); err != nil {
log.Printf("Error saving database config: %v", err)
}
```
### Loading Key-Values
```go
dbConfig, err := cfg.LoadKeyValues("database.yml")
if err != nil {
log.Printf("Error loading database config: %v", err)
}
port := dbConfig["port"]
```
## Configuration Directory
The service automatically resolves appropriate directories for storing configuration and data, respecting XDG standards on Linux/Unix-like systems and standard paths on other OSs.
- **Config Path**: `<ConfigDir>/config.json`
- **Config Dir**: `~/.local/share/lethean/config` (example on Linux)
- **Data Dir**: `~/.local/share/lethean/data`
- **Cache Dir**: `~/.cache/lethean`
These paths are accessible via the `Service` struct fields (e.g., `cfg.ConfigDir`).

50
pkg/config/docs/cli.md Normal file
View file

@ -0,0 +1,50 @@
# CLI Documentation (`cmd/demo-cli`)
The `demo-cli` is a command-line interface application built to demonstrate the capabilities of the Config Module and serve the frontend application. It uses the `cobra` library for command management.
## Installation
You can run the CLI directly using `go run`:
```bash
go run ./cmd/demo-cli <command>
```
Or build it into a binary:
```bash
go build -o demo-cli ./cmd/demo-cli
./demo-cli <command>
```
## Commands
### `serve`
The `serve` command starts an HTTP server that serves both the Angular frontend and a demo API endpoint.
**Usage:**
```bash
go run ./cmd/demo-cli serve
```
**Features:**
- **Frontend Serving**: Serves static files from `ui/dist/config/browser`.
- **API Endpoint**: Exposes a demo endpoint at `/api/v1/demo`.
- **Port**: Listens on port `8080`.
**Example Output:**
```
Listening on :8080...
```
Access the application at `http://localhost:8080`.
### Root Command
Running the CLI without any subcommands prints the help message or executes the default action (if configured).
```bash
go run ./cmd/demo-cli
```

View file

@ -0,0 +1,66 @@
# Frontend Documentation (`ui`)
The frontend of this project is an Angular application located in the `ui` directory. It is designed to be built as a custom element (Web Component) or a standard Angular application.
## Prerequisites
Ensure you have the following installed:
- Node.js (Latest LTS recommended)
- npm (comes with Node.js)
## Setup
Navigate to the `ui` directory and install the dependencies:
```bash
cd ui
npm install
```
## Development
To start the local development server:
```bash
ng serve
```
This will run the application at `http://localhost:4200/`. The application automatically reloads if you change any of the source files.
## Building
The project can be built for production using the standard Angular CLI build command:
```bash
ng build
```
The build artifacts will be stored in the `dist/` directory.
### Custom Element Build
*Note: If the application is configured as a Custom Element (Web Component), the build output in `dist/` typically includes a main JavaScript file that can be included in other HTML pages.*
## Testing
### Unit Tests
Unit tests are written using Jasmine and run with Karma. To execute them:
```bash
ng test
```
### End-to-End Tests
End-to-end tests can be run via:
```bash
ng e2e
```
## Project Structure
- `src/`: Source code of the Angular application.
- `angular.json`: Angular CLI configuration.
- `package.json`: Project dependencies and scripts.

239
pkg/config/formats.go Normal file
View file

@ -0,0 +1,239 @@
package config
import (
"encoding/json"
"encoding/xml"
"fmt"
"os"
"path/filepath"
"strings"
"gopkg.in/ini.v1"
"gopkg.in/yaml.v2"
)
// ConfigFormat defines an interface for loading and saving configuration data in
// various formats. Each format implementation is responsible for serializing and
// deserializing data between a file and a map of key-value pairs.
type ConfigFormat interface {
// Load reads data from the specified path and returns it as a map.
Load(path string) (map[string]interface{}, error)
// Save writes the provided data map to the specified path.
Save(path string, data map[string]interface{}) error
}
// JSONFormat implements the ConfigFormat interface for JSON files. It provides
// methods to read from and write to files in JSON format.
type JSONFormat struct{}
// Load reads a JSON file from the given path and decodes it into a map.
// The keys of the map are strings, and the values are of type interface{}.
func (f *JSONFormat) Load(path string) (map[string]interface{}, error) {
data, err := os.ReadFile(path)
if err != nil {
return nil, err
}
var result map[string]interface{}
if err := json.Unmarshal(data, &result); err != nil {
return nil, err
}
return result, nil
}
// Save encodes the provided map into JSON format and writes it to the given
// path. The output is indented for readability.
func (f *JSONFormat) Save(path string, data map[string]interface{}) error {
jsonData, err := json.MarshalIndent(data, "", " ")
if err != nil {
return err
}
return os.WriteFile(path, jsonData, 0644)
}
// YAMLFormat implements the ConfigFormat interface for YAML files. It provides
// methods to read from and write to files in YAML format.
type YAMLFormat struct{}
// Load reads a YAML file from the given path and decodes it into a map.
func (f *YAMLFormat) Load(path string) (map[string]interface{}, error) {
data, err := os.ReadFile(path)
if err != nil {
return nil, err
}
var result map[string]interface{}
if err := yaml.Unmarshal(data, &result); err != nil {
return nil, err
}
return result, nil
}
// Save encodes the provided map into YAML format and writes it to the given
// path.
func (f *YAMLFormat) Save(path string, data map[string]interface{}) error {
yamlData, err := yaml.Marshal(data)
if err != nil {
return err
}
return os.WriteFile(path, yamlData, 0644)
}
// INIFormat implements the ConfigFormat interface for INI files. It handles
// the structured format of INI files, including sections and keys.
type INIFormat struct{}
// Load reads an INI file and converts its sections and keys into a single map.
// Keys in the map are formed by concatenating the section name and key name with
// a dot (e.g., "section.key").
func (f *INIFormat) Load(path string) (map[string]interface{}, error) {
cfg, err := ini.Load(path)
if err != nil {
return nil, err
}
result := make(map[string]interface{})
for _, section := range cfg.Sections() {
for _, key := range section.Keys() {
result[section.Name()+"."+key.Name()] = key.Value()
}
}
return result, nil
}
// Save writes a map of key-value pairs to an INI file. Keys in the map are
// split by a dot to determine the section and key for the INI file.
func (f *INIFormat) Save(path string, data map[string]interface{}) error {
cfg := ini.Empty()
for key, value := range data {
parts := strings.SplitN(key, ".", 2)
section := ini.DefaultSection
keyName := parts[0]
if len(parts) > 1 {
section = parts[0]
keyName = parts[1]
}
if _, err := cfg.Section(section).NewKey(keyName, fmt.Sprintf("%v", value)); err != nil {
return err
}
}
return cfg.SaveTo(path)
}
// XMLFormat implements the ConfigFormat interface for XML files. It uses a
// simple structure with a root "config" element containing a series of "entry"
// elements, each with a "key" and "value".
type XMLFormat struct{}
// xmlEntry is a helper struct for marshaling and unmarshaling XML data.
type xmlEntry struct {
Key string `xml:"key"`
Value string `xml:"value"`
}
// Load reads an XML file and parses it into a map. It expects the XML to have
// a specific structure as defined by the xmlEntry struct.
func (f *XMLFormat) Load(path string) (map[string]interface{}, error) {
data, err := os.ReadFile(path)
if err != nil {
return nil, err
}
var v struct {
XMLName xml.Name `xml:"config"`
Entries []xmlEntry `xml:"entry"`
}
if err := xml.Unmarshal(data, &v); err != nil {
return nil, err
}
result := make(map[string]interface{})
for _, entry := range v.Entries {
result[entry.Key] = entry.Value
}
return result, nil
}
// Save writes a map of key-value pairs to an XML file. The data is structured
// with a root "config" element and child "entry" elements.
func (f *XMLFormat) Save(path string, data map[string]interface{}) error {
var entries []xmlEntry
for key, value := range data {
entries = append(entries, xmlEntry{
Key: key,
Value: fmt.Sprintf("%v", value),
})
}
xmlData, err := xml.MarshalIndent(struct {
XMLName xml.Name `xml:"config"`
Entries []xmlEntry `xml:"entry"`
}{Entries: entries}, "", " ")
if err != nil {
return err
}
return os.WriteFile(path, xmlData, 0644)
}
// GetConfigFormat returns a ConfigFormat implementation based on the file
// extension of the provided path. This allows the config service to dynamically
// handle different file formats.
//
// Example:
//
// format, err := GetConfigFormat("settings.json")
// if err != nil {
// log.Fatal(err)
// }
// // format is now a JSONFormat
func GetConfigFormat(path string) (ConfigFormat, error) {
ext := strings.ToLower(filepath.Ext(path))
switch ext {
case ".json":
return &JSONFormat{}, nil
case ".yaml", ".yml":
return &YAMLFormat{}, nil
case ".ini":
return &INIFormat{}, nil
case ".xml":
return &XMLFormat{}, nil
default:
return nil, fmt.Errorf("unsupported config format: %s", ext)
}
}
// SaveKeyValues saves a map of key-value pairs to a file in the config
// directory. The file format is determined by the extension of the `key`
// parameter. This method is a convenient way to store structured data in a
// format of choice.
//
// Example:
//
// data := map[string]interface{}{"host": "localhost", "port": 8080}
// err := cfg.SaveKeyValues("database.yml", data)
// if err != nil {
// log.Printf("Error saving database config: %v", err)
// }
func (s *Service) SaveKeyValues(key string, data map[string]interface{}) error {
format, err := GetConfigFormat(key)
if err != nil {
return err
}
filePath := filepath.Join(s.ConfigDir, key)
return format.Save(filePath, data)
}
// LoadKeyValues loads a map of key-value pairs from a file in the config
// directory. The file format is determined by the extension of the `key`
// parameter. This allows for easy retrieval of data stored in various formats.
//
// Example:
//
// dbConfig, err := cfg.LoadKeyValues("database.yml")
// if err != nil {
// log.Printf("Error loading database config: %v", err)
// }
// port, ok := dbConfig["port"].(int)
// // ...
func (s *Service) LoadKeyValues(key string) (map[string]interface{}, error) {
format, err := GetConfigFormat(key)
if err != nil {
return nil, err
}
filePath := filepath.Join(s.ConfigDir, key)
return format.Load(filePath)
}

107
pkg/config/formats_test.go Normal file
View file

@ -0,0 +1,107 @@
package config
import (
"os"
"reflect"
"testing"
)
func TestConfigFormats(t *testing.T) {
tempDir, err := os.MkdirTemp("", "config-test")
if err != nil {
t.Fatalf("Failed to create temp dir: %v", err)
}
defer os.RemoveAll(tempDir)
service := &Service{
ConfigDir: tempDir,
}
testData := map[string]interface{}{
"key1": "value1",
"key2": 123.0,
"key3": true,
}
testCases := []struct {
format string
filename string
}{
{"json", "test.json"},
{"yaml", "test.yaml"},
{"ini", "test.ini"},
{"xml", "test.xml"},
}
for _, tc := range testCases {
t.Run(tc.format, func(t *testing.T) {
// Test SaveKeyValues
err := service.SaveKeyValues(tc.filename, testData)
if err != nil {
t.Fatalf("SaveKeyValues failed for %s: %v", tc.format, err)
}
// Test LoadKeyValues
loadedData, err := service.LoadKeyValues(tc.filename)
if err != nil {
t.Fatalf("LoadKeyValues failed for %s: %v", tc.format, err)
}
// INI format saves everything as strings, so we need to adjust the expected data
expectedData := testData
if tc.format == "ini" {
expectedData = map[string]interface{}{
"DEFAULT.key1": "value1",
"DEFAULT.key2": "123",
"DEFAULT.key3": "true",
}
}
if tc.format == "yaml" {
// The yaml library unmarshals numbers as int if they don't have a decimal point.
if val, ok := loadedData["key2"].(int); ok {
loadedData["key2"] = float64(val)
}
}
if tc.format == "xml" {
expectedData = map[string]interface{}{
"key1": "value1",
"key2": "123",
"key3": "true",
}
}
if !reflect.DeepEqual(expectedData, loadedData) {
t.Errorf("Loaded data does not match original data for %s.\nExpected: %v\nGot: %v", tc.format, expectedData, loadedData)
}
})
}
}
func TestGetConfigFormat(t *testing.T) {
testCases := []struct {
filename string
expectedType interface{}
expectError bool
}{
{"config.json", &JSONFormat{}, false},
{"config.yaml", &YAMLFormat{}, false},
{"config.yml", &YAMLFormat{}, false},
{"config.ini", &INIFormat{}, false},
{"config.xml", &XMLFormat{}, false},
{"config.txt", nil, true},
}
for _, tc := range testCases {
t.Run(tc.filename, func(t *testing.T) {
format, err := GetConfigFormat(tc.filename)
if (err != nil) != tc.expectError {
t.Fatalf("Expected error: %v, got: %v", tc.expectError, err)
}
if !tc.expectError && reflect.TypeOf(format) != reflect.TypeOf(tc.expectedType) {
t.Errorf("Expected format type %T, got %T", tc.expectedType, format)
}
})
}
}

20
pkg/config/go.mod Normal file
View file

@ -0,0 +1,20 @@
module github.com/Snider/Core/pkg/config
go 1.25
require (
github.com/adrg/xdg v0.5.3
github.com/spf13/cobra v1.10.1
gopkg.in/ini.v1 v1.67.0
gopkg.in/yaml.v2 v2.4.0
)
require (
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/kr/pretty v0.3.1 // indirect
github.com/rogpeppe/go-internal v1.14.1 // indirect
github.com/spf13/pflag v1.0.10 // indirect
github.com/stretchr/testify v1.11.1 // indirect
golang.org/x/sys v0.38.0 // indirect
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
)

28
pkg/config/go.sum Normal file
View file

@ -0,0 +1,28 @@
github.com/adrg/xdg v0.5.3 h1:xRnxJXne7+oWDatRhR1JLnvuccuIeCoBu2rtuLqQB78=
github.com/adrg/xdg v0.5.3/go.mod h1:nlTsY+NNiCBGCK2tpm09vRqfVzrc2fLmXGpBLF0zlTQ=
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/spf13/cobra v1.10.1 h1:lJeBwCfmrnXthfAupyUTzJ/J4Nc1RsHC/mSRU2dll/s=
github.com/spf13/cobra v1.10.1/go.mod h1:7SmJGaTHFVBY0jW4NXGluQoLvhqFQM+6XSKD+P4XaB0=
github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

View file

@ -0,0 +1,17 @@
# Editor configuration, see https://editorconfig.org
root = true
[*]
charset = utf-8
indent_style = space
indent_size = 2
insert_final_newline = true
trim_trailing_whitespace = true
[*.ts]
quote_type = single
ij_typescript_use_double_quotes = false
[*.md]
max_line_length = off
trim_trailing_whitespace = false

43
pkg/config/ui/.gitignore vendored Normal file
View file

@ -0,0 +1,43 @@
# See https://docs.github.com/get-started/getting-started-with-git/ignoring-files for more about ignoring files.
# Compiled output
/dist
/tmp
/out-tsc
/bazel-out
# Node
/node_modules
npm-debug.log
yarn-error.log
# IDEs and editors
.idea/
.project
.classpath
.c9/
*.launch
.settings/
*.sublime-workspace
# Visual Studio Code
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
.history/*
# Miscellaneous
/.angular/cache
.sass-cache/
/connect.lock
/coverage
/libpeerconnection.log
testem.log
/typings
__screenshots__/
# System files
.DS_Store
Thumbs.db

4
pkg/config/ui/.vscode/extensions.json vendored Normal file
View file

@ -0,0 +1,4 @@
{
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=827846
"recommendations": ["angular.ng-template"]
}

20
pkg/config/ui/.vscode/launch.json vendored Normal file
View file

@ -0,0 +1,20 @@
{
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"name": "ng serve",
"type": "chrome",
"request": "launch",
"preLaunchTask": "npm: start",
"url": "http://localhost:4200/"
},
{
"name": "ng test",
"type": "chrome",
"request": "launch",
"preLaunchTask": "npm: test",
"url": "http://localhost:9876/debug.html"
}
]
}

42
pkg/config/ui/.vscode/tasks.json vendored Normal file
View file

@ -0,0 +1,42 @@
{
// For more information, visit: https://go.microsoft.com/fwlink/?LinkId=733558
"version": "2.0.0",
"tasks": [
{
"type": "npm",
"script": "start",
"isBackground": true,
"problemMatcher": {
"owner": "typescript",
"pattern": "$tsc",
"background": {
"activeOnStart": true,
"beginsPattern": {
"regexp": "(.*?)"
},
"endsPattern": {
"regexp": "bundle generation complete"
}
}
}
},
{
"type": "npm",
"script": "test",
"isBackground": true,
"problemMatcher": {
"owner": "typescript",
"pattern": "$tsc",
"background": {
"activeOnStart": true,
"beginsPattern": {
"regexp": "(.*?)"
},
"endsPattern": {
"regexp": "bundle generation complete"
}
}
}
}
]
}

59
pkg/config/ui/README.md Normal file
View file

@ -0,0 +1,59 @@
# Config
This project was generated using [Angular CLI](https://github.com/angular/angular-cli) version 20.3.9.
## Development server
To start a local development server, run:
```bash
ng serve
```
Once the server is running, open your browser and navigate to `http://localhost:4200/`. The application will automatically reload whenever you modify any of the source files.
## Code scaffolding
Angular CLI includes powerful code scaffolding tools. To generate a new component, run:
```bash
ng generate component component-name
```
For a complete list of available schematics (such as `components`, `directives`, or `pipes`), run:
```bash
ng generate --help
```
## Building
To build the project run:
```bash
ng build
```
This will compile your project and store the build artifacts in the `dist/` directory. By default, the production build optimizes your application for performance and speed.
## Running unit tests
To execute unit tests with the [Karma](https://karma-runner.github.io) test runner, use the following command:
```bash
ng test
```
## Running end-to-end tests
For end-to-end (e2e) testing, run:
```bash
ng e2e
```
Angular CLI does not come with an end-to-end testing framework by default. You can choose one that suits your needs.
## Additional Resources
For more information on using the Angular CLI, including detailed command references, visit the [Angular CLI Overview and Command Reference](https://angular.dev/tools/cli) page.

View file

@ -0,0 +1,99 @@
{
"$schema": "./node_modules/@angular/cli/lib/config/schema.json",
"version": 1,
"newProjectRoot": "projects",
"projects": {
"config": {
"projectType": "application",
"schematics": {
"@schematics/angular:component": {
"standalone": false
},
"@schematics/angular:directive": {
"standalone": false
},
"@schematics/angular:pipe": {
"standalone": false
}
},
"root": "",
"sourceRoot": "src",
"prefix": "app",
"architect": {
"build": {
"builder": "@angular/build:application",
"options": {
"browser": "src/main.ts",
"polyfills": [
"zone.js"
],
"tsConfig": "tsconfig.app.json",
"assets": [
{
"glob": "**/*",
"input": "public"
}
],
"styles": []
},
"configurations": {
"production": {
"budgets": [
{
"type": "initial",
"maximumWarning": "500kB",
"maximumError": "1MB"
},
{
"type": "anyComponentStyle",
"maximumWarning": "4kB",
"maximumError": "8kB"
}
],
"outputHashing": "none"
},
"development": {
"optimization": false,
"extractLicenses": false,
"sourceMap": true
}
},
"defaultConfiguration": "production"
},
"serve": {
"builder": "@angular/build:dev-server",
"configurations": {
"production": {
"buildTarget": "config:build:production"
},
"development": {
"buildTarget": "config:build:development"
}
},
"defaultConfiguration": "development"
},
"extract-i18n": {
"builder": "@angular/build:extract-i18n"
},
"test": {
"builder": "@angular/build:karma",
"options": {
"polyfills": [
"zone.js",
"zone.js/testing"
],
"tsConfig": "tsconfig.spec.json",
"assets": [
{
"glob": "**/*",
"input": "public"
}
],
"styles": [],
"scripts": []
}
}
}
}
}
}

9676
pkg/config/ui/package-lock.json generated Normal file

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,49 @@
{
"name": "config",
"version": "0.0.0",
"scripts": {
"ng": "ng",
"start": "ng serve",
"build": "ng build",
"watch": "ng build --watch --configuration development",
"test": "ng test --watch=false --browsers=ChromeHeadless"
},
"prettier": {
"printWidth": 100,
"singleQuote": true,
"overrides": [
{
"files": "*.html",
"options": {
"parser": "angular"
}
}
]
},
"private": true,
"dependencies": {
"@angular/common": "^20.3.0",
"@angular/compiler": "^20.3.0",
"@angular/core": "^20.3.0",
"@angular/elements": "^20.3.10",
"@angular/forms": "^20.3.0",
"@angular/platform-browser": "^20.3.0",
"@angular/router": "^20.3.0",
"rxjs": "~7.8.0",
"tslib": "^2.3.0",
"zone.js": "~0.15.0"
},
"devDependencies": {
"@angular/build": "^20.3.9",
"@angular/cli": "^20.3.9",
"@angular/compiler-cli": "^20.3.0",
"@types/jasmine": "~5.1.0",
"jasmine-core": "~5.9.0",
"karma": "~6.4.0",
"karma-chrome-launcher": "~3.2.0",
"karma-coverage": "~2.2.0",
"karma-jasmine": "~5.1.0",
"karma-jasmine-html-reporter": "~2.1.0",
"typescript": "~5.9.2"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

View file

@ -0,0 +1,23 @@
import { DoBootstrap, Injector, NgModule, provideBrowserGlobalErrorListeners } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { createCustomElement } from '@angular/elements';
import { App } from './app';
@NgModule({
imports: [
BrowserModule,
App
],
providers: [
provideBrowserGlobalErrorListeners()
]
})
export class AppModule implements DoBootstrap {
constructor(private injector: Injector) {
const el = createCustomElement(App, { injector });
customElements.define('config-element', el);
}
ngDoBootstrap() {}
}

View file

@ -0,0 +1 @@
<h1>Hello, {{ title() }}</h1>

View file

@ -0,0 +1,29 @@
import { TestBed } from '@angular/core/testing';
import { App } from './app';
describe('App', () => {
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [App],
}).compileComponents();
});
it('should create the app', () => {
const fixture = TestBed.createComponent(App);
const app = fixture.componentInstance;
expect(app).toBeTruthy();
});
it(`should have as title 'config'`, () => {
const fixture = TestBed.createComponent(App);
const app = fixture.componentInstance;
expect(app.title()).toEqual('config');
});
it('should render title', () => {
const fixture = TestBed.createComponent(App);
fixture.detectChanges();
const compiled = fixture.nativeElement as HTMLElement;
expect(compiled.querySelector('h1')?.textContent).toContain('Hello, config');
});
});

View file

@ -0,0 +1,10 @@
import { Component, signal } from '@angular/core';
@Component({
selector: 'config-element',
templateUrl: './app.html',
standalone: true
})
export class App {
public readonly title = signal('config');
}

View file

@ -0,0 +1,13 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Config</title>
<base href="/">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="icon" type="image/x-icon" href="favicon.ico">
</head>
<body>
<config-element></config-element>
</body>
</html>

View file

@ -0,0 +1,7 @@
import { platformBrowser } from '@angular/platform-browser';
import { AppModule } from './app/app-module';
platformBrowser().bootstrapModule(AppModule, {
ngZoneEventCoalescing: true,
})
.catch(err => console.error(err));

View file

@ -0,0 +1 @@
/* You can add global styles to this file, and also import other style files */

View file

@ -0,0 +1,15 @@
/* To learn more about Typescript configuration file: https://www.typescriptlang.org/docs/handbook/tsconfig-json.html. */
/* To learn more about Angular compiler options: https://angular.dev/reference/configs/angular-compiler-options. */
{
"extends": "./tsconfig.json",
"compilerOptions": {
"outDir": "./out-tsc/app",
"types": []
},
"include": [
"src/**/*.ts"
],
"exclude": [
"src/**/*.spec.ts"
]
}

View file

@ -0,0 +1,34 @@
/* To learn more about Typescript configuration file: https://www.typescriptlang.org/docs/handbook/tsconfig-json.html. */
/* To learn more about Angular compiler options: https://angular.dev/reference/configs/angular-compiler-options. */
{
"compileOnSave": false,
"compilerOptions": {
"strict": true,
"noImplicitOverride": true,
"noPropertyAccessFromIndexSignature": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true,
"skipLibCheck": true,
"isolatedModules": true,
"experimentalDecorators": true,
"importHelpers": true,
"target": "ES2022",
"module": "preserve"
},
"angularCompilerOptions": {
"enableI18nLegacyMessageIdFormat": false,
"strictInjectionParameters": true,
"strictInputAccessModifiers": true,
"typeCheckHostBindings": true,
"strictTemplates": true
},
"files": [],
"references": [
{
"path": "./tsconfig.app.json"
},
{
"path": "./tsconfig.spec.json"
}
]
}

View file

@ -0,0 +1,14 @@
/* To learn more about Typescript configuration file: https://www.typescriptlang.org/docs/handbook/tsconfig-json.html. */
/* To learn more about Angular compiler options: https://angular.dev/reference/configs/angular-compiler-options. */
{
"extends": "./tsconfig.json",
"compilerOptions": {
"outDir": "./out-tsc/spec",
"types": [
"jasmine"
]
},
"include": [
"src/**/*.ts"
]
}

View file

@ -278,6 +278,12 @@ func (c *Core) Config() Config {
return cfg
}
// Display returns the registered Display service.
func (c *Core) Display() Display {
d := MustServiceFor[Display](c, "display")
return d
}
func (c *Core) Core() *Core { return c }
// Assets returns the embedded filesystem containing the application's assets.

View file

@ -153,14 +153,14 @@ func TestCore_WithWails_Good(t *testing.T) {
assert.Equal(t, app, c.App)
}
//go:embed core/testdata
//go:embed testdata
var testFS embed.FS
func TestCore_WithAssets_Good(t *testing.T) {
c, err := New(WithAssets(testFS))
assert.NoError(t, err)
assets := c.Assets()
file, err := assets.Open("core/testdata/test.txt")
file, err := assets.Open("testdata/test.txt")
assert.NoError(t, err)
defer file.Close()
content, err := io.ReadAll(file)

View file

55
pkg/core/go.mod Normal file
View file

@ -0,0 +1,55 @@
module github.com/Snider/Core/pkg/core
go 1.25
require (
github.com/stretchr/testify v1.11.1
github.com/wailsapp/wails/v3 v3.0.0-alpha.41
)
require (
dario.cat/mergo v1.0.2 // indirect
github.com/Microsoft/go-winio v0.6.2 // indirect
github.com/ProtonMail/go-crypto v1.3.0 // indirect
github.com/adrg/xdg v0.5.3 // indirect
github.com/bep/debounce v1.2.1 // indirect
github.com/cloudflare/circl v1.6.1 // indirect
github.com/cyphar/filepath-securejoin v0.6.1 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/ebitengine/purego v0.9.1 // indirect
github.com/emirpasic/gods v1.18.1 // indirect
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect
github.com/go-git/go-billy/v5 v5.6.2 // indirect
github.com/go-git/go-git/v5 v5.16.4 // indirect
github.com/go-ole/go-ole v1.3.0 // indirect
github.com/godbus/dbus/v5 v5.2.0 // indirect
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect
github.com/jchv/go-winloader v0.0.0-20250406163304-c1995be93bd1 // indirect
github.com/kevinburke/ssh_config v1.4.0 // indirect
github.com/klauspost/cpuid/v2 v2.3.0 // indirect
github.com/leaanthony/go-ansi-parser v1.6.1 // indirect
github.com/leaanthony/u v1.1.1 // indirect
github.com/lmittmann/tint v1.1.2 // indirect
github.com/mattn/go-colorable v0.1.14 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/pjbgf/sha1cd v0.5.0 // indirect
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/rivo/uniseg v0.4.7 // indirect
github.com/samber/lo v1.52.0 // indirect
github.com/sergi/go-diff v1.4.0 // indirect
github.com/skeema/knownhosts v1.3.2 // indirect
github.com/wailsapp/go-webview2 v1.0.23 // indirect
github.com/wailsapp/mimetype v1.4.1 // indirect
github.com/xanzy/ssh-agent v0.3.3 // indirect
golang.org/x/crypto v0.45.0 // indirect
golang.org/x/net v0.47.0 // indirect
golang.org/x/sys v0.38.0 // indirect
golang.org/x/text v0.31.0 // indirect
gopkg.in/ini.v1 v1.67.0 // indirect
gopkg.in/warnings.v0 v0.1.2 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)

127
pkg/core/go.sum Normal file
View file

@ -0,0 +1,127 @@
dario.cat/mergo v1.0.2 h1:85+piFYR1tMbRrLcDwR18y4UKJ3aH1Tbzi24VRW1TK8=
github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY=
github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
github.com/ProtonMail/go-crypto v1.3.0 h1:ILq8+Sf5If5DCpHQp4PbZdS1J7HDFRXz/+xKBiRGFrw=
github.com/adrg/xdg v0.5.3 h1:xRnxJXne7+oWDatRhR1JLnvuccuIeCoBu2rtuLqQB78=
github.com/adrg/xdg v0.5.3/go.mod h1:nlTsY+NNiCBGCK2tpm09vRqfVzrc2fLmXGpBLF0zlTQ=
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8=
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4=
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio=
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=
github.com/bep/debounce v1.2.1 h1:v67fRdBA9UQu2NhLFXrSg0Brw7CexQekrBwDMM8bzeY=
github.com/bep/debounce v1.2.1/go.mod h1:H8yggRPQKLUhUoqrJC1bO2xNya7vanpDl7xR3ISbCJ0=
github.com/cloudflare/circl v1.6.1 h1:zqIqSPIndyBh1bjLVVDHMPpVKqp8Su/V+6MeDzzQBQ0=
github.com/cyphar/filepath-securejoin v0.6.1 h1:5CeZ1jPXEiYt3+Z6zqprSAgSWiggmpVyciv8syjIpVE=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/ebitengine/purego v0.9.1 h1:a/k2f2HQU3Pi399RPW1MOaZyhKJL9w/xFpKAg4q1s0A=
github.com/elazarl/goproxy v1.7.2 h1:Y2o6urb7Eule09PjlhQRGNsqRfPmYI3KKQLFpCAV3+o=
github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc=
github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ=
github.com/gliderlabs/ssh v0.3.8 h1:a4YXD1V7xMF9g5nTkdfnja3Sxy1PVDCj1Zg4Wb8vY6c=
github.com/gliderlabs/ssh v0.3.8/go.mod h1:xYoytBv1sV0aL3CavoDuJIQNURXkkfPA/wxQ1pL1fAU=
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI=
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmSxCcxctByoQdvwPiA7DTK7jaaFDBTtu0ic=
github.com/go-git/go-billy/v5 v5.6.2 h1:6Q86EsPXMa7c3YZ3aLAQsMA0VlWmy43r6FHqa/UNbRM=
github.com/go-git/go-billy/v5 v5.6.2/go.mod h1:rcFC2rAsp/erv7CMz9GczHcuD0D32fWzH+MJAU+jaUU=
github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399 h1:eMje31YglSBqCdIqdhKBW8lokaMrL3uTkpGYlE2OOT4=
github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399/go.mod h1:1OCfN199q1Jm3HZlxleg+Dw/mwps2Wbk9frAWm+4FII=
github.com/go-git/go-git/v5 v5.16.4 h1:7ajIEZHZJULcyJebDLo99bGgS0jRrOxzZG4uCk2Yb2Y=
github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE=
github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78=
github.com/godbus/dbus/v5 v5.2.0 h1:3WexO+U+yg9T70v9FdHr9kCxYlazaAXUhx2VMkbfax8=
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 h1:f+oWsMOmNPc8JmEHVZIycC7hBoQxHH9pNKQORJNozsQ=
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8/go.mod h1:wcDNUvekVysuuOpQKo3191zZyTpiI6se1N1ULghS0sw=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A=
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo=
github.com/jchv/go-winloader v0.0.0-20250406163304-c1995be93bd1 h1:njuLRcjAuMKr7kI3D85AXWkw6/+v9PwtV6M6o11sWHQ=
github.com/kevinburke/ssh_config v1.4.0 h1:6xxtP5bZ2E4NF5tuQulISpTO2z8XbtH8cg1PWkxoFkQ=
github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/leaanthony/go-ansi-parser v1.6.1 h1:xd8bzARK3dErqkPFtoF9F3/HgN8UQk0ed1YDKpEz01A=
github.com/leaanthony/go-ansi-parser v1.6.1/go.mod h1:+vva/2y4alzVmmIEpk9QDhA7vLC5zKDTRwfZGOp3IWU=
github.com/leaanthony/u v1.1.1 h1:TUFjwDGlNX+WuwVEzDqQwC2lOv0P4uhTQw7CMFdiK7M=
github.com/leaanthony/u v1.1.1/go.mod h1:9+o6hejoRljvZ3BzdYlVL0JYCwtnAsVuN9pVTQcaRfI=
github.com/lmittmann/tint v1.1.2 h1:2CQzrL6rslrsyjqLDwD11bZ5OpLBPU+g3G/r5LSfS8w=
github.com/matryer/is v1.4.0/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU=
github.com/matryer/is v1.4.1 h1:55ehd8zaGABKLXQUe2awZ99BD/PTc2ls+KV/dXphgEQ=
github.com/matryer/is v1.4.1/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU=
github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE=
github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/onsi/gomega v1.34.1 h1:EUMJIKUjM8sKjYbtxQI9A4z2o+rruxnzNvpknOXie6k=
github.com/onsi/gomega v1.34.1/go.mod h1:kU1QgUvBDLXBJq618Xvm2LUX6rSAfRaFRTcdOeDLwwY=
github.com/pjbgf/sha1cd v0.5.0 h1:a+UkboSi1znleCDUNT3M5YxjOnN1fz2FhN48FlwCxs0=
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ=
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
github.com/samber/lo v1.52.0 h1:Rvi+3BFHES3A8meP33VPAxiBZX/Aws5RxrschYGjomw=
github.com/sergi/go-diff v1.4.0 h1:n/SP9D5ad1fORl+llWyN+D6qoUETXNZARKjyY2/KVCw=
github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
github.com/skeema/knownhosts v1.3.2 h1:EDL9mgf4NzwMXCTfaxSD/o/a5fxDw/xL9nkU28JjdBg=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
github.com/wailsapp/go-webview2 v1.0.23 h1:jmv8qhz1lHibCc79bMM/a/FqOnnzOGEisLav+a0b9P0=
github.com/wailsapp/mimetype v1.4.1 h1:pQN9ycO7uo4vsUUuPeHEYoUkLVkaRntMnHJxVwYhwHs=
github.com/wailsapp/mimetype v1.4.1/go.mod h1:9aV5k31bBOv5z6u+QP8TltzvNGJPmNJD4XlAL3U+j3o=
github.com/wailsapp/wails/v3 v3.0.0-alpha.41 h1:DYcC1/vtO862sxnoyCOMfLLypbzpFWI257fR6zDYY+Y=
github.com/wailsapp/wails/v3 v3.0.0-alpha.41/go.mod h1:7i8tSuA74q97zZ5qEJlcVZdnO+IR7LT2KU8UpzYMPsw=
github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM=
github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw=
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.45.0 h1:jMBrvKuj23MTlT0bQEOBcAE0mjg8mK9RXFhRH6nyF3Q=
golang.org/x/exp v0.0.0-20250210185358-939b2ce775ac h1:l5+whBCLH3iH2ZNHYLbAe58bo7yrN4mVcnkHDYz5vvs=
golang.org/x/exp v0.0.0-20250210185358-939b2ce775ac/go.mod h1:hH+7mtFmImwwcMvScyxUhjuVHR3HGaDPMn9rMSUUbxo=
golang.org/x/net v0.0.0-20210505024714-0287a6fb4125/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200810151505-1b9f1253b3ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.37.0 h1:8EGAD0qCmHYZg6J17DvsMy9/wJ7/D/4pV/wfnld5lTU=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME=
gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

View file

@ -82,3 +82,22 @@ type Config interface {
// Set stores a configuration value by key.
Set(key string, v any) error
}
// WindowOption is an interface for applying configuration options to a window.
type WindowOption interface {
Apply(any)
}
// Display provides access to windowing and visual elements.
type Display interface {
// OpenWindow creates a new window with the given options.
OpenWindow(opts ...WindowOption) error
}
// ActionServiceStartup is a message sent when the application's services are starting up.
// This provides a hook for services to perform initialization tasks.
type ActionServiceStartup struct{}
// ActionServiceShutdown is a message sent when the application is shutting down.
// This allows services to perform cleanup tasks, such as saving state or closing resources.
type ActionServiceShutdown struct{}

View file

@ -8,6 +8,33 @@ import (
"github.com/wailsapp/wails/v3/pkg/application"
)
// ServiceRuntime is a helper struct embedded in services to provide access to the core application.
// It is generic and can be parameterized with a service-specific options struct.
type ServiceRuntime[T any] struct {
core *Core
opts T
}
// NewServiceRuntime creates a new ServiceRuntime instance for a service.
// This is typically called by a service's constructor.
func NewServiceRuntime[T any](c *Core, opts T) *ServiceRuntime[T] {
return &ServiceRuntime[T]{
core: c,
opts: opts,
}
}
// Core returns the central core instance, providing access to all registered services.
func (r *ServiceRuntime[T]) Core() *Core {
return r.core
}
// Config returns the registered Config service from the core application.
// This is a convenience method for accessing the application's configuration.
func (r *ServiceRuntime[T]) Config() Config {
return r.core.Config()
}
// Runtime is the container that holds all instantiated services.
// Its fields are the concrete types, allowing Wails to bind them directly.
// This struct is the primary entry point for the Wails application.

22
pkg/display/.github/workflows/go.yml vendored Normal file
View file

@ -0,0 +1,22 @@
name: Go
on: [push, pull_request]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up Go
uses: actions/setup-go@v5
with:
go-version: 1.22
- name: Install system dependencies
run: sudo apt-get update && sudo apt-get install -y libgtk-3-dev libwebkit2gtk-4.1-dev
- name: Install dependencies
run: go get .
- name: Test with coverage
run: go test -coverprofile=coverage.out
- name: Upload coverage to Codecov
uses: codecov/codecov-action@v4
with:
files: ./coverage.out
fail_ci_if_error: true

View file

@ -0,0 +1,24 @@
name: release
on:
push:
tags:
- 'v*'
jobs:
goreleaser:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v2
with:
fetch-depth: 0
- name: Set up Go
uses: actions/setup-go@v2
with:
go-version: 1.18
- name: Run GoReleaser
uses: goreleaser/goreleaser-action@v2
with:
version: latest
args: release --rm-dist
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

17
pkg/display/.gitignore vendored Normal file
View file

@ -0,0 +1,17 @@
# Go
*.exe
*.exe~
*.dll
*.so
*.dylib
*.test
*.out
*.prof
# Node
node_modules/
dist/
.DS_Store
npm-debug.log*
yarn-debug.log*
yarn-error.log*

View file

@ -0,0 +1,43 @@
# This is an example .goreleaser.yml file with some sensible defaults.
# Make sure to check the documentation at https://goreleaser.com
before:
hooks:
# You may remove this if you don't use go modules.
- go mod tidy
# you may remove this if you don't need go generate
- go generate ./...
builds:
- env:
- CGO_ENABLED=0
goos:
- linux
- windows
- darwin
archives:
- replacements:
darwin: Darwin
linux: Linux
windows: Windows
386: i386
amd64: x86_64
checksum:
name_template: 'checksums.txt'
snapshot:
name_template: "{{ incpatch .Version }}-next"
changelog:
sort: asc
filters:
exclude:
- '^docs:'
- '^test:'
brews:
- name: display
tap:
owner: Snider
name: homebrew-tap
commit_author:
name: goreleaserbot
email: goreleaser@carlosbecker.com
homepage: "https://github.com/Snider/display"
description: "A display module for the core web3 framework"
license: "EUPL-1.2"

287
pkg/display/LICENSE Normal file
View file

@ -0,0 +1,287 @@
EUROPEAN UNION PUBLIC LICENCE v. 1.2
EUPL © the European Union 2007, 2016
This European Union Public Licence (the EUPL) applies to the Work (as defined
below) which is provided under the terms of this Licence. Any use of the Work,
other than as authorised under this Licence is prohibited (to the extent such
use is covered by a right of the copyright holder of the Work).
The Work is provided under the terms of this Licence when the Licensor (as
defined below) has placed the following notice immediately following the
copyright notice for the Work:
Licensed under the EUPL
or has expressed by any other means his willingness to license under the EUPL.
1. Definitions
In this Licence, the following terms have the following meaning:
- The Licence: this Licence.
- The Original Work: the work or software distributed or communicated by the
Licensor under this Licence, available as Source Code and also as Executable
Code as the case may be.
- Derivative Works: the works or software that could be created by the
Licensee, based upon the Original Work or modifications thereof. This Licence
does not define the extent of modification or dependence on the Original Work
required in order to classify a work as a Derivative Work; this extent is
determined by copyright law applicable in the country mentioned in Article 15.
- The Work: the Original Work or its Derivative Works.
- The Source Code: the human-readable form of the Work which is the most
convenient for people to study and modify.
- The Executable Code: any code which has generally been compiled and which is
meant to be interpreted by a computer as a program.
- The Licensor: the natural or legal person that distributes or communicates
the Work under the Licence.
- Contributor(s): any natural or legal person who modifies the Work under the
Licence, or otherwise contributes to the creation of a Derivative Work.
- The Licensee or You: any natural or legal person who makes any usage of
the Work under the terms of the Licence.
- Distribution or Communication: any act of selling, giving, lending,
renting, distributing, communicating, transmitting, or otherwise making
available, online or offline, copies of the Work or providing access to its
essential functionalities at the disposal of any other natural or legal
person.
2. Scope of the rights granted by the Licence
The Licensor hereby grants You a worldwide, royalty-free, non-exclusive,
sublicensable licence to do the following, for the duration of copyright vested
in the Original Work:
- use the Work in any circumstance and for all usage,
- reproduce the Work,
- modify the Work, and make Derivative Works based upon the Work,
- communicate to the public, including the right to make available or display
the Work or copies thereof to the public and perform publicly, as the case may
be, the Work,
- distribute the Work or copies thereof,
- lend and rent the Work or copies thereof,
- sublicense rights in the Work or copies thereof.
Those rights can be exercised on any media, supports and formats, whether now
known or later invented, as far as the applicable law permits so.
In the countries where moral rights apply, the Licensor waives his right to
exercise his moral right to the extent allowed by law in order to make effective
the licence of the economic rights here above listed.
The Licensor grants to the Licensee royalty-free, non-exclusive usage rights to
any patents held by the Licensor, to the extent necessary to make use of the
rights granted on the Work under this Licence.
3. Communication of the Source Code
The Licensor may provide the Work either in its Source Code form, or as
Executable Code. If the Work is provided as Executable Code, the Licensor
provides in addition a machine-readable copy of the Source Code of the Work
along with each copy of the Work that the Licensor distributes or indicates, in
a notice following the copyright notice attached to the Work, a repository where
the Source Code is easily and freely accessible for as long as the Licensor
continues to distribute or communicate the Work.
4. Limitations on copyright
Nothing in this Licence is intended to deprive the Licensee of the benefits from
any exception or limitation to the exclusive rights of the rights owners in the
Work, of the exhaustion of those rights or of other applicable limitations
thereto.
5. Obligations of the Licensee
The grant of the rights mentioned above is subject to some restrictions and
obligations imposed on the Licensee. Those obligations are the following:
Attribution right: The Licensee shall keep intact all copyright, patent or
trademarks notices and all notices that refer to the Licence and to the
disclaimer of warranties. The Licensee must include a copy of such notices and a
copy of the Licence with every copy of the Work he/she distributes or
communicates. The Licensee must cause any Derivative Work to carry prominent
notices stating that the Work has been modified and the date of modification.
Copyleft clause: If the Licensee distributes or communicates copies of the
Original Works or Derivative Works, this Distribution or Communication will be
done under the terms of this Licence or of a later version of this Licence
unless the Original Work is expressly distributed only under this version of the
Licence — for example by communicating EUPL v. 1.2 only. The Licensee
(becoming Licensor) cannot offer or impose any additional terms or conditions on
the Work or Derivative Work that alter or restrict the terms of the Licence.
Compatibility clause: If the Licensee Distributes or Communicates Derivative
Works or copies thereof based upon both the Work and another work licensed under
a Compatible Licence, this Distribution or Communication can be done under the
terms of this Compatible Licence. For the sake of this clause, Compatible
Licence refers to the licences listed in the appendix attached to this Licence.
Should the Licensee's obligations under the Compatible Licence conflict with
his/her obligations under this Licence, the obligations of the Compatible
Licence shall prevail.
Provision of Source Code: When distributing or communicating copies of the Work,
the Licensee will provide a machine-readable copy of the Source Code or indicate
a repository where this Source will be easily and freely available for as long
as the Licensee continues to distribute or communicate the Work.
Legal Protection: This Licence does not grant permission to use the trade names,
trademarks, service marks, or names of the Licensor, except as required for
reasonable and customary use in describing the origin of the Work and
reproducing the content of the copyright notice.
6. Chain of Authorship
The original Licensor warrants that the copyright in the Original Work granted
hereunder is owned by him/her or licensed to him/her and that he/she has the
power and authority to grant the Licence.
Each Contributor warrants that the copyright in the modifications he/she brings
to the Work are owned by him/her or licensed to him/her and that he/she has the
power and authority to grant the Licence.
Each time You accept the Licence, the original Licensor and subsequent
Contributors grant You a licence to their contributions to the Work, under the
terms of this Licence.
7. Disclaimer of Warranty
The Work is a work in progress, which is continuously improved by numerous
Contributors. It is not a finished work and may therefore contain defects or
bugs inherent to this type of development.
For the above reason, the Work is provided under the Licence on an as is basis
and without warranties of any kind concerning the Work, including without
limitation merchantability, fitness for a particular purpose, absence of defects
or errors, accuracy, non-infringement of intellectual property rights other than
copyright as stated in Article 6 of this Licence.
This disclaimer of warranty is an essential part of the Licence and a condition
for the grant of any rights to the Work.
8. Disclaimer of Liability
Except in the cases of wilful misconduct or damages directly caused to natural
persons, the Licensor will in no event be liable for any direct or indirect,
material or moral, damages of any kind, arising out of the Licence or of the use
of the Work, including without limitation, damages for loss of goodwill, work
stoppage, computer failure or malfunction, loss of data or any commercial
damage, even if the Licensor has been advised of the possibility of such damage.
However, the Licensor will be liable under statutory product liability laws as
far such laws apply to the Work.
9. Additional agreements
While distributing the Work, You may choose to conclude an additional agreement,
defining obligations or services consistent with this Licence. However, if
accepting obligations, You may act only on your own behalf and on your sole
responsibility, not on behalf of the original Licensor or any other Contributor,
and only if You agree to indemnify, defend, and hold each Contributor harmless
for any liability incurred by, or claims asserted against such Contributor by
the fact You have accepted any warranty or additional liability.
10. Acceptance of the Licence
The provisions of this Licence can be accepted by clicking on an icon I agree
placed under the bottom of a window displaying the text of this Licence or by
affirming consent in any other similar way, in accordance with the rules of
applicable law. Clicking on that icon indicates your clear and irrevocable
acceptance of this Licence and all of its terms and conditions.
Similarly, you irrevocably accept this Licence and all of its terms and
conditions by exercising any rights granted to You by Article 2 of this Licence,
such as the use of the Work, the creation by You of a Derivative Work or the
Distribution or Communication by You of the Work or copies thereof.
11. Information to the public
In case of any Distribution or Communication of the Work by means of electronic
communication by You (for example, by offering to download the Work from a
remote location) the distribution channel or media (for example, a website) must
at least provide to the public the information requested by the applicable law
regarding the Licensor, the Licence and the way it may be accessible, concluded,
stored and reproduced by the Licensee.
12. Termination of the Licence
The Licence and the rights granted hereunder will terminate automatically upon
any breach by the Licensee of the terms of the Licence.
Such a termination will not terminate the licences of any person who has
received the Work from the Licensee under the Licence, provided such persons
remain in full compliance with the Licence.
13. Miscellaneous
Without prejudice of Article 9 above, the Licence represents the complete
agreement between the Parties as to the Work.
If any provision of the Licence is invalid or unenforceable under applicable
law, this will not affect the validity or enforceability of the Licence as a
whole. Such provision will be construed or reformed so as necessary to make it
valid and enforceable.
The European Commission may publish other linguistic versions or new versions of
this Licence or updated versions of the Appendix, so far this is required and
reasonable, without reducing the scope of the rights granted by the Licence. New
versions of the Licence will be published with a unique version number.
All linguistic versions of this Licence, approved by the European Commission,
have identical value. Parties can take advantage of the linguistic version of
their choice.
14. Jurisdiction
Without prejudice to specific agreement between parties,
- any litigation resulting from the interpretation of this License, arising
between the European Union institutions, bodies, offices or agencies, as a
Licensor, and any Licensee, will be subject to the jurisdiction of the Court
of Justice of the European Union, as laid down in article 272 of the Treaty on
the Functioning of the European Union,
- any litigation arising between other parties and resulting from the
interpretation of this License, will be subject to the exclusive jurisdiction
of the competent court where the Licensor resides or conducts its primary
business.
15. Applicable Law
Without prejudice to specific agreement between parties,
- this Licence shall be governed by the law of the European Union Member State
where the Licensor has his seat, resides or has his registered office,
- this licence shall be governed by Belgian law if the Licensor has no seat,
residence or registered office inside a European Union Member State.
Appendix
Compatible Licences according to Article 5 EUPL are:
- GNU General Public License (GPL) v. 2, v. 3
- GNU Affero General Public License (AGPL) v. 3
- Open Software License (OSL) v. 2.1, v. 3.0
- Eclipse Public License (EPL) v. 1.0
- CeCILL v. 2.0, v. 2.1
- Mozilla Public Licence (MPL) v. 2
- GNU Lesser General Public Licence (LGPL) v. 2.1, v. 3
- Creative Commons Attribution-ShareAlike v. 3.0 Unported (CC BY-SA 3.0) for
works other than software
- European Union Public Licence (EUPL) v. 1.1, v. 1.2
- Québec Free and Open-Source Licence — Reciprocity (LiLiQ-R) or Strong
Reciprocity (LiLiQ-R+).
The European Commission may update this Appendix to later versions of the above
licences without producing a new version of the EUPL, as long as they provide
the rights granted in Article 2 of this Licence and protect the covered Source
Code from exclusive appropriation.
All other changes or additions to this Appendix require the production of a new
EUPL version.

43
pkg/display/README.md Normal file
View file

@ -0,0 +1,43 @@
# Display
This repository is a display module for the core web3 framework. It includes a Go backend, an Angular custom element, and a full release cycle configuration.
## Getting Started
1. **Clone the repository:**
```bash
git clone https://github.com/Snider/display.git
```
2. **Install the dependencies:**
```bash
cd display
go mod tidy
cd ui
npm install
```
3. **Run the development server:**
```bash
go run ./cmd/demo-cli serve
```
This will start the Go backend and serve the Angular custom element.
## Building the Custom Element
To build the Angular custom element, run the following command:
```bash
cd ui
npm run build
```
This will create a single JavaScript file in the `dist` directory that you can use in any HTML page.
## Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
## License
This project is licensed under the EUPL-1.2 License - see the [LICENSE](LICENSE) file for details.

20
pkg/display/actions.go Normal file
View file

@ -0,0 +1,20 @@
package display
import "github.com/wailsapp/wails/v3/pkg/application"
// ActionOpenWindow is an IPC message used to request a new window. It contains
// the options for the new window.
//
// example:
//
// action := display.ActionOpenWindow{
// WebviewWindowOptions: application.WebviewWindowOptions{
// Name: "my-window",
// Title: "My Window",
// Width: 800,
// Height: 600,
// },
// }
type ActionOpenWindow struct {
application.WebviewWindowOptions
}

View file

@ -0,0 +1,29 @@
package cmd
import (
"fmt"
"os"
"github.com/spf13/cobra"
)
// rootCmd represents the base command when called without any subcommands
var rootCmd = &cobra.Command{
Use: "demo-cli",
Short: "A demo CLI for the display module",
Long: `A longer description that spans multiple lines and likely contains
examples and usage of using your application.`,
Run: func(cmd *cobra.Command, args []string) {
fmt.Println("Hello from the demo CLI!")
},
}
// Execute adds all child commands to the root command and sets flags
// appropriately. This is called by main.main(). It only needs to happen once
// to the rootCmd.
func Execute() {
if err := rootCmd.Execute(); err != nil {
fmt.Println(err)
os.Exit(1)
}
}

View file

@ -0,0 +1,34 @@
package cmd
import (
"fmt"
"log"
"net/http"
"github.com/spf13/cobra"
)
// serveCmd represents the serve command
var serveCmd = &cobra.Command{
Use: "serve",
Short: "Starts the HTTP server",
Long: `Starts the HTTP server to serve the frontend and the API.`,
Run: func(cmd *cobra.Command, args []string) {
http.HandleFunc("/api/v1/demo", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Hello, world!")
})
fs := http.FileServer(http.Dir("./ui/dist/display/browser"))
http.Handle("/", fs)
log.Println("Listening on :8080...")
err := http.ListenAndServe(":8080", nil)
if err != nil {
log.Fatal(err)
}
},
}
func init() {
rootCmd.AddCommand(serveCmd)
}

View file

@ -0,0 +1,9 @@
package main
import (
"github.com/Snider/Core/pkg/display/cmd/demo-cli/cmd"
)
func main() {
cmd.Execute()
}

184
pkg/display/display.go Normal file
View file

@ -0,0 +1,184 @@
package display
import (
"context"
"fmt"
"github.com/wailsapp/wails/v3/pkg/application"
"github.com/wailsapp/wails/v3/pkg/events"
)
// Options holds configuration for the display service.
// This struct is used to configure the display service at startup.
type Options struct{}
// Service manages windowing, dialogs, and other visual elements.
// It is the primary interface for interacting with the UI.
type Service struct {
app *application.App
config Options
}
// newDisplayService contains the common logic for initializing a Service struct.
// It is called by the New function.
func newDisplayService() (*Service, error) {
return &Service{}, nil
}
// New is the constructor for the display service.
// It creates a new Service and returns it.
//
// example:
//
// displayService, err := display.New()
// if err != nil {
// log.Fatal(err)
// }
func New() (*Service, error) {
s, err := newDisplayService()
if err != nil {
return nil, err
}
return s, nil
}
// Startup is called when the app starts. It initializes the display service
// and sets up the main application window and system tray.
//
// err := displayService.Startup(ctx)
// if err != nil {
// log.Fatal(err)
// }
func (s *Service) Startup(ctx context.Context) error {
s.app = application.Get()
s.app.Logger.Info("Display service started")
s.buildMenu()
s.systemTray()
return s.OpenWindow()
}
// handleOpenWindowAction processes a message to configure and create a new window
// using the specified name and options.
func (s *Service) handleOpenWindowAction(msg map[string]any) error {
opts := parseWindowOptions(msg)
s.app.Window.NewWithOptions(opts)
return nil
}
// parseWindowOptions extracts window configuration from a map and returns it
// as a `application.WebviewWindowOptions` struct. This function is used by
// `handleOpenWindowAction` to parse the incoming message.
func parseWindowOptions(msg map[string]any) application.WebviewWindowOptions {
opts := application.WebviewWindowOptions{}
if name, ok := msg["name"].(string); ok {
opts.Name = name
}
if optsMap, ok := msg["options"].(map[string]any); ok {
if title, ok := optsMap["Title"].(string); ok {
opts.Title = title
}
if width, ok := optsMap["Width"].(float64); ok {
opts.Width = int(width)
}
if height, ok := optsMap["Height"].(float64); ok {
opts.Height = int(height)
}
}
return opts
}
// ShowEnvironmentDialog displays a dialog containing detailed information about
// the application's runtime environment. This is useful for debugging and
// understanding the context in which the application is running.
//
// example:
//
// displayService.ShowEnvironmentDialog()
func (s *Service) ShowEnvironmentDialog() {
envInfo := s.app.Env.Info()
details := "Environment Information:\n\n"
details += fmt.Sprintf("Operating System: %s\n", envInfo.OS)
details += fmt.Sprintf("Architecture: %s\n", envInfo.Arch)
details += fmt.Sprintf("Debug Mode: %t\n\n", envInfo.Debug)
details += fmt.Sprintf("Dark Mode: %t\n\n", s.app.Env.IsDarkMode())
details += "Platform Information:"
// Add platform-specific details
for key, value := range envInfo.PlatformInfo {
details += fmt.Sprintf("\n%s: %v", key, value)
}
if envInfo.OSInfo != nil {
details += fmt.Sprintf("\n\nOS Details:\nName: %s\nVersion: %s",
envInfo.OSInfo.Name,
envInfo.OSInfo.Version)
}
dialog := s.app.Dialog.Info()
dialog.SetTitle("Environment Information")
dialog.SetMessage(details)
dialog.Show()
}
// OpenWindow creates a new window with the given options. If no options are
// provided, it will use the default options.
//
// example:
//
// err := displayService.OpenWindow(
// display.WithName("my-window"),
// display.WithTitle("My Window"),
// display.WithWidth(800),
// display.WithHeight(600),
// )
// if err != nil {
// log.Fatal(err)
// }
func (s *Service) OpenWindow(opts ...WindowOption) error {
wailsOpts := buildWailsWindowOptions(opts...)
s.app.Window.NewWithOptions(wailsOpts)
return nil
}
// buildWailsWindowOptions creates Wails window options from the given
// `WindowOption`s. This function is used by `OpenWindow` to construct the
// options for the new window.
func buildWailsWindowOptions(opts ...WindowOption) application.WebviewWindowOptions {
// Default options
winOpts := &WindowConfig{
Name: "main",
Title: "Core",
Width: 1280,
Height: 800,
URL: "/",
}
// Apply options
for _, opt := range opts {
opt.Apply(winOpts)
}
// Create Wails window options
return application.WebviewWindowOptions{
Name: winOpts.Name,
Title: winOpts.Title,
Width: winOpts.Width,
Height: winOpts.Height,
URL: winOpts.URL,
AlwaysOnTop: winOpts.AlwaysOnTop,
Hidden: winOpts.Hidden,
MinimiseButtonState: winOpts.MinimiseButtonState,
MaximiseButtonState: winOpts.MaximiseButtonState,
CloseButtonState: winOpts.CloseButtonState,
Frameless: winOpts.Frameless,
}
}
// monitorScreenChanges listens for theme change events and logs when the screen
// configuration changes.
func (s *Service) monitorScreenChanges() {
s.app.Event.OnApplicationEvent(events.Common.ThemeChanged, func(event *application.ApplicationEvent) {
s.app.Logger.Info("Screen configuration changed")
})
}

353
pkg/display/display_test.go Normal file
View file

@ -0,0 +1,353 @@
package display
import (
"reflect"
"testing"
"github.com/wailsapp/wails/v3/pkg/application"
)
func TestParseWindowOptions(t *testing.T) {
tests := []struct {
name string
msg map[string]any
want application.WebviewWindowOptions
}{
{
name: "Valid options",
msg: map[string]any{
"name": "main",
"options": map[string]any{
"Title": "My App",
"Width": 1024.0,
"Height": 768.0,
},
},
want: application.WebviewWindowOptions{
Name: "main",
Title: "My App",
Width: 1024,
Height: 768,
},
},
{
name: "All options valid",
msg: map[string]any{
"name": "secondary",
"options": map[string]any{
"Title": "Another Window",
"Width": 800.0,
"Height": 600.0,
},
},
want: application.WebviewWindowOptions{
Name: "secondary",
Title: "Another Window",
Width: 800,
Height: 600,
},
},
{
name: "Missing options",
msg: map[string]any{
"name": "main",
},
want: application.WebviewWindowOptions{
Name: "main",
},
},
{
name: "Empty message",
msg: map[string]any{},
want: application.WebviewWindowOptions{},
},
{
name: "Invalid width type",
msg: map[string]any{
"name": "main",
"options": map[string]any{
"Title": "My App",
"Width": "not a number",
"Height": 768.0,
},
},
want: application.WebviewWindowOptions{
Name: "main",
Title: "My App",
Height: 768,
},
},
{
name: "Invalid height type",
msg: map[string]any{
"name": "main",
"options": map[string]any{
"Title": "My App",
"Width": 1024.0,
"Height": "not a number",
},
},
want: application.WebviewWindowOptions{
Name: "main",
Title: "My App",
Width: 1024,
},
},
{
name: "Deeply nested and complex message",
msg: map[string]any{
"name": "main",
"options": map[string]any{
"Title": "My App",
"Width": 1024.0,
"Height": 768.0,
"nested": map[string]any{
"another_level": "some_value",
},
},
},
want: application.WebviewWindowOptions{
Name: "main",
Title: "My App",
Width: 1024,
Height: 768,
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := parseWindowOptions(tt.msg); !reflect.DeepEqual(got, tt.want) {
t.Errorf("parseWindowOptions() = %v, want %v", got, tt.want)
}
})
}
}
// mockWindowOption is a mock implementation of the WindowOption interface for testing.
type mockWindowOption struct {
applyFunc func(*WindowConfig)
}
func (m *mockWindowOption) Apply(opts *WindowConfig) {
m.applyFunc(opts)
}
func TestBuildWailsWindowOptions(t *testing.T) {
tests := []struct {
name string
opts []WindowOption
want application.WebviewWindowOptions
}{
{
name: "Default options",
opts: []WindowOption{},
want: application.WebviewWindowOptions{
Name: "main",
Title: "Core",
Width: 1280,
Height: 800,
URL: "/",
},
},
{
name: "Chaining many options",
opts: func() []WindowOption {
opts := make([]WindowOption, 1000)
for i := 0; i < 1000; i++ {
opts[i] = WithTitle("Test")
}
return opts
}(),
want: application.WebviewWindowOptions{
Name: "main",
Title: "Test",
Width: 1280,
Height: 800,
URL: "/",
},
},
{
name: "Override options",
opts: []WindowOption{
&mockWindowOption{
applyFunc: func(opts *WindowConfig) {
opts.Name = "test"
opts.Title = "Test Window"
opts.Width = 1920
opts.Height = 1080
opts.URL = "/test"
opts.AlwaysOnTop = true
opts.Hidden = true
opts.MinimiseButtonState = application.ButtonHidden
opts.MaximiseButtonState = application.ButtonDisabled
opts.CloseButtonState = application.ButtonEnabled
opts.Frameless = true
},
},
},
want: application.WebviewWindowOptions{
Name: "test",
Title: "Test Window",
Width: 1920,
Height: 1080,
URL: "/test",
AlwaysOnTop: true,
Hidden: true,
MinimiseButtonState: application.ButtonHidden,
MaximiseButtonState: application.ButtonDisabled,
CloseButtonState: application.ButtonEnabled,
Frameless: true,
},
},
{
name: "Nil options",
opts: nil,
want: application.WebviewWindowOptions{
Name: "main",
Title: "Core",
Width: 1280,
Height: 800,
URL: "/",
},
},
{
name: "Empty options slice",
opts: []WindowOption{},
want: application.WebviewWindowOptions{
Name: "main",
Title: "Core",
Width: 1280,
Height: 800,
URL: "/",
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := buildWailsWindowOptions(tt.opts...); !reflect.DeepEqual(got, tt.want) {
t.Errorf("buildWailsWindowOptions() = %v, want %v", got, tt.want)
}
})
}
}
func TestNewAndNewDisplayService(t *testing.T) {
s, err := New()
if err != nil {
t.Fatalf("New() error = %v, wantErr nil", err)
}
if s == nil {
t.Fatal("New() returned nil")
}
s, err = newDisplayService()
if err != nil {
t.Fatalf("newDisplayService() error = %v, wantErr nil", err)
}
if s == nil {
t.Fatal("newDisplayService() returned nil")
}
}
func TestWindowOptions(t *testing.T) {
config := &WindowConfig{}
WithName("test-name").Apply(config)
if config.Name != "test-name" {
t.Errorf("WithName() got = %v, want %v", config.Name, "test-name")
}
WithTitle("test-title").Apply(config)
if config.Title != "test-title" {
t.Errorf("WithTitle() got = %v, want %v", config.Title, "test-title")
}
WithWidth(100).Apply(config)
if config.Width != 100 {
t.Errorf("WithWidth() got = %v, want %v", config.Width, 100)
}
WithHeight(200).Apply(config)
if config.Height != 200 {
t.Errorf("WithHeight() got = %v, want %v", config.Height, 200)
}
WithURL("/testurl").Apply(config)
if config.URL != "/testurl" {
t.Errorf("WithURL() got = %v, want %v", config.URL, "/testurl")
}
WithAlwaysOnTop(true).Apply(config)
if !config.AlwaysOnTop {
t.Errorf("WithAlwaysOnTop() got = %v, want %v", config.AlwaysOnTop, true)
}
WithHidden(true).Apply(config)
if !config.Hidden {
t.Errorf("WithHidden() got = %v, want %v", config.Hidden, true)
}
WithMinimiseButtonState(application.ButtonHidden).Apply(config)
if config.MinimiseButtonState != application.ButtonHidden {
t.Errorf("WithMinimiseButtonState() got = %v, want %v", config.MinimiseButtonState, application.ButtonHidden)
}
WithMaximiseButtonState(application.ButtonDisabled).Apply(config)
if config.MaximiseButtonState != application.ButtonDisabled {
t.Errorf("WithMaximiseButtonState() got = %v, want %v", config.MaximiseButtonState, application.ButtonDisabled)
}
WithCloseButtonState(application.ButtonEnabled).Apply(config)
if config.CloseButtonState != application.ButtonEnabled {
t.Errorf("WithCloseButtonState() got = %v, want %v", config.CloseButtonState, application.ButtonEnabled)
}
WithFrameless(true).Apply(config)
if !config.Frameless {
t.Errorf("WithFrameless() got = %v, want %v", config.Frameless, true)
}
}
func TestService_HandleOpenWindowAction(t *testing.T) {
t.Skip("Skipping test that requires a running Wails application.")
s, _ := New()
_ = s.handleOpenWindowAction(map[string]any{})
}
func TestService_ShowEnvironmentDialog(t *testing.T) {
t.Skip("Skipping test that requires a running Wails application.")
s, _ := New()
s.ShowEnvironmentDialog()
}
func TestService_OpenWindow(t *testing.T) {
t.Skip("Skipping test that requires a running Wails application.")
s, _ := New()
_ = s.OpenWindow()
}
func TestService_MonitorScreenChanges(t *testing.T) {
t.Skip("Skipping test that requires a running Wails application.")
s, _ := New()
s.monitorScreenChanges()
}
func TestService_BuildMenu(t *testing.T) {
t.Skip("Skipping test that requires a running Wails application.")
s, _ := New()
s.buildMenu()
}
func TestService_SystemTray(t *testing.T) {
t.Skip("Skipping test that requires a running Wails application.")
s, _ := New()
s.systemTray()
}
func TestService_Startup(t *testing.T) {
t.Skip("Skipping test that requires a running Wails application.")
s, _ := New()
_ = s.Startup(nil)
}

View file

@ -0,0 +1,54 @@
# Backend Documentation
The backend is written in Go and uses the `github.com/Snider/display` package. It utilizes the Wails v3 framework to bridge Go and the web frontend.
## Core Types
### `Service`
The `Service` struct is the main entry point for the display logic.
- **Initialization:**
- `New() (*Service, error)`: Creates a new instance of the service.
- `Startup(ctx context.Context) error`: Initializes the Wails application, builds the menu, sets up the system tray, and opens the main window.
- **Window Management:**
- `OpenWindow(opts ...WindowOption) error`: Opens a new window with the specified options.
- **Dialogs:**
- `ShowEnvironmentDialog()`: Displays a native dialog containing information about the runtime environment (OS, Arch, Debug mode, etc.).
### `WindowConfig` & `WindowOption`
Window configuration is handled using the Functional Options pattern. The `WindowConfig` struct holds parameters like:
- `Name`, `Title`
- `Width`, `Height`
- `URL`
- `AlwaysOnTop`, `Hidden`, `Frameless`
- Window button states (`MinimiseButtonState`, `MaximiseButtonState`, `CloseButtonState`)
**Available Options:**
- `WithName(name string)`
- `WithTitle(title string)`
- `WithWidth(width int)`
- `WithHeight(height int)`
- `WithURL(url string)`
- `WithAlwaysOnTop(bool)`
- `WithHidden(bool)`
- `WithFrameless(bool)`
- `WithMinimiseButtonState(state)`
- `WithMaximiseButtonState(state)`
- `WithCloseButtonState(state)`
## Subsystems
### Menu (`menu.go`)
The `buildMenu` method constructs the application's main menu, adding standard roles like File, Edit, Window, and Help. It allows for platform-specific adjustments (e.g., AppMenu on macOS).
### System Tray (`tray.go`)
The `systemTray` method initializes the system tray icon and its context menu. It supports:
- Showing/Hiding all windows.
- Displaying environment info.
- Quitting the application.
- Attaching a hidden window for advanced tray interactions.
### Actions (`actions.go`)
Defines structured messages for Inter-Process Communication (IPC) or internal event handling, such as `ActionOpenWindow` which wraps `application.WebviewWindowOptions`.

View file

@ -0,0 +1,64 @@
# Development Guide
This guide covers how to set up the development environment, build the project, and run the demo.
## Prerequisites
1. **Go:** Version 1.25 or later.
2. **Node.js & npm:** For building the Angular frontend.
3. **Wails Dependencies:**
- **Linux:** `libgtk-3-dev`, `libwebkit2gtk-4.1-dev`
- **macOS/Windows:** Standard development tools (Xcode Command Line Tools / build-essential).
## Setup
1. Clone the repository:
```bash
git clone https://github.com/Snider/display.git
cd display
```
2. Install Go dependencies:
```bash
go mod tidy
```
3. Install Frontend dependencies:
```bash
cd ui
npm install
cd ..
```
## Running the Demo
The project includes a CLI to facilitate development.
### Serve Mode (Web Preview)
To start a simple HTTP server that serves the frontend and a mock API:
1. Build the frontend first:
```bash
cd ui && npm run build && cd ..
```
2. Run the serve command:
```bash
go run ./cmd/demo-cli serve
```
Access the app at `http://localhost:8080`.
## Building the Project
### Frontend
```bash
cd ui
npm run build
```
### Backend / Application
This project is a library/module. However, it can be tested via the demo CLI or by integrating it into a Wails application entry point.
To run the tests:
```bash
go test ./...
```

View file

@ -0,0 +1,29 @@
# Frontend Documentation
The frontend is an Angular application located in the `ui/` directory. It is designed to be built as a custom element or a standalone application that runs inside the Wails webview.
## Structure
- **Path:** `ui/`
- **Framework:** Angular
- **Output:** The build process generates artifacts in `ui/dist`.
## Development
The frontend can be developed using standard Angular CLI commands or via the provided demo CLI which serves the built files.
## Build Process
To build the frontend for production or integration with the Go backend:
```bash
cd ui
npm install
npm run build
```
This will compile the Angular application and place the output in the `dist/` directory, which the Go backend can then serve or embed.
## Integration
The frontend communicates with the Go backend through the Wails runtime. It can trigger actions defined in the backend (like opening windows) and receive events.

View file

@ -0,0 +1,25 @@
# Overview
The `display` module is a core component responsible for the visual presentation and system integration of the application. It leverages **Wails v3** to create a desktop application backend in Go and **Angular** for the frontend user interface.
## Architecture
The project consists of two main parts:
1. **Backend (Go):** Handles window management, system tray integration, application menus, and communication with the frontend. It is located in the root directory and packaged as a Go module.
2. **Frontend (Angular):** Provides the user interface. It is located in the `ui/` directory and is built as a custom element that interacts with the backend.
## Key Components
### Display Service (`display`)
The core service that manages the application lifecycle. It wraps the Wails application instance and exposes methods to:
- Open and configure windows.
- Manage the system tray.
- Show system dialogs (e.g., environment info).
### System Integration
- **Menu:** A standard application menu (File, Edit, View, etc.) is constructed in `menu.go`.
- **System Tray:** A system tray icon and menu are configured in `tray.go`, allowing quick access to common actions like showing/hiding windows or viewing environment info.
### Demo CLI
A command-line interface (`cmd/demo-cli`) is provided to run and test the display module. It includes a `serve` command for web-based development.

54
pkg/display/go.mod Normal file
View file

@ -0,0 +1,54 @@
module github.com/Snider/Core/pkg/display
go 1.25
require (
github.com/spf13/cobra v1.10.1
github.com/wailsapp/wails/v3 v3.0.0-alpha.41
)
require (
dario.cat/mergo v1.0.2 // indirect
github.com/Microsoft/go-winio v0.6.2 // indirect
github.com/ProtonMail/go-crypto v1.3.0 // indirect
github.com/adrg/xdg v0.5.3 // indirect
github.com/bep/debounce v1.2.1 // indirect
github.com/cloudflare/circl v1.6.1 // indirect
github.com/cyphar/filepath-securejoin v0.6.1 // indirect
github.com/ebitengine/purego v0.9.1 // indirect
github.com/emirpasic/gods v1.18.1 // indirect
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect
github.com/go-git/go-billy/v5 v5.6.2 // indirect
github.com/go-git/go-git/v5 v5.16.4 // indirect
github.com/go-ole/go-ole v1.3.0 // indirect
github.com/godbus/dbus/v5 v5.2.0 // indirect
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect
github.com/jchv/go-winloader v0.0.0-20250406163304-c1995be93bd1 // indirect
github.com/kevinburke/ssh_config v1.4.0 // indirect
github.com/klauspost/cpuid/v2 v2.3.0 // indirect
github.com/leaanthony/go-ansi-parser v1.6.1 // indirect
github.com/leaanthony/u v1.1.1 // indirect
github.com/lmittmann/tint v1.1.2 // indirect
github.com/mattn/go-colorable v0.1.14 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/pjbgf/sha1cd v0.5.0 // indirect
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/rivo/uniseg v0.4.7 // indirect
github.com/samber/lo v1.52.0 // indirect
github.com/sergi/go-diff v1.4.0 // indirect
github.com/skeema/knownhosts v1.3.2 // indirect
github.com/spf13/pflag v1.0.10 // indirect
github.com/wailsapp/go-webview2 v1.0.23 // indirect
github.com/wailsapp/mimetype v1.4.1 // indirect
github.com/xanzy/ssh-agent v0.3.3 // indirect
golang.org/x/crypto v0.45.0 // indirect
golang.org/x/net v0.47.0 // indirect
golang.org/x/sys v0.38.0 // indirect
golang.org/x/text v0.31.0 // indirect
gopkg.in/ini.v1 v1.67.0 // indirect
gopkg.in/warnings.v0 v0.1.2 // indirect
)

147
pkg/display/go.sum Normal file
View file

@ -0,0 +1,147 @@
dario.cat/mergo v1.0.2 h1:85+piFYR1tMbRrLcDwR18y4UKJ3aH1Tbzi24VRW1TK8=
dario.cat/mergo v1.0.2/go.mod h1:E/hbnu0NxMFBjpMIE34DRGLWqDy0g5FuKDhCb31ngxA=
github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY=
github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
github.com/ProtonMail/go-crypto v1.3.0 h1:ILq8+Sf5If5DCpHQp4PbZdS1J7HDFRXz/+xKBiRGFrw=
github.com/ProtonMail/go-crypto v1.3.0/go.mod h1:9whxjD8Rbs29b4XWbB8irEcE8KHMqaR2e7GWU1R+/PE=
github.com/adrg/xdg v0.5.3 h1:xRnxJXne7+oWDatRhR1JLnvuccuIeCoBu2rtuLqQB78=
github.com/adrg/xdg v0.5.3/go.mod h1:nlTsY+NNiCBGCK2tpm09vRqfVzrc2fLmXGpBLF0zlTQ=
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8=
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4=
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio=
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=
github.com/bep/debounce v1.2.1 h1:v67fRdBA9UQu2NhLFXrSg0Brw7CexQekrBwDMM8bzeY=
github.com/bep/debounce v1.2.1/go.mod h1:H8yggRPQKLUhUoqrJC1bO2xNya7vanpDl7xR3ISbCJ0=
github.com/cloudflare/circl v1.6.1 h1:zqIqSPIndyBh1bjLVVDHMPpVKqp8Su/V+6MeDzzQBQ0=
github.com/cloudflare/circl v1.6.1/go.mod h1:uddAzsPgqdMAYatqJ0lsjX1oECcQLIlRpzZh3pJrofs=
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
github.com/cyphar/filepath-securejoin v0.6.1 h1:5CeZ1jPXEiYt3+Z6zqprSAgSWiggmpVyciv8syjIpVE=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/ebitengine/purego v0.9.1 h1:a/k2f2HQU3Pi399RPW1MOaZyhKJL9w/xFpKAg4q1s0A=
github.com/elazarl/goproxy v1.7.2 h1:Y2o6urb7Eule09PjlhQRGNsqRfPmYI3KKQLFpCAV3+o=
github.com/elazarl/goproxy v1.7.2/go.mod h1:82vkLNir0ALaW14Rc399OTTjyNREgmdL2cVoIbS6XaE=
github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc=
github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ=
github.com/gliderlabs/ssh v0.3.8 h1:a4YXD1V7xMF9g5nTkdfnja3Sxy1PVDCj1Zg4Wb8vY6c=
github.com/gliderlabs/ssh v0.3.8/go.mod h1:xYoytBv1sV0aL3CavoDuJIQNURXkkfPA/wxQ1pL1fAU=
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI=
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmSxCcxctByoQdvwPiA7DTK7jaaFDBTtu0ic=
github.com/go-git/go-billy/v5 v5.6.2 h1:6Q86EsPXMa7c3YZ3aLAQsMA0VlWmy43r6FHqa/UNbRM=
github.com/go-git/go-billy/v5 v5.6.2/go.mod h1:rcFC2rAsp/erv7CMz9GczHcuD0D32fWzH+MJAU+jaUU=
github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399 h1:eMje31YglSBqCdIqdhKBW8lokaMrL3uTkpGYlE2OOT4=
github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399/go.mod h1:1OCfN199q1Jm3HZlxleg+Dw/mwps2Wbk9frAWm+4FII=
github.com/go-git/go-git/v5 v5.16.4 h1:7ajIEZHZJULcyJebDLo99bGgS0jRrOxzZG4uCk2Yb2Y=
github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE=
github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78=
github.com/godbus/dbus/v5 v5.2.0 h1:3WexO+U+yg9T70v9FdHr9kCxYlazaAXUhx2VMkbfax8=
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 h1:f+oWsMOmNPc8JmEHVZIycC7hBoQxHH9pNKQORJNozsQ=
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8/go.mod h1:wcDNUvekVysuuOpQKo3191zZyTpiI6se1N1ULghS0sw=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A=
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo=
github.com/jchv/go-winloader v0.0.0-20250406163304-c1995be93bd1 h1:njuLRcjAuMKr7kI3D85AXWkw6/+v9PwtV6M6o11sWHQ=
github.com/jchv/go-winloader v0.0.0-20250406163304-c1995be93bd1/go.mod h1:alcuEEnZsY1WQsagKhZDsoPCRoOijYqhZvPwLG0kzVs=
github.com/kevinburke/ssh_config v1.4.0 h1:6xxtP5bZ2E4NF5tuQulISpTO2z8XbtH8cg1PWkxoFkQ=
github.com/kevinburke/ssh_config v1.4.0/go.mod h1:q2RIzfka+BXARoNexmF9gkxEX7DmvbW9P4hIVx2Kg4M=
github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y=
github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/leaanthony/go-ansi-parser v1.6.1 h1:xd8bzARK3dErqkPFtoF9F3/HgN8UQk0ed1YDKpEz01A=
github.com/leaanthony/go-ansi-parser v1.6.1/go.mod h1:+vva/2y4alzVmmIEpk9QDhA7vLC5zKDTRwfZGOp3IWU=
github.com/leaanthony/u v1.1.1 h1:TUFjwDGlNX+WuwVEzDqQwC2lOv0P4uhTQw7CMFdiK7M=
github.com/leaanthony/u v1.1.1/go.mod h1:9+o6hejoRljvZ3BzdYlVL0JYCwtnAsVuN9pVTQcaRfI=
github.com/lmittmann/tint v1.1.2 h1:2CQzrL6rslrsyjqLDwD11bZ5OpLBPU+g3G/r5LSfS8w=
github.com/lmittmann/tint v1.1.2/go.mod h1:HIS3gSy7qNwGCj+5oRjAutErFBl4BzdQP6cJZ0NfMwE=
github.com/matryer/is v1.4.0/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU=
github.com/matryer/is v1.4.1 h1:55ehd8zaGABKLXQUe2awZ99BD/PTc2ls+KV/dXphgEQ=
github.com/matryer/is v1.4.1/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU=
github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE=
github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/onsi/gomega v1.34.1 h1:EUMJIKUjM8sKjYbtxQI9A4z2o+rruxnzNvpknOXie6k=
github.com/onsi/gomega v1.34.1/go.mod h1:kU1QgUvBDLXBJq618Xvm2LUX6rSAfRaFRTcdOeDLwwY=
github.com/pjbgf/sha1cd v0.5.0 h1:a+UkboSi1znleCDUNT3M5YxjOnN1fz2FhN48FlwCxs0=
github.com/pjbgf/sha1cd v0.5.0/go.mod h1:lhpGlyHLpQZoxMv8HcgXvZEhcGs0PG/vsZnEJ7H0iCM=
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ=
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/samber/lo v1.52.0 h1:Rvi+3BFHES3A8meP33VPAxiBZX/Aws5RxrschYGjomw=
github.com/samber/lo v1.52.0/go.mod h1:4+MXEGsJzbKGaUEQFKBq2xtfuznW9oz/WrgyzMzRoM0=
github.com/sergi/go-diff v1.4.0 h1:n/SP9D5ad1fORl+llWyN+D6qoUETXNZARKjyY2/KVCw=
github.com/sergi/go-diff v1.4.0/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4=
github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
github.com/skeema/knownhosts v1.3.2 h1:EDL9mgf4NzwMXCTfaxSD/o/a5fxDw/xL9nkU28JjdBg=
github.com/skeema/knownhosts v1.3.2/go.mod h1:bEg3iQAuw+jyiw+484wwFJoKSLwcfd7fqRy+N0QTiow=
github.com/spf13/cobra v1.10.1 h1:lJeBwCfmrnXthfAupyUTzJ/J4Nc1RsHC/mSRU2dll/s=
github.com/spf13/cobra v1.10.1/go.mod h1:7SmJGaTHFVBY0jW4NXGluQoLvhqFQM+6XSKD+P4XaB0=
github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk=
github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/wailsapp/go-webview2 v1.0.23 h1:jmv8qhz1lHibCc79bMM/a/FqOnnzOGEisLav+a0b9P0=
github.com/wailsapp/mimetype v1.4.1 h1:pQN9ycO7uo4vsUUuPeHEYoUkLVkaRntMnHJxVwYhwHs=
github.com/wailsapp/mimetype v1.4.1/go.mod h1:9aV5k31bBOv5z6u+QP8TltzvNGJPmNJD4XlAL3U+j3o=
github.com/wailsapp/wails/v3 v3.0.0-alpha.41 h1:DYcC1/vtO862sxnoyCOMfLLypbzpFWI257fR6zDYY+Y=
github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM=
github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw=
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.45.0 h1:jMBrvKuj23MTlT0bQEOBcAE0mjg8mK9RXFhRH6nyF3Q=
golang.org/x/exp v0.0.0-20250210185358-939b2ce775ac h1:l5+whBCLH3iH2ZNHYLbAe58bo7yrN4mVcnkHDYz5vvs=
golang.org/x/exp v0.0.0-20250210185358-939b2ce775ac/go.mod h1:hH+7mtFmImwwcMvScyxUhjuVHR3HGaDPMn9rMSUUbxo=
golang.org/x/net v0.0.0-20210505024714-0287a6fb4125/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200810151505-1b9f1253b3ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.37.0 h1:8EGAD0qCmHYZg6J17DvsMy9/wJ7/D/4pV/wfnld5lTU=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME=
gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

33
pkg/display/menu.go Normal file
View file

@ -0,0 +1,33 @@
package display
import (
"runtime"
"github.com/wailsapp/wails/v3/pkg/application"
)
// buildMenu creates and sets the main application menu. This function is called
// during the startup of the display service.
func (s *Service) buildMenu() {
appMenu := s.app.Menu.New()
if runtime.GOOS == "darwin" {
appMenu.AddRole(application.AppMenu)
}
appMenu.AddRole(application.FileMenu)
appMenu.AddRole(application.ViewMenu)
appMenu.AddRole(application.EditMenu)
workspace := appMenu.AddSubmenu("Workspace")
workspace.Add("New").OnClick(func(ctx *application.Context) { /* TODO */ })
workspace.Add("List").OnClick(func(ctx *application.Context) { /* TODO */ })
// Add brand-specific menu items
//if s.brand == DeveloperHub {
// appMenu.AddSubmenu("Developer")
//}
appMenu.AddRole(application.WindowMenu)
appMenu.AddRole(application.HelpMenu)
s.app.Menu.Set(appMenu)
}

73
pkg/display/tray.go Normal file
View file

@ -0,0 +1,73 @@
package display
import (
_ "embed"
"github.com/wailsapp/wails/v3/pkg/application"
)
// systemTray configures and creates the system tray icon and menu. This
// function is called during the startup of the display service.
func (s *Service) systemTray() {
systray := s.app.SystemTray.New()
systray.SetTooltip("Core")
systray.SetLabel("Core")
//appTrayIcon, _ := d.assets.ReadFile("assets/apptray.png")
//
//if runtime.GOOS == "darwin" {
// systray.SetTemplateIcon(appTrayIcon)
//} else {
// // Support for light/dark mode icons
// systray.SetDarkModeIcon(appTrayIcon)
// systray.SetIcon(appTrayIcon)
//}
// Create a hidden window for the system tray menu to interact with
trayWindow := s.app.Window.NewWithOptions(application.WebviewWindowOptions{
Name: "system-tray",
Title: "System Tray Status",
URL: "system-tray.html",
Width: 400,
Frameless: true,
Hidden: true,
})
systray.AttachWindow(trayWindow).WindowOffset(5)
// --- Build Tray Menu ---
trayMenu := s.app.Menu.New()
trayMenu.Add("Open Desktop").OnClick(func(ctx *application.Context) {
for _, window := range s.app.Window.GetAll() {
window.Show()
}
})
trayMenu.Add("Close Desktop").OnClick(func(ctx *application.Context) {
for _, window := range s.app.Window.GetAll() {
window.Hide()
}
})
trayMenu.Add("Environment Info").OnClick(func(ctx *application.Context) {
s.ShowEnvironmentDialog()
})
// Add brand-specific menu items
//switch d.brand {
//case AdminHub:
// trayMenu.Add("Manage Workspace").OnClick(func(ctx *application.Context) { /* TODO */ })
//case ServerHub:
// trayMenu.Add("Server Control").OnClick(func(ctx *application.Context) { /* TODO */ })
//case GatewayHub:
// trayMenu.Add("Routing Table").OnClick(func(ctx *application.Context) { /* TODO */ })
//case DeveloperHub:
// trayMenu.Add("Debug Console").OnClick(func(ctx *application.Context) { /* TODO */ })
//case ClientHub:
// trayMenu.Add("Connect").OnClick(func(ctx *application.Context) { /* TODO */ })
// trayMenu.Add("Disconnect").OnClick(func(ctx *application.Context) { /* TODO */ })
//}
trayMenu.AddSeparator()
trayMenu.Add("Quit").OnClick(func(ctx *application.Context) {
s.app.Quit()
})
systray.SetMenu(trayMenu)
}

View file

@ -0,0 +1,17 @@
# Editor configuration, see https://editorconfig.org
root = true
[*]
charset = utf-8
indent_style = space
indent_size = 2
insert_final_newline = true
trim_trailing_whitespace = true
[*.ts]
quote_type = single
ij_typescript_use_double_quotes = false
[*.md]
max_line_length = off
trim_trailing_whitespace = false

43
pkg/display/ui/.gitignore vendored Normal file
View file

@ -0,0 +1,43 @@
# See https://docs.github.com/get-started/getting-started-with-git/ignoring-files for more about ignoring files.
# Compiled output
/dist
/tmp
/out-tsc
/bazel-out
# Node
/node_modules
npm-debug.log
yarn-error.log
# IDEs and editors
.idea/
.project
.classpath
.c9/
*.launch
.settings/
*.sublime-workspace
# Visual Studio Code
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
.history/*
# Miscellaneous
/.angular/cache
.sass-cache/
/connect.lock
/coverage
/libpeerconnection.log
testem.log
/typings
__screenshots__/
# System files
.DS_Store
Thumbs.db

View file

@ -0,0 +1,4 @@
{
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=827846
"recommendations": ["angular.ng-template"]
}

20
pkg/display/ui/.vscode/launch.json vendored Normal file
View file

@ -0,0 +1,20 @@
{
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"name": "ng serve",
"type": "chrome",
"request": "launch",
"preLaunchTask": "npm: start",
"url": "http://localhost:4200/"
},
{
"name": "ng test",
"type": "chrome",
"request": "launch",
"preLaunchTask": "npm: test",
"url": "http://localhost:9876/debug.html"
}
]
}

42
pkg/display/ui/.vscode/tasks.json vendored Normal file
View file

@ -0,0 +1,42 @@
{
// For more information, visit: https://go.microsoft.com/fwlink/?LinkId=733558
"version": "2.0.0",
"tasks": [
{
"type": "npm",
"script": "start",
"isBackground": true,
"problemMatcher": {
"owner": "typescript",
"pattern": "$tsc",
"background": {
"activeOnStart": true,
"beginsPattern": {
"regexp": "(.*?)"
},
"endsPattern": {
"regexp": "bundle generation complete"
}
}
}
},
{
"type": "npm",
"script": "test",
"isBackground": true,
"problemMatcher": {
"owner": "typescript",
"pattern": "$tsc",
"background": {
"activeOnStart": true,
"beginsPattern": {
"regexp": "(.*?)"
},
"endsPattern": {
"regexp": "bundle generation complete"
}
}
}
}
]
}

59
pkg/display/ui/README.md Normal file
View file

@ -0,0 +1,59 @@
# CoreElementTemplate
This project was generated using [Angular CLI](https://github.com/angular/angular-cli) version 20.3.9.
## Development server
To start a local development server, run:
```bash
ng serve
```
Once the server is running, open your browser and navigate to `http://localhost:4200/`. The application will automatically reload whenever you modify any of the source files.
## Code scaffolding
Angular CLI includes powerful code scaffolding tools. To generate a new component, run:
```bash
ng generate component component-name
```
For a complete list of available schematics (such as `components`, `directives`, or `pipes`), run:
```bash
ng generate --help
```
## Building
To build the project run:
```bash
ng build
```
This will compile your project and store the build artifacts in the `dist/` directory. By default, the production build optimizes your application for performance and speed.
## Running unit tests
To execute unit tests with the [Karma](https://karma-runner.github.io) test runner, use the following command:
```bash
ng test
```
## Running end-to-end tests
For end-to-end (e2e) testing, run:
```bash
ng e2e
```
Angular CLI does not come with an end-to-end testing framework by default. You can choose one that suits your needs.
## Additional Resources
For more information on using the Angular CLI, including detailed command references, visit the [Angular CLI Overview and Command Reference](https://angular.dev/tools/cli) page.

View file

@ -0,0 +1,81 @@
{
"$schema": "./node_modules/@angular/cli/lib/config/schema.json",
"version": 1,
"newProjectRoot": "projects",
"projects": {
"display": {
"projectType": "application",
"schematics": {
"@schematics/angular:component": {
"standalone": false
},
"@schematics/angular:directive": {
"standalone": false
},
"@schematics/angular:pipe": {
"standalone": false
}
},
"root": "",
"sourceRoot": "src",
"prefix": "app",
"architect": {
"build": {
"builder": "@angular/build:application",
"options": {
"browser": "src/main.ts",
"polyfills": [
"zone.js"
],
"tsConfig": "tsconfig.app.json",
"assets": [
{
"glob": "**/*",
"input": "public"
}
],
"styles": []
},
"configurations": {
"production": {
"budgets": [
{
"type": "initial",
"maximumWarning": "500kB",
"maximumError": "1MB"
},
{
"type": "anyComponentStyle",
"maximumWarning": "4kB",
"maximumError": "8kB"
}
],
"outputHashing": "none"
},
"development": {
"optimization": false,
"extractLicenses": false,
"sourceMap": true
}
},
"defaultConfiguration": "production"
},
"serve": {
"builder": "@angular/build:dev-server",
"configurations": {
"production": {
"buildTarget": "display:build:production"
},
"development": {
"buildTarget": "display:build:development"
}
},
"defaultConfiguration": "development"
},
"extract-i18n": {
"builder": "@angular/build:extract-i18n"
}
}
}
}
}

8316
pkg/display/ui/package-lock.json generated Normal file

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,49 @@
{
"name": "display",
"version": "0.0.0",
"scripts": {
"ng": "ng",
"start": "ng serve",
"build": "ng build",
"watch": "ng build --watch --configuration development",
"test": "ng test"
},
"prettier": {
"printWidth": 100,
"singleQuote": true,
"overrides": [
{
"files": "*.html",
"options": {
"parser": "angular"
}
}
]
},
"private": true,
"dependencies": {
"@angular/common": "^20.3.0",
"@angular/compiler": "^20.3.0",
"@angular/core": "^20.3.0",
"@angular/elements": "^20.3.10",
"@angular/forms": "^20.3.0",
"@angular/platform-browser": "^20.3.0",
"@angular/router": "^20.3.0",
"rxjs": "~7.8.0",
"tslib": "^2.3.0",
"zone.js": "~0.15.0"
},
"devDependencies": {
"@angular/build": "^20.3.9",
"@angular/cli": "^20.3.9",
"@angular/compiler-cli": "^20.3.0",
"@types/jasmine": "~5.1.0",
"jasmine-core": "~5.9.0",
"karma": "~6.4.0",
"karma-chrome-launcher": "~3.2.0",
"karma-coverage": "~2.2.0",
"karma-jasmine": "~5.1.0",
"karma-jasmine-html-reporter": "~2.1.0",
"typescript": "~5.9.2"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

View file

@ -0,0 +1,23 @@
import { DoBootstrap, Injector, NgModule, provideBrowserGlobalErrorListeners } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { createCustomElement } from '@angular/elements';
import { App } from './app';
@NgModule({
imports: [
BrowserModule,
App
],
providers: [
provideBrowserGlobalErrorListeners()
]
})
export class AppModule implements DoBootstrap {
constructor(private injector: Injector) {
const el = createCustomElement(App, { injector });
customElements.define('core-display', el);
}
ngDoBootstrap() {}
}

View file

@ -0,0 +1 @@
<h1>Hello, {{ title() }}</h1>

View file

@ -0,0 +1,10 @@
import { Component, signal } from '@angular/core';
@Component({
selector: 'core-display',
templateUrl: './app.html',
standalone: true
})
export class App {
protected readonly title = signal('display');
}

View file

@ -0,0 +1,13 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Display</title>
<base href="/">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="icon" type="image/x-icon" href="favicon.ico">
</head>
<body>
<core-display></core-display>
</body>
</html>

View file

@ -0,0 +1,7 @@
import { platformBrowser } from '@angular/platform-browser';
import { AppModule } from './app/app-module';
platformBrowser().bootstrapModule(AppModule, {
ngZoneEventCoalescing: true,
})
.catch(err => console.error(err));

View file

@ -0,0 +1 @@
/* You can add global styles to this file, and also import other style files */

View file

@ -0,0 +1,15 @@
/* To learn more about Typescript configuration file: https://www.typescriptlang.org/docs/handbook/tsconfig-json.html. */
/* To learn more about Angular compiler options: https://angular.dev/reference/configs/angular-compiler-options. */
{
"extends": "./tsconfig.json",
"compilerOptions": {
"outDir": "./out-tsc/app",
"types": []
},
"include": [
"src/**/*.ts"
],
"exclude": [
"src/**/*.spec.ts"
]
}

View file

@ -0,0 +1,34 @@
/* To learn more about Typescript configuration file: https://www.typescriptlang.org/docs/handbook/tsconfig-json.html. */
/* To learn more about Angular compiler options: https://angular.dev/reference/configs/angular-compiler-options. */
{
"compileOnSave": false,
"compilerOptions": {
"strict": true,
"noImplicitOverride": true,
"noPropertyAccessFromIndexSignature": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true,
"skipLibCheck": true,
"isolatedModules": true,
"experimentalDecorators": true,
"importHelpers": true,
"target": "ES2022",
"module": "preserve"
},
"angularCompilerOptions": {
"enableI18nLegacyMessageIdFormat": false,
"strictInjectionParameters": true,
"strictInputAccessModifiers": true,
"typeCheckHostBindings": true,
"strictTemplates": true
},
"files": [],
"references": [
{
"path": "./tsconfig.app.json"
},
{
"path": "./tsconfig.spec.json"
}
]
}

View file

@ -0,0 +1,14 @@
/* To learn more about Typescript configuration file: https://www.typescriptlang.org/docs/handbook/tsconfig-json.html. */
/* To learn more about Angular compiler options: https://angular.dev/reference/configs/angular-compiler-options. */
{
"extends": "./tsconfig.json",
"compilerOptions": {
"outDir": "./out-tsc/spec",
"types": [
"jasmine"
]
},
"include": [
"src/**/*.ts"
]
}

111
pkg/display/window.go Normal file
View file

@ -0,0 +1,111 @@
package display
import "github.com/wailsapp/wails/v3/pkg/application"
// WindowConfig holds the configuration for a window. This struct is used to
// create a new window with the specified options.
type WindowConfig struct {
Name string
Title string
Width int
Height int
URL string
AlwaysOnTop bool
Hidden bool
MinimiseButtonState application.ButtonState
MaximiseButtonState application.ButtonState
CloseButtonState application.ButtonState
Frameless bool
}
// WindowOption is an interface for applying configuration options to a
// WindowConfig.
type WindowOption interface {
Apply(*WindowConfig)
}
// WindowOptionFunc is a function that implements the WindowOption interface.
// This allows us to use ordinary functions as window options.
type WindowOptionFunc func(*WindowConfig)
// Apply calls the underlying function to apply the configuration.
func (f WindowOptionFunc) Apply(c *WindowConfig) {
f(c)
}
// WithName sets the name of the window.
func WithName(name string) WindowOption {
return WindowOptionFunc(func(c *WindowConfig) {
c.Name = name
})
}
// WithTitle sets the title of the window.
func WithTitle(title string) WindowOption {
return WindowOptionFunc(func(c *WindowConfig) {
c.Title = title
})
}
// WithWidth sets the width of the window.
func WithWidth(width int) WindowOption {
return WindowOptionFunc(func(c *WindowConfig) {
c.Width = width
})
}
// WithHeight sets the height of the window.
func WithHeight(height int) WindowOption {
return WindowOptionFunc(func(c *WindowConfig) {
c.Height = height
})
}
// WithURL sets the URL that the window will load.
func WithURL(url string) WindowOption {
return WindowOptionFunc(func(c *WindowConfig) {
c.URL = url
})
}
// WithAlwaysOnTop sets the window to always be on top of other windows.
func WithAlwaysOnTop(alwaysOnTop bool) WindowOption {
return WindowOptionFunc(func(c *WindowConfig) {
c.AlwaysOnTop = alwaysOnTop
})
}
// WithHidden sets the window to be hidden when it is created.
func WithHidden(hidden bool) WindowOption {
return WindowOptionFunc(func(c *WindowConfig) {
c.Hidden = hidden
})
}
// WithMinimiseButtonState sets the state of the minimise button.
func WithMinimiseButtonState(state application.ButtonState) WindowOption {
return WindowOptionFunc(func(c *WindowConfig) {
c.MinimiseButtonState = state
})
}
// WithMaximiseButtonState sets the state of the maximise button.
func WithMaximiseButtonState(state application.ButtonState) WindowOption {
return WindowOptionFunc(func(c *WindowConfig) {
c.MaximiseButtonState = state
})
}
// WithCloseButtonState sets the state of the close button.
func WithCloseButtonState(state application.ButtonState) WindowOption {
return WindowOptionFunc(func(c *WindowConfig) {
c.CloseButtonState = state
})
}
// WithFrameless sets the window to be frameless.
func WithFrameless(frameless bool) WindowOption {
return WindowOptionFunc(func(c *WindowConfig) {
c.Frameless = frameless
})
}

248
pkg/help/.github/copilot-instructions.md vendored Normal file
View file

@ -0,0 +1,248 @@
# Copilot Instructions for Help Module
## Project Overview
This repository contains the `help` module, which was formerly part of the `Snider/Core` framework. The module provides assistance and documentation functionality for applications, allowing them to embed and display help documentation.
The project consists of three main components:
1. **Go Library**: A Go module that provides a help service for displaying documentation
2. **Documentation**: MkDocs-based documentation that can be embedded in applications
3. **Angular UI Component**: A custom HTML element built with Angular Elements for the help interface
## Technology Stack
- **Go Library**: Go 1.25 with embedded file systems
- **Documentation**: MkDocs Material theme with Python
- **UI Component**: Angular 20.3+ with Angular Elements
- **Build Tools**:
- GoReleaser for Go library releases
- Angular CLI for UI builds
- Task (Taskfile) for documentation builds
- **Package Management**:
- Go modules for Go dependencies
- pip for Python/MkDocs dependencies
- npm for Node.js/Angular dependencies
## Project Structure
```
.
├── help.go # Main Go library implementation
├── help_test.go # Go tests
├── go.mod # Go module definition
├── examples/ # Example applications
│ ├── show_help/ # Example: showing help
│ └── show_at/ # Example: showing help at specific anchor
├── src/ # MkDocs documentation source
│ ├── index.md # Main documentation page
│ ├── images/ # Documentation images
│ └── stylesheets/ # Custom CSS
├── public/ # Built documentation (embedded in Go binary)
├── ui/ # Angular help UI component
│ ├── src/ # Angular source code
│ ├── public/ # Static assets
│ └── package.json # Node.js dependencies
├── mkdocs.yml # MkDocs configuration
├── taskfile.dist.yml # Task runner configuration
└── .github/ # GitHub workflows and configurations
```
## Development Setup
### Prerequisites
- Go 1.25 or later
- Python 3 with pip
- Node.js and npm
- Task (task runner) - optional but recommended
### Initial Setup
1. Install Go dependencies:
```bash
go mod tidy
```
2. Install documentation dependencies:
```bash
pip install -r requirements.txt
```
3. Install UI dependencies (if working on the Angular component):
```bash
cd ui
npm install
```
### Running the Documentation Server
Using Task (recommended):
```bash
task dev
```
Or using MkDocs directly:
```bash
mkdocs serve
```
This starts a live-reloading development server for the documentation at `http://localhost:8000`.
## Building
### Build Documentation
The documentation is built into the `public` directory and embedded in the Go binary:
```bash
task build
# or
mkdocs build --clean -d public
```
### Build UI Component
```bash
cd ui
npm run build
```
This creates the Angular custom element in the `dist` directory.
## Testing
### Go Tests
Run all Go tests with coverage:
```bash
go test -v -coverprofile=coverage.out ./...
```
The test suite includes:
- Service initialization tests
- Display functionality tests
- Error handling tests
- Custom source and asset loading tests
### UI Tests
Run Angular tests:
```bash
cd ui
npm test
```
## Code Style and Conventions
### Go Code
- Follow standard Go conventions and formatting (use `gofmt` or `go fmt`)
- Use meaningful variable and function names
- Keep functions focused and single-purpose
- Follow the existing patterns for interfaces (Logger, App, Core, Display, Help)
- Use context.Context for service lifecycle management
- Embed static assets using `//go:embed` directives
### Documentation (Markdown)
- Follow MkDocs Material conventions
- Use clear, concise language
- Include code examples where appropriate
- Keep line length reasonable for readability
- Use proper heading hierarchy
### Angular/TypeScript Code
- Follow Angular style guide
- Use Prettier for code formatting (configuration in `ui/package.json`)
- Settings:
- Print width: 100 characters
- Single quotes preferred
- Angular parser for HTML files
### General Guidelines
- Write clear, self-documenting code
- Add comments for complex logic or non-obvious decisions
- Keep commits atomic and well-described
- Follow the existing code patterns in the repository
## API Overview
### Main Interfaces
- **Help**: Main interface for the help service with `Show()`, `ShowAt(anchor string)`, and `ServiceStartup(ctx context.Context)` methods
- **Core**: Application core interface providing ACTION dispatch and App access
- **App**: Application interface providing Logger access
- **Logger**: Logging interface with Info and Error methods
- **Display**: Marker interface for display service dependency checking
### Options
The `Options` struct allows configuration:
- `Source`: Path to custom static site directory
- `Assets`: Custom `fs.FS` for documentation assets (can be `embed.FS` or any `fs.FS` implementation)
### Usage Pattern
```go
// Initialize with default embedded documentation
helpService, err := help.New(help.Options{})
// Or with custom embedded assets
helpService, err := help.New(help.Options{
Assets: myDocs,
})
// Or with custom directory source
helpService, err := help.New(help.Options{
Source: "path/to/docs",
})
// Start the service
err = helpService.ServiceStartup(ctx)
// Show help
err = helpService.Show()
// Show help at specific location
err = helpService.ShowAt("ui/how/settings#resetPassword")
```
## Important Notes for AI Assistants
1. **Minimal Changes**: Make the smallest possible changes to achieve the goal
2. **No Breaking Changes**: Don't modify working code unless necessary
3. **Testing**: Always run `go test ./...` before and after changes to ensure nothing breaks
4. **Build Verification**: Ensure `go build` succeeds after making changes
5. **Dependencies**: Check for security vulnerabilities before adding new dependencies using `govulncheck` or similar security scanning tools
6. **Module Name**: The Go module is `github.com/Snider/help` - do not change this
7. **Embedded Assets**: The `public` directory is embedded in the Go binary via `//go:embed all:public/*`
8. **Documentation**: When changing documentation, rebuild with `task build` or `mkdocs build`
## CI/CD Workflows
- **Go CI** (`.github/workflows/go.yml`): Runs tests and uploads coverage on push/PR to main
- **Release** (`.github/workflows/release.yml`): Uses GoReleaser to build and publish releases
## Contributing
When contributing to this repository:
1. Run tests before making changes: `go test ./...`
2. Make minimal, focused changes
3. Run tests after changes to verify nothing broke
4. Follow the existing code style
5. Update documentation if the API changes
6. Ensure all CI checks pass
7. Keep commits atomic with clear messages
## Common Tasks
- **Run tests**: `go test -v ./...`
- **Run tests with coverage**: `go test -v -coverprofile=coverage.out ./...`
- **Build documentation**: `task build` or `mkdocs build --clean -d public`
- **Serve documentation**: `task dev` or `mkdocs serve`
- **Format Go code**: `go fmt ./...`
- **Tidy dependencies**: `go mod tidy`

31
pkg/help/.github/workflows/go.yml vendored Normal file
View file

@ -0,0 +1,31 @@
name: Go CI
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
jobs:
test:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Set up Go
uses: actions/setup-go@v5
with:
go-version: '1.25'
- name: Install system dependencies
run: sudo apt-get update && sudo apt-get install -y libgtk-3-dev libwebkit2gtk-4.1-dev
- name: Install dependencies
run: go mod tidy
- name: Run tests and generate coverage report
run: go test -v -coverprofile=coverage.out ./...
- name: Upload coverage to Codecov
uses: codecov/codecov-action@v4

24
pkg/help/.github/workflows/release.yml vendored Normal file
View file

@ -0,0 +1,24 @@
name: release
on:
push:
tags:
- 'v*'
jobs:
goreleaser:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v2
with:
fetch-depth: 0
- name: Set up Go
uses: actions/setup-go@v2
with:
go-version: 1.18
- name: Run GoReleaser
uses: goreleaser/goreleaser-action@v2
with:
version: latest
args: release --rm-dist
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

18
pkg/help/.gitignore vendored Normal file
View file

@ -0,0 +1,18 @@
# Go
*.exe
*.exe~
*.dll
*.so
*.dylib
*.test
*.out
*.prof
# Node
node_modules/
dist/
.DS_Store
npm-debug.log*
yarn-debug.log*
yarn-error.log*
demo-cli

43
pkg/help/.goreleaser.yaml Normal file
View file

@ -0,0 +1,43 @@
# This is an example .goreleaser.yml file with some sensible defaults.
# Make sure to check the documentation at https://goreleaser.com
before:
hooks:
# You may remove this if you don't use go modules.
- go mod tidy
# you may remove this if you don't need go generate
- go generate ./...
builds:
- env:
- CGO_ENABLED=0
goos:
- linux
- windows
- darwin
archives:
- replacements:
darwin: Darwin
linux: Linux
windows: Windows
386: i386
amd64: x86_64
checksum:
name_template: 'checksums.txt'
snapshot:
name_template: "{{ incpatch .Version }}-next"
changelog:
sort: asc
filters:
exclude:
- '^docs:'
- '^test:'
brews:
- name: help
tap:
owner: Snider
name: homebrew-tap
commit_author:
name: goreleaserbot
email: goreleaser@carlosbecker.com
homepage: "https://github.com/Snider/help"
description: "Help module for the core framework"
license: "EUPL-1.2"

287
pkg/help/LICENSE Normal file
View file

@ -0,0 +1,287 @@
EUROPEAN UNION PUBLIC LICENCE v. 1.2
EUPL © the European Union 2007, 2016
This European Union Public Licence (the EUPL) applies to the Work (as defined
below) which is provided under the terms of this Licence. Any use of the Work,
other than as authorised under this Licence is prohibited (to the extent such
use is covered by a right of the copyright holder of the Work).
The Work is provided under the terms of this Licence when the Licensor (as
defined below) has placed the following notice immediately following the
copyright notice for the Work:
Licensed under the EUPL
or has expressed by any other means his willingness to license under the EUPL.
1. Definitions
In this Licence, the following terms have the following meaning:
- The Licence: this Licence.
- The Original Work: the work or software distributed or communicated by the
Licensor under this Licence, available as Source Code and also as Executable
Code as the case may be.
- Derivative Works: the works or software that could be created by the
Licensee, based upon the Original Work or modifications thereof. This Licence
does not define the extent of modification or dependence on the Original Work
required in order to classify a work as a Derivative Work; this extent is
determined by copyright law applicable in the country mentioned in Article 15.
- The Work: the Original Work or its Derivative Works.
- The Source Code: the human-readable form of the Work which is the most
convenient for people to study and modify.
- The Executable Code: any code which has generally been compiled and which is
meant to be interpreted by a computer as a program.
- The Licensor: the natural or legal person that distributes or communicates
the Work under the Licence.
- Contributor(s): any natural or legal person who modifies the Work under the
Licence, or otherwise contributes to the creation of a Derivative Work.
- The Licensee or You: any natural or legal person who makes any usage of
the Work under the terms of the Licence.
- Distribution or Communication: any act of selling, giving, lending,
renting, distributing, communicating, transmitting, or otherwise making
available, online or offline, copies of the Work or providing access to its
essential functionalities at the disposal of any other natural or legal
person.
2. Scope of the rights granted by the Licence
The Licensor hereby grants You a worldwide, royalty-free, non-exclusive,
sublicensable licence to do the following, for the duration of copyright vested
in the Original Work:
- use the Work in any circumstance and for all usage,
- reproduce the Work,
- modify the Work, and make Derivative Works based upon the Work,
- communicate to the public, including the right to make available or display
the Work or copies thereof to the public and perform publicly, as the case may
be, the Work,
- distribute the Work or copies thereof,
- lend and rent the Work or copies thereof,
- sublicense rights in the Work or copies thereof.
Those rights can be exercised on any media, supports and formats, whether now
known or later invented, as far as the applicable law permits so.
In the countries where moral rights apply, the Licensor waives his right to
exercise his moral right to the extent allowed by law in order to make effective
the licence of the economic rights here above listed.
The Licensor grants to the Licensee royalty-free, non-exclusive usage rights to
any patents held by the Licensor, to the extent necessary to make use of the
rights granted on the Work under this Licence.
3. Communication of the Source Code
The Licensor may provide the Work either in its Source Code form, or as
Executable Code. If the Work is provided as Executable Code, the Licensor
provides in addition a machine-readable copy of the Source Code of the Work
along with each copy of the Work that the Licensor distributes or indicates, in
a notice following the copyright notice attached to the Work, a repository where
the Source Code is easily and freely accessible for as long as the Licensor
continues to distribute or communicate the Work.
4. Limitations on copyright
Nothing in this Licence is intended to deprive the Licensee of the benefits from
any exception or limitation to the exclusive rights of the rights owners in the
Work, of the exhaustion of those rights or of other applicable limitations
thereto.
5. Obligations of the Licensee
The grant of the rights mentioned above is subject to some restrictions and
obligations imposed on the Licensee. Those obligations are the following:
Attribution right: The Licensee shall keep intact all copyright, patent or
trademarks notices and all notices that refer to the Licence and to the
disclaimer of warranties. The Licensee must include a copy of such notices and a
copy of the Licence with every copy of the Work he/she distributes or
communicates. The Licensee must cause any Derivative Work to carry prominent
notices stating that the Work has been modified and the date of modification.
Copyleft clause: If the Licensee distributes or communicates copies of the
Original Works or Derivative Works, this Distribution or Communication will be
done under the terms of this Licence or of a later version of this Licence
unless the Original Work is expressly distributed only under this version of the
Licence — for example by communicating EUPL v. 1.2 only. The Licensee
(becoming Licensor) cannot offer or impose any additional terms or conditions on
the Work or Derivative Work that alter or restrict the terms of the Licence.
Compatibility clause: If the Licensee Distributes or Communicates Derivative
Works or copies thereof based upon both the Work and another work licensed under
a Compatible Licence, this Distribution or Communication can be done under the
terms of this Compatible Licence. For the sake of this clause, Compatible
Licence refers to the licences listed in the appendix attached to this Licence.
Should the Licensee's obligations under the Compatible Licence conflict with
his/her obligations under this Licence, the obligations of the Compatible
Licence shall prevail.
Provision of Source Code: When distributing or communicating copies of the Work,
the Licensee will provide a machine-readable copy of the Source Code or indicate
a repository where this Source will be easily and freely available for as long
as the Licensee continues to distribute or communicate the Work.
Legal Protection: This Licence does not grant permission to use the trade names,
trademarks, service marks, or names of the Licensor, except as required for
reasonable and customary use in describing the origin of the Work and
reproducing the content of the copyright notice.
6. Chain of Authorship
The original Licensor warrants that the copyright in the Original Work granted
hereunder is owned by him/her or licensed to him/her and that he/she has the
power and authority to grant the Licence.
Each Contributor warrants that the copyright in the modifications he/she brings
to the Work are owned by him/her or licensed to him/her and that he/she has the
power and authority to grant the Licence.
Each time You accept the Licence, the original Licensor and subsequent
Contributors grant You a licence to their contributions to the Work, under the
terms of this Licence.
7. Disclaimer of Warranty
The Work is a work in progress, which is continuously improved by numerous
Contributors. It is not a finished work and may therefore contain defects or
bugs inherent to this type of development.
For the above reason, the Work is provided under the Licence on an as is basis
and without warranties of any kind concerning the Work, including without
limitation merchantability, fitness for a particular purpose, absence of defects
or errors, accuracy, non-infringement of intellectual property rights other than
copyright as stated in Article 6 of this Licence.
This disclaimer of warranty is an essential part of the Licence and a condition
for the grant of any rights to the Work.
8. Disclaimer of Liability
Except in the cases of wilful misconduct or damages directly caused to natural
persons, the Licensor will in no event be liable for any direct or indirect,
material or moral, damages of any kind, arising out of the Licence or of the use
of the Work, including without limitation, damages for loss of goodwill, work
stoppage, computer failure or malfunction, loss of data or any commercial
damage, even if the Licensor has been advised of the possibility of such damage.
However, the Licensor will be liable under statutory product liability laws as
far such laws apply to the Work.
9. Additional agreements
While distributing the Work, You may choose to conclude an additional agreement,
defining obligations or services consistent with this Licence. However, if
accepting obligations, You may act only on your own behalf and on your sole
responsibility, not on behalf of the original Licensor or any other Contributor,
and only if You agree to indemnify, defend, and hold each Contributor harmless
for any liability incurred by, or claims asserted against such Contributor by
the fact You have accepted any warranty or additional liability.
10. Acceptance of the Licence
The provisions of this Licence can be accepted by clicking on an icon I agree
placed under the bottom of a window displaying the text of this Licence or by
affirming consent in any other similar way, in accordance with the rules of
applicable law. Clicking on that icon indicates your clear and irrevocable
acceptance of this Licence and all of its terms and conditions.
Similarly, you irrevocably accept this Licence and all of its terms and
conditions by exercising any rights granted to You by Article 2 of this Licence,
such as the use of the Work, the creation by You of a Derivative Work or the
Distribution or Communication by You of the Work or copies thereof.
11. Information to the public
In case of any Distribution or Communication of the Work by means of electronic
communication by You (for example, by offering to download the Work from a
remote location) the distribution channel or media (for example, a website) must
at least provide to the public the information requested by the applicable law
regarding the Licensor, the Licence and the way it may be accessible, concluded,
stored and reproduced by the Licensee.
12. Termination of the Licence
The Licence and the rights granted hereunder will terminate automatically upon
any breach by the Licensee of the terms of the Licence.
Such a termination will not terminate the licences of any person who has
received the Work from the Licensee under the Licence, provided such persons
remain in full compliance with the Licence.
13. Miscellaneous
Without prejudice of Article 9 above, the Licence represents the complete
agreement between the Parties as to the Work.
If any provision of the Licence is invalid or unenforceable under applicable
law, this will not affect the validity or enforceability of the Licence as a
whole. Such provision will be construed or reformed so as necessary to make it
valid and enforceable.
The European Commission may publish other linguistic versions or new versions of
this Licence or updated versions of the Appendix, so far this is required and
reasonable, without reducing the scope of the rights granted by the Licence. New
versions of the Licence will be published with a unique version number.
All linguistic versions of this Licence, approved by the European Commission,
have identical value. Parties can take advantage of the linguistic version of
their choice.
14. Jurisdiction
Without prejudice to specific agreement between parties,
- any litigation resulting from the interpretation of this License, arising
between the European Union institutions, bodies, offices or agencies, as a
Licensor, and any Licensee, will be subject to the jurisdiction of the Court
of Justice of the European Union, as laid down in article 272 of the Treaty on
the Functioning of the European Union,
- any litigation arising between other parties and resulting from the
interpretation of this License, will be subject to the exclusive jurisdiction
of the competent court where the Licensor resides or conducts its primary
business.
15. Applicable Law
Without prejudice to specific agreement between parties,
- this Licence shall be governed by the law of the European Union Member State
where the Licensor has his seat, resides or has his registered office,
- this licence shall be governed by Belgian law if the Licensor has no seat,
residence or registered office inside a European Union Member State.
Appendix
Compatible Licences according to Article 5 EUPL are:
- GNU General Public License (GPL) v. 2, v. 3
- GNU Affero General Public License (AGPL) v. 3
- Open Software License (OSL) v. 2.1, v. 3.0
- Eclipse Public License (EPL) v. 1.0
- CeCILL v. 2.0, v. 2.1
- Mozilla Public Licence (MPL) v. 2
- GNU Lesser General Public Licence (LGPL) v. 2.1, v. 3
- Creative Commons Attribution-ShareAlike v. 3.0 Unported (CC BY-SA 3.0) for
works other than software
- European Union Public Licence (EUPL) v. 1.1, v. 1.2
- Québec Free and Open-Source Licence — Reciprocity (LiLiQ-R) or Strong
Reciprocity (LiLiQ-R+).
The European Commission may update this Appendix to later versions of the above
licences without producing a new version of the EUPL, as long as they provide
the rights granted in Article 2 of this Licence and protect the covered Source
Code from exclusive appropriation.
All other changes or additions to this Appendix require the production of a new
EUPL version.

92
pkg/help/README.md Normal file
View file

@ -0,0 +1,92 @@
# Help Module
[![Go Reference](https://pkg.go.dev/badge/github.com/Snider/help.svg)](https://pkg.go.dev/github.com/Snider/help)
[![Go Report Card](https://goreportcard.com/badge/github.com/Snider/help)](https://goreportcard.com/report/github.com/Snider/help)
[![Codecov](https://codecov.io/gh/Snider/help/branch/main/graph/badge.svg)](https://codecov.io/gh/Snider/help)
[![Build Status](https://github.com/Snider/help/actions/workflows/go.yml/badge.svg)](https://github.com/Snider/help/actions/workflows/go.yml)
[![License: EUPL-1.2](https://img.shields.io/badge/License-EUPL--1.2-yellow.svg)](https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12)
This repository contains the `help` module, which was formerly part of the `Snider/Core` framework. This module provides assistance and documentation functionality.
## Getting Started
This project uses `mkdocs-material` to build the documentation. To get started, you will need to have Python and `pip` installed.
1. **Install the dependencies:**
```bash
pip install -r requirements.txt
```
2. **Run the development server:**
```bash
mkdocs serve
```
## Usage
To use the `help` module, you first need to import it in your Go project:
```go
import "github.com/Snider/help"
```
Next, initialize the help service by calling the `New` function. The `New` function accepts an `Options` struct, which allows you to configure the documentation source.
### Using a custom `embed.FS`
You can provide your own `embed.FS` as a documentation source. This is useful when you want to bundle the documentation with your application.
```go
import (
"embed"
"github.com/Snider/help"
)
//go:embed all:my-docs/build
var myDocs embed.FS
func main() {
helpService, err := help.New(help.Options{
Assets: myDocs,
})
if err != nil {
// Handle error
}
// ...
}
```
### Custom Static Site Source
You can also provide a custom directory containing a static website as the documentation source. To do this, set the `Source` field in the `Options` struct to the path of your static site directory:
```go
helpService, err := help.New(help.Options{
Source: "path/to/your/static/site",
})
if err != nil {
// Handle error
}
```
Once the help service is initialized, you can use the `Show()` and `ShowAt()` methods to display the documentation.
### Displaying Help
The `Show()` method opens the help window to the main page.
```go
err := helpService.Show()
if err != nil {
// Handle error
}
```
The `ShowAt()` method opens the help window to a specific anchor. The provided anchor is normalized into a URL. For example, calling `ShowAt("ui/how/settings#resetPassword")` will open the help window to a URL similar to `http://localhost:8080/docs/ui/how/settings/index.html#resetPassword`. The exact URL depends on how your display service resolves these paths.
```go
err := helpService.ShowAt("ui/how/settings#resetPassword")
if err != nil {
// Handle error
}
```

Some files were not shown because too many files have changed in this diff Show more