Skip to content

Commit

Permalink
Initial try at CLI
Browse files Browse the repository at this point in the history
  • Loading branch information
oxisto committed Dec 7, 2023
1 parent 7889650 commit 234625c
Show file tree
Hide file tree
Showing 14 changed files with 411 additions and 291 deletions.
4 changes: 2 additions & 2 deletions .github/labeler.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@
"finance":
- "finance/**/*"

"repl":
- "repl/**/*"
"cli":
- "cli/**/*"

"ui":
- "ui/**/*"
12 changes: 12 additions & 0 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,18 @@
"mode": "auto",
"program": "${workspaceFolder}/cmd/mgo",
"console": "integratedTerminal",
"cwd": "${workspaceFolder}",
"args": [
"list-portfolios"
]
},
{
"name": "Launch cmd/moneyd",
"type": "go",
"request": "launch",
"mode": "auto",
"program": "${workspaceFolder}/cmd/moneyd",
"console": "integratedTerminal",
"cwd": "${workspaceFolder}"
}
]
Expand Down
27 changes: 22 additions & 5 deletions .vscode/tasks.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,26 @@
"tasks": [
{
"type": "go",
"label": "go: build cmd/mgo",
"label": "go: build cmd/moneyd",
"command": "build",
"args": [
"${workspaceFolder}/cmd/moneyd"
],
"problemMatcher": [
"$go"
],
"group": {
"kind": "build",
"isDefault": true
},
"detail": "cd /Users/oxisto/Repositories/money-gopher; go build ${workspaceFolder}/cmd/moneyd"
},
{
"type": "shell",
"label": "go: install cmd/mgo",
"command": "go",
"args": [
"install",
"${workspaceFolder}/cmd/mgo"
],
"problemMatcher": [
Expand All @@ -15,20 +32,20 @@
"kind": "build",
"isDefault": true
},
"detail": "cd /Users/oxisto/Repositories/money-gopher; go build ${workspaceFolder}/cmd/mgo"
"detail": "cd /Users/oxisto/Repositories/money-gopher; go build ${workspaceFolder}/cmd/moneyd"
},
{
"type": "shell",
"label": "go: generate cmd/mgo",
"command": "go generate",
"label": "go: generate ./...",
"command": "go generate ./...",
"problemMatcher": [
"$go"
],
"group": {
"kind": "build",
"isDefault": true
},
"detail": "cd /Users/oxisto/Repositories/money-gopher; go generate ${workspaceFolder}/cmd/mgo"
"detail": "cd /Users/oxisto/Repositories/money-gopher; go generate ./..."
}
]
}
58 changes: 58 additions & 0 deletions cli/cli.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
// Copyright 2023 Christian Banse
//
// 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.
//
// This file is part of The Money Gopher.

// repl provides a simple Read-Eval-Print-Loop (REPL) to issue commands to an
// integrated client.
package cli

import (
"fmt"
"os"
)

var cmdMap map[string]Command = make(map[string]Command)

// AddCommand adds a command using the specific symbol.
func AddCommand(symbol string, cmd Command) {
cmdMap[symbol] = cmd
}

// Session holds all necessary information about the current CLI session.
type Session struct {
}

// Run runs our CLI command, based on the args. We keep it very simple for now
// without any extra package, so we just take the first arg and see if if
// matches any of our commands
func Run(args []string) {
var (
cmd Command
ok bool
s *Session
)

// Create a new session. TODO(oxisto): We do not yet have auth, but in the
// future we need to fetch a token here
s = new(Session)

// Try to look up command in our command map
cmd, ok = cmdMap[args[1]]
if ok {
cmd.Exec(s, os.Args[1:]...)
} else {
fmt.Print("Command not found.\n")
}
}
15 changes: 2 additions & 13 deletions repl/commands.go → cli/commands.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,20 +14,9 @@
//
// This file is part of The Money Gopher.

package repl
package cli

// Command is a command executed by the REPL.
type Command interface {
Exec(r *REPL, args ...string)
}

type quitCmd struct{}

// Exec implements [Command].
func (*quitCmd) Exec(r *REPL, args ...string) {
r.done = true
}

func init() {
AddCommand("quit", &quitCmd{})
Exec(s *Session, args ...string)
}
30 changes: 30 additions & 0 deletions cli/commands/init.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
// Copyright 2023 Christian Banse
//
// 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.
//
// This file is part of The Money Gopher.

package commands

import "github.com/oxisto/money-gopher/cli"

func init() {
cli.AddCommand("list-securities", &listSecuritiesCmd{})
cli.AddCommand("update-quote", &triggerQuoteUpdate{})
cli.AddCommand("update-all-quotes", &triggerQuoteUpdateAll{})

cli.AddCommand("create-portfolio", &createPortfolio{})
cli.AddCommand("list-portfolios", &listPortfolio{})
cli.AddCommand("portfolio-snapshot", &portfolioSnapshot{})
cli.AddCommand("import-transactions", &importTransactions{})
}
173 changes: 173 additions & 0 deletions cli/commands/portfolio.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
// Copyright 2023 Christian Banse
//
// 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.
//
// This file is part of The Money Gopher.

package commands

import (
"context"
"fmt"
"io"
"log"
"net/http"
"os"
"strings"

"github.com/fatih/color"
"github.com/oxisto/money-gopher/cli"
portfoliov1 "github.com/oxisto/money-gopher/gen"
"github.com/oxisto/money-gopher/gen/portfoliov1connect"

"connectrpc.com/connect"
"google.golang.org/protobuf/types/known/timestamppb"
)

type createPortfolio struct{}

func (cmd *createPortfolio) Exec(s *cli.Session, args ...string) {
client := portfoliov1connect.NewPortfolioServiceClient(
http.DefaultClient, "http://localhost:8080",
connect.WithHTTPGet(),
)
res, err := client.CreatePortfolio(
context.Background(),
connect.NewRequest(&portfoliov1.CreatePortfolioRequest{
Portfolio: &portfoliov1.Portfolio{
Name: args[1],
DisplayName: args[2],
},
}),
)
if err != nil {
log.Println(err)
} else {
log.Println(res)
}
}

type listPortfolio struct{}

func (cmd *listPortfolio) Exec(s *cli.Session, args ...string) {
client := portfoliov1connect.NewPortfolioServiceClient(
http.DefaultClient, "http://localhost:8080",
connect.WithHTTPGet(),
)
res, err := client.ListPortfolios(
context.Background(),
connect.NewRequest(&portfoliov1.ListPortfoliosRequest{}),
)
if err != nil {
log.Println(err)
} else {
in := `This is a list of all portfolios.
`

for _, portfolio := range res.Msg.Portfolios {
snapshot, _ := client.GetPortfolioSnapshot(
context.Background(),
connect.NewRequest(&portfoliov1.GetPortfolioSnapshotRequest{
PortfolioName: portfolio.Name,
}),
)

in += fmt.Sprintf(`
| %-*s |
| %s | %s |
| %-*s | %*s |
| %-*s | %*s |
`,
15+15+3, color.New(color.FgWhite, color.Bold).Sprint(portfolio.DisplayName),
strings.Repeat("-", 15),
strings.Repeat("-", 15),
15, "Market Value",
15, fmt.Sprintf("%.02f €", snapshot.Msg.TotalMarketValue),
15, "Performance",
15, fmt.Sprintf("%s € (%s %%)",
greenOrRed(snapshot.Msg.TotalProfitOrLoss),
greenOrRed(snapshot.Msg.TotalGains*100),
),
)
}

//out, _ := glamour.Render(in, "dark")
fmt.Println(in)
}
}

func greenOrRed(f float32) string {
if f < 0 {
return color.RedString("%.02f", f)
} else {
return color.GreenString("%.02f", f)
}
}

type portfolioSnapshot struct{}

// Exec implements [cli.Command]
func (cmd *portfolioSnapshot) Exec(s *cli.Session, args ...string) {
client := portfoliov1connect.NewPortfolioServiceClient(
http.DefaultClient, "http://localhost:8080",
connect.WithHTTPGet(),
)
res, err := client.GetPortfolioSnapshot(
context.Background(),
connect.NewRequest(&portfoliov1.GetPortfolioSnapshotRequest{
PortfolioName: "My Portfolio",
Time: timestamppb.Now(),
}),
)
if err != nil {
log.Println(err)
} else {
log.Println(res.Msg)
}
}

type importTransactions struct{}

// Exec implements [cli.Command]
func (cmd *importTransactions) Exec(s *cli.Session, args ...string) {
// Read from args[1]
f, err := os.Open(args[1])
if err != nil {
log.Println(err)
return
}
defer f.Close()

b, err := io.ReadAll(f)
if err != nil {
log.Println(err)
return
}

client := portfoliov1connect.NewPortfolioServiceClient(
http.DefaultClient, "http://localhost:8080",
connect.WithHTTPGet(),
)
res, err := client.ImportTransactions(
context.Background(),
connect.NewRequest(&portfoliov1.ImportTransactionsRequest{
PortfolioName: args[0],
FromCsv: string(b),
}),
)
if err != nil {
log.Println(err)
} else {
log.Println(res.Msg)
}
}
Loading

0 comments on commit 234625c

Please sign in to comment.