cobra_commander 0.4.0 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: fb2f9f51193bd4ec022e4f12eae0b43f02d0f4f7adac086ac8c38f2bd7f3bf56
4
- data.tar.gz: bb44e1c12845cab98efdcb8cf1da499f67fa335bd781bf14f4c07d11ed3c9eb5
3
+ metadata.gz: 0a05f3e1ab4ccff9ca040dcf4d694aebc28df0f50160067cf73eaea89f4bad59
4
+ data.tar.gz: d0132e226b7527e72ebb84e3844116dd3a0c2ed8ecc1394d9df5f7fad5f777d8
5
5
  SHA512:
6
- metadata.gz: fd838a67943d9e2ae8cc7038b81ccacaaea719f8cc08cea7704df5147ffc1609ef2a91622549d423954003465d1ec310b0d09bf10ec8f3cf915850e0dbdbe39c
7
- data.tar.gz: a4dec3544d6a5fbcd258b759b9ba143955acf054b4ff44af1fb25d0f3391f37741e9c2d9e31221fd192ec08c843f39b62833a6741de6313e17453e37ca82722b
6
+ metadata.gz: 1cb90f61448f1bc69088418fc86dafbc25fce09d069507e22fc22f0a2d9a63eccf95ce3edeb13b6ee2657e19303489c2b59087944ddc07597c7d0e201c36a307
7
+ data.tar.gz: '023961e1f0abe297e0d0cacc169b3da23fac68f833627d01d480f06d0822019877cd1ac78bb20bf7f951b2bbdce04a0c0ddbb7b3b444a90669f18c967a43775c'
data/CHANGELOG.md CHANGED
@@ -2,6 +2,12 @@
2
2
 
3
3
  ## Unreleased
4
4
 
5
+ ## Version 0.5.0 - 2018-09-20
6
+
7
+ * Renames `dependencies_of` to `dependents_of`. PR [#25](https://github.com/powerhome/cobra_commander/pull/25)
8
+ * Add `dependencies_of` command list the direct and indirect dependencies of one component. PR [#25](https://github.com/powerhome/cobra_commander/pull/25)
9
+ * Add `do` command allow executing a command in the context of each component. PR [#26](https://github.com/powerhome/cobra_commander/pull/26)
10
+
5
11
  ## Version 0.4.0 - 2018-09-06
6
12
 
7
13
  * Add `dependencies_of` command to permit listing or counting the dependencies of a particular component. PR [#24](https://github.com/powerhome/cobra_commander/pull/24)
data/README.md CHANGED
@@ -28,11 +28,15 @@ Or install it yourself as:
28
28
 
29
29
  ```bash
30
30
  Commands:
31
- cobra changes APP_PATH [--results=RESULTS] [--branch=BRANCH] # Prints list of changed files
32
- cobra graph APP_PATH [--format=FORMAT] # Outputs graph
33
- cobra help [COMMAND] # Describe available commands or one specific command
34
- cobra ls APP_PATH # Prints tree of components for an app
35
- cobra version # Prints version
31
+ cobra changes APP_PATH [--results=RESULTS] [--branch=BRANCH] # Prints list of changed files
32
+ cobra graph APP_PATH [--format=FORMAT] # Outputs graph
33
+ cobra help [COMMAND] # Describe available commands or one specific command
34
+ cobra ls APP_PATH [--format=list|tree] # Prints tree of components for an app
35
+ cobra version # Prints version
36
+ cobra do [command] # Executes the command in the context of each component
37
+ cobra dependencies_of [component] [--app=/path/to/app] [--format=list|tree] # Lists the dependencies of the component in the app context
38
+ cobra dependents_of [component] [--app=/path/to/app] [--format=list|count] # Lists or counts the components that depend directly or indirectly on the given component
39
+ cobra version # Prints version
36
40
  ```
37
41
 
38
42
  ## Development
@@ -3,14 +3,19 @@
3
3
  require "cobra_commander/cli"
4
4
  require "cobra_commander/component_tree"
5
5
  require "cobra_commander/version"
6
- require "cobra_commander/formatted_output"
7
6
  require "cobra_commander/graph"
8
7
  require "cobra_commander/change"
9
8
  require "cobra_commander/affected"
9
+ require "cobra_commander/output"
10
+ require "cobra_commander/executor"
10
11
 
11
12
  # Tools for working with Component Based Rails Apps (see http://shageman.github.io/cbra.info/).
12
13
  # Includes tools for graphing the components of an app and their relationships, as well as selectively
13
14
  # testing components based on changes made.
14
15
  module CobraCommander
15
16
  UMBRELLA_APP_NAME = "App"
17
+
18
+ def self.umbrella_tree(path)
19
+ ComponentTree.new(UMBRELLA_APP_NAME, path)
20
+ end
16
21
  end
@@ -13,7 +13,7 @@ module CobraCommander
13
13
  @root_dir = Dir.chdir(path) { `git rev-parse --show-toplevel`.chomp }
14
14
  @results = results
15
15
  @branch = branch
16
- @tree = ComponentTree.new(path).to_h
16
+ @tree = CobraCommander.umbrella_tree(path).to_h
17
17
  @affected = Affected.new(@tree, changes, path)
18
18
  end
19
19
 
@@ -5,16 +5,40 @@ require "thor"
5
5
  module CobraCommander
6
6
  # Implements the tool's CLI
7
7
  class CLI < Thor
8
- desc "ls APP_PATH", "Prints tree of components for an app"
9
- def ls(app_path)
10
- puts FormattedOutput.new(app_path).run!
8
+ desc "do [command]", "Executes the command in the context of each component in [app]"
9
+ method_option :app, default: Dir.pwd, aliases: "-a", desc: "App path (default: CWD)"
10
+ def do(command)
11
+ tree = CobraCommander.umbrella_tree(options.app)
12
+ executor = Executor.new(tree)
13
+ executor.exec(command)
11
14
  end
12
15
 
13
- desc "dependencies_of APP_PATH", "Outputs count of components in APP_PATH dependent on COMPONENT"
14
- method_option :component, required: true, aliases: "-c", desc: "Name of component. Ex: my_component"
16
+ desc "ls [app_path]", "Prints tree of components for an app"
17
+ method_option :app, default: Dir.pwd, aliases: "-a", desc: "App path (default: CWD)"
18
+ method_option :format, default: "tree", aliases: "-f", desc: "Format (list or tree, default: list)"
19
+ def ls(app_path = nil)
20
+ Output.print(
21
+ CobraCommander.umbrella_tree(app_path || options.app),
22
+ options.format
23
+ )
24
+ end
25
+
26
+ desc "dependents_of [component]", "Outputs count of components in [app] dependent on [component]"
27
+ method_option :app, default: Dir.pwd, aliases: "-a", desc: "Path to the root app where the component is mounted"
15
28
  method_option :format, default: "count", aliases: "-f", desc: "count or list"
16
- def dependencies_of(app_path)
17
- puts FormattedOutput.new(app_path).dependencies_of!(@options[:component], @options[:format])
29
+ def dependents_of(component)
30
+ dependents = CobraCommander.umbrella_tree(options.app).dependents_of(component)
31
+ puts "list" == options.format ? dependents.map(&:name) : dependents.size
32
+ end
33
+
34
+ desc "dependencies_of [component]", "Outputs a list of components that [component] depends on within [app] context"
35
+ method_option :app, default: Dir.pwd, aliases: "-a", desc: "App path (default: CWD)"
36
+ method_option :format, default: "list", aliases: "-f", desc: "Format (list or tree, default: list)"
37
+ def dependencies_of(component)
38
+ Output.print(
39
+ CobraCommander.umbrella_tree(options.app).subtree(component),
40
+ options.format
41
+ )
18
42
  end
19
43
 
20
44
  desc "version", "Prints version"
@@ -25,14 +49,14 @@ module CobraCommander
25
49
  desc "graph APP_PATH [--format=FORMAT]", "Outputs graph"
26
50
  method_option :format, default: "png", aliases: "-f", desc: "Accepts png or dot"
27
51
  def graph(app_path)
28
- Graph.new(app_path, @options[:format]).generate!
52
+ Graph.new(app_path, options.format).generate!
29
53
  end
30
54
 
31
55
  desc "changes APP_PATH [--results=RESULTS] [--branch=BRANCH]", "Prints list of changed files"
32
56
  method_option :results, default: "test", aliases: "-r", desc: "Accepts test, full, name or json"
33
57
  method_option :branch, default: "master", aliases: "-b", desc: "Specified target to calculate against"
34
58
  def changes(app_path)
35
- Change.new(app_path, @options[:results], @options[:branch]).run!
59
+ Change.new(app_path, options.results, options.branch).run!
36
60
  end
37
61
  end
38
62
  end
@@ -1,139 +1,167 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "bundler"
3
4
  require "json"
4
5
 
5
6
  module CobraCommander
6
- # Representation of the tree of components and their dependencies
7
+ # Represents a dependency tree in a given context
7
8
  class ComponentTree
8
- def initialize(path)
9
- @root_path = path
9
+ attr_reader :name, :path
10
+
11
+ def initialize(name, path, ancestry = Set.new)
12
+ @name = name
13
+ @path = path
14
+ @ancestry = ancestry
15
+ @ruby = Ruby.new(path)
16
+ @js = Js.new(path)
17
+ @type = type_of_component
18
+ end
19
+
20
+ def flatten
21
+ _flatten(self)
22
+ end
23
+
24
+ def subtree(name)
25
+ _subtree(name, self)
26
+ end
27
+
28
+ def depends_on?(component_name)
29
+ dependencies.any? do |component|
30
+ component.name == component_name || component.depends_on?(component_name)
31
+ end
32
+ end
33
+
34
+ def dependents_of(component_name)
35
+ depends = depends_on?(component_name) ? self : nil
36
+ dependents_below = dependencies.map do |component|
37
+ component.dependents_of(component_name)
38
+ end
39
+ [depends, dependents_below].flatten.compact.uniq(&:name)
10
40
  end
11
41
 
12
42
  def to_h
13
- Tree.new(UMBRELLA_APP_NAME, @root_path).to_h
43
+ {
44
+ name: @name,
45
+ path: path,
46
+ type: @type,
47
+ ancestry: @ancestry,
48
+ dependencies: dependencies.map(&:to_h),
49
+ }
14
50
  end
15
51
 
16
- # Generates component tree
17
- class Tree
18
- def initialize(name, path, ancestry = Set.new)
19
- @name = name
20
- @root_path = path
21
- @ancestry = ancestry
22
- @ruby = Ruby.new(path)
23
- @js = Js.new(path)
24
- @type = type_of_component
52
+ def dependencies
53
+ @deps ||= begin
54
+ deps = @ruby.dependencies + @js.dependencies
55
+ deps.sort_by { |dep| dep[:name] }
56
+ .map(&method(:dep_representation))
25
57
  end
58
+ end
26
59
 
27
- def to_h
28
- {
29
- name: @name,
30
- path: @root_path,
31
- type: @type,
32
- ancestry: @ancestry,
33
- dependencies: dependencies.map(&method(:dep_representation)),
34
- }
60
+ private
61
+
62
+ def _flatten(component)
63
+ component.dependencies.map do |dep|
64
+ [dep] + _flatten(dep)
65
+ end.flatten.uniq(&:name)
66
+ end
67
+
68
+ def _subtree(name, tree)
69
+ return tree if tree.name == name
70
+ tree.dependencies.each do |component|
71
+ return _subtree(name, component)
35
72
  end
73
+ end
36
74
 
37
- private
75
+ def type_of_component
76
+ return "Ruby & JS" if @ruby.gem? && @js.node?
77
+ return "Ruby" if @ruby.gem?
78
+ return "JS" if @js.node?
79
+ end
38
80
 
39
- def type_of_component
40
- return "Ruby & JS" if @ruby.gem? && @js.node?
41
- return "Ruby" if @ruby.gem?
42
- return "JS" if @js.node?
81
+ def dep_representation(dep)
82
+ full_path = File.expand_path(File.join(path, dep[:path]))
83
+ ancestry = @ancestry + [{ name: @name, path: path, type: @type }]
84
+ ComponentTree.new(dep[:name], full_path, ancestry)
85
+ end
86
+
87
+ # Calculates ruby dependencies
88
+ class Ruby
89
+ def initialize(root_path)
90
+ @root_path = root_path
43
91
  end
44
92
 
45
93
  def dependencies
46
94
  @deps ||= begin
47
- deps = @ruby.dependencies + @js.dependencies
48
- deps.sort_by { |dep| dep[:name] }
95
+ return [] unless gem?
96
+ gems = bundler_definition.dependencies.select { |dep| path?(dep.source) }
97
+ format(gems)
49
98
  end
50
99
  end
51
100
 
52
- def dep_representation(dep)
53
- full_path = File.expand_path(File.join(@root_path, dep[:path]))
54
- ancestry = @ancestry + [{ name: @name, path: @root_path, type: @type }]
55
- self.class.new(dep[:name], full_path, ancestry).to_h
101
+ def path?(source)
102
+ return if source.nil?
103
+ source_has_path = source.respond_to?(:path?) ? source.path? : source.is_a_path?
104
+ source_has_path && source.path.to_s != "."
56
105
  end
57
106
 
58
- # Calculates ruby dependencies
59
- class Ruby
60
- def initialize(root_path)
61
- @root_path = root_path
62
- end
63
-
64
- def dependencies
65
- @deps ||= begin
66
- return [] unless gem?
67
- gems = bundler_definition.dependencies.select { |dep| path?(dep.source) }
68
- format(gems)
69
- end
70
- end
71
-
72
- def path?(source)
73
- return if source.nil?
74
- source_has_path = source.respond_to?(:path?) ? source.path? : source.is_a_path?
75
- source_has_path && source.path.to_s != "."
76
- end
77
-
78
- def format(deps)
79
- deps.map do |dep|
80
- path = File.join(dep.source.path, dep.name)
81
- { name: dep.name, path: path }
82
- end
107
+ def format(deps)
108
+ deps.map do |dep|
109
+ path = File.join(dep.source.path, dep.name)
110
+ { name: dep.name, path: path }
83
111
  end
112
+ end
84
113
 
85
- def gem?
86
- @gem ||= File.exist?(gemfile_path)
87
- end
114
+ def gem?
115
+ @gem ||= File.exist?(gemfile_path)
116
+ end
88
117
 
89
- def bundler_definition
90
- ::Bundler::Definition.build(gemfile_path, gemfile_lock_path, nil)
91
- end
118
+ def bundler_definition
119
+ ::Bundler::Definition.build(gemfile_path, gemfile_lock_path, nil)
120
+ end
92
121
 
93
- def gemfile_path
94
- File.join(@root_path, "Gemfile")
95
- end
122
+ def gemfile_path
123
+ File.join(@root_path, "Gemfile")
124
+ end
96
125
 
97
- def gemfile_lock_path
98
- File.join(@root_path, "Gemfile.lock")
99
- end
126
+ def gemfile_lock_path
127
+ File.join(@root_path, "Gemfile.lock")
100
128
  end
129
+ end
101
130
 
102
- # Calculates js dependencies
103
- class Js
104
- def initialize(root_path)
105
- @root_path = root_path
106
- end
131
+ # Calculates js dependencies
132
+ class Js
133
+ def initialize(root_path)
134
+ @root_path = root_path
135
+ end
107
136
 
108
- def dependencies
109
- @deps ||= begin
110
- return [] unless node?
111
- json = JSON.parse(File.read(package_json_path))
112
- format combined_deps(json)
113
- end
137
+ def dependencies
138
+ @deps ||= begin
139
+ return [] unless node?
140
+ json = JSON.parse(File.read(package_json_path))
141
+ format combined_deps(json)
114
142
  end
143
+ end
115
144
 
116
- def format(deps)
117
- return [] if deps.nil?
118
- linked_deps = deps.select { |_, v| v.start_with? "link:" }
119
- linked_deps.map do |_, v|
120
- relational_path = v.split("link:")[1]
121
- dep_name = relational_path.split("/")[-1]
122
- { name: dep_name, path: relational_path }
123
- end
145
+ def format(deps)
146
+ return [] if deps.nil?
147
+ linked_deps = deps.select { |_, v| v.start_with? "link:" }
148
+ linked_deps.map do |_, v|
149
+ relational_path = v.split("link:")[1]
150
+ dep_name = relational_path.split("/")[-1]
151
+ { name: dep_name, path: relational_path }
124
152
  end
153
+ end
125
154
 
126
- def node?
127
- @node ||= File.exist?(package_json_path)
128
- end
155
+ def node?
156
+ @node ||= File.exist?(package_json_path)
157
+ end
129
158
 
130
- def package_json_path
131
- File.join(@root_path, "package.json")
132
- end
159
+ def package_json_path
160
+ File.join(@root_path, "package.json")
161
+ end
133
162
 
134
- def combined_deps(json)
135
- Hash(json["dependencies"]).merge(Hash(json["devDependencies"]))
136
- end
163
+ def combined_deps(json)
164
+ Hash(json["dependencies"]).merge(Hash(json["devDependencies"]))
137
165
  end
138
166
  end
139
167
  end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ module CobraCommander
4
+ # Execute commands on all components of a ComponentTree
5
+ class Executor
6
+ def initialize(tree)
7
+ @tree = tree
8
+ end
9
+
10
+ def exec(command, printer = $stdout)
11
+ @tree.flatten.each do |component|
12
+ printer.puts "===> #{component.name} (#{component.path})"
13
+ output, = run_in_component(component, command)
14
+ printer.puts output
15
+ end
16
+ end
17
+
18
+ private
19
+
20
+ def run_in_component(component, command)
21
+ Dir.chdir(component.path) do
22
+ Open3.capture2e(env_vars(component), command)
23
+ end
24
+ end
25
+
26
+ def env_vars(component)
27
+ { "CURRENT_COMPONENT" => component.name, "CURRENT_COMPONENT_PATH" => component.path }
28
+ end
29
+ end
30
+ end
@@ -8,7 +8,7 @@ module CobraCommander
8
8
  class Graph
9
9
  def initialize(app_path, format)
10
10
  @format = format
11
- @tree = ComponentTree.new(app_path).to_h
11
+ @tree = CobraCommander.umbrella_tree(app_path).to_h
12
12
  end
13
13
 
14
14
  def generate!
@@ -0,0 +1,74 @@
1
+ # frozen_string_literal: true
2
+
3
+ module CobraCommander
4
+ # Module for pretty printing dependency trees
5
+ module Output
6
+ def self.print(tree, format)
7
+ output = format == "list" ? Output::FlatList.new(tree) : Output::Tree.new(tree)
8
+ puts output.to_s
9
+ end
10
+
11
+ # Flattens a tree and prints unique items
12
+ class FlatList
13
+ def initialize(tree)
14
+ @tree = tree
15
+ end
16
+
17
+ def to_s
18
+ @tree.flatten.map(&:name)
19
+ end
20
+ end
21
+
22
+ # Prints the tree in a nice tree form
23
+ class Tree
24
+ attr_accessor :tree
25
+
26
+ SPACE = " "
27
+ BAR = "│   "
28
+ TEE = "├── "
29
+ CORNER = "└── "
30
+
31
+ def initialize(tree)
32
+ @tree = tree
33
+ end
34
+
35
+ def to_s
36
+ StringIO.new.tap do |io|
37
+ io.puts @tree.name
38
+ list_dependencies(io, @tree)
39
+ end.string
40
+ end
41
+
42
+ private
43
+
44
+ def list_dependencies(io, deps, outdents = [])
45
+ deps.dependencies.each do |dep|
46
+ decide_on_line(io, deps, dep, outdents)
47
+ end
48
+ nil
49
+ end
50
+
51
+ def decide_on_line(io, parent, dep, outdents)
52
+ if parent.dependencies.last != dep
53
+ add_tee(io, outdents, dep)
54
+ else
55
+ add_corner(io, outdents, dep)
56
+ end
57
+ end
58
+
59
+ def add_tee(io, outdents, dep)
60
+ io.puts line(outdents, TEE, dep.name)
61
+ list_dependencies(io, dep, (outdents + [BAR]))
62
+ end
63
+
64
+ def add_corner(io, outdents, dep)
65
+ io.puts line(outdents, CORNER, dep.name)
66
+ list_dependencies(io, dep, (outdents + [SPACE]))
67
+ end
68
+
69
+ def line(outdents, sym, name)
70
+ (outdents + [sym] + [name]).join
71
+ end
72
+ end
73
+ end
74
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module CobraCommander
4
- VERSION = "0.4.0"
4
+ VERSION = "0.5.0"
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: cobra_commander
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.0
4
+ version: 0.5.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ben Langfeld
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: exe
11
11
  cert_chain: []
12
- date: 2018-09-06 00:00:00.000000000 Z
12
+ date: 2018-09-20 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: thor
@@ -170,8 +170,9 @@ files:
170
170
  - lib/cobra_commander/change.rb
171
171
  - lib/cobra_commander/cli.rb
172
172
  - lib/cobra_commander/component_tree.rb
173
- - lib/cobra_commander/formatted_output.rb
173
+ - lib/cobra_commander/executor.rb
174
174
  - lib/cobra_commander/graph.rb
175
+ - lib/cobra_commander/output.rb
175
176
  - lib/cobra_commander/version.rb
176
177
  homepage: http://tech.powerhrg.com/cobra_commander/
177
178
  licenses:
@@ -193,7 +194,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
193
194
  version: '0'
194
195
  requirements: []
195
196
  rubyforge_project:
196
- rubygems_version: 2.7.3
197
+ rubygems_version: 2.7.7
197
198
  signing_key:
198
199
  specification_version: 4
199
200
  summary: Tools for working with Component Based Rails Apps
@@ -1,76 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "cobra_commander/component_tree"
4
-
5
- module CobraCommander
6
- # Formats CLI ls output
7
- class FormattedOutput
8
- attr_accessor :tree
9
-
10
- SPACE = " "
11
- BAR = "│   "
12
- TEE = "├── "
13
- CORNER = "└── "
14
-
15
- def initialize(app_path)
16
- @tree = ComponentTree.new(app_path).to_h
17
- end
18
-
19
- def run!
20
- puts @tree[:name]
21
- list_dependencies(@tree)
22
- nil
23
- end
24
-
25
- def dependencies_of!(component_name, format)
26
- @component_name = component_name
27
-
28
- results = @tree[:dependencies].map do |component|
29
- if @component_name == component[:name]
30
- @tree[:name]
31
- elsif dependency?(component)
32
- component[:name]
33
- end
34
- end.compact
35
-
36
- "list" == format ? results : results.size
37
- end
38
-
39
- private
40
-
41
- def dependency?(deps)
42
- deps[:dependencies].each do |dep|
43
- return true if @component_name == dep[:name] || dependency?(dep)
44
- end
45
- false
46
- end
47
-
48
- def list_dependencies(deps, outdents = [])
49
- deps[:dependencies].each do |dep|
50
- decide_on_line(deps, dep, outdents)
51
- end
52
- end
53
-
54
- def decide_on_line(parent, dep, outdents)
55
- if parent[:dependencies].last != dep
56
- add_tee(outdents, dep)
57
- else
58
- add_corner(outdents, dep)
59
- end
60
- end
61
-
62
- def add_tee(outdents, dep)
63
- puts line(outdents, TEE, dep[:name])
64
- list_dependencies(dep, (outdents + [BAR]))
65
- end
66
-
67
- def add_corner(outdents, dep)
68
- puts line(outdents, CORNER, dep[:name])
69
- list_dependencies(dep, (outdents + [SPACE]))
70
- end
71
-
72
- def line(outdents, sym, name)
73
- (outdents + [sym] + [name]).join
74
- end
75
- end
76
- end