yard-virtus2 0.0.5

Sign up to get free protection for your applications and to get access to all the features.
Files changed (44) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +24 -0
  3. data/.idea/modules.xml +8 -0
  4. data/Gemfile +17 -0
  5. data/Gemfile.lock +75 -0
  6. data/Guardfile +11 -0
  7. data/LICENSE.txt +19 -0
  8. data/README.md +61 -0
  9. data/Rakefile +9 -0
  10. data/example/README.md +4 -0
  11. data/example/address.rb +7 -0
  12. data/example/city.rb +5 -0
  13. data/example/user.rb +9 -0
  14. data/lib/yard-virtus.rb +18 -0
  15. data/lib/yard/virtus/code_objects.rb +2 -0
  16. data/lib/yard/virtus/code_objects/attribute_reader.rb +26 -0
  17. data/lib/yard/virtus/code_objects/attribute_writer.rb +48 -0
  18. data/lib/yard/virtus/declarations.rb +4 -0
  19. data/lib/yard/virtus/declarations/options.rb +53 -0
  20. data/lib/yard/virtus/declarations/type.rb +71 -0
  21. data/lib/yard/virtus/declarations/virtus_attribute.rb +73 -0
  22. data/lib/yard/virtus/declarations/virtus_model.rb +40 -0
  23. data/lib/yard/virtus/handlers.rb +2 -0
  24. data/lib/yard/virtus/handlers/include_virtus_model.rb +32 -0
  25. data/lib/yard/virtus/handlers/virtus_attribute.rb +58 -0
  26. data/lib/yard/virtus/mixin_handler_monkey_patch.rb +25 -0
  27. data/lib/yard/virtus/version.rb +5 -0
  28. data/spec/code_objects/attribute_reader_spec.rb +38 -0
  29. data/spec/code_objects/attribute_writer_spec.rb +75 -0
  30. data/spec/declarations/options_spec.rb +50 -0
  31. data/spec/declarations/type_spec.rb +37 -0
  32. data/spec/declarations/virtus_attribute_spec.rb +73 -0
  33. data/spec/declarations/virtus_model_spec.rb +35 -0
  34. data/spec/examples/include_virtus_model_001.rb.txt +15 -0
  35. data/spec/examples/virtus_attribute_001.rb.txt +65 -0
  36. data/spec/handlers/include_virtus_model_spec.rb +22 -0
  37. data/spec/handlers/virtus_attribute_spec.rb +67 -0
  38. data/spec/spec_helper.rb +65 -0
  39. data/spec/support/helpers/handler_helpers.rb +9 -0
  40. data/spec/support/helpers/parsing_helpers.rb +13 -0
  41. data/spec/support/matchers/attribute_matchers.rb +39 -0
  42. data/spec/support/matchers/method_type_matchers.rb +11 -0
  43. data/yard-virtus2.gemspec +22 -0
  44. metadata +119 -0
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 0ae62b7d9da5e4d35e74362a3d73c7cd4353ea2feddc511a5376e38458d0ac6a
4
+ data.tar.gz: 2238ad54bd66edc345d6559799e52b8f88e9a7596581773cb6791c06a67f0eae
5
+ SHA512:
6
+ metadata.gz: de41f36807697d23143d8e889ff7624a846beed3d020d6a35812d796d1adf08bf91aec7a233c3c7aec89930ea6909bec0a29e15f6f5f75fd91b51a3eef22f46f
7
+ data.tar.gz: 015f709229bfc35820b6f88cbd0fea86a687598af4c0045bd7ef33dc1fd4310e7f7b1ca1a4e16d4578ffa04e705671ac4220cf9b194df189b9b6d89557825236
@@ -0,0 +1,24 @@
1
+ # Ignore bundler config and scripts/binaries
2
+ /.bundle
3
+ /.bin
4
+
5
+ # Ignore YARD run-off
6
+ .yardoc/
7
+
8
+ # Ignore automatically generated documentation
9
+ # for sample project
10
+ example/doc
11
+
12
+ doc
13
+
14
+ .DS_Store
15
+ *~
16
+ *#
17
+ *.o
18
+ *.dylib
19
+ *.a
20
+ *.so
21
+ .#*
22
+ *.rbc
23
+ .rvmrc
24
+ tmp/
@@ -0,0 +1,8 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <project version="4">
3
+ <component name="ProjectModuleManager">
4
+ <modules>
5
+ <module fileurl="file://$PROJECT_DIR$/.idea/yard-virtus.iml" filepath="$PROJECT_DIR$/.idea/yard-virtus.iml" />
6
+ </modules>
7
+ </component>
8
+ </project>
data/Gemfile ADDED
@@ -0,0 +1,17 @@
1
+ source "https://rubygems.org"
2
+
3
+ gem "virtus", "= 1.0.2"
4
+ gem "yard", "= 0.8.7.4"
5
+
6
+ group :development do
7
+ gem "rspec"
8
+
9
+ gem "guard-rspec", "~> 4.2.9", require: false
10
+ gem "terminal-notifier-guard"
11
+
12
+ gem "rake"
13
+ gem "pry"
14
+
15
+ # Github Flavored Markdown support
16
+ gem "redcarpet"
17
+ end
@@ -0,0 +1,75 @@
1
+ GEM
2
+ remote: https://rubygems.org/
3
+ specs:
4
+ axiom-types (0.1.1)
5
+ descendants_tracker (~> 0.0.4)
6
+ ice_nine (~> 0.11.0)
7
+ thread_safe (~> 0.3, >= 0.3.1)
8
+ celluloid (0.15.2)
9
+ timers (~> 1.1.0)
10
+ coderay (1.1.0)
11
+ coercible (1.0.0)
12
+ descendants_tracker (~> 0.0.1)
13
+ descendants_tracker (0.0.4)
14
+ thread_safe (~> 0.3, >= 0.3.1)
15
+ diff-lcs (1.2.5)
16
+ equalizer (0.0.9)
17
+ ffi (1.9.3)
18
+ formatador (0.2.5)
19
+ guard (2.6.1)
20
+ formatador (>= 0.2.4)
21
+ listen (~> 2.7)
22
+ lumberjack (~> 1.0)
23
+ pry (>= 0.9.12)
24
+ thor (>= 0.18.1)
25
+ guard-rspec (4.2.9)
26
+ guard (~> 2.1)
27
+ rspec (>= 2.14, < 4.0)
28
+ ice_nine (0.11.0)
29
+ listen (2.7.5)
30
+ celluloid (>= 0.15.2)
31
+ rb-fsevent (>= 0.9.3)
32
+ rb-inotify (>= 0.9)
33
+ lumberjack (1.0.6)
34
+ method_source (0.8.2)
35
+ pry (0.9.12.6)
36
+ coderay (~> 1.0)
37
+ method_source (~> 0.8)
38
+ slop (~> 3.4)
39
+ rake (10.3.2)
40
+ rb-fsevent (0.9.4)
41
+ rb-inotify (0.9.4)
42
+ ffi (>= 0.5.0)
43
+ redcarpet (3.1.2)
44
+ rspec (2.14.1)
45
+ rspec-core (~> 2.14.0)
46
+ rspec-expectations (~> 2.14.0)
47
+ rspec-mocks (~> 2.14.0)
48
+ rspec-core (2.14.8)
49
+ rspec-expectations (2.14.5)
50
+ diff-lcs (>= 1.1.3, < 2.0)
51
+ rspec-mocks (2.14.6)
52
+ slop (3.5.0)
53
+ terminal-notifier-guard (1.5.3)
54
+ thor (0.19.1)
55
+ thread_safe (0.3.3)
56
+ timers (1.1.0)
57
+ virtus (1.0.2)
58
+ axiom-types (~> 0.1)
59
+ coercible (~> 1.0)
60
+ descendants_tracker (~> 0.0.3)
61
+ equalizer (~> 0.0.9)
62
+ yard (0.8.7.4)
63
+
64
+ PLATFORMS
65
+ ruby
66
+
67
+ DEPENDENCIES
68
+ guard-rspec (~> 4.2.9)
69
+ pry
70
+ rake
71
+ redcarpet
72
+ rspec
73
+ terminal-notifier-guard
74
+ virtus (= 1.0.2)
75
+ yard (= 0.8.7.4)
@@ -0,0 +1,11 @@
1
+ # A sample Guardfile
2
+ # More info at https://github.com/guard/guard#readme
3
+
4
+ guard :rspec, cmd: "bundle exec rspec" do
5
+ watch(%r{^spec/(.+)/.+_spec\.rb$})
6
+
7
+ watch(%r{^lib/yard/virtus/(.+)/(.+)\.rb$}) { |m| "spec/#{m[1]}/#{m[2]}_spec.rb" }
8
+
9
+ watch('spec/spec_helper.rb') { "spec" }
10
+ watch(%r{spec/support/(.+)/*.rb$}) { "spec" }
11
+ end
@@ -0,0 +1,19 @@
1
+ Copyright (c) 2014 Dmitry Dzema
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ of this software and associated documentation files (the "Software"), to deal
5
+ in the Software without restriction, including without limitation the rights
6
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ copies of the Software, and to permit persons to whom the Software is
8
+ furnished to do so, subject to the following conditions:
9
+
10
+ The above copyright notice and this permission notice shall be included in all
11
+ copies or substantial portions of the Software.
12
+
13
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19
+ SOFTWARE.
@@ -0,0 +1,61 @@
1
+ # yard-virtus
2
+
3
+
4
+ This gem helps to generate YARD documentation for classes built using [Virtus](https://github.com/solnic/virtus). It extracts information about attributes, their types and writers so you don't need to specify it manually.
5
+
6
+ This gem depends on exact details of implementation of Virtus so it's locked to a particular version. In the future I wan to adapt versioning scheme for this gem which will reflect supported Virtus version.
7
+
8
+ ## Install
9
+
10
+ Add the gem to your Gemfile (inside development or documentation group):
11
+
12
+ ``` ruby
13
+ gem "yard-virtus", "= 0.0.4"
14
+ ```
15
+
16
+ ## Usage
17
+
18
+ If you use YARD via Rake and `YARD::Rake::YardocTask` or via custom ruby script add
19
+
20
+ ``` ruby
21
+ require "yard-virtus"
22
+ ```
23
+
24
+ If you use YARD command line tool use `--plugin` switch like this
25
+
26
+ ```shell
27
+ yard --plugin virtus
28
+ ```
29
+
30
+ ### Workaround for UndocumentableError Warnings
31
+
32
+ Standard YARD mixin handler throws `UndocumentableError` when it encounters
33
+ mixin which uses method call (like `include Virtus.model`). It serves as a warning
34
+ and does not break parsing process but it could be annoying if you have a lot
35
+ of Virtus based models. This gem includes mokey-patch for `YARD::Handlers::Ruby::MixinHandler`.
36
+ It is not loaded by default and you need to do it explicitly in your Rakefile with:
37
+
38
+ ```ruby
39
+ require "yard/virtus/mixin_handler_monkey_patch"
40
+ ```
41
+
42
+ ## Work in Progress
43
+
44
+ This library is still work in progress and is not recommended for production use.
45
+
46
+ ### TODO
47
+
48
+ * Detect if class gets virtus functionality via inheritance (partially done).
49
+ * Attach documentation about various features inherited from Virtus to namespaces.
50
+ * Extract default values of attributes.
51
+
52
+ ## Notice
53
+
54
+ This library uses eval with $SAFE level 3 to evaluate list of options in
55
+ `attribute` declarations. Setting $SAFE to level 3 means it can not do
56
+ destructive operations on your file system but it can damage objects which
57
+ are already in memory.
58
+
59
+ ### Author
60
+
61
+ [Dmitry Dzema](https://github.com/DimaD) ([@dzema](https://twitter.com/dzema))
@@ -0,0 +1,9 @@
1
+ require File.join(File.dirname(__FILE__), "lib", "yard-virtus")
2
+ require File.join(File.dirname(__FILE__), "lib", "yard-virtus")
3
+ require File.join(File.dirname(__FILE__), "lib", "yard/virtus/mixin_handler_monkey_patch")
4
+
5
+ desc "Build documentation for sample code set"
6
+ YARD::Rake::YardocTask.new("example") do |t|
7
+ t.files = ["example/**/*.rb"]
8
+ t.options = ["--no-private", "--markup-provider=redcarpet", "--markup=markdown", "--output-dir=./example/doc"]
9
+ end
@@ -0,0 +1,4 @@
1
+ # virtus-yardoc example
2
+
3
+ This is a simple project to demonstrate virtus-yard features.
4
+ Code here is devised from main Virtus reame.
@@ -0,0 +1,7 @@
1
+ class Address
2
+ include Virtus.model
3
+
4
+ attribute :street, String
5
+ attribute :zipcode, String
6
+ attribute :city, City
7
+ end
@@ -0,0 +1,5 @@
1
+ class City
2
+ include Virtus.model
3
+
4
+ attribute :name, String
5
+ end
@@ -0,0 +1,9 @@
1
+ class User
2
+ include Virtus.model
3
+
4
+ attribute :name, String
5
+ attribute :age, Integer
6
+
7
+ # User can have work address or home address
8
+ attribute :addresses, Array[Address]
9
+ end
@@ -0,0 +1,18 @@
1
+ require "virtus"
2
+ require "yard"
3
+
4
+ _dir = File.dirname(__FILE__)
5
+ require File.join(_dir, "yard", "virtus", "version")
6
+
7
+ require File.join(_dir, "yard", "virtus", "handlers")
8
+ require File.join(_dir, "yard", "virtus", "declarations")
9
+ require File.join(_dir, "yard", "virtus", "code_objects")
10
+
11
+ # According to ruby conventions if library is called
12
+ # `yard-virtus` it should be included via
13
+ #
14
+ # require "yard/virtus"
15
+ #
16
+ # But YARD plugins do not follow this convention and require
17
+ # gem names like `yard-*` and being required by the same name.
18
+ # Hence this confusing filename.
@@ -0,0 +1,2 @@
1
+ require File.join(File.dirname(__FILE__), "code_objects", "attribute_reader")
2
+ require File.join(File.dirname(__FILE__), "code_objects", "attribute_writer")
@@ -0,0 +1,26 @@
1
+ module YARD
2
+ module Virtus
3
+ module CodeObjects
4
+ class AttributeReader
5
+ attr_reader :attr_name, :type
6
+
7
+ # @param [Symbol] attr name of attribute
8
+ # @param [String] type string representation of type in YARD format
9
+ def initialize(attr, type)
10
+ @attr_name = attr
11
+ @type = type
12
+ end
13
+
14
+ def method_name
15
+ attr_name
16
+ end
17
+
18
+ def yard_method_object(namespace)
19
+ YARD::CodeObjects::MethodObject.new(namespace, method_name, :instance).tap do |mo|
20
+ mo.add_tag YARD::Tags::Library.new.return_tag("[#{type}]")
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,48 @@
1
+ module YARD
2
+ module Virtus
3
+ module CodeObjects
4
+ class AttributeWriter
5
+ attr_reader :attr_name, :type
6
+
7
+ # @param [Symbol] attr name of attribute
8
+ # @param [String] type string representation of type in YARD format
9
+ # @param [Boolean] is_private indicates if writer is part of private API
10
+ def initialize(attr, type, is_private=false)
11
+ @attr_name = attr
12
+ @type = type
13
+ @is_private = is_private
14
+ end
15
+
16
+ def method_name
17
+ :"#{attr_name}="
18
+ end
19
+
20
+ def yard_method_object(namespace)
21
+ YARD::CodeObjects::MethodObject.new(namespace, method_name, :instance).tap do |mo|
22
+ mo.parameters = [["value", default_value]]
23
+ mo.add_tag param_tag("value", type)
24
+ mo.add_tag private_tag if private?
25
+ end
26
+ end
27
+
28
+ protected
29
+
30
+ def default_value
31
+ nil
32
+ end
33
+
34
+ def private?
35
+ !!@is_private
36
+ end
37
+
38
+ def param_tag(param_name, param_type)
39
+ YARD::Tags::Tag.new("param", nil, param_type, param_name)
40
+ end
41
+
42
+ def private_tag
43
+ YARD::Tags::Tag.new("private", "")
44
+ end
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,4 @@
1
+ require File.join(File.dirname(__FILE__), "declarations", "virtus_model")
2
+ require File.join(File.dirname(__FILE__), "declarations", "virtus_attribute")
3
+ require File.join(File.dirname(__FILE__), "declarations", "type")
4
+ require File.join(File.dirname(__FILE__), "declarations", "options")
@@ -0,0 +1,53 @@
1
+ module YARD
2
+ module Virtus
3
+ module Declarations
4
+ # Options declaration wraps AST which represents
5
+ # options hash.
6
+ #
7
+ # @example
8
+ # # this is AST for hash part of call `hello :default => 0, :writer => :private`
9
+ # ast = s(s(:assoc, s(:symbol_literal, s(:symbol, s(:ident, "default"))),
10
+ # s(:int, "0")),
11
+ # s(:assoc, s(:symbol_literal, s(:symbol, s(:ident, "writer"))),
12
+ # s(:symbol_literal, s(:symbol, s(:ident, "private")))))
13
+ # options = YARD::Virtus::Declarations::Options.new(ast)
14
+ # options[:writer] # => :private
15
+ class Options
16
+ attr_reader :ast
17
+
18
+ # @param [YARD::Parser::Ruby::AstNode, nil] ast
19
+ def initialize(ast)
20
+ @ast = ast
21
+ @data = if ast.kind_of?(YARD::Parser::Ruby::AstNode)
22
+ safe_eval("{#{ast.source}}")
23
+ else
24
+ {}
25
+ end
26
+ end
27
+
28
+ # Get option value by key.
29
+ def [](key)
30
+ data[key]
31
+ end
32
+
33
+ # Predicate to check if there are any options.
34
+ def empty?
35
+ data.empty?
36
+ end
37
+
38
+ protected
39
+ attr_reader :data
40
+
41
+ # It's not the best idea to use eval but interpreting AST tree to search for hash
42
+ # values is not fun either.
43
+ # @todo replace with AST tree interpreter
44
+ def safe_eval(source)
45
+ proc do |src|
46
+ $SAFE = 3;
47
+ eval(src)
48
+ end.call(source.untaint)
49
+ end
50
+ end
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,71 @@
1
+ module YARD
2
+ module Virtus
3
+ module Declarations
4
+ # Type declaration wraps AST which represents type of Virtus attribute.
5
+ # It's job is to translate AST into type string which YARD can understand.
6
+ #
7
+ # @example
8
+ # # this is AST for `Array[String]`.
9
+ # ast = s(:aref, s(:var_ref, s(:const, "Array")),
10
+ # s(s(:var_ref, s(:const, "String")), false))
11
+ #
12
+ # type = YARD::Virtus::Declarations::Type.new(ast)
13
+ # type.yard_type_string # => "Array<String>"
14
+ class Type
15
+ attr_reader :ast
16
+
17
+ # @param [YARD::Parser::Ruby::ReferenceNode, YARD::Parser::Ruby::AstNode] ast
18
+ def initialize(ast)
19
+ @ast = ast
20
+ end
21
+
22
+ # Get type string for provided AST.
23
+ #
24
+ # @return [String] if provided AST can be transformed into type
25
+ # @return [nil] if can not transform AST into type string
26
+ def yard_type_string
27
+ if association?(ast)
28
+ yard_type_from_association(ast)
29
+ elsif collection?(ast)
30
+ yard_type_from_collection(ast)
31
+ elsif ast.ref?
32
+ yard_type_from_reference(ast)
33
+ elsif !ast[0].nil?
34
+ Type.new(ast[0]).yard_type_string
35
+ else
36
+ nil
37
+ end
38
+ end
39
+
40
+ protected
41
+ def yard_type_from_reference(tree)
42
+ tree.path.join("::")
43
+ end
44
+
45
+ def yard_type_from_association(tree)
46
+ collection = tree[0].jump(:var_ref)
47
+ key, value = extract_kv_from_association(ast[1])
48
+
49
+ "%s{%s => %s}" % [collection, key, value].map { |type| Type.new(type).yard_type_string }
50
+ end
51
+
52
+ def yard_type_from_collection(ast)
53
+ "%s<%s>" % [ast[0], ast[1]].map { |type| Type.new(type).yard_type_string }
54
+ end
55
+
56
+ def association?(tree)
57
+ tree.jump(:assoc) != tree
58
+ end
59
+
60
+ def collection?(tree)
61
+ tree.type == :aref
62
+ end
63
+ def extract_kv_from_association(tree)
64
+ assoc = tree.jump(:assoc)
65
+
66
+ return assoc[0], assoc[1]
67
+ end
68
+ end
69
+ end
70
+ end
71
+ end