akainaa 0.1.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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 305e44e37e3cdb6430af4d8bc1135516f8d0fa050af34c84684ec1bc03ace5fd
4
+ data.tar.gz: 3360375cb4c185139a82c5c3694cb01cd174f3be3a4d45cd58ea7544239bdb09
5
+ SHA512:
6
+ metadata.gz: a7e609d7142282087b333c4eb842e77c4e8136ec7d90abbed23b4e32afa07541a46c5435ca0402a33c8b1755df1ea99b3aa85edfc8710805dfaf4d3dfbec0ace
7
+ data.tar.gz: de14f04bbe4c5cf13355311c4550a931128964adf26d8316944adaa8c8e32f6f63bad1a9ae35e73b39489a3c299f8a64d115ae285e4c2ce1b3633d86d1bab63f
data/CHANGELOG.md ADDED
@@ -0,0 +1,5 @@
1
+ ## [Unreleased]
2
+
3
+ ## [0.1.0] - 2024-03-09
4
+
5
+ - Initial release
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 Shia
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,54 @@
1
+ # Akainaa (赤いなぁ)
2
+
3
+ ![page view](./img/webui.png)
4
+
5
+ Akainaa is a gem that employs the Coverage library to record
6
+ the number of executed count of code in a rack application
7
+ and provides a Web UI that shows the recorded status.
8
+ This gem can be used for the following purposes:
9
+
10
+ - super rough code profiler: The intensity of the red background for each line is proportional to the number of times it has been executed.
11
+ - A tool helps to understand what happened in a request: Akainaa have the reset button on Web UI, so user can easily record code execution for only one request.
12
+
13
+ ## Installation
14
+
15
+ Install the gem and add to the application's Gemfile by executing:
16
+
17
+ $ bundle add akainaa
18
+
19
+ If bundler is not being used to manage dependencies, install the gem by executing:
20
+
21
+ $ gem install akainaa
22
+
23
+ ## Usage
24
+
25
+ call `Akainaa.start` before your application load, and mount middleware.
26
+ Here is example:
27
+
28
+ ```ruby
29
+ require 'akainaa'
30
+
31
+ Akainaa.start(project_dir: File.expand_path(__dir__))
32
+
33
+ require_relative 'app'
34
+
35
+ use Akainaa::Middleware
36
+ run App
37
+ ```
38
+
39
+ Boot up application, do something, and access `/akainaa`.
40
+ It will show Web UI what and how many executed.
41
+
42
+ ## Development
43
+
44
+ After checking out the repo, run `bin/setup` to install dependencies. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
45
+
46
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
47
+
48
+ ## Contributing
49
+
50
+ Bug reports and pull requests are welcome on GitHub at https://github.com/riseshia/akainaa.
51
+
52
+ ## License
53
+
54
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
data/Rakefile ADDED
@@ -0,0 +1,4 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler/gem_tasks"
4
+ task default: %i[]
data/akainaa.gemspec ADDED
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "lib/akainaa/version"
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = "akainaa"
7
+ spec.version = Akainaa::VERSION
8
+ spec.authors = ["Shia"]
9
+ spec.email = ["rise.shia@gmail.com"]
10
+
11
+ spec.summary = "Minimum rack middleware for coverage"
12
+ spec.description = spec.summary
13
+ spec.homepage = "https://github.com/riseshia/akainaa"
14
+ spec.license = "MIT"
15
+ spec.required_ruby_version = ">= 3.0.0"
16
+
17
+ spec.metadata["homepage_uri"] = spec.homepage
18
+ spec.metadata["source_code_uri"] = spec.homepage
19
+ spec.metadata["changelog_uri"] = "#{spec.homepage}/blob/main/CHANGELOG.md"
20
+
21
+ # Specify which files should be added to the gem when it is released.
22
+ # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
23
+ spec.files = Dir.chdir(__dir__) do
24
+ `git ls-files -z`.split("\x0").reject do |f|
25
+ (File.expand_path(f) == __FILE__) ||
26
+ f.start_with?(*%w[bin/ test/ spec/ features/ .git .github appveyor Gemfile])
27
+ end
28
+ end
29
+ spec.bindir = "exe"
30
+ spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
31
+ spec.require_paths = ["lib"]
32
+
33
+ # Uncomment to register a new dependency of your gem
34
+ # spec.add_dependency "example-gem", "~> 1.0"
35
+
36
+ # For more information and examples about making a new gem, check out our
37
+ # guide at: https://bundler.io/guides/creating_gem.html
38
+ end
data/img/webui.png ADDED
Binary file
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Akainaa
4
+ VERSION = "0.1.0"
5
+ end
data/lib/akainaa.rb ADDED
@@ -0,0 +1,272 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'coverage'
4
+
5
+ require_relative 'akainaa/version'
6
+
7
+ module Akainaa
8
+ class Error < StandardError; end
9
+
10
+ class << self
11
+ attr_accessor :project_dir
12
+
13
+ def start(project_dir:)
14
+ @project_dir = project_dir
15
+ @project_dir += '/' unless @project_dir.end_with?('/')
16
+
17
+ Coverage.start(lines: true)
18
+ end
19
+
20
+ def peek_result
21
+ Coverage
22
+ .peek_result
23
+ .select { |k, _v| k.start_with?(project_dir) }
24
+ .transform_keys { |k| k.sub(project_dir, '') }
25
+ end
26
+
27
+ def reset
28
+ Coverage.result(stop: false, clear: true)
29
+ end
30
+ end
31
+
32
+ class Middleware
33
+ PADDING = 4
34
+
35
+ def initialize(app)
36
+ @app = app
37
+ end
38
+
39
+ def call(env)
40
+ if env['PATH_INFO'] == '/akainaa'
41
+ path = extract_path_from_query(env)
42
+ html = render_page(path)
43
+
44
+ [200, { 'Content-Type' => 'text/html;charset=utf-8' }, [html]]
45
+ elsif env['PATH_INFO'] == '/akainaa/reset'
46
+ path = extract_path_from_query(env)
47
+ Akainaa.reset
48
+
49
+ [302, { 'Location' => "/akainaa?path=#{path}" }, [html]]
50
+ else
51
+ @app.call(env)
52
+ end
53
+ end
54
+
55
+ private def extract_path_from_query(env)
56
+ matched = env['QUERY_STRING'].match(/path=([^&]+)/)
57
+ matched ? matched[1] : 'app.rb'
58
+ end
59
+
60
+ private def render_line(lineno, code, count, count_top)
61
+ <<~HTML
62
+ <div class="line pure-g count-p#{count_top}">
63
+ <div class="executed-count pure-u-2-24">
64
+ <p>#{count}</p>
65
+ </div>
66
+ <div class="lineno pure-u-2-24">
67
+ <p>#{lineno}:</p>
68
+ </div>
69
+ <div class="code pure-u-20-24">
70
+ <pre class="code language-ruby">#{code}</pre>
71
+ </code>
72
+ </div>
73
+ </div>
74
+ HTML
75
+ end
76
+
77
+ private def render_filelist(coverage_result, current_path:)
78
+ files = coverage_result.keys
79
+ max_count_on_proj = coverage_result
80
+ .values
81
+ .map { |cv| cv[:lines].reject(&:nil?).sum }
82
+ .max + 1
83
+ max_count_witdh = max_count_on_proj.to_s.size
84
+
85
+ li_elements = files.sort.map do |file|
86
+ total_count_on_file = coverage_result[file][:lines].reject(&:nil?).sum
87
+ count_top = (total_count_on_file * 10 / max_count_on_proj).to_i * 10
88
+
89
+ class_suffix = file == current_path ? ' current' : ''
90
+ <<~HTML
91
+ <li class="pure-menu-item">
92
+ <a href="/akainaa?path=#{file}" class="pure-menu-link filepath#{class_suffix} count-p#{count_top}"">(#{total_count_on_file.to_s.rjust(max_count_witdh)}) #{file}</a>
93
+ </li>
94
+ HTML
95
+ end.join
96
+
97
+ <<~HTML
98
+ <div>
99
+ <div class="pure-menu">
100
+ <span class="pure-menu-heading">赤いなぁ</span>
101
+ <ul class="pure-menu-list">
102
+ <li class="pure-menu-item">
103
+ <a href="/akainaa/reset" class="pure-button pure-button-primary">Reset</a>
104
+ </li>
105
+ <li class="pure-menu-heading">Files</li>
106
+ #{li_elements}
107
+ </ul>
108
+ </div>
109
+ </div>
110
+ HTML
111
+ end
112
+
113
+ private def render_page(path)
114
+ result = Akainaa.peek_result
115
+
116
+ path_result = result[path]
117
+
118
+ if path_result.nil?
119
+ return "There is emtpy result for #{path}"
120
+ end
121
+
122
+ unless File.exist?(path)
123
+ return "<" + "p>#{path} not found.<" + "/p>"
124
+ end
125
+
126
+ coverage_on_line = path_result[:lines]
127
+ max_count_on_file = coverage_on_line.reject(&:nil?).max + 1
128
+
129
+ lines = []
130
+ File.read(path).each_line.with_index do |line, index|
131
+ count = coverage_on_line[index].to_i
132
+ count_top = (count * 10 / max_count_on_file).to_i * 10
133
+ line = render_line(index + 1, line, coverage_on_line[index], count_top)
134
+
135
+ lines << line
136
+ end
137
+
138
+ filelist = render_filelist(result, current_path: path)
139
+
140
+ <<~HTML
141
+ <!DOCTYPE html>
142
+ <html>
143
+ <head>
144
+ <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/purecss@3.0.0/build/pure-min.css" integrity="sha384-X38yfunGUhNzHpBaEBsWLO+A0HDYOQi8ufWDkZ0k9e0eXz/tH3II7uKZ9msv++Ls" crossorigin="anonymous">
145
+ <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/default.min.css">
146
+ <link rel="stylesheet" href="//cdnjs.cloudflare.com/ajax/libs/highlight.js/11.4.0/styles/a11y-light.min.css">
147
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/highlight.min.js"></script>
148
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/languages/ruby.min.js"></script>
149
+ <style>
150
+ .sidebar {
151
+ background-color: #eee;
152
+ }
153
+
154
+ .pure-menu-heading {
155
+ font-weight: bold;
156
+ }
157
+
158
+ .line {
159
+ line-height: 20px;
160
+ }
161
+
162
+ .executed-count, .lineno {
163
+ text-align: right;
164
+ }
165
+
166
+ p {
167
+ padding: 0 5 0 5;
168
+ margin: 0px;
169
+ }
170
+
171
+ pre.code {
172
+ margin: 0px;
173
+ background: transparent;
174
+ }
175
+
176
+ a.filepath {
177
+ text-wrap: balance;
178
+ word-break: break-all;
179
+ color: #000;
180
+ }
181
+ a.filepath.current {
182
+ font-weight: bold;
183
+ }
184
+
185
+ div.count-p90 {
186
+ background-color: #ff392e;
187
+ }
188
+ div.count-p90:hover {
189
+ background-color: #fedcda;
190
+ }
191
+ div.count-p80 {
192
+ background-color: #ff5047;
193
+ }
194
+ div.count-p80:hover {
195
+ background-color: #fedcda;
196
+ }
197
+ div.count-p70 {
198
+ background-color: #ff675f;
199
+ }
200
+ div.count-p70:hover {
201
+ background-color: #fedcda;
202
+ }
203
+ div.count-p60 {
204
+ background-color: #ff7f78;
205
+ }
206
+ div.count-p50 {
207
+ background-color: #ff9690;
208
+ }
209
+ div.count-p40 {
210
+ background-color: #ffada9;
211
+ }
212
+ div.count-p30 {
213
+ background-color: #fec4c1;
214
+ }
215
+ div.count-p20 {
216
+ background-color: #fedcda;
217
+ }
218
+ div.count-p10, div.count-p0 {
219
+ background-color: #ffffff;
220
+ }
221
+ .pure-menu-item > a.count-p90 {
222
+ background-color: #ff392e;
223
+ }
224
+ .pure-menu-item > a.count-p80 {
225
+ background-color: #ff5047;
226
+ }
227
+ .pure-menu-item > a.count-p70 {
228
+ background-color: #ff675f;
229
+ }
230
+ .pure-menu-item > a.count-p60 {
231
+ background-color: #ff7f78;
232
+ }
233
+ .pure-menu-item > a.count-p50 {
234
+ background-color: #ff9690;
235
+ }
236
+ .pure-menu-item > a.count-p40 {
237
+ background-color: #ffada9;
238
+ }
239
+ .pure-menu-item > a.count-p30 {
240
+ background-color: #ffffff;
241
+ }
242
+ .pure-menu-item > a.count-p20 {
243
+ background-color: #ffffff;
244
+ }
245
+ .pure-menu-item > a.count-p10 {
246
+ background-color: #ffffff;
247
+ }
248
+ .pure-menu-item > a.count-p0 {
249
+ background-color: #ffffff;
250
+ }
251
+ </style>
252
+ </head>
253
+ <body>
254
+ <div id="layout" class="pure-g">
255
+ <div class="sidebar pure-u-1-4">
256
+ #{filelist}
257
+ </div>
258
+ <div class="content pure-u-3-4">
259
+ #{lines.join("\n")}
260
+ </div>
261
+ </div>
262
+ <script>
263
+ document.querySelectorAll('pre.code').forEach(el => {
264
+ hljs.highlightElement(el);
265
+ });
266
+ </script>
267
+ </body>
268
+ </html>
269
+ HTML
270
+ end
271
+ end
272
+ end
@@ -0,0 +1 @@
1
+ 3.3.0
data/sample/Gemfile ADDED
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gem "sinatra", "~> 4.0"
6
+ gem "sinatra-contrib", "~> 4.0"
7
+
8
+ gem "puma", "~> 6.4"
9
+
10
+ gem "akainaa", path: "../"
@@ -0,0 +1,48 @@
1
+ PATH
2
+ remote: ..
3
+ specs:
4
+ akainaa (0.1.0)
5
+
6
+ GEM
7
+ remote: https://rubygems.org/
8
+ specs:
9
+ base64 (0.2.0)
10
+ multi_json (1.15.0)
11
+ mustermann (3.0.0)
12
+ ruby2_keywords (~> 0.0.1)
13
+ nio4r (2.7.0)
14
+ puma (6.4.2)
15
+ nio4r (~> 2.0)
16
+ rack (3.0.9.1)
17
+ rack-protection (4.0.0)
18
+ base64 (>= 0.1.0)
19
+ rack (>= 3.0.0, < 4)
20
+ rack-session (2.0.0)
21
+ rack (>= 3.0.0)
22
+ ruby2_keywords (0.0.5)
23
+ sinatra (4.0.0)
24
+ mustermann (~> 3.0)
25
+ rack (>= 3.0.0, < 4)
26
+ rack-protection (= 4.0.0)
27
+ rack-session (>= 2.0.0, < 3)
28
+ tilt (~> 2.0)
29
+ sinatra-contrib (4.0.0)
30
+ multi_json (>= 0.0.2)
31
+ mustermann (~> 3.0)
32
+ rack-protection (= 4.0.0)
33
+ sinatra (= 4.0.0)
34
+ tilt (~> 2.0)
35
+ tilt (2.3.0)
36
+
37
+ PLATFORMS
38
+ ruby
39
+ x86_64-linux
40
+
41
+ DEPENDENCIES
42
+ akainaa!
43
+ puma (~> 6.4)
44
+ sinatra (~> 4.0)
45
+ sinatra-contrib (~> 4.0)
46
+
47
+ BUNDLED WITH
48
+ 2.5.3
data/sample/app.rb ADDED
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'sinatra/base'
4
+ require 'sinatra/json'
5
+ require 'securerandom'
6
+
7
+ require_relative './user'
8
+ require_relative './notification'
9
+ require_relative './util'
10
+
11
+ class App < Sinatra::Base
12
+ enable :logging
13
+
14
+ get '/api/me' do
15
+ 1.times { Util.do_something }
16
+ 2.times { Util.do_something }
17
+ 3.times { Util.do_something }
18
+ 4.times { Util.do_something }
19
+ 5.times { Util.do_something }
20
+ 6.times { Util.do_something }
21
+ 7.times { Util.do_something }
22
+ 8.times { Util.do_something }
23
+ 9.times { Util.do_something }
24
+ 10.times { Util.do_something }
25
+
26
+ json(
27
+ name: "riseshia",
28
+ )
29
+ end
30
+ end
data/sample/config.ru ADDED
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'akainaa'
4
+
5
+ Akainaa.start(project_dir: File.expand_path(__dir__))
6
+
7
+ require_relative 'app'
8
+
9
+ use Akainaa::Middleware
10
+ run App
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Notification
4
+ 50.times { nil }
5
+
6
+ module_function
7
+
8
+ def where(id:) = []
9
+ end
data/sample/theme.rb ADDED
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Theme
4
+ 40.times { nil }
5
+ module_function
6
+
7
+ def find_by(user_id:)
8
+ "light"
9
+ end
10
+ end
data/sample/user.rb ADDED
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative './theme'
4
+
5
+ module User
6
+ 30.times { nil }
7
+ module_function
8
+
9
+ def find(id)
10
+ {
11
+ id: id,
12
+ theme: Theme.find_by(user_id: id),
13
+ }
14
+ end
15
+ end
data/sample/util.rb ADDED
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Util
4
+ module_function
5
+
6
+ def do_something = []
7
+ end
metadata ADDED
@@ -0,0 +1,63 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: akainaa
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Shia
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2024-03-14 00:00:00.000000000 Z
12
+ dependencies: []
13
+ description: Minimum rack middleware for coverage
14
+ email:
15
+ - rise.shia@gmail.com
16
+ executables: []
17
+ extensions: []
18
+ extra_rdoc_files: []
19
+ files:
20
+ - CHANGELOG.md
21
+ - LICENSE
22
+ - README.md
23
+ - Rakefile
24
+ - akainaa.gemspec
25
+ - img/webui.png
26
+ - lib/akainaa.rb
27
+ - lib/akainaa/version.rb
28
+ - sample/.ruby-version
29
+ - sample/Gemfile
30
+ - sample/Gemfile.lock
31
+ - sample/app.rb
32
+ - sample/config.ru
33
+ - sample/notification.rb
34
+ - sample/theme.rb
35
+ - sample/user.rb
36
+ - sample/util.rb
37
+ homepage: https://github.com/riseshia/akainaa
38
+ licenses:
39
+ - MIT
40
+ metadata:
41
+ homepage_uri: https://github.com/riseshia/akainaa
42
+ source_code_uri: https://github.com/riseshia/akainaa
43
+ changelog_uri: https://github.com/riseshia/akainaa/blob/main/CHANGELOG.md
44
+ post_install_message:
45
+ rdoc_options: []
46
+ require_paths:
47
+ - lib
48
+ required_ruby_version: !ruby/object:Gem::Requirement
49
+ requirements:
50
+ - - ">="
51
+ - !ruby/object:Gem::Version
52
+ version: 3.0.0
53
+ required_rubygems_version: !ruby/object:Gem::Requirement
54
+ requirements:
55
+ - - ">="
56
+ - !ruby/object:Gem::Version
57
+ version: '0'
58
+ requirements: []
59
+ rubygems_version: 3.6.0.dev
60
+ signing_key:
61
+ specification_version: 4
62
+ summary: Minimum rack middleware for coverage
63
+ test_files: []