qdocs 0.2.2 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 75a08fcc7bf5ea74395c1b0f2bc6ec49e1716379a5d153261653442bcba0a010
4
- data.tar.gz: 7d8c561702a327a76be14139f0e39e8ba9740be8d30d9eb320cfeb60502c454b
3
+ metadata.gz: 9c361421b501da1e4ae6c3cea59c9b28c3a5f8b50b2e3b4bf519d8b5ab97cafb
4
+ data.tar.gz: df3a642f6604fc8ae7c6a086a19e8178a7b3f48b119c5b6b4b9470e6a054edac
5
5
  SHA512:
6
- metadata.gz: 6c2a3550627278b51b4743a18b781b71988d8cd2ed4025245414548bdc1dcbe9f3d5e73b71b8d94fba39927bef76db6f378463d6ad901acc6e397b45690e695e
7
- data.tar.gz: 63fc2b5e5a0ea435bf0f7d72eb3fce8a381dfc9dd8c0d6707f84e7b95b1c9b77d65a0d3556531eefdd5a6129fb16e3ef7c32f461cacbaaf438a6235e9e3ae1b4
6
+ metadata.gz: 035d39b7f119206e715a9134c97949eb598144b2eb06cd35ca816f179948cd89b2e38336c2574dbe4e7975c319dda6ce8d74199f77429060cefd45fbe646131c
7
+ data.tar.gz: 7091c36e72815b43cb3c0b218fa3e1cbb8175a79db535bd1ebd11a59bed08141563c220db0ae5d1b4787971b4c4d5a7886679d6288c23a1c9dd470a1861e754f
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- qdocs (0.2.2)
4
+ qdocs (0.3.0)
5
5
  method_source (~> 1)
6
6
  rack (> 1)
7
7
 
data/README.md CHANGED
@@ -27,13 +27,20 @@ Or install it yourself as:
27
27
 
28
28
  ## Usage
29
29
 
30
- This gem offers CLI usage, or server usage:
30
+ This gem offers CLI usage, text editor integration, or viewing in a browser:
31
31
 
32
- #### Server usage:
32
+ #### Starting a server manually:
33
33
  `$ qdocs --server`
34
34
 
35
35
 
36
- `$ curl 'http://localhost:8080/?input=User%2Efind'`
36
+ `$ curl 'http://localhost:7593/?input=User%2Efind' # view JSON response`
37
+
38
+ or view it in your browser, to be able to navigate through a GUI:
39
+
40
+ `$ open 'http://localhost:7593/?input=User%2Efind'`
41
+
42
+ <img width="1346" alt="Screenshot 2021-05-24 at 11 30 29" src="https://user-images.githubusercontent.com/43235608/119334821-771c1980-bc83-11eb-8027-af9c31338885.png">
43
+
37
44
 
38
45
  #### CLI usage
39
46
 
@@ -62,33 +69,42 @@ $ qdocs 'Set#length'
62
69
  **also provides support for constants which are recognised to be ActiveRecord models:**
63
70
 
64
71
  ```
65
- $ curl 'http://localhost:7593/?input=User%2Femail%2F'
72
+ $ curl 'http://localhost:7593/?input=User%2Eemail'
66
73
  {
74
+ "original_input": "User#id",
67
75
  "constant": {
68
76
  "name": "User",
69
77
  "type": "Class"
70
78
  },
71
- "query_type": "methods",
79
+ "query_type": "instance_method",
72
80
  "attributes": {
73
- "constant": "User",
74
- "singleton_methods": [
75
- "find_by_unconfirmed_email_with_errors"
76
- ],
77
- "instance_methods": [
78
- "postpone_email_change?",
79
- "postpone_email_change_until_confirmation_and_regenerate_confirmation_token",
80
- "send_email_changed_notification?",
81
- "send_verification_email"
82
- ]
81
+ "defined_at": "~/.rvm/gems/ruby-2.7.1/gems/activerecord-6.0.3.6/lib/active_record/attribute_methods/primary_key.rb:18",
82
+ "source": "def id\n _read_attribute(@primary_key)\nend\n",
83
+ "arity": 0,
84
+ "parameters": {},
85
+ "comment": "# Returns the primary key column's value.",
86
+ "name": "id",
87
+ "belongs_to": "ActiveRecord::AttributeMethods::PrimaryKey",
88
+ "super_method": null
83
89
  }
84
90
  }
85
91
 
86
92
  ```
87
93
 
94
+ #### Editor integration
95
+
96
+ See below
97
+
88
98
  #### Further usage examples:
89
99
 
90
100
  `$ qdocs --help`
91
101
 
102
+ ## Editor integration
103
+
104
+ Feel free to add others!
105
+
106
+ - [Vim](https://github.com/johansenja/qdocs-vim)
107
+
92
108
  ## Development
93
109
 
94
110
  After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
@@ -97,7 +113,7 @@ To install this gem onto your local machine, run `bundle exec rake install`. To
97
113
 
98
114
  ## Contributing
99
115
 
100
- Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/qdocs.
116
+ Bug reports and pull requests are welcome on GitHub at https://github.com/johansenja/qdocs.
101
117
 
102
118
  ## License
103
119
 
data/lib/qdocs.rb CHANGED
@@ -13,6 +13,8 @@ module Qdocs
13
13
 
14
14
  class UnknownPatternError < StandardError; end
15
15
 
16
+ class InvalidArgumentError < StandardError; end
17
+
16
18
  module Helpers
17
19
  def source_location_to_str(source_location)
18
20
  if source_location && source_location.length == 2
@@ -44,10 +46,17 @@ module Qdocs
44
46
  end
45
47
 
46
48
  def render_response(const, type, attrs)
49
+ const_name = if const.is_a?(Class) || const.is_a?(Module)
50
+ const.name
51
+ elsif instance_of?(Class) || const.instance_of?(Module)
52
+ const.inspect
53
+ else
54
+ const.to_s
55
+ end
47
56
  {
48
57
  original_input: @original_input,
49
58
  constant: {
50
- name: const.name,
59
+ name: const_name,
51
60
  type: const.class.name,
52
61
  },
53
62
  query_type: type,
@@ -71,11 +80,43 @@ module Qdocs
71
80
 
72
81
  const_sl = Object.const_source_location const
73
82
 
74
- render_response(constant, :constant, {
75
- source_location: source_location_to_str(const_sl),
76
- instance_methods: own_methods(constant.instance_methods).sort,
77
- singleton_methods: own_methods(constant.methods).sort,
78
- })
83
+ if constant.instance_of?(Class) ||
84
+ constant.instance_of?(Module)
85
+ render_response(constant, :constant, {
86
+ source_location: source_location_to_str(const_sl),
87
+ instance_methods: own_methods(constant.instance_methods).sort,
88
+ singleton_methods: own_methods(constant.methods).sort,
89
+ included_modules: constant.included_modules,
90
+ constants: constant.constants,
91
+ constant_type: constant.class.name,
92
+ inheritance_chain: build_inheritance_chain(constant).map(&:name),
93
+ })
94
+ else
95
+ render_response(constant, :constant, {
96
+ source_location: source_location_to_str(const_sl),
97
+ instance_methods: nil,
98
+ singleton_methods: nil,
99
+ included_modules: nil,
100
+ constants: nil,
101
+ value: constant,
102
+ constant_type: constant.class.name,
103
+ inheritance_chain: nil,
104
+ })
105
+ end
106
+ end
107
+
108
+ private
109
+
110
+ def build_inheritance_chain(initial)
111
+ chain = [initial]
112
+ klass = initial
113
+ while klass.respond_to?(:superclass)
114
+ break unless (sc = klass.superclass)
115
+
116
+ chain << sc
117
+ klass = sc
118
+ end
119
+ chain
79
120
  end
80
121
  end
81
122
 
@@ -119,7 +160,7 @@ module Qdocs
119
160
  rescue NameError
120
161
  raise UnknownMethodError, "No method #{meth.inspect} for #{constant}. Did you mean #{constant}/#{meth}/ ?"
121
162
  end
122
- when ::Method
163
+ when ::Method, UnboundMethod
123
164
  meth
124
165
  else
125
166
  raise InvalidArgumentError, "#{meth.inspect} must be of type Symbol, String, or Method"
@@ -143,7 +184,7 @@ module Qdocs
143
184
  comment: (method.comment.strip rescue nil),
144
185
  name: method.name,
145
186
  belongs_to: method.owner,
146
- super_method: sup ? Handler::Method.new.show(sup.owner, sup, type) : nil,
187
+ super_method: sup ? Handler::Method.new(@original_input).show(sup.owner, sup, type) : nil,
147
188
  })
148
189
  end
149
190
  end
@@ -12,11 +12,15 @@ module Qdocs
12
12
  default: col.default,
13
13
  null: col.null,
14
14
  default_function: col.default_function,
15
+ name: col.name,
15
16
  }
16
17
  end
17
18
 
18
19
  def if_active_record(constant)
19
- if Object.const_defined?("::ActiveRecord::Base") && constant < ::ActiveRecord::Base
20
+ if Object.const_defined?("::ActiveRecord::Base") &&
21
+ constant.respond_to?(:<) &&
22
+ constant < ::ActiveRecord::Base &&
23
+ !constant.abstract_class
20
24
  yield constant
21
25
  end
22
26
  end
@@ -32,7 +36,7 @@ module Qdocs
32
36
  if_active_record(con) do |klass|
33
37
  constant = klass
34
38
  klass.columns.each do |col|
35
- active_record_attributes_for col
39
+ database_attributes[col.name.to_sym] = active_record_attributes_for col
36
40
  end
37
41
  end
38
42
  end
@@ -70,7 +74,13 @@ module Qdocs
70
74
  if database_attributes.empty?
71
75
  attrs
72
76
  else
73
- { **attrs, database_attributes: database_attributes }
77
+ {
78
+ **attrs,
79
+ attributes: {
80
+ **attrs[:attributes],
81
+ database_attributes: database_attributes,
82
+ },
83
+ }
74
84
  end
75
85
  end
76
86
 
data/lib/qdocs/server.rb CHANGED
@@ -7,18 +7,62 @@ module Qdocs
7
7
  params = req.params
8
8
  case env["REQUEST_PATH"]
9
9
  when "/"
10
- body = JSON.pretty_generate(Qdocs.lookup(params["input"]))
11
- [200, { "Content-Type" => "application/json; charset=utf-8" }, [body]]
10
+ resp = Qdocs.lookup(params["input"])
11
+ # this is a bit ugly but ok 🤷
12
+ format = req.env.fetch("HTTP_ACCEPT", "")
13
+ body, content_type = case format
14
+ when %r{text/html}
15
+ require "erb"
16
+ template = case resp[:query_type]
17
+ when :methods
18
+ "constant/show"
19
+ when :instance_method, :singleton_method, :class_method, :method
20
+ "method/show"
21
+ when :active_record_attribute
22
+ "method/active_record_show"
23
+ when :constant, :active_record_class
24
+ "constant/show"
25
+ end
26
+ [render_html(resp, template), "text/html"]
27
+ else
28
+ [JSON.pretty_generate(resp), "application/json"]
29
+ end
30
+ [200, { "Content-Type" => "#{content_type}" }, [body]]
12
31
  else
13
- [404, { "Content-Type" => "text/html; charset=utf-8" }, ["Not Found"]]
32
+ [404, { "Content-Type" => "text/plain; charset=utf-8" }, ["Not Found"]]
14
33
  end
15
34
  rescue Qdocs::UnknownClassError,
16
35
  Qdocs::UnknownMethodTypeError,
17
36
  Qdocs::UnknownMethodError,
18
37
  Qdocs::UnknownPatternError => e
19
- [404, { "Content-Type" => "text/html; charset=utf-8" }, ["Not found: #{e.message}"]]
38
+ [404, { "Content-Type" => "text/plain; charset=utf-8" }, ["Not found: #{e.message}"]]
20
39
  rescue => e
21
- [500, { "Content-Type" => "text/html; charset=utf-8" }, ["Error: #{e.message}"]]
40
+ pp e.backtrace
41
+ [500, { "Content-Type" => "text/plain; charset=utf-8" }, ["Error: #{e.message}"]]
42
+ end
43
+
44
+ private
45
+
46
+ def render_html(resp, template)
47
+ layout_contents = File.read(
48
+ File.join(
49
+ __dir__,
50
+ "..",
51
+ "views",
52
+ "common.html.erb"
53
+ )
54
+ )
55
+ specific_contents = File.read(
56
+ File.join(
57
+ __dir__,
58
+ "..",
59
+ "views",
60
+ "#{template}.html.erb"
61
+ )
62
+ )
63
+ @resp, @const_name = resp, resp.dig(:constant, :name)
64
+ @specific_contents = ERB.new(specific_contents).result(binding)
65
+ ERB.new(layout_contents).result(binding)
22
66
  end
23
67
  end
24
68
  end
data/lib/qdocs/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Qdocs
4
- VERSION = "0.2.2"
4
+ VERSION = "0.3.0"
5
5
  end
@@ -0,0 +1,65 @@
1
+ <html>
2
+ <head>
3
+ <title>Qdocs - <%= @resp[:original_input] %></title>
4
+ <style>
5
+ html, body {
6
+ font-family: monospace;
7
+ color: black;
8
+ background-color: white;
9
+ }
10
+
11
+ code {
12
+ background-color: #eee;
13
+ color: rebeccapurple;
14
+ padding: 0.5em;
15
+ line-height: 2em
16
+ }
17
+
18
+ section {
19
+ padding: 8px;
20
+ border: 1px solid #333;
21
+ border-radius: 3px;
22
+ margin: 16px 0;
23
+ }
24
+
25
+ th {
26
+ border-bottom: 1px solid #333;
27
+ }
28
+
29
+ th, td {
30
+ padding: 10px 24px;
31
+ }
32
+
33
+ @media(prefers-color-scheme: dark) {
34
+ html, body {
35
+ background-color: #333;
36
+ color: #fefefe
37
+ }
38
+
39
+ a {
40
+ color: cyan;
41
+ }
42
+
43
+ a:visited {
44
+ color: magenta
45
+ }
46
+
47
+ code {
48
+ background-color: #604e49;
49
+ color: #c7ff83;
50
+ }
51
+
52
+ section {
53
+ border-color: #fefefe;
54
+ }
55
+
56
+ th {
57
+ border-bottom-color: #fefefe;
58
+ }
59
+ }
60
+ </style>
61
+ </head>
62
+ <body>
63
+ <%= @specific_contents %>
64
+ </body>
65
+ </html>
@@ -0,0 +1,80 @@
1
+ <% attributes = @resp[:attributes] %>
2
+ <section>
3
+ <h1><%= CGI.escapeHTML(@const_name) %>: <%= attributes[:constant_type] %></h1>
4
+ </section>
5
+ <% if attributes[:source_location] %>
6
+ <section>
7
+ <% file, line_num = attributes[:source_location].split(":") %>
8
+ <h2>Defined at</h2>
9
+ <p><a href="file://<%= file %>" target="_blank"><%= file %></a>, line number <%= line_num %></p>
10
+ </section>
11
+ <% end %>
12
+ <% if attributes[:value] %>
13
+ <section>
14
+ <h2>Constant value</h2>
15
+ <p><%= CGI.escapeHTML(attributes[:value].to_s) %></p>
16
+ </section>
17
+ <% end %>
18
+ <% if attributes[:singleton_methods] && !attributes[:singleton_methods].empty? %>
19
+ <section>
20
+ <h2>Singleton Methods</h2>
21
+ <ul>
22
+ <% attributes[:singleton_methods].each do |m| %>
23
+ <li><a href="/?input=<%= @const_name %><%= CGI.escape(".")%><%= m.to_s %>"><%= m.inspect %></a></li>
24
+ <% end %>
25
+ </ul>
26
+ </section>
27
+ <% end %>
28
+ <% if attributes[:instance_methods] && !attributes[:instance_methods].empty? %>
29
+ <section>
30
+ <h2>Instance Methods</h2>
31
+ <ul>
32
+ <% attributes[:instance_methods].each do |m| %>
33
+ <li><a href="/?input=<%= @const_name %><%= CGI.escape("#")%><%= m.to_s %>"><%= m.inspect %></a></li>
34
+ <% end %>
35
+ </ul>
36
+ </section>
37
+ <% end %>
38
+ <% if attributes[:database_attributes] && !attributes[:database_attributes].empty? %>
39
+ <section>
40
+ <h2>Active Record columns</h2>
41
+ <ul>
42
+ <% attributes[:database_attributes].each_key do |name| %>
43
+ <li><a href="/?input=<%= @const_name %><%= CGI.escape("#") %><%= name.to_s %>"><%= name.inspect %></a></li>
44
+ <% end %>
45
+ </ul>
46
+ </section>
47
+ <% end %>
48
+ <% ic = attributes[:inheritance_chain] %>
49
+ <% if ic && !ic.empty? %>
50
+ <section>
51
+ <h2>Inheritance chain</h2>
52
+ <ul>
53
+ <% ic.each do |a| %>
54
+ <li><a href="/?input=<%= a.to_s %>"><%= a %></a></li>
55
+ <% end %>
56
+ </ul>
57
+ </section>
58
+ <% end %>
59
+ <% included_modules = attributes[:included_modules] %>
60
+ <% if included_modules && !included_modules.empty? %>
61
+ <section>
62
+ <h2>Included Modules</h2>
63
+ <ul>
64
+ <% included_modules.each do |m| %>
65
+ <li><a href="/?input=<%= m.to_s %>"><%= CGI.escapeHTML(m.to_s) %></a></li>
66
+ <% end %>
67
+ </ul>
68
+ </section>
69
+ <% end %>
70
+ <% constants = attributes[:constants] %>
71
+ <% if constants && !constants.empty? %>
72
+ <section>
73
+ <h2>Constants</h2>
74
+ <ul>
75
+ <% constants.each do |c| %>
76
+ <li><a href="/?input=<%= @const_name %><%= CGI.escape("::") %><%= c %>"><%= CGI.escapeHTML(@const_name) %>::<%= CGI.escapeHTML(c.to_s) %></a></li>
77
+ <% end %>
78
+ </ul>
79
+ </section>
80
+ <% end %>
@@ -0,0 +1,18 @@
1
+ <% attributes = @resp[:attributes] %>
2
+ <section>
3
+ <h1><a href="/?input=<%= @const_name %>"><%= @const_name %></a>#<%= attributes[:name] %></h1>
4
+ </section>
5
+ <section>
6
+ <dl>
7
+ <dt>Name</dt>
8
+ <dd><%= attributes[:name] %></dd>
9
+ <dt>Type</dt>
10
+ <dd><%= attributes[:type] %></dd>
11
+ <dt>Can be null?</dt>
12
+ <dd><%= attributes[:null] ? "yes" : "no" %></dd>
13
+ <dt>Default</dt>
14
+ <dd><%= attributes[:default_function] || attributes[:default] || "null" %></dd>
15
+ <dt>Comment</dt>
16
+ <dd><%= attributes[:comment] || "-" %></dd>
17
+ </dl>
18
+ </section>
@@ -0,0 +1,53 @@
1
+ <% attributes = @resp[:attributes] %>
2
+ <% method_type = @resp[:query_type] == :instance_method ? "#" : "." %>
3
+ <section>
4
+ <h1><a href="/?input=<%= @const_name %>"><%= CGI.escapeHTML(@const_name.to_s) %></a><%= method_type %><%= CGI.escapeHTML(@resp.dig(:attributes, :name).to_s) %></h1>
5
+ </section>
6
+ <section>
7
+ <h2>Arity: <%= attributes[:arity] %></h2>
8
+ <p>See <a target="_blank" href="https://ruby-doc.org/core-3.0.1/Method.html#method-i-arity">the official docs</a> for reference</p>
9
+ </section>
10
+ <% if attributes[:parameters].length.positive? %>
11
+ <section>
12
+ <h2>Parameters</h2>
13
+ <p>See <a href="https://ruby-doc.org/core-3.0.1/Method.html#method-i-parameters">the official docs</a> for reference</p>
14
+ <table>
15
+ <thead>
16
+ <tr><th>Name</th><th>Kind</th></tr>
17
+ </thead>
18
+ <tbody>
19
+ <% attributes[:parameters].each do |name, type| %>
20
+ <tr><td><%= name %></td><td><%= type %></td></tr>
21
+ <% end %>
22
+ </tbody>
23
+ </table>
24
+ </section>
25
+ <% end %>
26
+ <% if attributes[:defined_at] || attributes[:comment] || attributes[:source] %>
27
+ <section>
28
+ <% if attributes[:defined_at] %>
29
+ <% file, line_num = attributes[:defined_at].split(":") %>
30
+ <h2>Defined at</h2>
31
+ <p><a href="file://<%= file %>" target="_blank"><%= file %></a>, line number <%= line_num %></p>
32
+ <% end %>
33
+ <% if attributes[:comment] %>
34
+ <h2>Comment</h2>
35
+ <pre><code><%= attributes[:comment] %></code></pre>
36
+ <% end %>
37
+ <% if attributes[:source] %>
38
+ <h2>Source</h2>
39
+ <pre><code><%= attributes[:source] %></code></pre>
40
+ <% end %>
41
+ </section>
42
+ <% end %>
43
+ <% super_method = attributes.dig :super_method, :attributes %>
44
+ <% if super_method %>
45
+ <section>
46
+ <h2>Super method</h2>
47
+ <p>
48
+ <a href="/?input=<%= CGI.escape(super_method[:belongs_to].name.to_s) %><%= CGI.escape(method_type) %><%= super_method[:name] %>">
49
+ <%= CGI.escapeHTML(super_method[:belongs_to].name.to_s) %> - <%= super_method[:name] %>
50
+ </a>
51
+ </p>
52
+ </section>
53
+ <% end %>
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: qdocs
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.2
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Joseph Johansen
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2021-05-22 00:00:00.000000000 Z
11
+ date: 2021-05-24 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: method_source
@@ -60,6 +60,10 @@ files:
60
60
  - lib/qdocs/active_record.rb
61
61
  - lib/qdocs/server.rb
62
62
  - lib/qdocs/version.rb
63
+ - lib/views/common.html.erb
64
+ - lib/views/constant/show.html.erb
65
+ - lib/views/method/active_record_show.html.erb
66
+ - lib/views/method/show.html.erb
63
67
  - qdocs.gemspec
64
68
  homepage:
65
69
  licenses: