github.com/vescale/zgraph@v0.0.0-20230410094002-959c02d50f95/cmd/zgraph/main.go (about) 1 // Copyright 2022 zGraph Authors. All rights reserved. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package main 16 17 import ( 18 "context" 19 "database/sql" 20 "errors" 21 "fmt" 22 "io" 23 "strings" 24 25 tea "github.com/charmbracelet/bubbletea" 26 "github.com/jedib0t/go-pretty/v6/table" 27 "github.com/jedib0t/go-pretty/v6/text" 28 "github.com/knz/bubbline" 29 "github.com/spf13/cobra" 30 _ "github.com/vescale/zgraph" 31 ) 32 33 type options struct { 34 global struct { 35 dataDir string 36 } 37 play struct { 38 } 39 serve struct { 40 } 41 } 42 43 func main() { 44 var opt options 45 cmd := cobra.Command{ 46 Use: "zgraph [command] [flags]", 47 Long: `zgraph is a command line tool for wrapping the embeddable graph 48 database 'github.com/vescale/zgraph'', and provide a convenient 49 approach to experiencing the power to graph database`, 50 Example: strings.TrimLeft(` 51 zgraph play # Launch a zGraph playground 52 zgraph play --datadir ./test # Specify the data directory of the playground 53 zgraph service # Launch a zGraph as a data service`, "\n"), 54 RunE: func(cmd *cobra.Command, args []string) error { 55 return cmd.Help() 56 }, 57 SilenceErrors: true, 58 DisableFlagsInUseLine: true, 59 } 60 61 // Global options 62 cmd.Flags().StringVarP(&opt.global.dataDir, "datadir", "D", "./data", "Specify the data directory path") 63 64 // Subcommands 65 cmd.AddCommand(playCmd(&opt)) 66 cmd.AddCommand(servCmd(&opt)) 67 68 err := cmd.Execute() 69 cobra.CheckErr(err) 70 } 71 72 func playCmd(opt *options) *cobra.Command { 73 cmd := &cobra.Command{ 74 Use: "play -D <dirname>", 75 Short: "Run the zgraph as a playground", 76 RunE: func(cmd *cobra.Command, args []string) error { 77 db, err := sql.Open("zgraph", opt.global.dataDir) 78 if err != nil { 79 return err 80 } 81 defer db.Close() 82 83 conn, err := db.Conn(context.Background()) 84 if err != nil { 85 return err 86 } 87 defer conn.Close() 88 89 interact(conn) 90 91 return nil 92 }, 93 SilenceErrors: true, 94 } 95 96 return cmd 97 } 98 99 func servCmd(opt *options) *cobra.Command { 100 cmd := &cobra.Command{ 101 Use: "service -D <dirname> -L :8080 --apis api.yaml", 102 Short: "Run the zgraph as a data service instance", 103 RunE: func(cmd *cobra.Command, args []string) error { 104 // TODO: implement the data API. 105 return nil 106 }, 107 SilenceErrors: true, 108 } 109 110 return cmd 111 } 112 113 func interact(conn *sql.Conn) { 114 fmt.Println("Welcome to zGraph interactive command line.") 115 116 m := bubbline.New() 117 m.Prompt = "zgraph> " 118 m.NextPrompt = " > " 119 m.CheckInputComplete = func(input [][]rune, line, col int) bool { 120 inputLine := string(input[line]) 121 if len(input) == 1 && strings.TrimSpace(inputLine) == "" { 122 return true 123 } 124 return strings.Contains(inputLine, ";") 125 } 126 127 var lastStmt string 128 for { 129 m.Reset() 130 if _, err := tea.NewProgram(m).Run(); err != nil { 131 outputError(err) 132 continue 133 } 134 135 if m.Err != nil { 136 if m.Err == io.EOF { 137 // No more input. 138 break 139 } 140 if errors.Is(m.Err, bubbline.ErrInterrupted) { 141 // Entered Ctrl+C to cancel input. 142 fmt.Println("^C") 143 } else { 144 outputError(m.Err) 145 } 146 continue 147 } 148 149 input := lastStmt + m.Value() 150 stmts := strings.Split(input, ";") 151 for i := 0; i < len(stmts)-1; i++ { 152 stmt := strings.TrimSpace(stmts[i]) 153 if stmt == "" { 154 continue 155 } 156 runQuery(conn, stmt) 157 } 158 lastStmt = stmts[len(stmts)-1] 159 } 160 } 161 162 func runQuery(conn *sql.Conn, query string) { 163 rows, err := conn.QueryContext(context.Background(), query) 164 if err != nil { 165 outputError(err) 166 return 167 } 168 defer rows.Close() 169 170 output, err := render(rows) 171 if err != nil { 172 outputError(err) 173 return 174 } 175 if len(output) > 0 { 176 fmt.Println(output) 177 } 178 } 179 180 func render(rows *sql.Rows) (string, error) { 181 w := table.NewWriter() 182 w.Style().Format = table.FormatOptions{ 183 Footer: text.FormatDefault, 184 Header: text.FormatDefault, 185 Row: text.FormatDefault, 186 } 187 188 cols, err := rows.Columns() 189 if err != nil { 190 return "", err 191 } 192 193 if len(cols) > 0 { 194 var header []any 195 for _, col := range cols { 196 header = append(header, col) 197 } 198 w.AppendHeader(header) 199 } 200 201 dest := make([]any, len(cols)) 202 for i := range cols { 203 var anyStr sql.NullString 204 dest[i] = &anyStr 205 } 206 207 for rows.Next() { 208 if err := rows.Scan(dest...); err != nil { 209 return "", err 210 } 211 var row []any 212 for _, d := range dest { 213 anyStr := *d.(*sql.NullString) 214 row = append(row, anyStr.String) 215 } 216 w.AppendRow(row) 217 } 218 if rows.Err() != nil { 219 return "", rows.Err() 220 } 221 return w.Render(), nil 222 } 223 224 func outputError(err error) { 225 fmt.Printf("Error: %v\n", err) 226 }