refactor(ax): continue AX surface alignment
This commit is contained in:
parent
a8caedaf55
commit
fc34a75fb2
11 changed files with 180 additions and 88 deletions
62
go.sum
62
go.sum
|
|
@ -1,7 +1,11 @@
|
|||
dappco.re/go/core v0.8.0-alpha.1 h1:gj7+Scv+L63Z7wMxbJYHhaRFkHJo2u4MMPuUSv/Dhtk=
|
||||
dappco.re/go/core v0.8.0-alpha.1/go.mod h1:f2/tBZ3+3IqDrg2F5F598llv0nmb/4gJVCFzM5geE4A=
|
||||
dario.cat/mergo v1.0.2/go.mod h1:E/hbnu0NxMFBjpMIE34DRGLWqDy0g5FuKDhCb31ngxA=
|
||||
forge.lthn.ai/Snider/Borg v0.3.1 h1:gfC1ZTpLoZai07oOWJiVeQ8+qJYK8A795tgVGJHbVL8=
|
||||
forge.lthn.ai/Snider/Borg v0.3.1/go.mod h1:Z7DJD0yHXsxSyM7Mjl6/g4gH1NBsIz44Bf5AFlV76Wg=
|
||||
forge.lthn.ai/Snider/Enchantrix v0.0.4/go.mod h1:OGCwuVeZPq3OPe2h6TX/ZbgEjHU6B7owpIBeXQGbSe0=
|
||||
github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
|
||||
github.com/ProtonMail/go-crypto v1.3.0/go.mod h1:9whxjD8Rbs29b4XWbB8irEcE8KHMqaR2e7GWU1R+/PE=
|
||||
github.com/aws/aws-sdk-go-v2 v1.41.4 h1:10f50G7WyU02T56ox1wWXq+zTX9I1zxG46HYuG1hH/k=
|
||||
github.com/aws/aws-sdk-go-v2 v1.41.4/go.mod h1:mwsPRE8ceUUpiTgF7QmQIJ7lgsKUPQOUl3o72QBrE1o=
|
||||
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.7 h1:3kGOqnh1pPeddVa/E37XNTaWJ8W6vrbYV9lJEkCnhuY=
|
||||
|
|
@ -24,47 +28,105 @@ github.com/aws/aws-sdk-go-v2/service/s3 v1.97.1 h1:csi9NLpFZXb9fxY7rS1xVzgPRGMt7
|
|||
github.com/aws/aws-sdk-go-v2/service/s3 v1.97.1/go.mod h1:qXVal5H0ChqXP63t6jze5LmFalc7+ZE7wOdLtZ0LCP0=
|
||||
github.com/aws/smithy-go v1.24.2 h1:FzA3bu/nt/vDvmnkg+R8Xl46gmzEDam6mZ1hzmwXFng=
|
||||
github.com/aws/smithy-go v1.24.2/go.mod h1:YE2RhdIuDbA5E5bTdciG9KrW3+TiEONeUWCqxX9i1Fc=
|
||||
github.com/bep/debounce v1.2.1/go.mod h1:H8yggRPQKLUhUoqrJC1bO2xNya7vanpDl7xR3ISbCJ0=
|
||||
github.com/clipperhouse/uax29/v2 v2.4.0/go.mod h1:Wn1g7MK6OoeDT0vL+Q0SQLDz/KpfsVRgg6W7ihQeh4g=
|
||||
github.com/cloudflare/circl v1.6.3/go.mod h1:2eXP6Qfat4O/Yhh8BznvKnJ+uzEoTQ6jVKJRn81BiS4=
|
||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||
github.com/cyphar/filepath-securejoin v0.6.1/go.mod h1:A8hd4EnAeyujCJRrICiOWqjS1AX0a9kM5XL+NwKoYSc=
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
|
||||
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
|
||||
github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ=
|
||||
github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU=
|
||||
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmSxCcxctByoQdvwPiA7DTK7jaaFDBTtu0ic=
|
||||
github.com/go-git/go-billy/v5 v5.7.0/go.mod h1:/1IUejTKH8xipsAcdfcSAlUlo2J7lkYV8GTKxAT/L3E=
|
||||
github.com/go-git/go-git/v5 v5.16.4/go.mod h1:4Ge4alE/5gPs30F2H1esi2gPd69R0C39lolkucHBOp8=
|
||||
github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78=
|
||||
github.com/godbus/dbus/v5 v5.2.2/go.mod h1:3AAv2+hPq5rdnr5txxxRwiGjPXamgoIHgz9FPBfOp3c=
|
||||
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8/go.mod h1:wcDNUvekVysuuOpQKo3191zZyTpiI6se1N1ULghS0sw=
|
||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/go-github/v39 v39.2.0/go.mod h1:C1s8C5aCC9L+JXIYpJM5GYytdX52vC1bLvHEF1IhBrE=
|
||||
github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU=
|
||||
github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e h1:ijClszYn+mADRFY17kjQEVQ1XRhq2/JR1M3sGqeJoxs=
|
||||
github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA=
|
||||
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/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||
github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k=
|
||||
github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM=
|
||||
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
|
||||
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo=
|
||||
github.com/jchv/go-winloader v0.0.0-20250406163304-c1995be93bd1/go.mod h1:alcuEEnZsY1WQsagKhZDsoPCRoOijYqhZvPwLG0kzVs=
|
||||
github.com/kevinburke/ssh_config v1.4.0/go.mod h1:q2RIzfka+BXARoNexmF9gkxEX7DmvbW9P4hIVx2Kg4M=
|
||||
github.com/klauspost/compress v1.18.4/go.mod h1:R0h/fSBs8DE4ENlcrlib3PsXS61voFxhIs2DeRhCvJ4=
|
||||
github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
|
||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/labstack/echo/v4 v4.13.3/go.mod h1:o90YNEeQWjDozo584l7AwhJMHN0bOC4tAfg+Xox9q5g=
|
||||
github.com/labstack/gommon v0.4.2/go.mod h1:QlUFxVM+SNXhDL/Z7YhocGIBYOiwB0mXm1+1bAPHPyU=
|
||||
github.com/leaanthony/go-ansi-parser v1.6.1/go.mod h1:+vva/2y4alzVmmIEpk9QDhA7vLC5zKDTRwfZGOp3IWU=
|
||||
github.com/leaanthony/gosod v1.0.4/go.mod h1:GKuIL0zzPj3O1SdWQOdgURSuhkF+Urizzxh26t9f1cw=
|
||||
github.com/leaanthony/slicer v1.6.0/go.mod h1:o/Iz29g7LN0GqH3aMjWAe90381nyZlDNquK+mtH2Fj8=
|
||||
github.com/leaanthony/u v1.1.1/go.mod h1:9+o6hejoRljvZ3BzdYlVL0JYCwtnAsVuN9pVTQcaRfI=
|
||||
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/mattn/go-runewidth v0.0.19/go.mod h1:XBkDxAl56ILZc9knddidhrOlY5R/pDhgLpndooCuJAs=
|
||||
github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db/go.mod h1:l0dey0ia/Uv7NcFFVbCLtqEBQbrT4OCwCSKTEv6enCw=
|
||||
github.com/ncruces/go-strftime v1.0.0 h1:HMFp8mLCTPp341M/ZnA4qaf7ZlsbTc+miZjCLOFAw7w=
|
||||
github.com/ncruces/go-strftime v1.0.0/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls=
|
||||
github.com/pjbgf/sha1cd v0.5.0/go.mod h1:lhpGlyHLpQZoxMv8HcgXvZEhcGs0PG/vsZnEJ7H0iCM=
|
||||
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
|
||||
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/samber/lo v1.52.0/go.mod h1:4+MXEGsJzbKGaUEQFKBq2xtfuznW9oz/WrgyzMzRoM0=
|
||||
github.com/schollz/progressbar/v3 v3.18.0/go.mod h1:IsO3lpbaGuzh8zIMzgY3+J8l4C8GjO0Y9S69eFvNsec=
|
||||
github.com/sergi/go-diff v1.4.0/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4=
|
||||
github.com/skeema/knownhosts v1.3.2/go.mod h1:bEg3iQAuw+jyiw+484wwFJoKSLwcfd7fqRy+N0QTiow=
|
||||
github.com/spf13/cobra v1.10.2/go.mod h1:7C1pvHqHw5A4vrJfjNwvOdzYu0Gml16OCs2GRiTUUS4=
|
||||
github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
|
||||
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/tkrajina/go-reflector v0.5.8/go.mod h1:ECbqLgccecY5kPmPmXg1MrHW585yMcDkVl6IvJe64T4=
|
||||
github.com/ulikunitz/xz v0.5.15/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
|
||||
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
|
||||
github.com/valyala/fasttemplate v1.2.2/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ=
|
||||
github.com/wailsapp/go-webview2 v1.0.23/go.mod h1:qJmWAmAmaniuKGZPWwne+uor3AHMB5PFhqiK0Bbj8kc=
|
||||
github.com/wailsapp/mimetype v1.4.1/go.mod h1:9aV5k31bBOv5z6u+QP8TltzvNGJPmNJD4XlAL3U+j3o=
|
||||
github.com/wailsapp/wails/v2 v2.11.0/go.mod h1:jrf0ZaM6+GBc1wRmXsM8cIvzlg0karYin3erahI4+0k=
|
||||
github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw=
|
||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||
golang.org/x/crypto v0.49.0 h1:+Ng2ULVvLHnJ/ZFEq4KdcDd/cfjrrjjNSXNzxg0Y4U4=
|
||||
golang.org/x/crypto v0.49.0/go.mod h1:ErX4dUh2UM+CFYiXZRTcMpEcN8b/1gxEuv3nODoYtCA=
|
||||
golang.org/x/exp v0.0.0-20260212183809-81e46e3db34a/go.mod h1:K79w1Vqn7PoiZn+TkNpx3BUWUQksGO3JcVX6qIjytmA=
|
||||
golang.org/x/mod v0.34.0 h1:xIHgNUUnW6sYkcM5Jleh05DvLOtwc6RitGHbDk4akRI=
|
||||
golang.org/x/mod v0.34.0/go.mod h1:ykgH52iCZe79kzLLMhyCUzhMci+nQj+0XkbXpNYtVjY=
|
||||
golang.org/x/net v0.52.0/go.mod h1:R1MAz7uMZxVMualyPXb+VaqGSa3LIaUqk0eEt3w36Sw=
|
||||
golang.org/x/oauth2 v0.35.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA=
|
||||
golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4=
|
||||
golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.42.0 h1:omrd2nAlyT5ESRdCLYdm3+fMfNFE/+Rf4bDIQImRJeo=
|
||||
golang.org/x/sys v0.42.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
|
||||
golang.org/x/telemetry v0.0.0-20260311193753-579e4da9a98c/go.mod h1:TpUTTEp9frx7rTdLpC9gFG9kdI7zVLFTFFlqaH2Cncw=
|
||||
golang.org/x/term v0.41.0/go.mod h1:3pfBgksrReYfZ5lvYM0kSO0LIkAl4Yl2bXOkKP7Ec2A=
|
||||
golang.org/x/text v0.35.0/go.mod h1:khi/HExzZJ2pGnjenulevKNX1W67CUy0AsXcNubPGCA=
|
||||
golang.org/x/tools v0.43.0 h1:12BdW9CeB3Z+J/I/wj34VMl8X+fEXBxVR90JeMX5E7s=
|
||||
golang.org/x/tools v0.43.0/go.mod h1:uHkMso649BX2cZK6+RpuIPXS3ho2hZo4FVwfoy1vIk0=
|
||||
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/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||
gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
modernc.org/cc/v4 v4.27.1 h1:9W30zRlYrefrDV2JE2O8VDtJ1yPGownxciz5rrbQZis=
|
||||
|
|
|
|||
93
io.go
93
io.go
|
|
@ -59,7 +59,7 @@ type Medium interface {
|
|||
IsDir(path string) bool
|
||||
}
|
||||
|
||||
// Example: info := io.FileInfo{name: "app.yaml", size: 8, mode: 0644}
|
||||
// Example: info := io.NewFileInfo("app.yaml", 8, 0644, time.Unix(0, 0), false)
|
||||
type FileInfo struct {
|
||||
name string
|
||||
size int64
|
||||
|
|
@ -80,7 +80,8 @@ func (info FileInfo) IsDir() bool { return info.isDir }
|
|||
|
||||
func (info FileInfo) Sys() any { return nil }
|
||||
|
||||
// Example: entry := io.DirEntry{name: "app.yaml", mode: 0644}
|
||||
// Example: info := io.NewFileInfo("app.yaml", 8, 0644, time.Unix(0, 0), false)
|
||||
// Example: entry := io.NewDirEntry("app.yaml", false, 0644, info)
|
||||
type DirEntry struct {
|
||||
name string
|
||||
isDir bool
|
||||
|
|
@ -96,6 +97,28 @@ func (entry DirEntry) Type() fs.FileMode { return entry.mode.Type() }
|
|||
|
||||
func (entry DirEntry) Info() (fs.FileInfo, error) { return entry.info, nil }
|
||||
|
||||
// Example: info := io.NewFileInfo("app.yaml", 8, 0644, time.Unix(0, 0), false)
|
||||
func NewFileInfo(name string, size int64, mode fs.FileMode, modTime time.Time, isDir bool) FileInfo {
|
||||
return FileInfo{
|
||||
name: name,
|
||||
size: size,
|
||||
mode: mode,
|
||||
modTime: modTime,
|
||||
isDir: isDir,
|
||||
}
|
||||
}
|
||||
|
||||
// Example: info := io.NewFileInfo("app.yaml", 8, 0644, time.Unix(0, 0), false)
|
||||
// Example: entry := io.NewDirEntry("app.yaml", false, 0644, info)
|
||||
func NewDirEntry(name string, isDir bool, mode fs.FileMode, info fs.FileInfo) DirEntry {
|
||||
return DirEntry{
|
||||
name: name,
|
||||
isDir: isDir,
|
||||
mode: mode,
|
||||
info: info,
|
||||
}
|
||||
}
|
||||
|
||||
// Example: _ = io.Local.Read("/etc/hostname")
|
||||
var Local Medium
|
||||
|
||||
|
|
@ -384,10 +407,7 @@ type MemoryFile struct {
|
|||
type MockFile = MemoryFile
|
||||
|
||||
func (file *MemoryFile) Stat() (fs.FileInfo, error) {
|
||||
return FileInfo{
|
||||
name: file.name,
|
||||
size: int64(len(file.content)),
|
||||
}, nil
|
||||
return NewFileInfo(file.name, int64(len(file.content)), 0, time.Time{}, false), nil
|
||||
}
|
||||
|
||||
func (file *MemoryFile) Read(buffer []byte) (int, error) {
|
||||
|
|
@ -468,32 +488,24 @@ func (medium *MemoryMedium) List(path string) ([]fs.DirEntry, error) {
|
|||
dirName := rest[:idx]
|
||||
if !seen[dirName] {
|
||||
seen[dirName] = true
|
||||
entries = append(entries, DirEntry{
|
||||
name: dirName,
|
||||
isDir: true,
|
||||
mode: fs.ModeDir | 0755,
|
||||
info: FileInfo{
|
||||
name: dirName,
|
||||
isDir: true,
|
||||
mode: fs.ModeDir | 0755,
|
||||
},
|
||||
})
|
||||
entries = append(entries, NewDirEntry(
|
||||
dirName,
|
||||
true,
|
||||
fs.ModeDir|0755,
|
||||
NewFileInfo(dirName, 0, fs.ModeDir|0755, time.Time{}, true),
|
||||
))
|
||||
}
|
||||
}
|
||||
continue
|
||||
}
|
||||
if !seen[rest] {
|
||||
seen[rest] = true
|
||||
entries = append(entries, DirEntry{
|
||||
name: rest,
|
||||
isDir: false,
|
||||
mode: 0644,
|
||||
info: FileInfo{
|
||||
name: rest,
|
||||
size: int64(len(content)),
|
||||
mode: 0644,
|
||||
},
|
||||
})
|
||||
entries = append(entries, NewDirEntry(
|
||||
rest,
|
||||
false,
|
||||
0644,
|
||||
NewFileInfo(rest, int64(len(content)), 0644, time.Time{}, false),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -510,16 +522,12 @@ func (medium *MemoryMedium) List(path string) ([]fs.DirEntry, error) {
|
|||
}
|
||||
if !seen[rest] {
|
||||
seen[rest] = true
|
||||
entries = append(entries, DirEntry{
|
||||
name: rest,
|
||||
isDir: true,
|
||||
mode: fs.ModeDir | 0755,
|
||||
info: FileInfo{
|
||||
name: rest,
|
||||
isDir: true,
|
||||
mode: fs.ModeDir | 0755,
|
||||
},
|
||||
})
|
||||
entries = append(entries, NewDirEntry(
|
||||
rest,
|
||||
true,
|
||||
fs.ModeDir|0755,
|
||||
NewFileInfo(rest, 0, fs.ModeDir|0755, time.Time{}, true),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -532,19 +540,10 @@ func (medium *MemoryMedium) Stat(path string) (fs.FileInfo, error) {
|
|||
if !ok {
|
||||
modTime = time.Now()
|
||||
}
|
||||
return FileInfo{
|
||||
name: core.PathBase(path),
|
||||
size: int64(len(content)),
|
||||
mode: 0644,
|
||||
modTime: modTime,
|
||||
}, nil
|
||||
return NewFileInfo(core.PathBase(path), int64(len(content)), 0644, modTime, false), nil
|
||||
}
|
||||
if _, ok := medium.Dirs[path]; ok {
|
||||
return FileInfo{
|
||||
name: core.PathBase(path),
|
||||
isDir: true,
|
||||
mode: fs.ModeDir | 0755,
|
||||
}, nil
|
||||
return NewFileInfo(core.PathBase(path), 0, fs.ModeDir|0755, time.Time{}, true), nil
|
||||
}
|
||||
return nil, core.E("io.MemoryMedium.Stat", core.Concat("path not found: ", path), fs.ErrNotExist)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -511,8 +511,6 @@ func TestClient_EmptyPaths_Ugly(t *testing.T) {
|
|||
err = m.EnsureDir("")
|
||||
assert.NoError(t, err)
|
||||
|
||||
// IsDir empty path (should be true for root, but current impl returns false for "")
|
||||
// Wait, I noticed IsDir returns false for "" in the code.
|
||||
assert.False(t, m.IsDir(""))
|
||||
|
||||
// Exists empty path (root exists)
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import (
|
|||
goio "io"
|
||||
"io/fs"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
|
@ -11,7 +12,7 @@ import (
|
|||
|
||||
// --- MemoryMedium Tests ---
|
||||
|
||||
func TestClient_NewMemoryMedium_Good(t *testing.T) {
|
||||
func TestMemoryMedium_NewMemoryMedium_Good(t *testing.T) {
|
||||
medium := NewMemoryMedium()
|
||||
assert.NotNil(t, medium)
|
||||
assert.NotNil(t, medium.Files)
|
||||
|
|
@ -20,6 +21,31 @@ func TestClient_NewMemoryMedium_Good(t *testing.T) {
|
|||
assert.Empty(t, medium.Dirs)
|
||||
}
|
||||
|
||||
func TestMemoryMedium_NewFileInfo_Good(t *testing.T) {
|
||||
info := NewFileInfo("app.yaml", 8, 0644, time.Unix(0, 0), false)
|
||||
|
||||
assert.Equal(t, "app.yaml", info.Name())
|
||||
assert.Equal(t, int64(8), info.Size())
|
||||
assert.Equal(t, fs.FileMode(0644), info.Mode())
|
||||
assert.True(t, info.ModTime().Equal(time.Unix(0, 0)))
|
||||
assert.False(t, info.IsDir())
|
||||
assert.Nil(t, info.Sys())
|
||||
}
|
||||
|
||||
func TestMemoryMedium_NewDirEntry_Good(t *testing.T) {
|
||||
info := NewFileInfo("app.yaml", 8, 0644, time.Unix(0, 0), false)
|
||||
entry := NewDirEntry("app.yaml", false, 0644, info)
|
||||
|
||||
assert.Equal(t, "app.yaml", entry.Name())
|
||||
assert.False(t, entry.IsDir())
|
||||
assert.Equal(t, fs.FileMode(0), entry.Type())
|
||||
|
||||
entryInfo, err := entry.Info()
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, "app.yaml", entryInfo.Name())
|
||||
assert.Equal(t, int64(8), entryInfo.Size())
|
||||
}
|
||||
|
||||
func TestClient_MockMedium_Read_Good(t *testing.T) {
|
||||
m := NewMockMedium()
|
||||
m.Files["test.txt"] = "hello world"
|
||||
|
|
|
|||
|
|
@ -31,6 +31,8 @@ type Node struct {
|
|||
var _ coreio.Medium = (*Node)(nil)
|
||||
var _ fs.ReadFileFS = (*Node)(nil)
|
||||
|
||||
// Example: nodeTree := node.New()
|
||||
// Example: _ = nodeTree.Write("config/app.yaml", "port: 8080")
|
||||
func New() *Node {
|
||||
return &Node{files: make(map[string]*dataFile)}
|
||||
}
|
||||
|
|
@ -123,6 +125,7 @@ func (node *Node) LoadTar(data []byte) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
// Example: _ = nodeTree.WalkNode("config", func(_ string, _ fs.DirEntry, _ error) error { return nil })
|
||||
func (node *Node) WalkNode(root string, fn fs.WalkDirFunc) error {
|
||||
return fs.WalkDir(node, root, fn)
|
||||
}
|
||||
|
|
@ -175,6 +178,7 @@ func (node *Node) WalkWithOptions(root string, fn fs.WalkDirFunc, options WalkOp
|
|||
})
|
||||
}
|
||||
|
||||
// Example: content, _ := nodeTree.ReadFile("config/app.yaml")
|
||||
func (node *Node) ReadFile(name string) ([]byte, error) {
|
||||
name = core.TrimPrefix(name, "/")
|
||||
file, ok := node.files[name]
|
||||
|
|
|
|||
1
s3/s3.go
1
s3/s3.go
|
|
@ -108,7 +108,6 @@ func New(options Options) (*Medium, error) {
|
|||
return medium, nil
|
||||
}
|
||||
|
||||
// objectKey maps a virtual path to the full S3 object key.
|
||||
func (medium *Medium) objectKey(filePath string) string {
|
||||
// Clean the path using a leading "/" to sandbox traversal attempts,
|
||||
// then strip the "/" prefix. This ensures ".." can't escape.
|
||||
|
|
|
|||
|
|
@ -86,8 +86,6 @@ func (medium *Medium) Close() error {
|
|||
return nil
|
||||
}
|
||||
|
||||
// normaliseEntryPath normalises a path for consistent storage.
|
||||
// Uses a leading "/" before Clean to sandbox traversal attempts.
|
||||
func normaliseEntryPath(filePath string) string {
|
||||
clean := path.Clean("/" + filePath)
|
||||
if clean == "/" {
|
||||
|
|
@ -424,7 +422,10 @@ func (medium *Medium) List(filePath string) ([]fs.DirEntry, error) {
|
|||
}
|
||||
}
|
||||
|
||||
return entries, rows.Err()
|
||||
if err := rows.Err(); err != nil {
|
||||
return nil, core.E("sqlite.List", "rows", err)
|
||||
}
|
||||
return entries, nil
|
||||
}
|
||||
|
||||
func (medium *Medium) Stat(filePath string) (fs.FileInfo, error) {
|
||||
|
|
|
|||
|
|
@ -45,7 +45,6 @@ func (medium *Medium) Close() error {
|
|||
return medium.store.Close()
|
||||
}
|
||||
|
||||
// splitGroupKeyPath splits a group/key path into store components.
|
||||
func splitGroupKeyPath(entryPath string) (group, key string) {
|
||||
clean := path.Clean(entryPath)
|
||||
clean = core.TrimPrefix(clean, "/")
|
||||
|
|
@ -166,7 +165,10 @@ func (medium *Medium) List(entryPath string) ([]fs.DirEntry, error) {
|
|||
}
|
||||
entries = append(entries, &keyValueDirEntry{name: groupName, isDir: true})
|
||||
}
|
||||
return entries, rows.Err()
|
||||
if err := rows.Err(); err != nil {
|
||||
return nil, core.E("store.List", "rows", err)
|
||||
}
|
||||
return entries, nil
|
||||
}
|
||||
|
||||
if key != "" {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
// Package workspace creates encrypted workspaces on top of io.Medium.
|
||||
//
|
||||
// service, _ := workspace.New(workspace.Options{Core: core.New(), Crypt: cryptProvider})
|
||||
// service, _ := workspace.New(workspace.Options{Core: core.New(), CryptProvider: cryptProvider})
|
||||
// workspaceID, _ := service.CreateWorkspace("alice", "pass123")
|
||||
// _ = service.SwitchWorkspace(workspaceID)
|
||||
// _ = service.WorkspaceFileSet("notes/todo.txt", "ship it")
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ import (
|
|||
"dappco.re/go/core/io"
|
||||
)
|
||||
|
||||
// Example: service, _ := workspace.New(workspace.Options{Core: core.New(), Crypt: cryptProvider})
|
||||
// Example: service, _ := workspace.New(workspace.Options{Core: core.New(), CryptProvider: cryptProvider})
|
||||
type Workspace interface {
|
||||
CreateWorkspace(identifier, password string) (string, error)
|
||||
SwitchWorkspace(workspaceID string) error
|
||||
|
|
@ -41,26 +41,24 @@ type WorkspaceCommand struct {
|
|||
WorkspaceID string
|
||||
}
|
||||
|
||||
// Example: service, _ := workspace.New(workspace.Options{Core: core.New(), Crypt: cryptProvider})
|
||||
// Example: service, _ := workspace.New(workspace.Options{Core: core.New(), CryptProvider: cryptProvider})
|
||||
type Options struct {
|
||||
// Core is the Core runtime used by the service.
|
||||
Core *core.Core
|
||||
// Crypt is the PGP key generation dependency.
|
||||
Crypt CryptProvider
|
||||
CryptProvider CryptProvider
|
||||
}
|
||||
|
||||
// Example: service, _ := workspace.New(workspace.Options{Core: core.New(), Crypt: cryptProvider})
|
||||
// Example: service, _ := workspace.New(workspace.Options{Core: core.New(), CryptProvider: cryptProvider})
|
||||
type Service struct {
|
||||
crypt CryptProvider
|
||||
cryptProvider CryptProvider
|
||||
activeWorkspaceID string
|
||||
rootPath string
|
||||
medium io.Medium
|
||||
lock sync.RWMutex
|
||||
stateLock sync.RWMutex
|
||||
}
|
||||
|
||||
var _ Workspace = (*Service)(nil)
|
||||
|
||||
// Example: service, _ := workspace.New(workspace.Options{Core: core.New(), Crypt: cryptProvider})
|
||||
// Example: service, _ := workspace.New(workspace.Options{Core: core.New(), CryptProvider: cryptProvider})
|
||||
// workspaceID, _ := service.CreateWorkspace("alice", "pass123")
|
||||
func New(options Options) (*Service, error) {
|
||||
home := resolveWorkspaceHomeDirectory()
|
||||
|
|
@ -72,14 +70,14 @@ func New(options Options) (*Service, error) {
|
|||
if options.Core == nil {
|
||||
return nil, core.E("workspace.New", "core is required", fs.ErrInvalid)
|
||||
}
|
||||
|
||||
service := &Service{
|
||||
rootPath: rootPath,
|
||||
medium: io.Local,
|
||||
if options.CryptProvider == nil {
|
||||
return nil, core.E("workspace.New", "crypt provider is required", fs.ErrInvalid)
|
||||
}
|
||||
|
||||
if options.Crypt != nil {
|
||||
service.crypt = options.Crypt
|
||||
service := &Service{
|
||||
cryptProvider: options.CryptProvider,
|
||||
rootPath: rootPath,
|
||||
medium: io.Local,
|
||||
}
|
||||
|
||||
if err := service.medium.EnsureDir(rootPath); err != nil {
|
||||
|
|
@ -91,11 +89,11 @@ func New(options Options) (*Service, error) {
|
|||
|
||||
// Example: workspaceID, _ := service.CreateWorkspace("alice", "pass123")
|
||||
func (service *Service) CreateWorkspace(identifier, password string) (string, error) {
|
||||
service.lock.Lock()
|
||||
defer service.lock.Unlock()
|
||||
service.stateLock.Lock()
|
||||
defer service.stateLock.Unlock()
|
||||
|
||||
if service.crypt == nil {
|
||||
return "", core.E("workspace.CreateWorkspace", "crypt service not available", nil)
|
||||
if service.cryptProvider == nil {
|
||||
return "", core.E("workspace.CreateWorkspace", "crypt provider not available", nil)
|
||||
}
|
||||
|
||||
hash := sha256.Sum256([]byte(identifier))
|
||||
|
|
@ -115,7 +113,7 @@ func (service *Service) CreateWorkspace(identifier, password string) (string, er
|
|||
}
|
||||
}
|
||||
|
||||
privKey, err := service.crypt.CreateKeyPair(identifier, password)
|
||||
privKey, err := service.cryptProvider.CreateKeyPair(identifier, password)
|
||||
if err != nil {
|
||||
return "", core.E("workspace.CreateWorkspace", "failed to generate keys", err)
|
||||
}
|
||||
|
|
@ -129,8 +127,8 @@ func (service *Service) CreateWorkspace(identifier, password string) (string, er
|
|||
|
||||
// Example: _ = service.SwitchWorkspace(workspaceID)
|
||||
func (service *Service) SwitchWorkspace(workspaceID string) error {
|
||||
service.lock.Lock()
|
||||
defer service.lock.Unlock()
|
||||
service.stateLock.Lock()
|
||||
defer service.stateLock.Unlock()
|
||||
|
||||
workspaceDirectory, err := service.resolveWorkspaceDirectory("workspace.SwitchWorkspace", workspaceID)
|
||||
if err != nil {
|
||||
|
|
@ -144,8 +142,6 @@ func (service *Service) SwitchWorkspace(workspaceID string) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
// resolveActiveWorkspaceFilePath resolves a file path inside the active workspace files root.
|
||||
// It rejects empty names and traversal outside the workspace root.
|
||||
func (service *Service) resolveActiveWorkspaceFilePath(operation, workspaceFilePath string) (string, error) {
|
||||
if service.activeWorkspaceID == "" {
|
||||
return "", core.E(operation, "no active workspace", nil)
|
||||
|
|
@ -163,8 +159,8 @@ func (service *Service) resolveActiveWorkspaceFilePath(operation, workspaceFileP
|
|||
|
||||
// Example: content, _ := service.WorkspaceFileGet("notes/todo.txt")
|
||||
func (service *Service) WorkspaceFileGet(workspaceFilePath string) (string, error) {
|
||||
service.lock.RLock()
|
||||
defer service.lock.RUnlock()
|
||||
service.stateLock.RLock()
|
||||
defer service.stateLock.RUnlock()
|
||||
|
||||
filePath, err := service.resolveActiveWorkspaceFilePath("workspace.WorkspaceFileGet", workspaceFilePath)
|
||||
if err != nil {
|
||||
|
|
@ -175,8 +171,8 @@ func (service *Service) WorkspaceFileGet(workspaceFilePath string) (string, erro
|
|||
|
||||
// Example: _ = service.WorkspaceFileSet("notes/todo.txt", "ship it")
|
||||
func (service *Service) WorkspaceFileSet(workspaceFilePath, content string) error {
|
||||
service.lock.Lock()
|
||||
defer service.lock.Unlock()
|
||||
service.stateLock.Lock()
|
||||
defer service.stateLock.Unlock()
|
||||
|
||||
filePath, err := service.resolveActiveWorkspaceFilePath("workspace.WorkspaceFileSet", workspaceFilePath)
|
||||
if err != nil {
|
||||
|
|
|
|||
|
|
@ -8,12 +8,12 @@ import (
|
|||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
type stubCrypt struct {
|
||||
type stubCryptProvider struct {
|
||||
key string
|
||||
err error
|
||||
}
|
||||
|
||||
func (s stubCrypt) CreateKeyPair(_, _ string) (string, error) {
|
||||
func (s stubCryptProvider) CreateKeyPair(_, _ string) (string, error) {
|
||||
if s.err != nil {
|
||||
return "", s.err
|
||||
}
|
||||
|
|
@ -26,11 +26,16 @@ func newTestService(t *testing.T) (*Service, string) {
|
|||
tempHome := t.TempDir()
|
||||
t.Setenv("HOME", tempHome)
|
||||
|
||||
svc, err := New(Options{Core: core.New(), Crypt: stubCrypt{key: "private-key"}})
|
||||
svc, err := New(Options{Core: core.New(), CryptProvider: stubCryptProvider{key: "private-key"}})
|
||||
require.NoError(t, err)
|
||||
return svc, tempHome
|
||||
}
|
||||
|
||||
func TestService_New_MissingCryptProvider_Bad(t *testing.T) {
|
||||
_, err := New(Options{Core: core.New()})
|
||||
require.Error(t, err)
|
||||
}
|
||||
|
||||
func TestService_Workspace_RoundTrip_Good(t *testing.T) {
|
||||
s, tempHome := newTestService(t)
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue