Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor config and args #104

Merged
merged 6 commits into from
Jun 22, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -290,6 +290,11 @@ that contains the following settings:
exclude:
- 'some-glob-pattern/**/*.ts'

# The only files that will be included by dep-tree. If a file does not
# match any of the provided patters, it is ignored.
only:
- 'some-glob-pattern/**/*.ts'

# Whether to unwrap re-exports to the target file or not.
# Imagine that you have the following setup:
#
Expand Down
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
Original file line number Diff line number Diff line change
@@ -1,7 +1 @@
{
"tree": {
"cmd/.root_test/main.py": null
},
"circularDependencies": [],
"errors": {}
}
selected entrypoint /Users/gabriel/dep-tree/dep-tree/cmd/.root_test/main.py is explicitly ignored
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"tree": {
"cmd/.root_test/main.py": null
},
"circularDependencies": [],
"errors": {}
}
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()

// For 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
12 changes: 10 additions & 2 deletions cmd/root_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,11 @@ func TestRoot(t *testing.T) {
Name: "tree .root_test/main.py --json --exclude .root_test/dep.py",
},
{
Name: "tree .root_test/main.py --json --exclude .root_test/*.py",
Name: "tree .root_test/main.py --json --exclude .root_test/*.py",
JustExpectAtLeast: 90,
},
{
Name: "tree .root_test/main.py --json --exclude .root_test/d*.py",
},
{
Name: "tree .root_test/main.py --json --config .root_test/.dep-tree.yml",
Expand Down Expand Up @@ -116,7 +120,11 @@ func TestRoot(t *testing.T) {
name = strings.ReplaceAll(name, "*", "_")
if tt.JustExpectAtLeast > 0 {
a := require.New(t)
a.Greater(len(b.String()), tt.JustExpectAtLeast)
if err != nil {
a.Greater(len(err.Error()), tt.JustExpectAtLeast)
} else {
a.Greater(len(b.String()), tt.JustExpectAtLeast)
}
} else {
if err != nil {
utils.GoldenTest(t, filepath.Join(testFolder, name), err.Error())
Expand Down
Loading
Loading