Skip to content

Commit bf45f39

Browse files
committed
issue #70: proof of concept refactoring of diff command
1 parent 1522818 commit bf45f39

File tree

9 files changed

+150
-78
lines changed

9 files changed

+150
-78
lines changed

docs/changelog.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@
6868
"2013-12-27T00:00:00Z": 2
6969
}
7070
71-
$ maintainer github contribution diff --base=/tmp/snap.01.2013.json 2013
71+
$ maintainer github contribution diff /tmp/snap.01.2013.json 2013
7272
Day / Week #46 #48 #49 #50
7373
---------------------- --------------- --------------- --------------- -----------
7474
Sunday - - - -
@@ -81,7 +81,7 @@
8181
---------------------- --------------- --------------- --------------- -----------
8282
The diff between head{"/tmp/snap.02.2013.json"} → base{"/tmp/snap.01.2013.json"}
8383
84-
$ maintainer github contribution diff --base=/tmp/snap.01.2013.json --head=/tmp/snap.02.2013.json
84+
$ maintainer github contribution diff /tmp/snap.01.2013.json /tmp/snap.02.2013.json
8585
```
8686

8787
* Suggests a reasonable date to contribute

internal/command/github/contribution.go

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@ import (
1212
"go.octolab.org/toolset/maintainer/internal/command/github/view"
1313
"go.octolab.org/toolset/maintainer/internal/config"
1414
"go.octolab.org/toolset/maintainer/internal/model/github/contribution"
15-
"go.octolab.org/toolset/maintainer/internal/pkg/config/flag"
1615
"go.octolab.org/toolset/maintainer/internal/pkg/http"
1716
"go.octolab.org/toolset/maintainer/internal/pkg/time"
1817
"go.octolab.org/toolset/maintainer/internal/pkg/unsafe"
@@ -25,7 +24,7 @@ func Contribution(cnf *config.Tool) *cobra.Command {
2524
}
2625

2726
//
28-
// $ maintainer github contribution diff --base=/tmp/snap.01.2013.json --head=/tmp/snap.02.2013.json
27+
// $ maintainer github contribution diff /tmp/snap.01.2013.json /tmp/snap.02.2013.json
2928
//
3029
// Day / Week #46 #48 #49 #50
3130
// ---------------------- --------------- --------------- --------------- -----------
@@ -39,15 +38,13 @@ func Contribution(cnf *config.Tool) *cobra.Command {
3938
// ---------------------- --------------- --------------- --------------- -----------
4039
// The diff between head{"/tmp/snap.02.2013.json"} → base{"/tmp/snap.01.2013.json"}
4140
//
42-
// $ maintainer github contribution diff --base=/tmp/snap.01.2013.json 2013
41+
// $ maintainer github contribution diff /tmp/snap.01.2013.json 2013
4342
//
4443
diff := cobra.Command{
4544
Use: "diff",
46-
Args: cobra.MaximumNArgs(1),
45+
Args: cobra.ExactArgs(2),
4746
RunE: exec.ContributionDiff(cnf),
4847
}
49-
flag.Adopt(diff.Flags()).File("base", "", "path to a base file")
50-
flag.Adopt(diff.Flags()).File("head", "", "path to a head file")
5148
cmd.AddCommand(&diff)
5249

5350
//

internal/command/github/exec/contribution_diff.go

Lines changed: 42 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -2,66 +2,69 @@ package exec
22

33
import (
44
"fmt"
5+
"regexp"
56

67
"github.com/spf13/cobra"
78

89
"go.octolab.org/toolset/maintainer/internal/command/github/run"
910
"go.octolab.org/toolset/maintainer/internal/config"
10-
"go.octolab.org/toolset/maintainer/internal/pkg/config/flag"
11+
"go.octolab.org/toolset/maintainer/internal/model/github/contribution"
1112
"go.octolab.org/toolset/maintainer/internal/pkg/http"
1213
"go.octolab.org/toolset/maintainer/internal/pkg/time"
1314
"go.octolab.org/toolset/maintainer/internal/service/github"
1415
)
1516

1617
func ContributionDiff(cnf *config.Tool) Runner {
18+
isYear := regexp.MustCompile(`^\d{4}$`)
19+
wrap := func(err error, input string) error {
20+
return fmt.Errorf(
21+
"please provide the argument in format YYYY, e.g., 2006: %w",
22+
fmt.Errorf("invalid argument %q: %w", input, err),
23+
)
24+
}
25+
26+
// input validation:
27+
// - Args: cobra.ExactArgs(2)
28+
// - base{file|date(year)} head{file|date(year)}
1729
return func(cmd *cobra.Command, args []string) error {
1830
service := github.New(http.TokenSourcedClient(cmd.Context(), cnf.Token))
19-
date := time.TruncateToYear(time.Now().UTC())
20-
21-
// input validation: files{params}, date(year){args}
22-
var baseSource, headSource string
23-
dst, err := flag.Adopt(cmd.Flags()).GetFile("base")
24-
if err != nil {
25-
return err
26-
}
27-
if dst == nil {
28-
return fmt.Errorf("please provide a base file by `--base` parameter")
29-
}
30-
baseSource = dst.Name()
3131

32-
src, err := flag.Adopt(cmd.Flags()).GetFile("head")
33-
if err != nil {
34-
return err
35-
}
36-
if src == nil && len(args) == 0 {
37-
return fmt.Errorf("please provide a compared file by `--head` parameter or year in args")
38-
}
39-
if src != nil && len(args) > 0 {
40-
return fmt.Errorf("please omit `--head` or argument, only one of them is allowed")
41-
}
42-
if len(args) == 1 {
43-
var err error
44-
wrap := func(err error) error {
45-
return fmt.Errorf(
46-
"please provide argument in format YYYY, e.g., 2006: %w",
47-
fmt.Errorf("invalid argument %q: %w", args[0], err),
48-
)
32+
var base run.ContributionSource
33+
if input := args[0]; isYear.MatchString(input) {
34+
year, err := time.Parse(time.RFC3339Year, input)
35+
if err != nil {
36+
return wrap(err, input)
4937
}
5038

51-
switch input := args[0]; len(input) {
52-
case len(time.RFC3339Year):
53-
date, err = time.Parse(time.RFC3339Year, input)
54-
default:
55-
err = fmt.Errorf("unsupported format")
39+
base = &contribution.UpstreamSource{
40+
Provider: service,
41+
Year: year,
42+
}
43+
} else {
44+
base = &contribution.FileSource{
45+
Provider: cnf.FS,
46+
Path: input,
5647
}
48+
}
49+
50+
var head run.ContributionSource
51+
if input := args[1]; isYear.MatchString(input) {
52+
year, err := time.Parse(time.RFC3339Year, input)
5753
if err != nil {
58-
return wrap(err)
54+
return wrap(err, input)
55+
}
56+
57+
head = &contribution.UpstreamSource{
58+
Provider: service,
59+
Year: year,
5960
}
60-
headSource = fmt.Sprintf("upstream:year(%s)", date.Format(time.RFC3339Year))
6161
} else {
62-
headSource = src.Name()
62+
head = &contribution.FileSource{
63+
Provider: cnf.FS,
64+
Path: input,
65+
}
6366
}
6467

65-
return run.ContributionDiff(cmd.Context(), service, cmd, date, dst, src, baseSource, headSource)
68+
return run.ContributionDiff(cmd.Context(), base, head, cmd)
6669
}
6770
}

internal/command/github/run/contract.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,11 @@ import (
44
"context"
55

66
"go.octolab.org/toolset/maintainer/internal/model/github/contribution"
7-
"go.octolab.org/toolset/maintainer/internal/pkg/time"
87
)
98

10-
type Contributor interface {
11-
ContributionHeatMap(context.Context, time.Range) (contribution.HeatMap, error)
9+
type ContributionSource interface {
10+
Location() string
11+
Fetch(context.Context) (contribution.HeatMap, error)
1212
}
1313

1414
type Printer interface {

internal/command/github/run/contribution_diff.go

Lines changed: 8 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -2,42 +2,24 @@ package run
22

33
import (
44
"context"
5-
"encoding/json"
6-
"os"
75

86
"go.octolab.org/toolset/maintainer/internal/command/github/view"
9-
"go.octolab.org/toolset/maintainer/internal/model/github/contribution"
10-
"go.octolab.org/toolset/maintainer/internal/pkg/time"
117
)
128

139
func ContributionDiff(
1410
ctx context.Context,
15-
service Contributor,
11+
src, dst ContributionSource,
1612
printer Printer,
17-
18-
date time.Time,
19-
src, dst *os.File,
20-
baseSource, headSource string,
2113
) error {
22-
var (
23-
base contribution.HeatMap
24-
head contribution.HeatMap
25-
)
26-
if err := json.NewDecoder(dst).Decode(&base); err != nil {
14+
base, err := src.Fetch(ctx)
15+
if err != nil {
2716
return err
2817
}
29-
if src != nil {
30-
if err := json.NewDecoder(src).Decode(&head); err != nil {
31-
return err
32-
}
33-
} else {
34-
var err error
35-
scope := time.RangeByYears(date, 0, false).ExcludeFuture()
36-
head, err = service.ContributionHeatMap(ctx, scope)
37-
if err != nil {
38-
return err
39-
}
18+
19+
head, err := dst.Fetch(ctx)
20+
if err != nil {
21+
return err
4022
}
4123

42-
return view.ContributionDiff(printer, base.Diff(head), baseSource, headSource)
24+
return view.ContributionDiff(printer, base.Diff(head), src.Location(), dst.Location())
4325
}

internal/command/root.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ func New() *cobra.Command {
2323

2424
PersistentPreRunE: func(*cobra.Command, []string) error {
2525
// TODO:feature home dir and specific config
26-
return cnf.Load(afero.NewMemMapFs())
26+
return cnf.Load(afero.NewOsFs())
2727
},
2828

2929
SilenceErrors: false,

internal/config/tool.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ type GitHub struct {
1818
}
1919

2020
type Tool struct {
21+
FS afero.Fs `mapstructure:"-"`
2122
Git `mapstructure:"git,squash"`
2223
GitHub `mapstructure:"github,squash"`
2324

@@ -40,8 +41,9 @@ func (cnf *Tool) Bind(bind func(*viper.Viper) error) error {
4041

4142
func (cnf *Tool) Load(fs afero.Fs, bindings ...func(*viper.Viper) error) error {
4243
v := cnf.init().config
43-
4444
v.SetFs(fs)
45+
cnf.FS = fs
46+
4547
for _, bind := range bindings {
4648
if err := bind(v); err != nil {
4749
return err
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package contribution
2+
3+
import (
4+
"context"
5+
6+
"go.octolab.org/toolset/maintainer/internal/pkg/time"
7+
)
8+
9+
type Contributor interface {
10+
ContributionHeatMap(context.Context, time.Range) (HeatMap, error)
11+
}
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
package contribution
2+
3+
import (
4+
"context"
5+
"encoding/json"
6+
"fmt"
7+
"path/filepath"
8+
"strings"
9+
"time"
10+
11+
"github.com/spf13/afero"
12+
"go.octolab.org/safe"
13+
"go.octolab.org/unsafe"
14+
"gopkg.in/yaml.v2"
15+
16+
xtime "go.octolab.org/toolset/maintainer/internal/pkg/time"
17+
)
18+
19+
type FileSource struct {
20+
Provider afero.Fs
21+
Path string
22+
23+
data HeatMap
24+
}
25+
26+
func (src FileSource) Location() string {
27+
return fmt.Sprintf("file:%s", src.Path)
28+
}
29+
30+
func (src *FileSource) Fetch(_ context.Context) (HeatMap, error) {
31+
if src.data != nil {
32+
return src.data, nil
33+
}
34+
35+
file, err := src.Provider.Open(src.Path)
36+
if err != nil {
37+
return nil, err
38+
}
39+
defer safe.Close(file, unsafe.Ignore)
40+
41+
var data HeatMap
42+
format := strings.ToLower(filepath.Ext(file.Name()))
43+
switch format {
44+
case ".json":
45+
err := json.NewDecoder(file).Decode(&data)
46+
src.data = data
47+
return data, err
48+
case ".yml", ".yaml":
49+
err := yaml.NewDecoder(file).Decode(&data)
50+
src.data = data
51+
return data, err
52+
default:
53+
return nil, fmt.Errorf("unsupported format: %s", format)
54+
}
55+
}
56+
57+
type UpstreamSource struct {
58+
Provider Contributor
59+
Year time.Time
60+
61+
data HeatMap
62+
}
63+
64+
func (src UpstreamSource) Location() string {
65+
return fmt.Sprintf("upstream:year(%s)", src.Year.Format(xtime.RFC3339Year))
66+
}
67+
68+
func (src *UpstreamSource) Fetch(ctx context.Context) (HeatMap, error) {
69+
if src.data != nil {
70+
return src.data, nil
71+
}
72+
73+
var err error
74+
scope := xtime.RangeByYears(src.Year, 0, false).ExcludeFuture()
75+
src.data, err = src.Provider.ContributionHeatMap(ctx, scope)
76+
return src.data, err
77+
}

0 commit comments

Comments
 (0)