From d159003b46de45c0f50f3f07fb285796631964db Mon Sep 17 00:00:00 2001 From: Manfred Touron Date: Thu, 13 Sep 2018 18:14:17 +0200 Subject: [PATCH] feat: refactor cli (#7) --- README.md | 6 +- db.go => cmd_db.go | 0 fetch.go => cmd_pull.go | 40 +- render.go => cmd_run.go | 66 +-- examples/depviz/.depviz.yml | 3 - examples/depviz/Makefile | 6 +- examples/depviz/depviz.svg | 847 +++++++++++++++++++----------------- graphviz.go | 15 +- issue.go | 19 +- main.go | 4 +- util.go | 18 + 11 files changed, 546 insertions(+), 478 deletions(-) rename db.go => cmd_db.go (100%) rename fetch.go => cmd_pull.go (81%) rename render.go => cmd_run.go (53%) delete mode 100644 examples/depviz/.depviz.yml diff --git a/README.md b/README.md index b1307b2d3..6fea9ae60 100644 --- a/README.md +++ b/README.md @@ -56,11 +56,11 @@ _inspired by this discussion: [jbenet/random-ideas#37](https://github.com/jbenet $ export GITHUB_TOKEN=xxxx # render and display the roadmap -$ depviz render --repos=moul/depviz | dot -Tpng > depviz-roadmap.png +$ depviz run moul/depviz | dot -Tpng > depviz-roadmap.png $ open depviz-roadmap.png # render and display the orphans -$ depviz render --repos=moul/depviz -t orphans | dot -Tpng > depviz-orphans.png +$ depviz run moul/depviz --show-orphans | dot -Tpng > depviz-orphans.png $ open depviz-orphans.png ``` @@ -69,7 +69,7 @@ $ open depviz-orphans.png ```console # install imgcat $ go get github.com/olivere/iterm2-imagetools/cmd/imgcat -$ depviz render | dot -Tpng | imgcat +$ depviz run https://github.com/moul/depviz/issues/42 | dot -Tpng | imgcat ``` ![](https://raw.githubusercontent.com/moul/depviz/master/examples/imgcat.png) diff --git a/db.go b/cmd_db.go similarity index 100% rename from db.go rename to cmd_db.go diff --git a/fetch.go b/cmd_pull.go similarity index 81% rename from fetch.go rename to cmd_pull.go index 1fb28fb10..33e6816d9 100644 --- a/fetch.go +++ b/cmd_pull.go @@ -6,7 +6,6 @@ import ( "fmt" "io/ioutil" "log" - "os" "sync" "github.com/google/go-github/github" @@ -19,53 +18,60 @@ import ( "golang.org/x/oauth2" ) -type fetchOptions struct { +type pullOptions struct { // db DBOpts dbOptions - // fetch + // pull Repos []string GithubToken string `mapstructure:"github-token"` + GitlabToken string `mapstructure:"gitlab-token"` // includeExternalDeps bool + + Targets []string } -func (opts fetchOptions) String() string { +func (opts pullOptions) String() string { out, _ := json.Marshal(opts) return string(out) } -func fetchSetupFlags(flags *pflag.FlagSet, opts *fetchOptions) { - flags.StringSliceVarP(&opts.Repos, "repos", "r", []string{}, "list of repositories to aggregate issues from") // FIXME: get the default value dynamically from .git, if present +func pullSetupFlags(flags *pflag.FlagSet, opts *pullOptions) { flags.StringVarP(&opts.GithubToken, "github-token", "", "", "GitHub Token with 'issues' access") + flags.StringVarP(&opts.GitlabToken, "gitlab-token", "", "", "GitLab Token with 'issues' access") viper.BindPFlags(flags) } -func newFetchCommand() *cobra.Command { - opts := &fetchOptions{} +func newPullCommand() *cobra.Command { + opts := &pullOptions{} cmd := &cobra.Command{ - Use: "fetch", + Use: "pull", RunE: func(cmd *cobra.Command, args []string) error { if err := viper.Unmarshal(opts); err != nil { return err } - return fetch(opts) + opts.Targets = args + return pull(opts) }, } - fetchSetupFlags(cmd.Flags(), opts) + pullSetupFlags(cmd.Flags(), opts) dbSetupFlags(cmd.Flags(), &opts.DBOpts) return cmd } -func fetch(opts *fetchOptions) error { - logger().Debug("fetch", zap.Stringer("opts", *opts)) +func pull(opts *pullOptions) error { + logger().Debug("pull", zap.Stringer("opts", *opts)) var ( wg sync.WaitGroup allIssues []*Issue out = make(chan []*Issue, 100) ) - wg.Add(len(opts.Repos)) - for _, repoURL := range opts.Repos { + + repos := getReposFromTargets(opts.Targets) + + wg.Add(len(repos)) + for _, repoURL := range repos { repo := NewRepo(repoURL) switch repo.Provider() { case GitHubProvider: @@ -107,7 +113,7 @@ func fetch(opts *fetchOptions) error { }(repo) case GitLabProvider: go func(repo Repo) { - client := gitlab.NewClient(nil, os.Getenv("GITLAB_TOKEN")) + client := gitlab.NewClient(nil, opts.GitlabToken) client.SetBaseURL(fmt.Sprintf("%s/api/v4", repo.SiteURL())) //projectID := url.QueryEscape(repo.RepoPath()) @@ -123,7 +129,7 @@ func fetch(opts *fetchOptions) error { for { issues, resp, err := client.Issues.ListProjectIssues(projectID, opts) if err != nil { - logger().Error("failed to fetch issues", zap.Error(err)) + logger().Error("failed to pull issues", zap.Error(err)) return } total += len(issues) diff --git a/render.go b/cmd_run.go similarity index 53% rename from render.go rename to cmd_run.go index 17a68b041..73cd759e9 100644 --- a/render.go +++ b/cmd_run.go @@ -13,67 +13,69 @@ import ( "go.uber.org/zap" ) -type renderOptions struct { - // fetch - FetchOpts fetchOptions - NoFetch bool +type runOptions struct { + // pull + PullOpts pullOptions + NoPull bool // db DBOpts dbOptions - // render - RenderType string + // run ShowClosed bool `mapstructure:"show-closed"` ShowOrphans bool EpicLabel string Destination string + + Targets []string //Preview bool } -func (opts renderOptions) String() string { +func (opts runOptions) String() string { out, _ := json.Marshal(opts) return string(out) } -func renderSetupFlags(flags *pflag.FlagSet, opts *renderOptions) { - flags.BoolVarP(&opts.NoFetch, "no-fetch", "f", false, "do not fetch new issues before rendering") - flags.StringVarP(&opts.RenderType, "type", "t", "roadmap", "graph type ('roadmap', 'orphans')") +func runSetupFlags(flags *pflag.FlagSet, opts *runOptions) { + flags.BoolVarP(&opts.NoPull, "no-pull", "f", false, "do not pull new issues before runing") flags.BoolVarP(&opts.ShowClosed, "show-closed", "", false, "show closed issues") flags.BoolVarP(&opts.ShowOrphans, "show-orphans", "", false, "show issues not linked to an epic") - flags.StringVarP(&opts.EpicLabel, "epic-label", "", "", "label used for epics (empty means issues with dependencies but without dependants)") + flags.StringVarP(&opts.EpicLabel, "epic-label", "", "epic", "label used for epics (empty means issues with dependencies but without dependants)") flags.StringVarP(&opts.Destination, "destination", "", "-", "destination ('-' for stdout)") //flags.BoolVarP(&opts.Preview, "preview", "p", false, "preview result") viper.BindPFlags(flags) } -func newRenderCommand() *cobra.Command { - opts := &renderOptions{} +func newRunCommand() *cobra.Command { + opts := &runOptions{} cmd := &cobra.Command{ - Use: "render", + Use: "run", RunE: func(cmd *cobra.Command, args []string) error { if err := viper.Unmarshal(opts); err != nil { return err } - if err := viper.Unmarshal(&opts.FetchOpts); err != nil { + if err := viper.Unmarshal(&opts.PullOpts); err != nil { return err } if err := viper.Unmarshal(&opts.DBOpts); err != nil { return err } - opts.FetchOpts.DBOpts = opts.DBOpts - return render(opts) + opts.PullOpts.DBOpts = opts.DBOpts + opts.PullOpts.Targets = args + opts.Targets = args + return run(opts) }, } - renderSetupFlags(cmd.Flags(), opts) - fetchSetupFlags(cmd.Flags(), &opts.FetchOpts) + runSetupFlags(cmd.Flags(), opts) + pullSetupFlags(cmd.Flags(), &opts.PullOpts) dbSetupFlags(cmd.Flags(), &opts.DBOpts) return cmd } -func render(opts *renderOptions) error { - logger().Debug("render", zap.Stringer("opts", *opts)) - if !opts.NoFetch || !dbExists(&opts.DBOpts) { - if err := fetch(&opts.FetchOpts); err != nil { +func run(opts *runOptions) error { + logger().Debug("run", zap.Stringer("opts", *opts)) + if !opts.NoPull || !dbExists(&opts.DBOpts) { + if err := pull(&opts.PullOpts); err != nil { return err } } @@ -87,18 +89,16 @@ func render(opts *renderOptions) error { return errors.Wrap(err, "failed to prepare issues") } - var out string - switch opts.RenderType { - case "roadmap": - out, err = roadmapGraph(issues, opts) - case "orphans": - out, err = orphansGraph(issues, opts) - default: - err = fmt.Errorf("unknown graph type: %q", opts.RenderType) + issues.processEpicLinks() + if !opts.ShowClosed { + issues.HideClosed() } - if err != nil { - return errors.Wrap(err, "failed to render graph") + if !opts.ShowOrphans && issues.HasNonOrphans() { + issues.HideOrphans() } + issues.processEpicLinks() + + out, err := graphviz(issues, opts) var dest io.WriteCloser switch opts.Destination { diff --git a/examples/depviz/.depviz.yml b/examples/depviz/.depviz.yml deleted file mode 100644 index 0d22fa5f5..000000000 --- a/examples/depviz/.depviz.yml +++ /dev/null @@ -1,3 +0,0 @@ -repos: moul/depviz -epic-label: epic -show-closed: true \ No newline at end of file diff --git a/examples/depviz/Makefile b/examples/depviz/Makefile index a6617f082..cbde678d3 100644 --- a/examples/depviz/Makefile +++ b/examples/depviz/Makefile @@ -1,9 +1,7 @@ .PHONY: run run: - depviz render -f | dot -Tsvg > depviz.svg - depviz render -t orphans -f | dot -Tsvg > orphans.svg - cat orphans.svg | grep -s text || rm orphans.svg + depviz run --show-closed moul/depviz | dot -Tsvg > depviz.svg .PHONY: preview preview: - depviz render -f | dot -Tpng | imgcat + depviz run | dot -Tpng | imgcat diff --git a/examples/depviz/depviz.svg b/examples/depviz/depviz.svg index 0380d99ba..b6555ec69 100644 --- a/examples/depviz/depviz.svg +++ b/examples/depviz/depviz.svg @@ -4,549 +4,598 @@ - - + + G - - + + -moul/depviz#8 - - - -moul/depviz#8: -depviz is easy to -use - - - -epic - - +https://github.com/moul/depviz/issues/9 + + + +moul/depviz#9: +depviz is easy to +hack + + + +epic + + - + -moul/depviz#6 - - - -moul/depviz#6: Web -version - - - -enhancement - - +https://github.com/moul/depviz/issues/18 + + + +moul/depviz#18: Add +badges in README + + + +enhancement + + - + -moul/depviz#8->moul/depviz#6 - +https://github.com/moul/depviz/issues/9->https://github.com/moul/depviz/issues/18 + - + -moul/depviz#7 - - - -moul/depviz#7: -Refactor CLI to be -easier to run -projects without -configuration files - - - -enhancement - - -p0 - - +https://github.com/moul/depviz/issues/22 + + + +moul/depviz#22: Add +a license + - + -moul/depviz#8->moul/depviz#7 - +https://github.com/moul/depviz/issues/9->https://github.com/moul/depviz/issues/22 + - + -moul/depviz#24 - - - -moul/depviz#24: Add -a issue-focus mode - +https://github.com/moul/depviz/issues/2 + + + +moul/depviz#2: Setup +CI + + + +enhancement + + - + -moul/depviz#8->moul/depviz#24 - +https://github.com/moul/depviz/issues/9->https://github.com/moul/depviz/issues/2 + - + -moul/depviz#5 - - - -moul/depviz#5: Add -Docker image - - - -enhancement - - +https://github.com/moul/depviz/issues/33 + + + +moul/depviz#33: Use +a database to store +issues in a smarter +way + + + +enhancement + + - + -moul/depviz#8->moul/depviz#5 - +https://github.com/moul/depviz/issues/9->https://github.com/moul/depviz/issues/33 + - - -moul/depviz#25 - - - -moul/depviz#25: add -clickable links in -generated graph - + + +https://github.com/moul/depviz/issues/18->https://github.com/moul/depviz/issues/2 + + + + +https://github.com/moul/depviz/issues/5 + + + +moul/depviz#5: Add +Docker image + + + +enhancement + + - - -moul/depviz#8->moul/depviz#25 - + + +https://github.com/moul/depviz/issues/2->https://github.com/moul/depviz/issues/5 + - - -moul/depviz#12 - - - -moul/depviz#12: I -can use depviz to -manage my company - - - -epic - - + + +https://github.com/moul/depviz/issues/11 + + + +moul/depviz#11: +depviz is pluggable +with other workflows +/ systems + + + +epic + + - - -moul/depviz#12->moul/depviz#8 - + + +https://github.com/moul/depviz/issues/11->https://github.com/moul/depviz/issues/5 + - + -moul/depviz#21 - - - -moul/depviz#21: Show -epic's completion -rate - - - -enhancement - - +https://github.com/moul/depviz/issues/4 + + + +moul/depviz#4: +Export to OPML + + + +enhancement + + - + -moul/depviz#12->moul/depviz#21 - +https://github.com/moul/depviz/issues/11->https://github.com/moul/depviz/issues/4 + - + -moul/depviz#32 - - - -moul/depviz#32: -Support GitLab - +https://github.com/moul/depviz/issues/36 + + + +moul/depviz#36: +DepViz has "cool" +features + + + +epic + + - - -moul/depviz#12->moul/depviz#32 - - - + -moul/depviz#10 - - - -moul/depviz#10: -depviz is easy to -discover - - - -epic - - +https://github.com/moul/depviz/issues/35 + + + +moul/depviz#35: +Support PRs + + + +enhancement + + - + + +https://github.com/moul/depviz/issues/36->https://github.com/moul/depviz/issues/35 + + + -moul/depviz#3 - - - -moul/depviz#3: Add -examples - +https://github.com/moul/depviz/issues/34 + + + +moul/depviz#34: Add +a warn when two +issues "fixes" the +same one + + + +enhancement + + - - -moul/depviz#10->moul/depviz#3 - + + +https://github.com/moul/depviz/issues/36->https://github.com/moul/depviz/issues/34 + - + -moul/depviz#18 - - - -moul/depviz#18: Add -badges in README - - - -enhancement - - +https://github.com/moul/depviz/issues/37 + + + +moul/depviz#37: Add +an option to update +label(s) based on +rules + + + +enhancement + + - - -moul/depviz#10->moul/depviz#18 - + + +https://github.com/moul/depviz/issues/36->https://github.com/moul/depviz/issues/37 + - + -moul/depviz#19 - - - -moul/depviz#19: Add -a logo in the README - - - -enhancement - - +https://github.com/moul/depviz/issues/13 + + + +moul/depviz#13: I +can use depviz to +plan all my projects + + + +epic + + - - -moul/depviz#10->moul/depviz#19 - - - - -moul/depviz#2 - - - -moul/depviz#2: Setup -CI - - - -enhancement - - + + +https://github.com/moul/depviz/issues/12 + + + +moul/depviz#12: I +can use depviz to +manage my company + + + +epic + + - - -moul/depviz#18->moul/depviz#2 - + + +https://github.com/moul/depviz/issues/13->https://github.com/moul/depviz/issues/12 + - - -moul/depviz#36 - - - -moul/depviz#36: -DepViz has "cool" -features - - - -epic - - + + +https://github.com/moul/depviz/issues/8 + + + +moul/depviz#8: +depviz is easy to +use + + + +epic + + - - -moul/depviz#34 - - - -moul/depviz#34: Add -a warn when two -issues "fixes" the -same one - + + +https://github.com/moul/depviz/issues/12->https://github.com/moul/depviz/issues/8 + + + + +https://github.com/moul/depviz/issues/21 + + + +moul/depviz#21: Show +epic's completion +rate + + + +enhancement + + - + -moul/depviz#36->moul/depviz#34 - +https://github.com/moul/depviz/issues/12->https://github.com/moul/depviz/issues/21 + - - -moul/depviz#35 - - - -moul/depviz#35: -Support PRs - + + +https://github.com/moul/depviz/issues/32 + + + +moul/depviz#32: +Support GitLab + + + +enhancement + + - + -moul/depviz#36->moul/depviz#35 - +https://github.com/moul/depviz/issues/12->https://github.com/moul/depviz/issues/32 + - - -moul/depviz#13 - - - -moul/depviz#13: I -can use depviz to -plan all my projects - - - -epic - - + + +https://github.com/moul/depviz/issues/8->https://github.com/moul/depviz/issues/5 + + + + +https://github.com/moul/depviz/issues/25 + + + +moul/depviz#25: add +clickable links in +generated graph + - - -moul/depviz#13->moul/depviz#12 - + + +https://github.com/moul/depviz/issues/8->https://github.com/moul/depviz/issues/25 + - - -moul/depviz#11 - - - -moul/depviz#11: -depviz is pluggable -with other workflows -/ systems - - - -epic - - + + +https://github.com/moul/depviz/issues/7 + + + +moul/depviz#7: +Refactor CLI to be +easier to run +projects without +configuration files + + + +enhancement + + +p0 + + - - -moul/depviz#11->moul/depviz#5 - + + +https://github.com/moul/depviz/issues/8->https://github.com/moul/depviz/issues/7 + - - -moul/depviz#4 - - - -moul/depviz#4: -Export to OPML - - - -enhancement - - + + +https://github.com/moul/depviz/issues/6 + + + +moul/depviz#6: Web +version + + + +enhancement + + - - -moul/depviz#11->moul/depviz#4 - - - - -moul/depviz#2->moul/depviz#5 - + + +https://github.com/moul/depviz/issues/8->https://github.com/moul/depviz/issues/6 + - - -moul/depviz#9 - - - -moul/depviz#9: -depviz is easy to -hack - - - -epic - - + + +https://github.com/moul/depviz/issues/24 + + + +moul/depviz#24: Add +an issue-focus mode + + + +enhancement + + - - -moul/depviz#9->moul/depviz#18 - + + +https://github.com/moul/depviz/issues/8->https://github.com/moul/depviz/issues/24 + - - -moul/depviz#9->moul/depviz#2 - + + +https://github.com/moul/depviz/issues/10 + + + +moul/depviz#10: +depviz is easy to +discover + + + +epic + + + - - -moul/depviz#33 - - - -moul/depviz#33: Use -a database to store -issues in a smarter -way - + + + +https://github.com/moul/depviz/issues/10->https://github.com/moul/depviz/issues/18 + + + + +https://github.com/moul/depviz/issues/19 + + + +moul/depviz#19: Add +a logo in the README + + + +enhancement + + - - -moul/depviz#9->moul/depviz#33 - + + +https://github.com/moul/depviz/issues/10->https://github.com/moul/depviz/issues/19 + - - -moul/depviz#22 - - - -moul/depviz#22: Add -a license - + + +https://github.com/moul/depviz/issues/3 + + + +moul/depviz#3: Add +examples + - - -moul/depviz#9->moul/depviz#22 - + + +https://github.com/moul/depviz/issues/10->https://github.com/moul/depviz/issues/3 + - + placeholder_1 -weight=1 +weight=1 - + placeholder_2 -weight=2 +weight=2 - + placeholder_3 -weight=3 +weight=3 - + placeholder_4 -weight=4 +weight=4 - + placeholder_5 -weight=5 +weight=5 - + placeholder_6 -weight=6 +weight=6 - + placeholder_7 -weight=7 +weight=7 - + placeholder_8 -weight=8 +weight=8 - + placeholder_14 -weight=14 +weight=14 - + placeholder_22 -weight=22 +weight=22 diff --git a/graphviz.go b/graphviz.go index 58402c3d7..c8671bbea 100644 --- a/graphviz.go +++ b/graphviz.go @@ -7,7 +7,7 @@ import ( "github.com/awalterschulze/gographviz" ) -func orphansGraph(issues Issues, opts *renderOptions) (string, error) { +func orphansGraph(issues Issues, opts *runOptions) (string, error) { if !opts.ShowClosed { issues.HideClosed() } @@ -60,16 +60,7 @@ func orphansGraph(issues Issues, opts *renderOptions) (string, error) { return g.String(), nil } -func roadmapGraph(issues Issues, opts *renderOptions) (string, error) { - if !opts.ShowClosed { - issues.HideClosed() - } - if !opts.ShowOrphans { - issues.HideOrphans() - issues.HideEpicLess() - } - issues.processEpicLinks() - +func graphviz(issues Issues, opts *runOptions) (string, error) { var ( invisStyle = map[string]string{"style": "invis"} weightMap = map[int]bool{} @@ -129,7 +120,7 @@ func roadmapGraph(issues Issues, opts *renderOptions) (string, error) { } // orphans cluster and placeholder - if opts.ShowOrphans { + if issues.HasOrphans() { panicIfErr(g.AddSubGraph("G", "cluster_orphans", map[string]string{"label": "orphans", "style": "dashed"})) panicIfErr(g.AddNode("cluster_orphans", "placeholder_orphans", invisStyle)) panicIfErr(g.AddEdge( diff --git a/issue.go b/issue.go index 99f14f1c7..c53fd0ad7 100644 --- a/issue.go +++ b/issue.go @@ -463,17 +463,26 @@ func (issues Issues) HideClosed() { func (issues Issues) HideOrphans() { for _, issue := range issues { - if issue.IsOrphan { + if issue.IsOrphan || !issue.LinkedWithEpic { issue.Hidden = true } } } -func (issues Issues) HideEpicLess() { +func (issues Issues) HasOrphans() bool { for _, issue := range issues { - if !issue.LinkedWithEpic { - issue.Hidden = true + if !issue.Hidden && issue.IsOrphan { + return true } } - issues.processEpicLinks() + return false +} + +func (issues Issues) HasNonOrphans() bool { + for _, issue := range issues { + if !issue.Hidden && !issue.IsOrphan && issue.LinkedWithEpic { + return true + } + } + return false } diff --git a/main.go b/main.go index f5d3237ec..efe49326e 100644 --- a/main.go +++ b/main.go @@ -64,8 +64,8 @@ func newRootCommand() *cobra.Command { return nil } cmd.AddCommand( - newFetchCommand(), - newRenderCommand(), + newPullCommand(), + newRunCommand(), newDBCommand(), ) viper.AutomaticEnv() diff --git a/util.go b/util.go index 127e8dd02..ccb766350 100644 --- a/util.go +++ b/util.go @@ -2,6 +2,7 @@ package main import ( "fmt" + "os" "strings" ) @@ -34,3 +35,20 @@ func panicIfErr(err error) { panic(err) } } + +func getReposFromTargets(targets []string) []string { + reposMap := map[string]bool{} + + for _, target := range targets { + if _, err := os.Stat(target); err == nil { + logger().Fatal("filesystem target are not yet supported") + } + repo := strings.Split(target, "/issues")[0] + reposMap[repo] = true + } + repos := []string{} + for repo := range reposMap { + repos = append(repos, repo) + } + return repos +}