qdocs 0.2.2 → 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
  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: