akainaa 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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: []