yard-api 0.2.3 → 0.3.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
  SHA1:
3
- metadata.gz: ab2e2a361011adda668edbcad1b71ba857fe61ab
4
- data.tar.gz: c3ed5abc5b2de588e9722179e97acbc4c044986a
3
+ metadata.gz: 4174d158765fdfb8f72c2625f74d77e6bff1cf27
4
+ data.tar.gz: e50d4045d98142419848d2898b6a4fcf0989d9dc
5
5
  SHA512:
6
- metadata.gz: d81dc23669668ed42dfc44e15c317b6199ab4be26cecfbc690abc0ab58b1209c0b15a984dd302ca7f0de8c00153f4e102ee0c060a354241724fc054c7b270957
7
- data.tar.gz: 936a31ac15cecde12085d147565e92f51edef9251370ef33d9488ba9aa25d82473d64f48ca756a1235a3e6dbbd3d3f56abcd03ce27079379164e689eaafda338
6
+ metadata.gz: da95663074496a8b87e5ffaa29e922b95a6d2a75f04fd64165bd9f635ea79333777f196d8ecc8f0d6725b32603b27f0bf138a518e6bf6700afec4075e6fd99db
7
+ data.tar.gz: b6672a201e37c8f16829dd9f19a6836c956fb530799dbcab0cdd335a730008732a7f3d0bc8665c9bd2162f9ee991d8ee8c97946125dd977a962a1c8ddd8933c8
data/README.md CHANGED
@@ -40,6 +40,10 @@ Read that file to view all the available options.
40
40
 
41
41
  ## Changelog
42
42
 
43
+ **29/7/2015 [0.3.0]**
44
+
45
+ - major rework of the linking logic, much improvements but some stuff is broken now
46
+
43
47
  **28/7/2015 [0.2.3]**
44
48
 
45
49
  - dropped the `@argument_scope` tag
data/config/yard_api.yml CHANGED
@@ -61,7 +61,7 @@ strict: false
61
61
  #
62
62
  # Just specify the url to your repository and the branch the documentation has
63
63
  # been generated for and things "should work".
64
- #
64
+ #
65
65
  # Example: https://github.com/amireh/yard-api
66
66
  github_url:
67
67
  github_branch: master
@@ -0,0 +1,19 @@
1
+ require 'yard/code_objects/class_object'
2
+
3
+ module YARD::CodeObjects
4
+ class APIObject < Base
5
+ def path
6
+ super().gsub(/\s/, '')
7
+ end
8
+
9
+ def title
10
+ [ object.title, name ].join('::')
11
+ end
12
+ end
13
+
14
+ class ClassObject < NamespaceObject
15
+ def title
16
+ self[:api_id] || super
17
+ end
18
+ end
19
+ end
@@ -4,6 +4,8 @@ module YARD::APIPlugin
4
4
  default_attr :format, 'html'
5
5
  default_attr :no_save, false
6
6
 
7
+ default_attr :output, 'compiled/doc/api'
8
+
7
9
  default_attr :title, 'Rails API Project'
8
10
  default_attr :url_title, 'my_app'
9
11
  default_attr :url_prefix, '/api'
@@ -20,8 +22,8 @@ module YARD::APIPlugin
20
22
 
21
23
  default_attr :one_file, false
22
24
  default_attr :strict, false
23
- default_attr :verbose, false
24
- default_attr :debug, false
25
+ default_attr :verbose, ENV['VERBOSE'] || false
26
+ default_attr :debug, ENV['DEBUG'] || false
25
27
  default_attr :theme, 'default'
26
28
 
27
29
  default_attr :tabular_arguments, false
@@ -39,6 +41,9 @@ module YARD::APIPlugin
39
41
  default_attr :show_footer, true
40
42
  default_attr :readme_page_title, 'Home'
41
43
 
44
+ default_attr :resource_index, false
45
+ default_attr :centered, true
46
+
42
47
  attr_accessor :readme
43
48
  end
44
49
  end
@@ -0,0 +1,9 @@
1
+ module YARD::APIPlugin
2
+ module Registry
3
+ def self.root
4
+ @root ||= begin
5
+ YARD::Registry.register YARD::CodeObjects::NamespaceObject.new(:root, :API)
6
+ end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,61 @@
1
+ module YARD::APIPlugin
2
+ class Serializer < ::YARD::Serializers::FileSystemSerializer
3
+ USNSEP = '__' # url-safe namespace separator
4
+ FSSEP = '/'
5
+
6
+ def self.topicize(str)
7
+ str.lines.first.gsub(/\W+/, '_').downcase
8
+ end
9
+
10
+ def serialize(object, data)
11
+ path = File.join(basepath, serialized_path(object))
12
+
13
+ if path.include?(' ')
14
+ debugger
15
+ end
16
+
17
+ log.debug "Serializing to #{path}"
18
+ File.open!(path, "wb") {|f| f.write data }
19
+ end
20
+
21
+ def serialized_path(object)
22
+ return object if object.is_a?(String)
23
+
24
+ fspath = nil
25
+
26
+ if object.is_a?(YARD::CodeObjects::ExtraFileObject)
27
+ fspath = 'file.' + object.name + (extension.empty? ? '' : ".#{extension}")
28
+ else
29
+ fspath = if object == YARD::Registry.root
30
+ "top-level-namespace"
31
+ else
32
+ self.class.topicize(get_api_id(object))
33
+ end
34
+
35
+ if object.is_a?(YARD::CodeObjects::MethodObject)
36
+ fspath += '_' + object.scope.to_s[0,1]
37
+ end
38
+
39
+ unless extension.empty?
40
+ fspath += ".#{extension}"
41
+ end
42
+ end
43
+
44
+ if (fspath.include?(' '))
45
+ debugger
46
+ end
47
+
48
+ fspath.gsub(/[^\w\.\-_\/]+/, '-')
49
+ end
50
+
51
+ def get_api_id(object)
52
+ if object[:api_id]
53
+ object.api_id
54
+ elsif tag = object.tag(:API)
55
+ tag.text.lines.first.strip
56
+ else
57
+ object.name.to_s
58
+ end
59
+ end
60
+ end
61
+ end
@@ -5,100 +5,100 @@ module YARD::Templates::Helpers::BaseHelper
5
5
  YARD::APIPlugin.options
6
6
  end
7
7
 
8
- def linkify_with_api(*args)
9
- # References to controller actions
10
- #
11
- # Syntax: api:ControllerName#method_name [TITLE OVERRIDE]
12
- #
13
- # @example Explicit reference with title defaulting to the action
14
- # # @see api:Assignments#create
15
- # # => <a href="assignments.html#method.assignments_api.create">create</a>
16
- #
17
- # @example Inline reference with an overriden title
18
- # # Here's a link to absolute {api:Assignments#destroy destruction}
19
- # # => <a href="assignments.html#method.assignments_api.destroy">destruction</a>
20
- #
21
- # @note Action links inside the All Resources section will be relative.
22
- if args.first.is_a?(String) && args.first =~ %r{^api:([^#]+)#(.*)}
23
- topic, controller = *lookup_topic($1.to_s)
24
- if topic
25
- html_file = "#{topicize topic.first}.html"
26
- action = $2
27
- link_url("#{html_file}#method.#{topicize(controller.name.to_s).sub("_controller", "")}.#{action}", args[1])
28
- else
29
- raise "couldn't find API link for #{args.first}"
30
- end
31
-
32
- # References to API objects defined by @object
33
- #
34
- # Syntax: api:ControllerName:Object+Name [TITLE OVERRIDE]
35
- #
36
- # @example Explicit resource reference with title defaulting to its name
37
- # # @see api:Assignments:Assignment
38
- # # => <a href="assignments.html#Assignment">Assignment</a>
39
- #
40
- # @example Explicit resource reference with an overriden title
41
- # # @return api:Assignments:AssignmentOverride An Assignment Override
42
- # # => <a href="assignments.html#Assignment">An Assignment Override</a>
43
- elsif args.first.is_a?(String) && args.first =~ %r{^api:([^:]+):(.*)}
44
- scope_name, resource_name = $1.downcase, $2.gsub('+', ' ')
45
- link_url("#{scope_name}.html##{resource_name}", args[1] || resource_name)
46
- elsif args.first.is_a?(String) && args.first == 'Appendix:' && args.size > 1
47
- __errmsg = "unable to locate referenced appendix '#{args[1]}'"
48
-
49
- unless appendix = lookup_appendix(args[1].to_s)
50
- raise __errmsg
51
- end
52
-
53
- topic, controller = *lookup_topic(appendix.namespace.to_s)
54
-
55
- if topic
56
- html_file = "#{topicize topic.first}.html"
57
- bookmark = "#{appendix.name.to_s.gsub(' ', '+')}-appendix"
58
- ret = link_url("#{html_file}##{bookmark}", appendix.title)
59
- else
60
- raise __errmsg
61
- end
62
-
63
- # A non-API link, delegate to YARD's HTML linker
64
- else
65
- linkify_without_api(*args)
66
- end
67
- end
68
-
69
- alias_method :linkify_without_api, :linkify
70
- alias_method :linkify, :linkify_with_api
71
-
72
- def lookup_topic(controller_name)
73
- controller = nil
74
- topic = options[:resources].find do |resource, controllers|
75
- controllers.detect do |_controller|
76
- if _controller.path.to_s == controller_name
77
- controller = _controller
78
- end
79
- end
80
- end
81
-
82
- [ topic, controller ]
83
- end
84
-
85
- def lookup_appendix(title)
86
- appendix = nil
87
-
88
- YARD::APIPlugin.log("Looking up appendix: #{title}") if api_options.verbose
89
-
90
- if object
91
- # try in the object scope
92
- appendix = YARD::Registry.at(".appendix.#{object.path}.#{title}")
93
-
94
- # try in the object's namespace scope
95
- if appendix.nil? && object.respond_to?(:namespace)
96
- appendix = YARD::Registry.at(".appendix.#{object.namespace.path}.#{title}")
97
- end
98
- end
99
-
100
- appendix
101
- end
8
+ # def linkify_with_api(*args)
9
+ # # References to controller actions
10
+ # #
11
+ # # Syntax: api:ControllerName#method_name [TITLE OVERRIDE]
12
+ # #
13
+ # # @example Explicit reference with title defaulting to the action
14
+ # # # @see api:Assignments#create
15
+ # # # => <a href="assignments.html#method.assignments_api.create">create</a>
16
+ # #
17
+ # # @example Inline reference with an overriden title
18
+ # # # Here's a link to absolute {api:Assignments#destroy destruction}
19
+ # # # => <a href="assignments.html#method.assignments_api.destroy">destruction</a>
20
+ # #
21
+ # # @note Action links inside the All Resources section will be relative.
22
+ # if args.first.is_a?(String) && args.first =~ %r{^api:([^#]+)#(.*)}
23
+ # topic, controller = *lookup_topic($1.to_s)
24
+ # if topic
25
+ # html_file = "#{topicize topic.first}.html"
26
+ # action = $2
27
+ # link_url("#{html_file}#method.#{topicize(controller.name.to_s).sub("_controller", "")}.#{action}", args[1])
28
+ # else
29
+ # raise "couldn't find API link for #{args.first}"
30
+ # end
31
+
32
+ # # References to API objects defined by @object
33
+ # #
34
+ # # Syntax: api:ControllerName:Object+Name [TITLE OVERRIDE]
35
+ # #
36
+ # # @example Explicit resource reference with title defaulting to its name
37
+ # # # @see api:Assignments:Assignment
38
+ # # # => <a href="assignments.html#Assignment">Assignment</a>
39
+ # #
40
+ # # @example Explicit resource reference with an overriden title
41
+ # # # @return api:Assignments:AssignmentOverride An Assignment Override
42
+ # # # => <a href="assignments.html#Assignment">An Assignment Override</a>
43
+ # elsif args.first.is_a?(String) && args.first =~ %r{^api:([^:]+):(.*)}
44
+ # scope_name, resource_name = $1.downcase, $2.gsub('+', ' ')
45
+ # link_url("#{scope_name}.html##{resource_name}", args[1] || resource_name)
46
+ # elsif args.first.is_a?(String) && args.first == 'Appendix:' && args.size > 1
47
+ # __errmsg = "unable to locate referenced appendix '#{args[1]}'"
48
+
49
+ # unless appendix = lookup_appendix(args[1].to_s)
50
+ # raise __errmsg
51
+ # end
52
+
53
+ # topic, controller = *lookup_topic(appendix.namespace.to_s)
54
+
55
+ # if topic
56
+ # html_file = "#{topicize topic.first}.html"
57
+ # bookmark = "#{appendix.name.to_s.gsub(' ', '+')}-appendix"
58
+ # ret = link_url("#{html_file}##{bookmark}", appendix.title)
59
+ # else
60
+ # raise __errmsg
61
+ # end
62
+
63
+ # # A non-API link, delegate to YARD's HTML linker
64
+ # else
65
+ # linkify_without_api(*args)
66
+ # end
67
+ # end
68
+
69
+ # alias_method :linkify_without_api, :linkify
70
+ # alias_method :linkify, :linkify_with_api
71
+
72
+ # def lookup_topic(controller_name)
73
+ # controller = nil
74
+ # topic = options[:resources].find do |resource, controllers|
75
+ # controllers.detect do |_controller|
76
+ # if _controller.path.to_s == controller_name
77
+ # controller = _controller
78
+ # end
79
+ # end
80
+ # end
81
+
82
+ # [ topic, controller ]
83
+ # end
84
+
85
+ # def lookup_appendix(title)
86
+ # appendix = nil
87
+
88
+ # YARD::APIPlugin.log("Looking up appendix: #{title}") if api_options.verbose
89
+
90
+ # if object
91
+ # # try in the object scope
92
+ # appendix = YARD::Registry.at(".appendix.#{object.path}.#{title}")
93
+
94
+ # # try in the object's namespace scope
95
+ # if appendix.nil? && object.respond_to?(:namespace)
96
+ # appendix = YARD::Registry.at(".appendix.#{object.namespace.path}.#{title}")
97
+ # end
98
+ # end
99
+
100
+ # appendix
101
+ # end
102
102
 
103
103
  def tag_partial(name, tag, locals={})
104
104
  options[:tag] = tag
@@ -121,4 +121,8 @@ module YARD::Templates::Helpers::BaseHelper
121
121
  def get_current_route
122
122
  get_current_routes.first
123
123
  end
124
+
125
+ def schema_is_model?(schema)
126
+ schema.has_key?('description') && schema.has_key?('properties')
127
+ end
124
128
  end
@@ -2,7 +2,7 @@ require 'yard/templates/helpers/html_helper'
2
2
 
3
3
  module YARD::Templates::Helpers::HtmlHelper
4
4
  def topicize(str)
5
- str.split("\n")[0].gsub(' ', '_').underscore
5
+ ::YARD::APIPlugin::Serializer.topicize(str)
6
6
  end
7
7
 
8
8
  def url_for_file(filename, anchor = nil)
@@ -11,6 +11,10 @@ module YARD::Templates::Helpers::HtmlHelper
11
11
  link
12
12
  end
13
13
 
14
+ # def url_for_api_object(name, object)
15
+ # "#{object.parent.path}::#{name}"
16
+ # end
17
+
14
18
  def static_pages()
15
19
  @@static_pages ||= begin
16
20
  locate_static_pages(YARD::APIPlugin.options)
@@ -133,4 +137,22 @@ module YARD::Templates::Helpers::HtmlHelper
133
137
  html = resolve_links(html)
134
138
  html
135
139
  end
140
+
141
+ def htmlify_tag_type(tag)
142
+ co = if tag.type =~ /API::(?:(\S+)::)?(\S+)\b/
143
+ # discard [] at the end denoting array of objects
144
+ P(tag.type.gsub(/\[\]$/, ''))
145
+ end
146
+
147
+ if co && !co.is_a?(YARD::CodeObjects::Proxy)
148
+ begin
149
+ linkify(co, tag.type.split(YARD::CodeObjects::NSEP)[1..-1].join(YARD::CodeObjects::NSEP))
150
+ rescue Exception => e
151
+ YARD::APIPlugin.logger.warn e
152
+ ""
153
+ end
154
+ else
155
+ tag.type
156
+ end
157
+ end
136
158
  end
@@ -24,6 +24,8 @@ module YARD
24
24
  case object.type
25
25
  when :root, :module, :constant
26
26
  false
27
+ when :api
28
+ true
27
29
  when :method, :class
28
30
  return false if object.tags('internal').any?
29
31
 
@@ -1,5 +1,5 @@
1
1
  module YARD
2
2
  module APIPlugin
3
- VERSION = "0.2.3"
3
+ VERSION = "0.3.0"
4
4
  end
5
5
  end
data/lib/yard-api.rb CHANGED
@@ -37,6 +37,10 @@ module YARD
37
37
  log.enter_level(level) { log.puts(message) }
38
38
  end
39
39
 
40
+ def self.logger
41
+ YARD::Logger.instance
42
+ end
43
+
40
44
  def self.on_error(message)
41
45
  if self.options.strict
42
46
  raise message
@@ -48,8 +52,11 @@ module YARD
48
52
 
49
53
  require 'yard-api/version'
50
54
  require 'yard-api/options'
55
+ require 'yard-api/registry'
51
56
  require 'yard-api/tags'
52
57
  require 'yard-api/verifier'
58
+ require 'yard-api/serializer'
59
+ require 'yard-api/code_objects/api_object'
53
60
  require 'yard-api/templates/helpers/base_helper'
54
61
  require 'yard-api/templates/helpers/html_helper'
55
62
  require 'yard-api/templates/helpers/route_helper'
@@ -63,6 +70,16 @@ module YARD
63
70
  class YardocOptions < Templates::TemplateOptions
64
71
  default_attr :resources, []
65
72
  default_attr :json_objects, []
73
+ default_attr :controllers, []
74
+ default_attr :current_route, nil
75
+ default_attr :inline_file, nil
76
+ default_attr :all_resources, nil
77
+ default_attr :tag, nil
78
+ default_attr :locale, nil
79
+ default_attr :multi_dialect, false
80
+ default_attr :argument_tags, []
81
+ default_attr :output, nil
82
+ default_attr :json_objects_map, {}
66
83
  end
67
84
  end
68
85
  end
@@ -0,0 +1,67 @@
1
+ require 'spec_helper'
2
+
3
+ module YARD
4
+ module Serializers
5
+ # A serializer that writes data to standard output.
6
+ class SpecSerializer < Base
7
+ # Creates a serializer to print text to stdout
8
+ #
9
+ # @param [Fixnum, nil] wrap if wrap is a number, wraps text to +wrap+
10
+ # columns, otherwise no wrapping is done.
11
+ def initialize(buffer)
12
+ @buffer = buffer
13
+ end
14
+
15
+ # Overrides serialize behaviour to write data to standard output
16
+ def serialize(object, data)
17
+ @buffer << data
18
+ end
19
+ end
20
+ end
21
+ end
22
+
23
+ describe YARD::Templates::Engine.template(:api, :fulldoc) do
24
+ before do
25
+ Registry.clear
26
+ set_option("output", "./tmp/doc")
27
+ end
28
+
29
+ describe '@object' do
30
+ it 'should register it as a CodeObject inside the API namespace' do
31
+ populate <<-'eof'
32
+ # @API Quizzes
33
+ #
34
+ # @object Quiz
35
+ # {
36
+ # "id": "Quiz",
37
+ # "description": "A quiz that can be taken by students.",
38
+ # "properties": {
39
+ # "id": {
40
+ # "type": "String"
41
+ # }
42
+ # }
43
+ # }
44
+ class QuizzesController < ApplicationController
45
+ end
46
+ eof
47
+
48
+ YARD::Templates::Engine.render({
49
+ objects: [ P('QuizzesController') ],
50
+ type: :fulldoc,
51
+ template: :api,
52
+ format: :html
53
+ })
54
+
55
+ expect(Registry.all.map(&:path).sort).to eq(%w[
56
+ API
57
+ API::Quiz
58
+ API::Quizzes
59
+ API::Quizzes::Quiz
60
+ QuizzesController
61
+ ])
62
+
63
+ expect(P('API::Quizzes::Quiz')).to be_truthy
64
+ expect(P('API::Quizzes::Quiz').parent).to eq P('API::Quizzes')
65
+ end
66
+ end
67
+ end
data/spec/spec_helper.rb CHANGED
@@ -1,3 +1,5 @@
1
+ require 'byebug'
2
+
1
3
  # This file was generated by the `rspec --init` command. Conventionally, all
2
4
  # specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`.
3
5
  # The generated `.rspec` file contains `--require spec_helper` which will cause this
@@ -111,4 +113,15 @@ RSpec.configure do |config|
111
113
  @options.reset_defaults
112
114
  @overridden_options.clear
113
115
  end
116
+
117
+ Registry = YARD::Registry
118
+
119
+ def T(template)
120
+ YARD::Templates::Engine.template(:api)
121
+ end
122
+
123
+ def populate(str=nil)
124
+ Registry.clear
125
+ YARD.parse_string str if str
126
+ end
114
127
  end
@@ -24,11 +24,7 @@ a.active {
24
24
  }
25
25
 
26
26
  code {
27
- margin: 0 2px;
28
- padding: 0px 5px;
29
- border: 1px solid #ddd;
30
- background-color: #f8f8f8;
31
- border-radius: 3px;
27
+ background-color: #f5f5f5;
32
28
  font-family: Consolas, "Liberation Mono", Courier, monospace;
33
29
  font-weight: bold;
34
30
  }
@@ -47,13 +43,13 @@ h2 {
47
43
 
48
44
  .endpoint__beta-banner {
49
45
  border-left: 5px solid rgba(255,0,0,0.65);
50
- background-color: #ffc;
46
+ background-color: #FFFBE6;
51
47
  padding: 0.6em;
52
48
  font-size: 85%;
53
49
  }
54
50
 
55
51
  .method-details__beta-flag {
56
- color: #CC0000;
52
+ color: #800;
57
53
  background: #fca;
58
54
  padding: 0.5em 0.6em;
59
55
  font-size: 85%;
@@ -77,6 +73,8 @@ h2 {
77
73
  color: inherit;
78
74
  padding: 0.35em 0;
79
75
  text-decoration: none;
76
+ font-size: 85%;
77
+ line-height: 1.2;
80
78
  }
81
79
 
82
80
  #sidebar a:hover {
@@ -88,28 +86,31 @@ h2 {
88
86
  }
89
87
 
90
88
  .sidebar__heading {
91
- color: #939da3;
89
+ color: #aaa;
92
90
  text-transform: uppercase;
93
91
  font-size: 1em;
94
92
  margin-bottom: 0.5em;
95
93
  padding-bottom: 0;
96
94
  margin-top: 1em;
95
+ font-weight: normal;
97
96
  }
98
97
 
99
98
  .endpoint__path {
100
- font-size: 90%;
99
+ font-size: 85%;
101
100
  font-weight: normal;
102
101
  font-family: Monaco, Consolas, Courier, monospace;
103
- padding: 1em;
104
- margin-top: 1em;
105
- background: #ffc;
102
+ padding: 0.5em;
103
+ margin: 1em 0;
104
+ background: #FFFBE6;
106
105
  }
107
106
 
108
- .method-details .verb {
107
+ .method-details__verb {
109
108
  font-weight: bold;
109
+ color: #800;
110
110
  }
111
+
111
112
  .method-details .id-fragment {
112
- color: #CC0000;
113
+ color: #800;
113
114
  }
114
115
 
115
116
  .method-details__defined-in {
@@ -135,7 +136,7 @@ h2 {
135
136
  /* highlight.js overrides */
136
137
  pre.code, div.syntaxhighlighter {
137
138
  font-family: Monaco, Consolas, Courier, monospace;
138
- background-color: #ffd;
139
+ background-color: #FFFBE6;
139
140
  font-size: 85%;
140
141
  overflow: auto;
141
142
  padding: 0.5em 1em;
@@ -157,18 +158,29 @@ pre.code, div.syntaxhighlighter {
157
158
 
158
159
  td.code textarea { display: none; }
159
160
 
160
- table#quicklinks {
161
- width: 100%;
161
+ .topic__quicklinks {
162
+ padding: 0;
163
+ margin: 0;
162
164
  }
163
- table#quicklinks tr {
164
- border: 1px solid black;
165
+
166
+ .topic__quicklinks li {
167
+ list-style: none;
168
+ display: inline-block;
169
+ margin-right: 1em;
170
+ margin-bottom: 1em;
165
171
  }
166
- table#quicklinks td a {
167
- padding: 6px;
172
+
173
+ .topic__quicklinks li a {
174
+ display: block;
175
+ padding: 0.5em 1em;
176
+ background-color: #f5f5f5;
177
+ text-decoration: none;
178
+ border-radius: 3px;
179
+ font-size: 85%;
168
180
  }
169
- table#quicklinks th {
170
- font-weight: bold;
171
- padding: 12px;
181
+
182
+ .topic__quicklinks li a:hover {
183
+ background-color: transparent;
172
184
  }
173
185
 
174
186
  a.method-details__name-link {
@@ -285,9 +297,6 @@ h4 {
285
297
  }
286
298
 
287
299
  code.argument-listing__argument-name {
288
- border: none;
289
- padding: 0;
290
- margin: 0;
291
300
  overflow: auto;
292
301
  }
293
302
 
@@ -301,7 +310,7 @@ code.argument-listing__argument-name {
301
310
 
302
311
  .argument-listing__argument-required {
303
312
  font-style: normal;
304
- color: #CC0000;
313
+ color: #800;
305
314
  font-weight: bold;
306
315
  opacity: 0.6;
307
316
  }
@@ -319,7 +328,7 @@ code.argument-listing__argument-name {
319
328
 
320
329
  .example-codeblocks__tabs {
321
330
  margin-bottom: -0.5em; /* negate the margin of the example block */
322
- background: #ffc;
331
+ background: #FFF7CC;
323
332
  padding: 0.5em 1em;
324
333
  font-size: 85%;
325
334
  }
@@ -331,4 +340,44 @@ code.argument-listing__argument-name {
331
340
 
332
341
  .example-codeblocks__tabs a.active {
333
342
  font-weight: bold;
343
+ }
344
+
345
+ .type-mute {
346
+ color: #777;
347
+ }
348
+
349
+ .object-synopsis__header {
350
+ padding: 1em 0;
351
+ background: #FFFBE6;
352
+ position: relative; /* for the toggler btn */
353
+ }
354
+
355
+ .object-synopsis__header-text {
356
+ display: block;
357
+ width: 40%;
358
+ max-width: 260px;
359
+ text-align: right;
360
+ }
361
+
362
+ .object-synopsis__toggler {
363
+ appearance: none;
364
+ -webkit-appearance: none;
365
+ -moz-appearance: none;
366
+ border: none;
367
+ padding: 0;
368
+
369
+ background-color: transparent;
370
+ font-size: small;
371
+ color: #4183c4;
372
+ position: absolute;
373
+ top: 50%;
374
+ right: 1em;
375
+ margin-top: -0.5em;
376
+ cursor: pointer;
377
+ font-weight: bold;
378
+ line-height: 1;
379
+ }
380
+
381
+ .object-synopsis__toggler:hover {
382
+ text-decoration: underline;
334
383
  }
@@ -1,6 +1,35 @@
1
+ /* global $:true, hljs:true */
2
+
3
+ function makeObjectSynopsisBlocksTogglable() {
4
+ var MARKER_CLASS = 'object-synopsis__hidden';
5
+
6
+ $('.object-synopsis__toggler').each(function() {
7
+ var $togglerButton = $(this);
8
+ var $objectSynopsis = $togglerButton.closest('.object-synopsis');
9
+ var $objectSynopsisContent = $objectSynopsis.find('.object-synopsis__content');
10
+
11
+ function render() {
12
+ if (!$togglerButton.hasClass(MARKER_CLASS)) {
13
+ $togglerButton.text('Hide');
14
+ $objectSynopsisContent.show();
15
+ }
16
+ else {
17
+ $togglerButton.text('Show');
18
+ $objectSynopsisContent.hide();
19
+ }
20
+ }
21
+
22
+ $togglerButton.on('click', function() {
23
+ $togglerButton.toggleClass(MARKER_CLASS);
24
+ render();
25
+ });
26
+
27
+ render();
28
+ });
29
+ }
30
+
1
31
  $(function() {
2
32
  $('.method-details__name').each(function(i, el) {
3
- var subtopic = $(el).data('subtopic');
4
33
  var $a = $(el).find('a');
5
34
  var anchorText = $.trim($a[0].innerHTML);
6
35
 
@@ -8,9 +37,9 @@ $(function() {
8
37
  return;
9
38
  }
10
39
 
11
- var $row = $('#quicklinks');
40
+ var $row = $('#topicQuicklinks');
12
41
  var $link = $('<a/>', {
13
- href: '#'+$(el).attr('name')
42
+ href: '#' + $(el).attr('name')
14
43
  }).html(anchorText);
15
44
 
16
45
  $('<li>').append($link).appendTo($row);
@@ -50,7 +79,9 @@ $(function() {
50
79
  $(this).addClass('active');
51
80
  });
52
81
  });
53
-
82
+
54
83
  $(this).find('a:first').click();
55
84
  });
56
- });
85
+
86
+ makeObjectSynopsisBlocksTogglable();
87
+ });
@@ -1,11 +1,21 @@
1
1
  require 'pathname'
2
+ require 'json'
2
3
 
3
4
  include YARD::Templates::Helpers::ModuleHelper
4
5
  include YARD::Templates::Helpers::FilterHelper
5
6
 
6
7
  def init
8
+ YARD::APIPlugin.logger.info "YARD-API: starting."
9
+
10
+ options.serializer = YARD::APIPlugin::Serializer.new
11
+ options.serializer.basepath = api_options.output
12
+
13
+ options.objects.each do |object|
14
+ object[:api_id] = object.tag('API').text.lines.first
15
+ end
16
+
7
17
  options[:resources] = options[:objects].
8
- group_by { |o| o.tags('API').first.text.split("\n").first }.
18
+ group_by { |o| o[:api_id] }.
9
19
  sort_by { |o| o.first }
10
20
 
11
21
  build_json_objects_map
@@ -15,13 +25,51 @@ def init
15
25
  return serialize_onefile_index
16
26
  end
17
27
 
18
- serialize_index if File.exists?(api_options['readme'])
28
+ serialize_index if File.exists?(api_options['readme'] || '')
19
29
  serialize_static_pages
20
30
  serialize_resource_index if api_options['resource_index']
21
31
 
22
32
  options.delete(:objects)
23
33
 
24
34
  options[:resources].each do |resource, controllers|
35
+ controllers.each do |controller|
36
+ if controller.is_a?(YARD::CodeObjects::NamespaceObject)
37
+ co = YARD::CodeObjects::ClassObject.new(
38
+ YARD::APIPlugin::Registry.root,
39
+ controller[:api_id]
40
+ )
41
+
42
+ YARD::Registry.register co
43
+
44
+ (controller.tags(:object) + controller.tags(:model)).each do |tag|
45
+ tag_co = YARD::CodeObjects::APIObject.new(co, tag.text.lines[0].strip)
46
+ tag_co.object = tag.object
47
+
48
+ # Make an alias on the global API namespace, for convenience.
49
+ # Now an object called "Bar" under the "Foo" controller can be
50
+ # referenced using [API::Bar] as well as [API::Foo::Bar] which will
51
+ # never face any conflicts.
52
+ shortcut_tag_co = YARD::CodeObjects::APIObject.new(YARD::APIPlugin::Registry.root, tag.text.lines[0].strip)
53
+ shortcut_tag_co.object = tag.object
54
+
55
+ # We need to override #namespace because #url_for() uses it to
56
+ # generate the url, which has to be done usign #object and not
57
+ # #namespace (which points to P("API") and we want
58
+ # P("API::#{tag.object.path}")).
59
+ shortcut_tag_co.namespace = tag.object
60
+
61
+ YARD::Registry.register(tag_co)
62
+ YARD::Registry.register(shortcut_tag_co)
63
+ end
64
+ end
65
+ end
66
+
67
+ # debugger
68
+
69
+ if controllers.length > 1
70
+ debugger
71
+ end
72
+
25
73
  serialize_resource(resource, controllers)
26
74
  end
27
75
  end
@@ -34,12 +82,21 @@ def serialize(object)
34
82
  end
35
83
 
36
84
  def serialize_resource(resource, controllers)
85
+ YARD::APIPlugin.logger.info('=' * 80)
86
+ YARD::APIPlugin.logger.info ">>> #{resource} <<< (#{controllers})"
87
+ YARD::APIPlugin.logger.info('-' * 80)
88
+
37
89
  options[:object] = resource
38
90
  options[:controllers] = controllers
39
- Templates::Engine.with_serializer("#{topicize resource}.html", options[:serializer]) do
40
- T('layout').run(options)
91
+
92
+ controllers.each do |controller|
93
+ Templates::Engine.with_serializer(controller, options.serializer) do
94
+ T('layout').run(options)
95
+ end
41
96
  end
97
+
42
98
  options.delete(:controllers)
99
+ YARD::APIPlugin.logger.info('-' * 80)
43
100
  end
44
101
 
45
102
  def serialize_index
@@ -93,12 +150,55 @@ def build_json_objects_map
93
150
  options[:json_objects] = {}
94
151
  options[:resources].each do |r,cs|
95
152
  cs.each do |controller|
96
- (controller.tags(:object) + controller.tags(:model)).each do |obj|
97
- name, json = obj.text.split(%r{\n+}, 2).map(&:strip)
98
- options[:json_objects_map][name] = topicize r
99
- options[:json_objects][r] ||= []
100
- options[:json_objects][r] << [name, json]
153
+ (controller.tags(:object) + controller.tags(:model)).each do |tag|
154
+ name, json_string = tag.text.split(%r{\n+}, 2).map(&:strip)
155
+
156
+ if json = parse_json(json_string)
157
+ options[:json_objects_map][name] = topicize r
158
+ options[:json_objects][r] ||= []
159
+ options[:json_objects][r] << [name, json]
160
+ end
101
161
  end
102
162
  end
103
163
  end
104
164
  end
165
+
166
+ def parse_json(json_string)
167
+ JSON::parse(json_string || '').tap do |json|
168
+ validate_json_schema(json, ->(msg) {
169
+ raise <<-MSG
170
+ #{'=' * 32}
171
+ #{msg.strip}
172
+ #{'-' * 32}
173
+ Offending JSON belongs to: "#{name}" in "#{tag.object.path}".
174
+ #{'=' * 32}
175
+ MSG
176
+ })
177
+ end
178
+ rescue JSON::ParserError => e
179
+ YARD::APIPlugin.on_error(
180
+ <<-MSG
181
+ #{'*' * 32}
182
+ A @#{tag.tag_name} docstring contains invalid JSON.
183
+ Offending JSON belongs to "#{name}" in "#{tag.object.path}".
184
+ ---
185
+ #{tag}
186
+ ---
187
+ #{e}
188
+ #{'*' * 32}
189
+ MSG
190
+ )
191
+
192
+ nil
193
+ end
194
+
195
+ def validate_json_schema(schema, on_error)
196
+ if schema_is_model?(schema)
197
+ if !schema['description'].is_a?(String)
198
+ on_error.call <<-MSG
199
+ Expected "description" to be a String, got #{schema['description'].class}.
200
+ Value: #{schema['description'].to_json}
201
+ MSG
202
+ end
203
+ end
204
+ end
@@ -10,20 +10,21 @@
10
10
  body {
11
11
  margin: 0 auto;
12
12
  width: <%= content_width %>px;
13
+ padding-left: <%= sidebar_width %>px;
13
14
  }
14
-
15
+
15
16
  #sidebar {
16
17
  margin-left: -<%= sidebar_width %>px;
17
18
  width: <%= sidebar_width %>px;
18
19
  left: auto;
19
20
  }
20
-
21
+
21
22
  #content {
22
23
  padding-left: 3em;
23
24
  }
24
- <% else %>
25
+ <% else %>
25
26
  body {
26
- padding-left: <%= sidebar_width + api_options.spacer %>px;
27
+ padding-left: <%= sidebar_width + (api_options.spacer || 0) %>px;
27
28
  padding-right: <%= api_options.spacer %>px;
28
29
  }
29
30
 
@@ -21,7 +21,7 @@
21
21
  <% end %>
22
22
 
23
23
  <% options[:resources].each do |resource, controllers| %>
24
- <%= sidebar_link resource, "#{topicize(resource)}.html" %>
24
+ <%= sidebar_link resource, controllers.first %>
25
25
  <% end %>
26
26
  </nav>
27
27
  </div>
@@ -7,7 +7,10 @@
7
7
  %>
8
8
 
9
9
  <div class='endpoint__path'>
10
- <span class="verb <%= route[:verb].downcase %>"><%= route[:verb] %></span>
10
+ <span class="method-details__verb <%= route[:verb].downcase %>">
11
+ <%= route[:verb] %>
12
+ </span>
13
+
11
14
  <%= formatted_route_path %>
12
15
  </div>
13
16
 
@@ -1,11 +1,13 @@
1
1
  <ul class="argument-listing">
2
- <% @argument_tags.each do |tag| %>
2
+ <% options[:argument_tags].each do |tag| %>
3
3
  <li class="argument-listing__argument">
4
4
  <div class="argument-listing__argument-details">
5
5
  <code class="argument-listing__argument-name"><%= h tag.name %></code>
6
- <span class="argument-listing__argument-type"><%= tag.type %></span>
6
+ <span class="argument-listing__argument-type">
7
+ <%= htmlify_tag_type(tag) %>
8
+ </span>
7
9
 
8
- <% if (tag.accepted_values || []).any? %>
10
+ <% if Array(tag.accepted_values).any? %>
9
11
  <span class="argument-listing__argument-values">
10
12
  <span>[ <%= tag.accepted_values.join(', ') %> ]</span>
11
13
  </span>
@@ -19,6 +21,8 @@
19
21
  <div class="argument-listing__argument-text">
20
22
  <% if !tag.text.empty? %>
21
23
  <%= html_markup_markdown(tag.text) %>
24
+ <% else %>
25
+ <em class="type-mute">No description provided.</em>
22
26
  <% end %>
23
27
  </div>
24
28
  </li>
@@ -1,4 +1,5 @@
1
- <% has_accepted_values = @argument_tags.any? { |e| Array(e.accepted_values).any? } %>
1
+ <% argument_tags = options[:argument_tags] %>
2
+ <% has_accepted_values = argument_tags.any? { |e| Array(e.accepted_values).any? } %>
2
3
 
3
4
  <table>
4
5
  <thead>
@@ -12,7 +13,7 @@
12
13
  </thead>
13
14
 
14
15
  <tbody>
15
- <% @argument_tags.each do |tag| %>
16
+ <% argument_tags.each do |tag| %>
16
17
  <tr>
17
18
  <td><code class="argument-name"><%= h tag.name %></code></td>
18
19
  <td><span class="argument-type"><%= tag.type %></span></td>
@@ -4,6 +4,7 @@
4
4
  <% else %>
5
5
  Returns <%= %w[a e i o u].include?(@resource_name[0]) ? 'an' : 'a' %>
6
6
  <% end %>
7
+
7
8
  <a href='<%= @resource_name %>.html#<%= @object_name %>'>
8
9
  <%= @is_list ? @object_name.pluralize : @object_name %>
9
10
  </a>
@@ -47,10 +47,10 @@ def emits
47
47
  end
48
48
 
49
49
  def argument
50
- # generic_tag :argument, :no_types => false, :label => "Request Parameters"
51
- @argument_tags = object.tags(:argument)
50
+ argument_tags = object.tags(:argument)
52
51
 
53
- if @argument_tags.any?
52
+ if argument_tags.any?
53
+ options[:argument_tags] = argument_tags
54
54
  erb('argument')
55
55
  end
56
56
  end
@@ -66,6 +66,11 @@ def returns
66
66
  @object_name = response_info.text.strip
67
67
  @is_list = false
68
68
  end
69
+
70
+ # if @object_name =~ /\{(\S+)\}/
71
+ # @object_name = $1
72
+ # end
73
+
69
74
  @resource_name = options[:json_objects_map][@object_name]
70
75
  return unless @resource_name
71
76
  erb(:returns)
@@ -4,17 +4,18 @@ def init
4
4
  sections :header, [:topic_doc, :method_details_list, [T('method_details')]]
5
5
  @resource = object
6
6
  @beta = options[:controllers].any? { |c| c.tag('beta') }
7
+ @meths = options[:controllers].map { |c| c.meths(:inherited => false, :included => false) }.flatten
8
+ @meths = run_verifier(@meths)
7
9
  end
8
10
 
9
11
  def method_details_list
10
- @meths = options[:controllers].map { |c| c.meths(:inherited => false, :included => false) }.flatten
11
- @meths = run_verifier(@meths)
12
12
  erb(:method_details_list)
13
13
  end
14
14
 
15
15
  def topic_doc
16
16
  @docstring = options[:controllers].map { |c| c.docstring }.join("\n\n")
17
17
  @object = @object.dup
18
+ @controller = options[:controllers].first
18
19
  def @object.source_type; nil; end
19
20
  @json_objects = options[:json_objects][@resource] || []
20
21
  erb(:topic_doc)
@@ -28,6 +29,21 @@ def properties_of_model(json)
28
29
  end
29
30
  end
30
31
 
32
+ def properties_of_model_as_tags(json)
33
+ props = json.has_key?('properties') ? json['properties'] : json
34
+ props.reduce([]) do |tags, (id, prop)|
35
+ is_required = prop.has_key?('required') ? prop['required'] : false
36
+ is_required_str = is_required ? 'Required' : 'Optional'
37
+
38
+ tag = YARD::APIPlugin::Tags::ArgumentTag.new(
39
+ nil,
40
+ "[#{is_required_str}, #{prop['type']}] #{id}\n #{prop['description']}"
41
+ )
42
+
43
+ tags << tag
44
+ end
45
+ end
46
+
31
47
  def word_wrap(text, col_width=80)
32
48
  text.gsub!( /(\S{#{col_width}})(?=\S)/, '\1 ' )
33
49
  text.gsub!( /(.{1,#{col_width}})(?:\s+|$)/, "\\1\n" )
@@ -13,15 +13,50 @@
13
13
  <section>
14
14
  <h2>Interfaces</h2>
15
15
 
16
- <ul id="quicklinks"></ul>
16
+ <% if @meths.empty? %>
17
+ <p>
18
+ <em class="type-mute">This API currently has no endpoints available.</em>
19
+ </p>
20
+ <% end %>
21
+
22
+ <ul id="topicQuicklinks" class="topic__quicklinks"></ul>
17
23
  </section>
18
24
 
19
- <% @json_objects.each do |name, json| %>
20
- <% properties = render_properties(json) %>
21
- <div class='object_definition'>
22
- <h3>
23
- <a name="<%= name %>"></a><%= name %> object synopsis:
24
- </h3>
25
- <pre class="example code"><%= html_syntax_highlight(properties ? properties : json, :plain) %></pre>
26
- </div>
27
- <% end %>
25
+ <% if @json_objects.any? %>
26
+ <section>
27
+ <h2>Object Synopses</h2>
28
+
29
+ <% @json_objects.each do |name, json| %>
30
+ <% if false %>
31
+ <% properties = render_properties(json) %>
32
+ <div class='object-synopsis'>
33
+ <h3>
34
+ <a name="<%= name %>"></a><%= name %> object synopsis:
35
+ </h3>
36
+ <pre class="example code"><%= html_syntax_highlight(properties ? properties : json, :plain) %></pre>
37
+ </div>
38
+ <% end %>
39
+
40
+ <div class="object-synopsis">
41
+ <h3 class="object-synopsis__header" id="<%= name %>-api">
42
+ <span class="object-synopsis__header-text"><%= name %></span>
43
+ <button class="object-synopsis__toggler"></button>
44
+ </h3>
45
+
46
+ <div class="object-synopsis__content">
47
+ <div class="object-synopsis__content-description">
48
+ <% if schema_is_model?(json) && !json['description'].empty? %>
49
+ <%= htmlify json['description'] %>
50
+ <% end %>
51
+ </div>
52
+
53
+ <%=
54
+ tag_partial("../../tags/html/argument/_list", nil, {
55
+ argument_tags: properties_of_model_as_tags(json)
56
+ })
57
+ %>
58
+ </div>
59
+ </div>
60
+ <% end %>
61
+ </section>
62
+ <% end %>
data/yard-api.gemspec CHANGED
@@ -19,4 +19,5 @@ Gem::Specification.new do |s|
19
19
  s.add_dependency 'yard-appendix'
20
20
  s.add_development_dependency 'rspec'
21
21
  s.add_development_dependency 'gem-release'
22
+ s.add_development_dependency 'byebug'
22
23
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: yard-api
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.3
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ahmad Amireh
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-07-28 00:00:00.000000000 Z
11
+ date: 2015-07-29 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: yard
@@ -66,6 +66,20 @@ dependencies:
66
66
  - - ">="
67
67
  - !ruby/object:Gem::Version
68
68
  version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: byebug
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
69
83
  description: |2
70
84
  TBD
71
85
  email: ahmad@instructure.com
@@ -78,9 +92,12 @@ files:
78
92
  - README.md
79
93
  - config/yard_api.yml
80
94
  - lib/yard-api.rb
95
+ - lib/yard-api/code_objects/api_object.rb
81
96
  - lib/yard-api/markup/redcarpet.rb
82
97
  - lib/yard-api/options.rb
83
98
  - lib/yard-api/railtie.rb
99
+ - lib/yard-api/registry.rb
100
+ - lib/yard-api/serializer.rb
84
101
  - lib/yard-api/tags.rb
85
102
  - lib/yard-api/tags/argument_tag.rb
86
103
  - lib/yard-api/templates/helpers/base_helper.rb
@@ -89,6 +106,7 @@ files:
89
106
  - lib/yard-api/verifier.rb
90
107
  - lib/yard-api/version.rb
91
108
  - lib/yard-api/yardoc_task.rb
109
+ - spec/fulldoc_spec.rb
92
110
  - spec/spec_helper.rb
93
111
  - spec/tags/argument_spec.rb
94
112
  - tasks/yard_api.rake