diff --git a/e2e/v0/cli/init_test.go b/e2e/v0/cli/init_test.go deleted file mode 100644 index 4054a4109..000000000 --- a/e2e/v0/cli/init_test.go +++ /dev/null @@ -1,70 +0,0 @@ -// Copyright 2022 The envd Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package cli - -import ( - "os" - "path/filepath" - - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - - e2e "github.com/tensorchord/envd/e2e/v0" - "github.com/tensorchord/envd/pkg/app" - "github.com/tensorchord/envd/pkg/home" - "github.com/tensorchord/envd/pkg/util/fileutil" -) - -var _ = Describe("init project", Ordered, func() { - var path string - BeforeAll(func() { - Expect(home.Initialize()).To(Succeed()) - envdApp := app.New() - err := envdApp.Run([]string{"envd.test", "--debug", "bootstrap"}) - Expect(err).To(Succeed()) - e2e.ResetEnvdApp() - path, err = os.MkdirTemp("", "envd_init_test_*") - Expect(err).To(Succeed()) - err = os.WriteFile(filepath.Join(path, "requirements.txt"), []byte("via"), 0666) - Expect(err).To(Succeed()) - }) - - It("init python env", func() { - envdApp := app.New() - err := envdApp.Run([]string{"envd.test", "--debug", "init", "-p", path}) - Expect(err).To(Succeed()) - exist, err := fileutil.FileExists(filepath.Join(path, "build.envd")) - Expect(err).To(Succeed()) - Expect(exist).To(BeTrue()) - }) - - Describe("run init env", Ordered, func() { - var e *e2e.Example - BeforeAll(func() { - // have to use `path` inside ginkgo closure - e = e2e.NewExample(path, "init_test") - e.RunContainer()() - }) - It("exec installed command inside container", func() { - _, err := e.Exec("via --help") - Expect(err).To(Succeed()) - e.DestroyContainer() - }) - }) - - AfterAll(func() { - os.RemoveAll(path) - }) -}) diff --git a/e2e/v1/cli/init_test.go b/e2e/v1/cli/init_test.go deleted file mode 100644 index 0a3772055..000000000 --- a/e2e/v1/cli/init_test.go +++ /dev/null @@ -1,70 +0,0 @@ -// Copyright 2022 The envd Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package cli - -import ( - "os" - "path/filepath" - - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - - e2e "github.com/tensorchord/envd/e2e/v1" - "github.com/tensorchord/envd/pkg/app" - "github.com/tensorchord/envd/pkg/home" - "github.com/tensorchord/envd/pkg/util/fileutil" -) - -var _ = Describe("init project", Ordered, func() { - var path string - BeforeAll(func() { - Expect(home.Initialize()).To(Succeed()) - envdApp := app.New() - err := envdApp.Run([]string{"envd.test", "--debug", "bootstrap"}) - Expect(err).To(Succeed()) - e2e.ResetEnvdApp() - path, err = os.MkdirTemp("", "envd_init_test_*") - Expect(err).To(Succeed()) - err = os.WriteFile(filepath.Join(path, "requirements.txt"), []byte("via"), 0666) - Expect(err).To(Succeed()) - }) - - It("init python env", func() { - envdApp := app.New() - err := envdApp.Run([]string{"envd.test", "--debug", "init", "-p", path}) - Expect(err).To(Succeed()) - exist, err := fileutil.FileExists(filepath.Join(path, "build.envd")) - Expect(err).To(Succeed()) - Expect(exist).To(BeTrue()) - }) - - Describe("run init env", Ordered, func() { - var e *e2e.Example - BeforeAll(func() { - // have to use `path` inside ginkgo closure - e = e2e.NewExample(path, "init_test") - e.RunContainer()() - }) - It("exec installed command inside container", func() { - _, err := e.Exec("via --help") - Expect(err).To(Succeed()) - e.DestroyContainer() - }) - }) - - AfterAll(func() { - os.RemoveAll(path) - }) -}) diff --git a/go.mod b/go.mod index 244f6143a..2c59e8e7b 100644 --- a/go.mod +++ b/go.mod @@ -41,12 +41,29 @@ require ( ) require ( + github.com/atotto/clipboard v0.1.4 // indirect + github.com/charmbracelet/bubbles v0.14.0 // indirect + github.com/charmbracelet/lipgloss v0.6.0 // indirect + github.com/sahilm/fuzzy v0.1.0 // indirect +) + +require ( + github.com/aymanbagabas/go-osc52 v1.0.3 // indirect github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869 // indirect + github.com/charmbracelet/bubbletea v0.23.1 github.com/containerd/ttrpc v1.1.0 // indirect github.com/klauspost/compress v1.15.11 // indirect + github.com/lucasb-eyer/go-colorful v1.2.0 // indirect + github.com/mattn/go-isatty v0.0.16 // indirect + github.com/mattn/go-localereader v0.0.1 // indirect github.com/moby/locker v1.0.1 // indirect + github.com/muesli/ansi v0.0.0-20211018074035-2e021307bc4b // indirect + github.com/muesli/cancelreader v0.2.2 // indirect + github.com/muesli/reflow v0.3.0 // indirect + github.com/muesli/termenv v0.13.0 // indirect github.com/op/go-logging v0.0.0-20160211212156-b2cb9fa56473 // indirect github.com/pkg/errors v0.9.1 // indirect + github.com/rivo/uniseg v0.2.0 // indirect github.com/segmentio/backo-go v1.0.0 // indirect ) @@ -89,7 +106,7 @@ require ( github.com/kr/pretty v0.3.0 // indirect github.com/kr/text v0.2.0 // indirect github.com/magiconair/properties v1.8.6 // indirect - github.com/mattn/go-runewidth v0.0.9 // indirect + github.com/mattn/go-runewidth v0.0.14 // indirect github.com/mitchellh/go-homedir v1.1.0 // indirect github.com/mitchellh/go-wordwrap v0.0.0-20150314170334-ad45545899c7 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect diff --git a/go.sum b/go.sum index f1281fbcb..8dbadfa77 100644 --- a/go.sum +++ b/go.sum @@ -79,6 +79,10 @@ github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kd github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= 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/atotto/clipboard v0.1.4 h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z4= +github.com/atotto/clipboard v0.1.4/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI= +github.com/aymanbagabas/go-osc52 v1.0.3 h1:DTwqENW7X9arYimJrPeGZcV0ln14sGMt3pHZspWD+Mg= +github.com/aymanbagabas/go-osc52 v1.0.3/go.mod h1:zT8H+Rk4VSabYN90pWyugflM3ZhpTZNC7cASDfUCdT4= github.com/aymerick/raymond v2.0.3-0.20180322193309-b565731e1464+incompatible/go.mod h1:osfaiScAUVup+UC9Nfq76eWqDhXlp+4UYaA8uhTBO6g= github.com/bcicen/ctop v0.7.7 h1:huwTYTAHqqObovkg50v8qdTCLSDvPCBvS+k8EM0tP7E= github.com/bcicen/ctop v0.7.7/go.mod h1:jO0mmP/wGx4ZEGCWAoMx8DYFNTYNs6YG8wzCGj+DX+Q= @@ -92,6 +96,15 @@ github.com/cenkalti/backoff/v4 v4.1.2/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInq github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/charmbracelet/bubbles v0.14.0 h1:DJfCwnARfWjZLvMglhSQzo76UZ2gucuHPy9jLWX45Og= +github.com/charmbracelet/bubbles v0.14.0/go.mod h1:bbeTiXwPww4M031aGi8UK2HT9RDWoiNibae+1yCMtcc= +github.com/charmbracelet/bubbletea v0.21.0/go.mod h1:GgmJMec61d08zXsOhqRC/AiOx4K4pmz+VIcRIm1FKr4= +github.com/charmbracelet/bubbletea v0.23.1 h1:CYdteX1wCiCzKNUlwm25ZHBIc1GXlYFyUIte8WPvhck= +github.com/charmbracelet/bubbletea v0.23.1/go.mod h1:JAfGK/3/pPKHTnAS8JIE2u9f61BjWTQY57RbT25aMXU= +github.com/charmbracelet/harmonica v0.2.0/go.mod h1:KSri/1RMQOZLbw7AHqgcBycp8pgJnQMYYT8QZRqZ1Ao= +github.com/charmbracelet/lipgloss v0.5.0/go.mod h1:EZLha/HbzEt7cYqdFPovlqy5FZPj0xFhg5SaqxScmgs= +github.com/charmbracelet/lipgloss v0.6.0 h1:1StyZB9vBSOyuZxQUcUwGr17JmojPNm87inij9N3wJY= +github.com/charmbracelet/lipgloss v0.6.0/go.mod h1:tHh2wr34xcHjC2HCXIlGSG1jaDF0S0atAUvBMP6Ppuk= github.com/checkpoint-restore/go-criu/v5 v5.0.0/go.mod h1:cfwC0EG7HMUenopBsUf9d89JlCLQIfgVcNsNN0t6T2M= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= @@ -418,9 +431,12 @@ 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/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= github.com/labstack/echo/v4 v4.1.11/go.mod h1:i541M3Fj6f76NZtHSj7TXnyM8n2gaodfvfxNnFqi74g= github.com/labstack/echo/v4 v4.5.0/go.mod h1:czIriw4a0C1dFun+ObrXp7ok03xON0N1awStJ6ArI7Y= github.com/labstack/gommon v0.3.0/go.mod h1:MULnywXg0yavhxWKc+lOruYdAhDwPK9wf0OL7NoOu+k= +github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY= +github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/magiconair/properties v1.8.6 h1:5ibWZ6iY0NctNGWo87LalDlEZ6R41TqbbDamhfG/Qzo= github.com/magiconair/properties v1.8.6/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60= @@ -434,10 +450,19 @@ github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hd github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= +github.com/mattn/go-isatty v0.0.16 h1:bq3VjFmv/sOjHtdEhmkEV4x1AJtvUvOJ2PFAZ5+peKQ= +github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-localereader v0.0.1 h1:ygSAOl7ZXTx4RdPYinUpg6W99U8jWvWi9Ye2JC/oIi4= +github.com/mattn/go-localereader v0.0.1/go.mod h1:8fBrzywKY7BI3czFoHkuzRoWE9C+EiG4R1k4Cjx5p88= github.com/mattn/go-runewidth v0.0.0-20170201023540-14207d285c6c/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0= github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= +github.com/mattn/go-runewidth v0.0.10/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk= +github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk= +github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= +github.com/mattn/go-runewidth v0.0.14 h1:+xnbZSEeDbOIg5/mE6JF0w6n9duR1l3/WmbinWVwUuU= +github.com/mattn/go-runewidth v0.0.14/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/mattn/goveralls v0.0.2/go.mod h1:8d1ZMHsd7fW6IRPKQh46F2WRpyib5/X4FOpevwGNQEw= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/mediocregopher/mediocre-go-lib v0.0.0-20181029021733-cb65787f37ed/go.mod h1:dSsfyI2zABAdhcbvkXqgxOxrCsbYeHCPgrZkku60dSg= @@ -472,6 +497,18 @@ github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A= github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= github.com/moul/http2curl v1.0.0/go.mod h1:8UbvGypXm98wA/IqH45anm5Y2Z6ep6O31QGOAZ3H0fQ= github.com/mrunalp/fileutils v0.5.0/go.mod h1:M1WthSahJixYnrXQl/DFQuteStB1weuxD2QJNHXfbSQ= +github.com/muesli/ansi v0.0.0-20211018074035-2e021307bc4b h1:1XF24mVaiu7u+CFywTdcDo2ie1pzzhwjt6RHqzpMU34= +github.com/muesli/ansi v0.0.0-20211018074035-2e021307bc4b/go.mod h1:fQuZ0gauxyBcmsdE3ZT4NasjaRdxmbCS0jRHsrWu3Ho= +github.com/muesli/cancelreader v0.2.0/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo= +github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA= +github.com/muesli/cancelreader v0.2.2/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo= +github.com/muesli/reflow v0.2.1-0.20210115123740-9e1d0d53df68/go.mod h1:Xk+z4oIWdQqJzsxyjgl3P22oYZnHdZ8FFTHAQQt5BMQ= +github.com/muesli/reflow v0.3.0 h1:IFsN6K9NfGtjeggFP+68I4chLZV2yIKsXJFNZ+eWh6s= +github.com/muesli/reflow v0.3.0/go.mod h1:pbwTDkVPibjO2kyvBQRBxTWEEGDGq0FlB1BIKtnHY/8= +github.com/muesli/termenv v0.11.1-0.20220204035834-5ac8409525e0/go.mod h1:Bd5NYQ7pd+SrtBSrSNoBBmXlcY8+Xj4BMJgh8qcZrvs= +github.com/muesli/termenv v0.11.1-0.20220212125758-44cd13922739/go.mod h1:Bd5NYQ7pd+SrtBSrSNoBBmXlcY8+Xj4BMJgh8qcZrvs= +github.com/muesli/termenv v0.13.0 h1:wK20DRpJdDX8b7Ek2QfhvqhRQFZ237RGRO0RQ/Iqdy0= +github.com/muesli/termenv v0.13.0/go.mod h1:sP1+uffeLaEYpyOTb8pLCUctGcGLnoFjSn4YJK5e2bc= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/nats-io/jwt v0.3.0/go.mod h1:fRYCDE99xlTsqUzISS1Bi75UBJ6ljOJQOAAu5VglpSg= github.com/nats-io/nats.go v1.8.1/go.mod h1:BrFz9vVn0fU3AcH9Vn4Kd7W0NpJ651tD5omQ3M8LwxM= @@ -547,6 +584,9 @@ github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7z github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= github.com/prometheus/procfs v0.7.3 h1:4jVXhlkAyzOScmCkXBTOLRLTz8EeU+eyjrwB/EPq0VU= github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= +github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= +github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY= +github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= @@ -558,6 +598,8 @@ github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQD 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/ryanuber/columnize v2.1.0+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= +github.com/sahilm/fuzzy v0.1.0 h1:FzWGaw2Opqyu+794ZQ9SYifWv2EIXpwP4q8dY1kDAwI= +github.com/sahilm/fuzzy v0.1.0/go.mod h1:VFvziUEIMCrT6A6tw2RFIXPXXmzXbOsSHF0DOI8ZK9Y= github.com/schollz/closestmatch v2.1.0+incompatible/go.mod h1:RtP1ddjLong6gTkbtmuhtR2uUrrJOpYzYRvbcPAid+g= github.com/sclevine/agouti v3.0.0+incompatible/go.mod h1:b4WX9W9L1sfQKXeJf1mUTLZKJ48R1S7H23Ji7oFO5Bw= github.com/seccomp/libseccomp-golang v0.9.1/go.mod h1:GbW5+tmTXfcxTToHLXlScSlAvWlF4P2Ca7zGrPiEpWo= @@ -886,9 +928,11 @@ golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220204135822-1c1b9b1eba6a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220825204002-c680a09ffe64/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.3.0 h1:w8ZOecv6NaNa/zC8944JTU3vz4u6Lagfk4RPQxv92NQ= diff --git a/pkg/app/init.go b/pkg/app/init.go index 2e1bed9f3..374d1d229 100644 --- a/pkg/app/init.go +++ b/pkg/app/init.go @@ -15,11 +15,7 @@ package app import ( - "bytes" - "embed" - "fmt" "io/fs" - "os" "path/filepath" "strings" "time" @@ -31,9 +27,6 @@ import ( "github.com/tensorchord/envd/pkg/util/fileutil" ) -//go:embed template -var templatef embed.FS - var CommandInit = &cli.Command{ Name: "init", Category: CategoryBasic, @@ -45,7 +38,6 @@ var CommandInit = &cli.Command{ Usage: "language usage. Support Python, R, Julia", Aliases: []string{"l"}, Required: false, - Value: "python", }, &cli.BoolFlag{ Name: "force", @@ -74,15 +66,7 @@ func isValidLang(lang string) bool { return false } -type pythonEnv struct { - pythonVersion string - requirements string - condaEnv string - indent string - notebook bool -} - -func NewPythonEnv(dir string) (*pythonEnv, error) { +func InitPythonEnv(dir string) error { requirements := "" condaEnv := "" err := filepath.WalkDir(dir, func(path string, d fs.DirEntry, err error) error { @@ -101,36 +85,20 @@ func NewPythonEnv(dir string) (*pythonEnv, error) { return nil } if isCondaEnvFile(d.Name()) && len(condaEnv) <= 0 { - condaEnv = relPath + selectionMap[LabelCondaEnv] = []string{relPath} } return nil }) if err != nil { - return nil, err + return err } - return &pythonEnv{ - pythonVersion: "python", // use the default one - requirements: requirements, - condaEnv: condaEnv, - indent: " ", - notebook: false, - }, nil -} -func (pe *pythonEnv) generate() []byte { - var buf bytes.Buffer - buf.WriteString("def build():\n") - buf.WriteString(fmt.Sprintf("%sbase(os=\"ubuntu20.04\", language=\"%s\")\n", pe.indent, pe.pythonVersion)) - if len(pe.requirements) > 0 { - buf.WriteString(fmt.Sprintf("%sinstall.python_packages(requirements=\"%s\")\n", pe.indent, pe.requirements)) - } - if len(pe.condaEnv) > 0 { - buf.WriteString(fmt.Sprintf("%sinstall.conda_packages(env_file=\"%s\")\n", pe.indent, pe.condaEnv)) + selectionMap[LabelPythonRequirement] = []string{requirements} + if len(requirements) == 0 { + startQuestion(PythonPackageChoice) } - if pe.notebook { - buf.WriteString(fmt.Sprintf("%sconfig.jupyter()\n", pe.indent)) - } - return buf.Bytes() + startQuestion(JupyterChoice) + return nil } // naive check @@ -146,14 +114,6 @@ func isCondaEnvFile(file string) bool { return false } -func initPythonEnv(dir string) ([]byte, error) { - env, err := NewPythonEnv(dir) - if err != nil { - return nil, err - } - return env.generate(), nil -} - func initCommand(clicontext *cli.Context) error { lang := strings.ToLower(clicontext.String("lang")) buildContext, err := filepath.Abs(clicontext.Path("path")) @@ -161,8 +121,12 @@ func initCommand(clicontext *cli.Context) error { if err != nil { return err } + if !isValidLang(lang) { - return errors.Errorf("invalid language (%s)", lang) + startQuestion(LanguageChoice) + lang = selectionMap[LabelLanguage][0] + } else { + selectionMap[LabelLanguage] = []string{lang} } defer func(start time.Time) { telemetry.GetReporter().Telemetry( @@ -178,18 +142,24 @@ func initCommand(clicontext *cli.Context) error { return errors.Errorf("build.envd already exists, use --force to overwrite it") } - var buildEnvdContent []byte if lang == "python" { - buildEnvdContent, err = initPythonEnv(buildContext) - } else { - buildEnvdContent, err = templatef.ReadFile("template/" + lang + ".envd") + err = InitPythonEnv(buildContext) + if err != nil { + return err + } + } else if lang == "r" { + startQuestion(RPackageChoice) } - if err != nil { - return err + + startQuestion(CudaChoice) + if selectionMap[LabelCudaChoice][0] == "Yes" { + startQuestion(CudaVersionChoice) } - err = os.WriteFile(filePath, buildEnvdContent, 0644) + + err = generateFile(clicontext) if err != nil { return errors.Wrapf(err, "Failed to create build.envd") } + return nil } diff --git a/pkg/app/interactive.go b/pkg/app/interactive.go new file mode 100644 index 000000000..54b1888e2 --- /dev/null +++ b/pkg/app/interactive.go @@ -0,0 +1,290 @@ +// Copyright 2022 The envd Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package app + +import ( + "bytes" + "fmt" + "os" + "path/filepath" + "strings" + + tea "github.com/charmbracelet/bubbletea" + "github.com/charmbracelet/lipgloss" + "github.com/cockroachdb/errors" + "github.com/urfave/cli/v2" +) + +var selectionMap = make(map[string][]string) +var itemStyle = lipgloss.NewStyle().Foreground(lipgloss.AdaptiveColor{Light: "0", Dark: "15"}) +var selectedItemStyle = lipgloss.NewStyle().Foreground(lipgloss.AdaptiveColor{Dark: "14", Light: "6"}) +var indentation = " " + +const ( + SINGLE_SELECT string = "single select" + MULTIPLE_SELECT string = "multiple select" + INPUT string = "input" +) + +const ( + LabelLanguage string = "Language" + LabelCuda string = "Cuda" + LabelCudaChoice string = "Cuda Choice" + LabelJupyterChoice string = "Jupyter Choice" + LabelPythonPackage string = "Python Package" + LabelRPackage string = "R Package" + LabelCondaEnv string = "Conda Env" + LabelPythonRequirement string = "Python Requirement" +) + +type model struct { + step int + cursor int + selected map[int]struct{} + input input + exit bool +} + +type input struct { + prompt string + inputType string + label string + options []string +} + +var LanguageChoice = input{ + prompt: "Choose a programming language", + inputType: SINGLE_SELECT, + label: LabelLanguage, + options: []string{ + "python", + "r", + "julia", + }, +} + +var PythonPackageChoice = input{ + prompt: "Choose your python packages", + inputType: MULTIPLE_SELECT, + label: LabelPythonPackage, + options: []string{ + "numpy", + "tensorflow", + }, +} + +var RPackageChoice = input{ + prompt: "Choose your r packages", + inputType: MULTIPLE_SELECT, + label: LabelRPackage, + options: []string{ + "remotes", + "rlang", + }, +} + +var CudaChoice = input{ + prompt: "Include Cuda?", + inputType: SINGLE_SELECT, + label: LabelCudaChoice, + options: []string{ + "Yes", + "No", + }, +} + +var CudaVersionChoice = input{ + prompt: "Choose a cuda version", + inputType: SINGLE_SELECT, + label: LabelCuda, + options: []string{ + "11.6.2", + "11.3.1", + "11.2.2", + }, +} + +var JupyterChoice = input{ + prompt: "Include Jupyter?", + inputType: SINGLE_SELECT, + label: LabelJupyterChoice, + options: []string{ + "Yes", + "No", + }, +} + +func InitModel(input input) model { + return model{ + input: input, + step: 0, + selected: make(map[int]struct{}), + exit: false, + } +} + +func (m model) View() string { + s := m.input.prompt + + switch m.input.inputType { + case SINGLE_SELECT, MULTIPLE_SELECT: + s += m.renderMultipleChoice() + + case INPUT: + // TODO: implement input if needed + + } + + s += "\nPress q to quit. " + if m.input.inputType == MULTIPLE_SELECT { + s += "Press space to select" + } + + return s +} + +func (m model) Init() tea.Cmd { + return nil +} + +func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { + choices := m.input.options + + switch msg := msg.(type) { + case tea.KeyMsg: + switch msg.String() { + case "ctrl+c", "q": + m.exit = true + return m, tea.Quit + case "up", "k": + if m.cursor > 0 { + m.cursor-- + } + case "down", "j": + if m.cursor < len(choices)-1 { + m.cursor++ + } + case " ": + if m.input.inputType == MULTIPLE_SELECT { + _, ok := m.selected[m.cursor] + if ok { + delete(m.selected, m.cursor) + } else { + m.selected[m.cursor] = struct{}{} + } + } + case "enter": + if m.input.inputType == SINGLE_SELECT { + m.selected[m.cursor] = struct{}{} + } + selectionMap[m.input.label] = []string{choices[m.cursor]} + m.addSelection() + return m, tea.Quit + } + } + + return m, nil +} + +func (m model) addSelection() { + selectionMap[m.input.label] = []string{} + for i := range m.selected { + selectionMap[m.input.label] = append(selectionMap[m.input.label], m.input.options[i]) + } +} + +func startQuestion(input input) { + p := tea.NewProgram(InitModel(input)) + m, err := p.Run() + if m.(model).exit { + os.Exit(0) + } + if err != nil { + fmt.Printf("There was an error generating build.envd: %v", err) + os.Exit(1) + } +} +func generateFile(clicontext *cli.Context) error { + var buf bytes.Buffer + buf.WriteString("def build():\n") + buf.WriteString(fmt.Sprintf("%sbase(os=\"ubuntu20.04\", language=\"%s\")\n", indentation, selectionMap[LabelLanguage][0])) + buf.WriteString(generatePackagesStr("python", selectionMap[LabelPythonPackage])) + buf.WriteString(generatePackagesStr("r", selectionMap[LabelRPackage])) + if len(selectionMap[LabelPythonRequirement]) > 0 { + buf.WriteString(fmt.Sprintf("%sinstall.python_packages(requirements=\"%s\")\n", indentation, selectionMap[LabelPythonRequirement][0])) + } + if len(selectionMap[LabelCondaEnv]) > 0 { + buf.WriteString(fmt.Sprintf("%sinstall.conda_packages(env_file=\"%s\")\n", indentation, selectionMap[LabelCondaEnv][0])) + } + if selectionMap[LabelCudaChoice][0] == "Yes" { + buf.WriteString(fmt.Sprintf("%scuda(version=\"%s\", cudann=\"8\")\n", indentation, selectionMap[LabelCuda][0])) + } + if len(selectionMap[LabelJupyterChoice]) > 0 && selectionMap[LabelJupyterChoice][0] == "Yes" { + buf.WriteString(fmt.Sprintf("%sconfig.jupyter()\n", indentation)) + } + + buildEnvdContent := buf.Bytes() + buildContext, err := filepath.Abs(clicontext.Path("path")) + if err != nil { + return err + } + filePath := filepath.Join(buildContext, "build.envd") + err = os.WriteFile(filePath, buildEnvdContent, 0644) + if err != nil { + return errors.Wrap(err, "Failed to create build.envd") + } + return nil +} + +func generatePackagesStr(name string, packages []string) string { + if len(packages) == 0 { + return "" + } + s := fmt.Sprintf("%sinstall.%s_packages(name = [\n", indentation, name) + for i, p := range packages { + s += fmt.Sprintf("%s\"%s\"", strings.Repeat(indentation, 2), p) + if i != len(packages)-1 { + s += ", " + } + s += "\n" + } + s += fmt.Sprintf("%s])\n", indentation) + return s +} + +func (m model) renderMultipleChoice() string { + s := "\n\n" + for i, choice := range m.input.options { + cursor := " " + style := itemStyle + if m.cursor == i { + cursor = ">" + style = selectedItemStyle + } + + if m.input.inputType == MULTIPLE_SELECT { + checked := " " + if _, ok := m.selected[i]; ok { + checked = "x" + } + s += style.Render((fmt.Sprintf("%s [%s] %s", cursor, checked, choice))) + "\n" + } + + if m.input.inputType == SINGLE_SELECT { + s += style.Render(fmt.Sprintf("%s %s", cursor, choice)) + "\n" + } + } + return s +}