Skip to content

Commit 761ba5e

Browse files
ajnavarrosmola
authored andcommitted
cmd: very basic REPL implementation (#97)
* cmd: very basic REPL implementation Basic REPL that only supports query executions.
1 parent 2bbc3bd commit 761ba5e

File tree

4 files changed

+227
-131
lines changed

4 files changed

+227
-131
lines changed

cmd/gitql/main.go

+1
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import (
1010
func main() {
1111
parser := flags.NewNamedParser("gitql", flags.Default)
1212
parser.AddCommand("query", "Execute a SQL query a repository.", "", &CmdQuery{})
13+
parser.AddCommand("shell", "Start an interactive session.", "", &CmdShell{})
1314
parser.AddCommand("version", "Show the version information.", "", &CmdVersion{})
1415

1516
_, err := parser.Parse()

cmd/gitql/query.go

+3-131
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,12 @@
11
package main
22

3-
import (
4-
"errors"
5-
"fmt"
6-
"io"
7-
"os"
8-
"path/filepath"
9-
10-
"github.com/gitql/gitql"
11-
gitqlgit "github.com/gitql/gitql/git"
12-
"github.com/gitql/gitql/internal/format"
13-
"github.com/gitql/gitql/sql"
14-
15-
"gopkg.in/src-d/go-git.v4"
16-
)
17-
183
type CmdQuery struct {
19-
cmd
4+
cmdQueryBase
205

21-
Path string `short:"p" long:"path" description:"Path where the git repository is located"`
226
Format string `short:"f" long:"format" default:"pretty" description:"Ouptut format. Formats supported: pretty, csv, json."`
237
Args struct {
248
SQL string `positional-arg-name:"sql" required:"true" description:"SQL query to execute"`
259
} `positional-args:"yes"`
26-
27-
r *git.Repository
28-
db sql.Database
2910
}
3011

3112
func (c *CmdQuery) Execute(args []string) error {
@@ -37,119 +18,10 @@ func (c *CmdQuery) Execute(args []string) error {
3718
return err
3819
}
3920

40-
return c.executeQuery()
41-
}
42-
43-
func (c *CmdQuery) validate() error {
44-
var err error
45-
c.Path, err = findDotGitFolder(c.Path)
46-
return err
47-
}
48-
49-
func (c *CmdQuery) buildDatabase() error {
50-
c.print("opening %q repository...\n", c.Path)
51-
52-
var err error
53-
c.r, err = git.NewFilesystemRepository(c.Path)
54-
if err != nil {
55-
return err
56-
}
57-
58-
empty, err := c.r.IsEmpty()
59-
if err != nil {
60-
return err
61-
}
62-
63-
if empty {
64-
return errors.New("error: the repository is empty")
65-
}
66-
67-
head, err := c.r.Head()
68-
if err != nil {
69-
return err
70-
}
71-
72-
c.print("current HEAD %q\n", head.Hash())
73-
74-
name := filepath.Base(filepath.Join(c.Path, ".."))
75-
c.db = gitqlgit.NewDatabase(name, c.r)
76-
return nil
77-
}
78-
79-
func (c *CmdQuery) executeQuery() error {
80-
c.print("executing %q at %q\n", c.Args.SQL, c.db.Name())
81-
82-
fmt.Println(c.Args.SQL)
83-
e := gitql.New()
84-
e.AddDatabase(c.db)
85-
schema, iter, err := e.Query(c.Args.SQL)
86-
if err != nil {
87-
return err
88-
}
89-
90-
return c.printQuery(schema, iter)
91-
}
92-
93-
func (c *CmdQuery) printQuery(schema sql.Schema, iter sql.RowIter) error {
94-
f, err := format.NewFormat(c.Format, os.Stdout)
21+
schema, rowIter, err := c.executeQuery(c.Args.SQL)
9522
if err != nil {
9623
return err
9724
}
9825

99-
headers := []string{}
100-
for _, f := range schema {
101-
headers = append(headers, f.Name)
102-
}
103-
104-
if err := f.WriteHeader(headers); err != nil {
105-
return err
106-
}
107-
108-
for {
109-
row, err := iter.Next()
110-
if err == io.EOF {
111-
break
112-
}
113-
if err != nil {
114-
return err
115-
}
116-
117-
record := make([]interface{}, len(row))
118-
for i := 0; i < len(row); i++ {
119-
record[i] = row[i]
120-
}
121-
122-
if err := f.Write(record); err != nil {
123-
return err
124-
}
125-
}
126-
127-
return f.Close()
128-
}
129-
130-
func findDotGitFolder(path string) (string, error) {
131-
if path == "" {
132-
var err error
133-
path, err = os.Getwd()
134-
if err != nil {
135-
return "", err
136-
}
137-
}
138-
139-
git := filepath.Join(path, ".git")
140-
_, err := os.Stat(git)
141-
if err == nil {
142-
return git, nil
143-
}
144-
145-
if !os.IsNotExist(err) {
146-
return "", err
147-
}
148-
149-
next := filepath.Join(path, "..")
150-
if next == path {
151-
return "", errors.New("unable to find a git repository")
152-
}
153-
154-
return findDotGitFolder(next)
26+
return c.printQuery(schema, rowIter, c.Format)
15527
}

cmd/gitql/query_base.go

+134
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
package main
2+
3+
import (
4+
"errors"
5+
"io"
6+
"os"
7+
"path/filepath"
8+
9+
"github.com/gitql/gitql"
10+
gitqlgit "github.com/gitql/gitql/git"
11+
"github.com/gitql/gitql/internal/format"
12+
"github.com/gitql/gitql/sql"
13+
14+
"gopkg.in/src-d/go-git.v4"
15+
)
16+
17+
type cmdQueryBase struct {
18+
cmd
19+
20+
Path string `short:"p" long:"path" description:"Path where the git repository is located"`
21+
22+
db sql.Database
23+
e *gitql.Engine
24+
}
25+
26+
func (c *cmdQueryBase) validate() error {
27+
var err error
28+
c.Path, err = findDotGitFolder(c.Path)
29+
return err
30+
}
31+
32+
func (c *cmdQueryBase) buildDatabase() error {
33+
c.print("opening %q repository...\n", c.Path)
34+
35+
var err error
36+
r, err := git.NewFilesystemRepository(c.Path)
37+
if err != nil {
38+
return err
39+
}
40+
41+
empty, err := r.IsEmpty()
42+
if err != nil {
43+
return err
44+
}
45+
46+
if empty {
47+
return errors.New("error: the repository is empty")
48+
}
49+
50+
head, err := r.Head()
51+
if err != nil {
52+
return err
53+
}
54+
55+
c.print("current HEAD %q\n", head.Hash())
56+
57+
name := filepath.Base(filepath.Join(c.Path, ".."))
58+
59+
c.db = gitqlgit.NewDatabase(name, r)
60+
c.e = gitql.New()
61+
c.e.AddDatabase(c.db)
62+
63+
return nil
64+
}
65+
66+
func (c *cmdQueryBase) executeQuery(sql string) (sql.Schema, sql.RowIter, error) {
67+
c.print("executing %q at %q\n", sql, c.db.Name())
68+
69+
return c.e.Query(sql)
70+
}
71+
72+
func (c *cmdQueryBase) printQuery(schema sql.Schema, iter sql.RowIter, formatId string) error {
73+
f, err := format.NewFormat(formatId, os.Stdout)
74+
if err != nil {
75+
return err
76+
}
77+
78+
headers := []string{}
79+
for _, f := range schema {
80+
headers = append(headers, f.Name)
81+
}
82+
83+
if err := f.WriteHeader(headers); err != nil {
84+
return err
85+
}
86+
87+
for {
88+
row, err := iter.Next()
89+
if err == io.EOF {
90+
break
91+
}
92+
if err != nil {
93+
return err
94+
}
95+
96+
dataRow := make([]interface{}, len(row))
97+
for i := range row {
98+
dataRow[i] = interface{}(row[i])
99+
}
100+
101+
if err := f.Write(dataRow); err != nil {
102+
return err
103+
}
104+
}
105+
106+
return f.Close()
107+
}
108+
109+
func findDotGitFolder(path string) (string, error) {
110+
if path == "" {
111+
var err error
112+
path, err = os.Getwd()
113+
if err != nil {
114+
return "", err
115+
}
116+
}
117+
118+
git := filepath.Join(path, ".git")
119+
_, err := os.Stat(git)
120+
if err == nil {
121+
return git, nil
122+
}
123+
124+
if !os.IsNotExist(err) {
125+
return "", err
126+
}
127+
128+
next := filepath.Join(path, "..")
129+
if next == path {
130+
return "", errors.New("unable to find a git repository")
131+
}
132+
133+
return findDotGitFolder(next)
134+
}

cmd/gitql/shell.go

+89
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
package main
2+
3+
import (
4+
"bufio"
5+
"bytes"
6+
"fmt"
7+
"os"
8+
"strings"
9+
)
10+
11+
type CmdShell struct {
12+
cmdQueryBase
13+
}
14+
15+
func (c *CmdShell) Execute(args []string) error {
16+
if err := c.validate(); err != nil {
17+
return err
18+
}
19+
20+
if err := c.buildDatabase(); err != nil {
21+
return err
22+
}
23+
24+
fmt.Print(`
25+
gitQL SHELL
26+
-----------
27+
You must end your queries with ';'
28+
29+
`)
30+
31+
s := bufio.NewScanner(os.Stdin)
32+
33+
s.Split(scanQueries)
34+
35+
for {
36+
fmt.Print("!> ")
37+
38+
if !s.Scan() {
39+
break
40+
}
41+
42+
query := s.Text()
43+
44+
query = strings.Replace(query, "\n", " ", -1)
45+
query = strings.TrimSpace(query)
46+
47+
fmt.Printf("\n--> Executing query: %s\n\n", query)
48+
49+
schema, rowIter, err := c.executeQuery(query)
50+
if err != nil {
51+
c.printError(err)
52+
continue
53+
}
54+
55+
if err := c.printQuery(schema, rowIter, "pretty"); err != nil {
56+
c.printError(err)
57+
}
58+
}
59+
60+
return s.Err()
61+
}
62+
63+
func (c *CmdShell) printError(err error) {
64+
fmt.Printf("ERROR: %v\n\n", err)
65+
}
66+
67+
func scanQueries(data []byte, atEOF bool) (int, []byte, error) {
68+
if atEOF && len(data) == 0 {
69+
return 0, nil, nil
70+
}
71+
if i := bytes.IndexByte(data, ';'); i >= 0 {
72+
// We have a full newline-terminated line.
73+
return i + 1, dropCR(data[0:i]), nil
74+
}
75+
// If we're at EOF, we have a final, non-terminated line. Return it.
76+
if atEOF {
77+
return len(data), dropCR(data), nil
78+
}
79+
// Request more data.
80+
return 0, nil, nil
81+
}
82+
83+
// dropCR drops a terminal \r from the data.
84+
func dropCR(data []byte) []byte {
85+
if len(data) > 0 && data[len(data)-1] == '\r' {
86+
return data[0 : len(data)-1]
87+
}
88+
return data
89+
}

0 commit comments

Comments
 (0)