society 0.13.2 → 1.0.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.
Files changed (39) hide show
  1. checksums.yaml +4 -4
  2. data/.bowerrc +3 -0
  3. data/.gitignore +2 -14
  4. data/README.md +18 -17
  5. data/bower.json +6 -0
  6. data/lib/society.rb +16 -6
  7. data/lib/society/association_processor.rb +206 -0
  8. data/lib/society/cli.rb +12 -9
  9. data/lib/society/edge.rb +15 -0
  10. data/lib/society/formatter/graph/json.rb +44 -0
  11. data/lib/society/formatter/report/html.rb +73 -0
  12. data/lib/society/formatter/report/json.rb +39 -0
  13. data/lib/society/formatter/report/templates/components/.gitignore +7 -0
  14. data/lib/society/formatter/report/templates/components/d3/d3.min.js +5 -0
  15. data/lib/society/formatter/report/templates/components/society-assets/society.css +70 -0
  16. data/lib/society/formatter/report/templates/components/society-assets/society.js +420 -0
  17. data/lib/society/formatter/report/templates/index.htm.haml +48 -0
  18. data/lib/society/object_graph.rb +3 -2
  19. data/lib/society/parser.rb +49 -32
  20. data/lib/society/reference_processor.rb +60 -0
  21. data/lib/society/version.rb +1 -1
  22. data/society.gemspec +4 -1
  23. data/society_graph.json +1 -0
  24. data/spec/association_processor_spec.rb +174 -0
  25. data/spec/cli_spec.rb +25 -0
  26. data/spec/fixtures/foo.rb +6 -18
  27. data/spec/formatter/graph/json_spec.rb +52 -0
  28. data/spec/formatter/report/html_spec.rb +75 -0
  29. data/spec/formatter/report/json_spec.rb +70 -0
  30. data/spec/object_graph_spec.rb +30 -0
  31. data/spec/parser_spec.rb +61 -0
  32. data/spec/reference_processor_spec.rb +18 -0
  33. data/spec/spec_helper.rb +3 -0
  34. metadata +77 -13
  35. data/lib/society/matrix.rb +0 -36
  36. data/lib/society/node.rb +0 -19
  37. data/spec/fixtures/bar.rb +0 -6
  38. data/spec/matrix_spec.rb +0 -27
  39. data/spec/node_spec.rb +0 -30
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 8f65f593c46ae32062a78ec4c22aaf53f55f7436
4
- data.tar.gz: 7f39c3d8b353ff9ad94568abcc690c90a5b878b7
3
+ metadata.gz: 6e981e4f78cf210bc9a370f895b47f44b66f48a1
4
+ data.tar.gz: 164a11a1e55e5921ca3dc32c58afbf984f09427f
5
5
  SHA512:
6
- metadata.gz: 109d6b030877005b7246c2702312cbac6213d9687b06d2e573ddfcb617cc5ec528f40db2799773800ba365666a965c0b8c3a60e49a2a23bfd8cf8c1af1001385
7
- data.tar.gz: 46b91d5ee61b7056f8709d03b1d0dc607482b7d53ac6d758b2b3f8813b4035cf66aebfa9a65debcbf0d37283b02759c8f35ac7efb08c47d23e635d3e9f11b538
6
+ metadata.gz: fb27a9fe4c863f8acd5ff810ab0d5c37be1f22082f1f1e412a7349c58ed4e616631a8852f60eb7f6fc8a96ac254266e56a509aa43e51274fc22a162cea206a88
7
+ data.tar.gz: f5859b87ee2efd9e3456c488298f0e2fe6d67ed25bfa497e56e42ea7b6a0710925584289c62d62d0621f7cac873c1549c833d7388391678348176bd79a0cb116
data/.bowerrc ADDED
@@ -0,0 +1,3 @@
1
+ {
2
+ "directory": "lib/society/formatter/report/templates/components"
3
+ }
data/.gitignore CHANGED
@@ -1,23 +1,11 @@
1
- *.gem
2
- *.rbc
3
1
  .bundle
4
2
  .config
5
- .yardoc
6
3
  Gemfile.lock
7
- InstalledFiles
8
- _yardoc
9
4
  coverage
10
- doc/
11
- lib/bundler/man
12
5
  pkg
13
6
  rdoc
14
7
  spec/reports
15
- test/tmp
16
- test/version_tmp
17
8
  tmp
18
- *.bundle
19
- *.so
20
- *.o
21
- *.a
22
- mkmf.log
23
9
  .console_history
10
+ doc/society
11
+
data/README.md CHANGED
@@ -1,6 +1,7 @@
1
1
  # Society
2
2
 
3
- Society analyzes and presents a social graph of relationships between classes or methods.
3
+ Society analyzes and presents a social graph of relationships between classes or
4
+ methods.
4
5
 
5
6
  ## Installation
6
7
 
@@ -18,31 +19,31 @@ Or install it yourself as:
18
19
 
19
20
  ## Usage
20
21
 
21
- Initialize a parser with source files:
22
+ From your terminal:
22
23
 
23
- parser = Society::Parser.new("path/to/models")
24
+ society from path/to/models
24
25
 
25
- Generate an object dependency graph:
26
+ and then open `doc/society/index.htm` in your browser.
26
27
 
27
- graph = parser.class_graph
28
+ The default format is HTML; you can skip the HTML interface and just get the
29
+ JSON by passing `--format json`
28
30
 
29
- Generate a method dependency graph:
31
+ Note that all JSON data is timestamped (regardless of output format) to store
32
+ snapshots of your project over time.
30
33
 
31
- graph = parser.method_graph
34
+ ## Updating assets
32
35
 
33
- Generate JSON dependency matrix for export to d3:
34
-
35
- parser.matrix.to_jsno
36
-
37
- ## TODO
38
-
39
- * Add fukuzatsu as a dependency
40
-
41
- * Wrap fukuzatsu parsing and remove duplicate classes
36
+ All JavaScript and CSS dependencies are checked into the repo, so any given
37
+ commit should have everything it needs. However, if you're developing the gem
38
+ and need to pull in updates from the
39
+ [society-assets](https://github.com/CoralineAda/society-assets) package, you
40
+ can do so on the command line with `$ bower update`.
42
41
 
43
42
  ## Contributing
44
43
 
45
- Please note that this project is released with a [Contributor Code of Conduct](https://github.com/Bantik/society/blob/master/CODE_OF_CONDUCT.md). By participating in this project you agree to abide by its terms.
44
+ Please note that this project is released with a [Contributor Code of Conduct]
45
+ (http://contributor-covenant.org/version/1/0/0/).
46
+ By participating in this project you agree to abide by its terms.
46
47
 
47
48
 
48
49
  1. Fork it ( https://github.com/[my-github-username]/society/fork )
data/bower.json ADDED
@@ -0,0 +1,6 @@
1
+ {
2
+ "name": "society",
3
+ "dependencies": {
4
+ "society-assets": "1.1.1"
5
+ }
6
+ }
data/lib/society.rb CHANGED
@@ -1,12 +1,22 @@
1
+ require "analyst"
1
2
  require "fileutils"
3
+ require "active_support/core_ext/string/inflections"
2
4
 
3
- module Society
4
- end
5
-
6
- require_relative "society/cli"
7
- require_relative "society/matrix"
8
- require_relative "society/node"
5
+ require_relative "society/association_processor"
6
+ require_relative "society/reference_processor"
7
+ require_relative "society/edge"
8
+ require_relative "society/formatter/graph/json"
9
+ require_relative "society/formatter/report/html"
10
+ require_relative "society/formatter/report/json"
9
11
  require_relative "society/object_graph"
10
12
  require_relative "society/parser"
11
13
  require_relative "society/version"
12
14
 
15
+ module Society
16
+
17
+ def self.new(path_to_files)
18
+ Society::Parser.for_files(path_to_files)
19
+ end
20
+
21
+ end
22
+
@@ -0,0 +1,206 @@
1
+ module Society
2
+
3
+ class AssociationProcessor
4
+
5
+ ACTIVE_MODEL_ASSOCIATIONS = %w[belongs_to has_one has_many has_and_belongs_to_many]
6
+
7
+ attr_reader :classes, :associations, :unresolved_associations
8
+
9
+ def initialize(classes)
10
+ @classes = classes
11
+ process
12
+ end
13
+
14
+ private
15
+
16
+ def process
17
+ default_resolver = DefaultResolver.new(classes)
18
+ through_resolver = ThroughResolver.new(classes)
19
+ poly_resolver = PolymorphicResolver.new(default_resolver)
20
+
21
+ classes.each do |klass|
22
+ klass.macros.select do |macro|
23
+ ACTIVE_MODEL_ASSOCIATIONS.include?(macro.name)
24
+ # TODO: make sure macro.target is 'self' (it pretty much always will be)
25
+ end.each do |association|
26
+ through_resolver.process(association, klass) or
27
+ poly_resolver.process(association, klass) or
28
+ default_resolver.process(association, klass)
29
+ end
30
+ end
31
+
32
+ @associations = default_resolver.associations
33
+ @unresolved_associations = default_resolver.unresolved_associations
34
+
35
+ @associations.concat(poly_resolver.associations).concat(through_resolver.associations)
36
+ @unresolved_associations.concat(through_resolver.unresolved_associations)
37
+ end
38
+
39
+
40
+ class ThroughResolver
41
+
42
+ attr_reader :associations, :unresolved_associations
43
+
44
+ def initialize(classes)
45
+ @classes = classes
46
+ @my_own_default_resolver = DefaultResolver.new(classes)
47
+ @associations = []
48
+ @unresolved_associations = []
49
+ end
50
+
51
+ def process(association, klass)
52
+ return false unless thru = through_of(association)
53
+
54
+ if source_type = association.arguments.last.to_hash[:source_type]
55
+ target = @classes.detect { |cls| cls.full_name == source_type }
56
+ else
57
+ joiner = klass.macros.detect do |macro|
58
+ ACTIVE_MODEL_ASSOCIATIONS.include?(macro.name) && macro.arguments.first.value == thru
59
+ end
60
+ joining_class_entities = @my_own_default_resolver.all_classes_for(joiner)
61
+
62
+ source_str = source_of(association).to_s
63
+
64
+ target = joining_class_entities.reduce(nil) do |_,cls|
65
+ assoc = find_association_in(cls, source_str)
66
+ break @my_own_default_resolver.process(assoc, cls) if assoc
67
+ end
68
+ end
69
+
70
+ if target
71
+ @associations << Edge.new(from: klass,
72
+ to: target,
73
+ meta: {
74
+ type: :through_association,
75
+ macro: association })
76
+ else
77
+ @unresolved_associations << { class: klass,
78
+ macro: association,
79
+ thru_macro: joiner,
80
+ joining_classes: joining_class_entities }
81
+ end
82
+ true
83
+ end
84
+
85
+ private
86
+
87
+ def find_association_in(klass, name)
88
+ klass.macros.detect do |macro|
89
+ ACTIVE_MODEL_ASSOCIATIONS.include?(macro.name) &&
90
+ macro.arguments.first.value.to_s.singularize == name.singularize
91
+ end
92
+ end
93
+
94
+ def through_of(association)
95
+ last_arg = association.arguments.last
96
+ last_arg.is_a?(Analyst::Entities::Hash) && last_arg.to_hash[:through]
97
+ end
98
+
99
+ def source_of(association)
100
+ association.arguments.last.to_hash[:source] || association.arguments.first.value
101
+ end
102
+
103
+ end
104
+
105
+
106
+ class DefaultResolver
107
+
108
+ attr_reader :associations, :unresolved_associations
109
+
110
+ def initialize(classes)
111
+ @classes = classes
112
+ @associations = []
113
+ @unresolved_associations = []
114
+ end
115
+
116
+ def process(association, klass)
117
+ if target = all_classes_for(association).first
118
+ @associations << Edge.new(from: klass,
119
+ to: target,
120
+ meta: {
121
+ type: :association,
122
+ macro: association })
123
+ else
124
+ @unresolved_associations << { class: klass,
125
+ target_name: target_class_name_for(association),
126
+ macro: association }
127
+ end
128
+ target
129
+ end
130
+
131
+ def all_classes_for(association)
132
+ target_name = target_class_name_for(association)
133
+ classes.select { |klass| klass.full_name == target_name }
134
+ end
135
+
136
+ private
137
+
138
+ attr_reader :classes
139
+
140
+ def target_class_name_for(association)
141
+ last_arg = association.arguments.last
142
+ if last_arg.is_a? Analyst::Entities::Hash
143
+ target_name = last_arg.to_hash[:class_name]
144
+ end
145
+ target_name ||= association.arguments.first.value.to_s.pluralize.classify
146
+ end
147
+
148
+ end
149
+
150
+
151
+ class PolymorphicResolver
152
+
153
+ def initialize(default_resolver)
154
+ @default_resolver = default_resolver
155
+ @polymorphics = []
156
+ @ases = []
157
+ end
158
+
159
+ # stores off any `polymorphic: true` or `as: :something` associations for
160
+ # later processing. also does default processing on `as: :something`
161
+ # associations. returns true iff association requires no further processing.
162
+ def process(association, klass)
163
+ last_arg = association.arguments.last
164
+ return false unless last_arg.is_a?(Analyst::Entities::Hash)
165
+
166
+ opts = last_arg.to_hash
167
+ if opts.key? :polymorphic
168
+ @polymorphics << { class: klass,
169
+ key: association.arguments.first.value,
170
+ macro: association }
171
+ return true
172
+
173
+ elsif opts.key? :as
174
+ if target = @default_resolver.process(association, klass)
175
+ @ases << { class: klass, target: target,
176
+ macro: association, key: opts[:as] }
177
+ end
178
+ return true
179
+ end
180
+
181
+ false
182
+ end
183
+
184
+ def associations
185
+ associations = []
186
+ @polymorphics.each do |poly|
187
+ matching = @ases.select do |as|
188
+ as[:key] == poly[:key] && as[:target] == poly[:class]
189
+ end
190
+ matching.each do |as|
191
+ associations << Edge.new(from: poly[:class],
192
+ to: as[:class],
193
+ meta: {
194
+ type: :polymorphic_association,
195
+ macro: poly[:macro],
196
+ complement: as[:macro] })
197
+ end
198
+ end
199
+ associations
200
+ end
201
+ end
202
+
203
+ end
204
+
205
+ end
206
+
data/lib/society/cli.rb CHANGED
@@ -1,26 +1,29 @@
1
1
  require "thor"
2
+ require "society"
2
3
 
3
4
  module Society
4
5
 
5
6
  class CLI < Thor
6
7
 
7
- desc_text = ""
8
+ desc_text = "Formats are html (default) and json."
9
+ desc_text << "Example: society from foo/ -f json -o ./society_data.json"
8
10
 
9
- desc "from PATH_TO_FILE", desc_text
10
- def from(path="./")
11
- parser = Society::Parser.new(path)
12
- parser.parse_files
13
- parser.report
11
+ desc "from PATH_TO_FILE [-f FORMAT] [-o OUTPUT_PATH]", desc_text
12
+ method_option :format, :type => :string, :default => 'html', :aliases => "-f"
13
+ method_option :output, :type => :string, :aliases => "-o"
14
+
15
+ def from(path)
16
+ Society.new(path).report(format, options['output'])
14
17
  end
15
18
 
16
19
  default_task :from
17
20
 
18
21
  private
19
22
 
20
- def formatter
21
- Formatters::Text
23
+ def format
24
+ options['format'] && options['format'].to_sym
22
25
  end
23
26
 
24
27
  end
25
28
 
26
- end
29
+ end
@@ -0,0 +1,15 @@
1
+ module Society
2
+ class Edge
3
+
4
+ attr_reader :from, :to
5
+ attr_accessor :meta
6
+
7
+ def initialize(from:, to:, meta:nil)
8
+ @from = from
9
+ @to = to
10
+ @meta = meta
11
+ end
12
+
13
+ end
14
+
15
+ end
@@ -0,0 +1,44 @@
1
+ require 'json'
2
+
3
+ module Society
4
+ module Formatter
5
+ module Graph
6
+ class JSON
7
+
8
+ def initialize(graph)
9
+ @nodes = graph.nodes
10
+ @edges = graph.edges
11
+ end
12
+
13
+ def to_json
14
+ to_hash.to_json
15
+ end
16
+
17
+ def to_hash
18
+ {
19
+ nodes: node_names.map { |name| { name: name } },
20
+ edges: named_edges.map do |edge|
21
+ {
22
+ from: node_names.index(edge.from),
23
+ to: node_names.index(edge.to)
24
+ }
25
+ end
26
+ }
27
+ end
28
+
29
+ private
30
+
31
+ attr_reader :nodes, :edges
32
+
33
+ def node_names
34
+ @node_names ||= nodes.map(&:full_name).uniq
35
+ end
36
+
37
+ def named_edges
38
+ @named_edges ||= edges.map { |edge| Edge.new(from: edge.from.full_name, to: edge.to.full_name) }
39
+ end
40
+
41
+ end
42
+ end
43
+ end
44
+ end