Skip to content

Commit

Permalink
Refactor config and args
Browse files Browse the repository at this point in the history
  • Loading branch information
gabotechs committed Jun 22, 2024
1 parent c6843ff commit 4b42c1c
Show file tree
Hide file tree
Showing 9 changed files with 154 additions and 138 deletions.
2 changes: 1 addition & 1 deletion cmd/.root_test/check.txt
100755 → 100644
Original file line number Diff line number Diff line change
@@ -1 +1 @@
open .dep-tree.yml: no such file or directory
when using the `check` subcommand, a .dep-tree.yml file must be provided, you can create one sample .dep-tree.yml file executing `dep-tree config` in your terminal
18 changes: 8 additions & 10 deletions cmd/check.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package cmd

import (
"errors"
"fmt"

"github.com/gabotechs/dep-tree/internal/config"
Expand All @@ -11,20 +12,21 @@ import (
"github.com/gabotechs/dep-tree/internal/check"
)

func CheckCmd() *cobra.Command {
func CheckCmd(cfgF func() (*config.Config, error)) *cobra.Command {
return &cobra.Command{
Use: "check",
Short: "Checks that the dependency rules defined in the configuration file are not broken",
GroupID: checkGroupId,
Args: cobra.ExactArgs(0),
RunE: func(cmd *cobra.Command, args []string) error {
if configPath == "" {
configPath = config.DefaultConfigPath
}
cfg, err := loadConfig()
cfg, err := cfgF()
if err != nil {
return err
}
if cfg.Source == "default" {
return errors.New("when using the `check` subcommand, a .dep-tree.yml file must be provided, you can create one sample .dep-tree.yml file executing `dep-tree config` in your terminal")
}

if len(cfg.Check.Entrypoints) == 0 {
return fmt.Errorf(`config file "%s" has no entrypoints`, cfg.Path)
}
Expand All @@ -33,11 +35,7 @@ func CheckCmd() *cobra.Command {
return err
}
parser := language.NewParser(lang)
parser.UnwrapProxyExports = cfg.UnwrapExports
parser.Exclude = cfg.Exclude
if err != nil {
return err
}
applyConfigToParser(parser, cfg)

return check.Check[*language.FileInfo](
parser,
Expand Down
13 changes: 6 additions & 7 deletions cmd/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,27 +2,26 @@ package cmd

import (
"errors"
"fmt"
"os"

"github.com/spf13/cobra"

"github.com/gabotechs/dep-tree/internal/config"
)

func ConfigCmd() *cobra.Command {
func ConfigCmd(_ func() (*config.Config, error)) *cobra.Command {
return &cobra.Command{
Use: "config",
Short: "Generates a sample config in case that there's not already one present",
Args: cobra.ExactArgs(0),
Aliases: []string{"init"},
RunE: func(cmd *cobra.Command, args []string) error {
if configPath == "" {
configPath = config.DefaultConfigPath
}
if _, err := os.Stat(configPath); errors.Is(err, os.ErrNotExist) {
return os.WriteFile(configPath, []byte(config.SampleConfig), 0o600)
path := config.DefaultConfigPath
if _, err := os.Stat(path); errors.Is(err, os.ErrNotExist) {
return os.WriteFile(path, []byte(config.SampleConfig), 0o600)
} else {
return errors.New("Cannot generate config file, as one already exists in " + configPath)
return fmt.Errorf("cannot generate config file, as one already exists in %s", path)
}
},
}
Expand Down
19 changes: 10 additions & 9 deletions cmd/entropy.go
Original file line number Diff line number Diff line change
@@ -1,18 +1,19 @@
package cmd

import (
"github.com/gabotechs/dep-tree/internal/graph"
"github.com/gabotechs/dep-tree/internal/language"
"github.com/spf13/cobra"

"github.com/gabotechs/dep-tree/internal/config"
"github.com/gabotechs/dep-tree/internal/entropy"
"github.com/gabotechs/dep-tree/internal/graph"
"github.com/gabotechs/dep-tree/internal/language"
)

var noBrowserOpen bool
var enableGui bool
var renderPath string
func EntropyCmd(cfgF func() (*config.Config, error)) *cobra.Command {
var noBrowserOpen bool
var enableGui bool
var renderPath string

func EntropyCmd() *cobra.Command {
cmd := &cobra.Command{
Use: "entropy",
Short: "(default) Renders a 3d force-directed graph in the browser",
Expand All @@ -23,7 +24,7 @@ func EntropyCmd() *cobra.Command {
if err != nil {
return err
}
cfg, err := loadConfig()
cfg, err := cfgF()
if err != nil {
return err
}
Expand All @@ -32,8 +33,8 @@ func EntropyCmd() *cobra.Command {
return err
}
parser := language.NewParser(lang)
parser.UnwrapProxyExports = cfg.UnwrapExports
parser.Exclude = cfg.Exclude
applyConfigToParser(parser, cfg)

err = entropy.Render(files, parser, entropy.RenderConfig{
NoOpen: noBrowserOpen,
EnableGui: enableGui,
Expand Down
21 changes: 9 additions & 12 deletions cmd/explain.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,17 @@ package cmd

import (
"os"
"path/filepath"
"slices"
"strings"

"github.com/gabotechs/dep-tree/internal/config"
"github.com/gabotechs/dep-tree/internal/explain"
"github.com/gabotechs/dep-tree/internal/graph"
"github.com/gabotechs/dep-tree/internal/language"
"github.com/spf13/cobra"
)

func ExplainCmd() *cobra.Command {
func ExplainCmd(cfgF func() (*config.Config, error)) *cobra.Command {
cmd := &cobra.Command{
Use: "explain",
Short: "Shows all the dependencies between two parts of the code",
Expand All @@ -29,26 +29,23 @@ func ExplainCmd() *cobra.Command {
return err
}

cfg, err := loadConfig()
cfg, err := cfgF()
if err != nil {
return err
}

lang, err := inferLang(fromFiles, cfg)
if err != nil {
return err
}

parser := language.NewParser(lang)
parser.UnwrapProxyExports = cfg.UnwrapExports
parser.Exclude = cfg.Exclude
applyConfigToParser(parser, cfg)

cwd, _ := os.Getwd()
for _, arg := range args {
if filepath.IsAbs(arg) {
parser.Include = append(parser.Include, arg)
} else {
parser.Include = append(parser.Include, filepath.Join(cwd, arg))
}
}
tempCfg := config.Config{Path: cwd, Only: args}
tempCfg.EnsureAbsPaths()
parser.Include = tempCfg.Only

deps, err := explain.Explain[*language.FileInfo](
parser,
Expand Down
120 changes: 58 additions & 62 deletions cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,21 +25,12 @@ const renderGroupId = "render"
const checkGroupId = "check"
const defaultCommand = "entropy"

var configPath string
var unwrapExports bool
var jsTsConfigPaths bool
var jsWorkspaces bool
var pythonExcludeConditionalImports bool
var exclude []string

var root *cobra.Command

func NewRoot(args []string) *cobra.Command {
if args == nil {
args = os.Args[1:]
}

root = &cobra.Command{
root := &cobra.Command{
Use: "dep-tree",
Version: "v0.22.2",
Short: "Visualize and check your project's dependency graph",
Expand All @@ -63,26 +54,65 @@ $ dep-tree check`,
root.SetErr(os.Stderr)
root.SetArgs(args)

root.AddCommand(
EntropyCmd(),
TreeCmd(),
CheckCmd(),
ConfigCmd(),
ExplainCmd(),
)

root.AddGroup(&cobra.Group{ID: renderGroupId, Title: "Visualize your dependencies graphically"})
root.AddGroup(&cobra.Group{ID: checkGroupId, Title: "Check your dependencies against your own rules"})
root.AddGroup(&cobra.Group{ID: explainGroupId, Title: "Display what are the dependencies between two portions of code"})

cliCfg := config.NewConfigCwd()

var fileConfigPath string

root.Flags().SortFlags = false
root.PersistentFlags().SortFlags = false
root.PersistentFlags().StringVarP(&configPath, "config", "c", "", "path to dep-tree's config file. (default .dep-tree.yml)")
root.PersistentFlags().BoolVar(&unwrapExports, "unwrap-exports", false, "trace re-exported symbols to the file where they are declared. (default false)")
root.PersistentFlags().StringArrayVar(&exclude, "exclude", nil, "Files that match this glob pattern will be ignored. You can provide an arbitrary number of --exclude flags.")
root.PersistentFlags().BoolVar(&jsTsConfigPaths, "js-tsconfig-paths", true, "follow the tsconfig.json paths while resolving imports.")
root.PersistentFlags().BoolVar(&jsWorkspaces, "js-workspaces", true, "take the workspaces attribute in the root package.json into account for resolving paths.")
root.PersistentFlags().BoolVar(&pythonExcludeConditionalImports, "python-exclude-conditional-imports", false, "exclude imports wrapped inside if or try statements. (default false)")
root.PersistentFlags().StringVarP(&fileConfigPath, "config", "c", "", "path to dep-tree's config file. (default .dep-tree.yml)")
root.PersistentFlags().BoolVar(&cliCfg.UnwrapExports, "unwrap-exports", false, "trace re-exported symbols to the file where they are declared. (default false)")
root.PersistentFlags().BoolVar(&cliCfg.Js.TsConfigPaths, "js-tsconfig-paths", true, "follow the tsconfig.json paths while resolving imports.")
root.PersistentFlags().BoolVar(&cliCfg.Js.Workspaces, "js-workspaces", true, "take the workspaces attribute in the root package.json into account for resolving paths.")
root.PersistentFlags().BoolVar(&cliCfg.Python.ExcludeConditionalImports, "python-exclude-conditional-imports", false, "exclude imports wrapped inside if or try statements. (default false)")
root.PersistentFlags().StringArrayVar(&cliCfg.Only, "only", nil, "Files that do not match this glob pattern will be ignored. You can provide an arbitrary number of --only flags.")
root.PersistentFlags().StringArrayVar(&cliCfg.Exclude, "exclude", nil, "Files that match this glob pattern will be ignored. You can provide an arbitrary number of --exclude flags.")

cfgF := func() (*config.Config, error) {
fileCfg, err := config.ParseConfigFromFile(fileConfigPath)
if err != nil {
return nil, err
}
fileCfg.EnsureAbsPaths()
cliCfg.EnsureAbsPaths()

// These settings prefer the CLI over the file config
for _, a := range []struct {
name string
source *bool
dest *bool
}{
{"unwrap-exports", &cliCfg.UnwrapExports, &fileCfg.UnwrapExports},
{"js-tsconfig-paths", &cliCfg.Js.TsConfigPaths, &fileCfg.Js.TsConfigPaths},
{"js-workspaces", &cliCfg.Js.Workspaces, &fileCfg.Js.Workspaces},
{"python-exclude-conditional-imports", &cliCfg.Python.ExcludeConditionalImports, &fileCfg.Python.ExcludeConditionalImports},
} {
if !root.PersistentFlags().Changed(a.name) {
*a.dest = *a.source
}
}
// NOTE: hard-enable this for now, as they don't produce a very good output.
fileCfg.Python.IgnoreFromImportsAsExports = true
fileCfg.Python.IgnoreDirectoryImports = true

// merge the exclusions and inclusions from the CLI and from the config.
fileCfg.Exclude = append(fileCfg.Exclude, cliCfg.Exclude...)
fileCfg.Only = append(fileCfg.Only, cliCfg.Only...)

return fileCfg, fileCfg.ValidatePatterns()
}

root.AddCommand(
EntropyCmd(cfgF),
TreeCmd(cfgF),
CheckCmd(cfgF),
ConfigCmd(cfgF),
ExplainCmd(cfgF),
)

switch {
case len(args) > 0 && utils.InArray(args[0], []string{"help", "completion", "-v", "--version", "-h", "--help"}):
Expand Down Expand Up @@ -202,44 +232,10 @@ func filesFromArgs(args []string) ([]string, error) {
return result, nil
}

func loadConfig() (*config.Config, error) {
cfg, err := config.ParseConfig(configPath)
if err != nil {
return nil, err
}
if root.PersistentFlags().Changed("unwrap-exports") {
cfg.UnwrapExports = unwrapExports
}
if root.PersistentFlags().Changed("js-tsconfig-paths") {
cfg.Js.TsConfigPaths = jsTsConfigPaths
}
if root.PersistentFlags().Changed("js-workspaces") {
cfg.Js.Workspaces = jsWorkspaces
}
if root.PersistentFlags().Changed("python-exclude-conditional-imports") {
cfg.Python.ExcludeConditionalImports = pythonExcludeConditionalImports
}
// NOTE: hard-enable this for now, as they don't produce a very good output.
cfg.Python.IgnoreFromImportsAsExports = true
cfg.Python.IgnoreDirectoryImports = true

absExclude := make([]string, len(exclude))
cwd, _ := os.Getwd()
for i, file := range exclude {
if !filepath.IsAbs(file) {
absExclude[i] = filepath.Join(cwd, file)
} else {
absExclude[i] = file
}
}
cfg.Exclude = append(cfg.Exclude, absExclude...)
// validate exclusion patterns.
for _, exclusion := range cfg.Exclude {
if _, err := utils.GlobstarMatch(exclusion, ""); err != nil {
return nil, fmt.Errorf("exclude pattern '%s' is not correctly formatted", exclusion)
}
}
return cfg, nil
func applyConfigToParser(parser *language.Parser, cfg *config.Config) {
parser.UnwrapProxyExports = cfg.UnwrapExports
parser.Exclude = cfg.Exclude
parser.Include = cfg.Only
}

func relPathDisplay(node *graph.Node[*language.FileInfo]) string {
Expand Down
11 changes: 6 additions & 5 deletions cmd/tree.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package cmd

import (
"github.com/gabotechs/dep-tree/internal/config"
"github.com/gabotechs/dep-tree/internal/graph"
"github.com/gabotechs/dep-tree/internal/language"
"github.com/gabotechs/dep-tree/internal/tree"
Expand All @@ -9,9 +10,9 @@ import (
"github.com/gabotechs/dep-tree/internal/tui"
)

var jsonFormat bool
func TreeCmd(cfgF func() (*config.Config, error)) *cobra.Command {
var jsonFormat bool

func TreeCmd() *cobra.Command {
cmd := &cobra.Command{
Use: "tree",
Short: "Render the dependency tree starting from the provided entrypoint",
Expand All @@ -22,7 +23,8 @@ func TreeCmd() *cobra.Command {
if err != nil {
return err
}
cfg, err := loadConfig()

cfg, err := cfgF()
if err != nil {
return err
}
Expand All @@ -33,8 +35,7 @@ func TreeCmd() *cobra.Command {
}

parser := language.NewParser(lang)
parser.UnwrapProxyExports = cfg.UnwrapExports
parser.Exclude = cfg.Exclude
applyConfigToParser(parser, cfg)

if jsonFormat {
t, err := tree.NewTree[*language.FileInfo](
Expand Down
Loading

0 comments on commit 4b42c1c

Please sign in to comment.