yard-virtus2 0.0.5

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 (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