calltally 0.1.0 → 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: 500a5c27806a402198ff657275586756c3a3d004f01b03cdad133b13b119c1bf
4
- data.tar.gz: 1f479fe7061ad208fb22d376519a6040d85bf93d80a92672e4ab01db51fdac59
3
+ metadata.gz: 7bbb05d3dd40bb7ec180388541a0d0290023f2a152834f8246c98f285dcf40b9
4
+ data.tar.gz: 363208eb22a5a347963ce4accb45ce85744f2e10507a74071d450f5af44d9a51
5
5
  SHA512:
6
- metadata.gz: eaa590ed4f0c510b262597e1b89fd08f8e01940a4abc35a6dc768018b1c1ee2220cb8f4ca141e9190f1e09be956368ab8b7b9be8ec316b1ced685e2e9548ff9d
7
- data.tar.gz: b9390cb686dbeb010039999fc4c93d53f2e0508468b3a4df2cb3a2fdeb06e0abbf7f8bb3da9948349ae82203ec9919cbfe21cce5792b2bba61762c1f4ea0d8c4
6
+ metadata.gz: 1c4797bf33d18a63b4230887986df0a917785f5d0064053f91bb9702089230de430ceeef0224fbd5a7178bd2b4f610c75b8ea336f823e8dd5cda82a34e070726
7
+ data.tar.gz: 015abe2bf689816f2c81951797fac0da9cefb1eebf51f0ed80990b76ff02f21453c1b508e4039524aeba2d22949b612006915ec4d3e6e20786e1119192aa1129
data/CHANGELOG.md ADDED
@@ -0,0 +1,58 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ ## [Unreleased]
9
+
10
+ ## [0.3.0] - 2025-09-20
11
+
12
+ ### Added
13
+ - Plugin system for extensible file processing
14
+ - New `--plugins` option to load external plugins
15
+ - Plugin API with `register`, `handle`, and `registered_exts` methods
16
+ - Plugins can register custom file handlers for any extension
17
+
18
+ ### Changed
19
+ - Updated Prism dependency to 1.5.1
20
+
21
+ ## [0.2.0] - 2025-09-14
22
+
23
+ ### Removed
24
+ - **BREAKING:** ERB file analysis
25
+ - `.erb` files are no longer analyzed
26
+ - Removed the `--erb` CLI option
27
+ - Disabled automatic ERB processing in the Rails profile
28
+ - This eliminates noise from ERB compilation artifacts (e.g., `to_s`, `safe_concat`, `concat`, etc.)
29
+
30
+ ### Fixed
31
+ - Method-call tallies better reflect actual Ruby code usage (no ERB-generated noise)
32
+
33
+ ### Rationale
34
+ ERB compilation introduces implementation artifacts that distort frequency counts:
35
+ - Implicit `to_s` for `<%= ... %>`
36
+ - Buffer operations (`safe_concat`, `concat`, `append`, etc.)
37
+ - Framework helpers (`html_escape`, etc.)
38
+
39
+ Focus for now is on Ruby code in models/controllers/services; view logic can be assessed via helpers or future plugins.
40
+
41
+ ### Migration
42
+ - Remove any use of `--erb` (it now errors/does nothing).
43
+ - If you relied on ERB counts, consider extracting key helpers to Ruby modules and scanning those instead.
44
+
45
+ ## [0.1.0] - 2025-09-13
46
+ ### Added
47
+ - Initial release
48
+ - Static analysis of method usage in Ruby/Rails codebases
49
+ - Modes: receiver×method pairs, methods only, receivers only
50
+ - Variable bucketing and receiver filters
51
+ - Rails project auto-detection
52
+ - Output formats: table, JSON, CSV
53
+ - Ruby 3.2+ compatibility
54
+
55
+ [Unreleased]: https://github.com/nsgc/calltally/compare/v0.3.0...HEAD
56
+ [0.3.0]: https://github.com/nsgc/calltally/compare/v0.2.0...v0.3.0
57
+ [0.2.0]: https://github.com/nsgc/calltally/compare/v0.1.0...v0.2.0
58
+ [0.1.0]: https://github.com/nsgc/calltally/releases/tag/v0.1.0
data/README.md CHANGED
@@ -44,8 +44,8 @@ Calltally automatically detects Rails projects and scans the right directories:
44
44
  # Auto-detects Rails and scans app/, lib/, config/
45
45
  calltally
46
46
 
47
- # Include ERB templates
48
- calltally --erb
47
+ # Analyze specific file patterns
48
+ calltally app/models
49
49
 
50
50
  # Focus on ActiveRecord methods
51
51
  calltally --methods where,find,joins --mode pairs
@@ -95,7 +95,6 @@ exclude: # Patterns to exclude
95
95
  - test
96
96
  - vendor
97
97
  top: 50 # Number of results to show
98
- include_erb: true # Process ERB files
99
98
  mode: pairs # pairs|methods|receivers
100
99
  skip_operators: true # Skip operators like +, -, ==
101
100
  ```
@@ -148,7 +147,6 @@ Options:
148
147
  -x, --exclude x,y Path parts to exclude
149
148
  -n, --top N Show top N results (default: 100)
150
149
  -v, --verbose Verbose output
151
- --erb Include .erb files (requires erubi gem)
152
150
 
153
151
  --mode MODE Output mode:
154
152
  - pairs: receiver-method pairs (default)
@@ -207,6 +205,26 @@ Calltally shows method calls in your codebase with their receivers:
207
205
  5. **Code reviews** - Quickly analyze unfamiliar codebases
208
206
  6. **Gem development** - See how your gem's methods are used
209
207
 
208
+ ## FAQ
209
+
210
+ ### Why does grep show different counts than CallTally?
211
+
212
+ CallTally counts method **calls**, not all text occurrences:
213
+ - `grep "Current.user"` finds both `Current.user` (getter) and `Current.user = value` (setter)
214
+ - CallTally only counts `Current.user` (the getter method call)
215
+ - Setters like `name=` are separate methods and not counted as `name`
216
+
217
+ ### Which file types are analyzed?
218
+
219
+ - **Ruby files**: `.rb`, `.ru`, `.rake`
220
+ - **Not included**: JavaScript, CSS, YAML, and other file types
221
+
222
+ ### Why do I see (result) as a receiver?
223
+
224
+ When methods are chained, CallTally shows intermediate results as `(result)`:
225
+ - `user.posts.first` → `(var).posts` + `(result).first`
226
+ - This happens because CallTally doesn't infer types without type annotations
227
+
210
228
  ## Contributing
211
229
 
212
230
  Bug reports and pull requests are welcome on GitHub at https://github.com/nsgc/calltally.
data/lib/calltally/cli.rb CHANGED
@@ -43,7 +43,6 @@ module Calltally
43
43
  -x, --exclude x,y Path parts to exclude
44
44
  -n, --top N Show top N (default: 100)
45
45
  -v, --verbose
46
- --erb Include .erb (requires 'erubi' gem)
47
46
  --mode MODE Output mode:
48
47
  - pairs (default): receiver-method pairs
49
48
  - methods: method names only
@@ -59,6 +58,7 @@ module Calltally
59
58
  --only-constants Show only constant receivers
60
59
  --only-results Show only method results receivers
61
60
  --[no-]skip-operators Skip operator methods like +, -, ==, [] (default: true)
61
+ --plugins x,y Enable plugins (e.g., erb for calltally-erb)
62
62
  --format F table(default)|json|csv
63
63
  -o, --output PATH Write result to file instead of STDOUT
64
64
  --config PATH Use a specific .calltally.yml
@@ -77,7 +77,6 @@ module Calltally
77
77
  opt.on("-x x,y", "--exclude x,y", Array) { |v| cli_opts["exclude"] = v }
78
78
  opt.on("-n N", "--top N", Integer) { |v| cli_opts["top"] = v }
79
79
  opt.on("-v", "--verbose") { cli_opts["verbose"] = true }
80
- opt.on("--erb") { cli_opts["include_erb"] = true }
81
80
  opt.on("--mode MODE", [:pairs, :methods, :receivers]) { |v| cli_opts["mode"] = v.to_s }
82
81
  opt.on("--receivers x,y", Array) { |v| cli_opts["receivers"] = v }
83
82
  opt.on("--methods x,y", Array) { |v| cli_opts["methods"] = v }
@@ -90,6 +89,7 @@ module Calltally
90
89
  opt.on("--only-constants") { (cli_opts["receiver_types"] ||= []) << "constants" }
91
90
  opt.on("--only-results") { (cli_opts["receiver_types"] ||= []) << "results" }
92
91
  opt.on("--[no-]skip-operators") { |v| cli_opts["skip_operators"] = v }
92
+ opt.on("--plugins x,y", Array) { |v| cli_opts["plugins"] = v }
93
93
  opt.on("--format F", [:table, :json, :csv]) { |v| cli_opts["format"] = v.to_s }
94
94
  opt.on("-o PATH", "--output PATH") { |v| cli_opts["output"] = v }
95
95
  opt.on("--config PATH") { |v| config_override = v }
@@ -109,11 +109,6 @@ module Calltally
109
109
 
110
110
  config = Calltally::Config.load(base_dir: base_dir, cli_opts: cli_opts)
111
111
 
112
- if config["profile"] == "rails" && config["include_erb"] && !Calltally::Config.erubi_available?
113
- warn "[calltally] Rails profile detected but 'erubi' not found. ERB will be skipped. Install 'erubi' to include ERB."
114
- config["include_erb"] = false
115
- end
116
-
117
112
  mode, rows = Calltally::Scanner.new(base_dir: base_dir, config: config).scan
118
113
 
119
114
  out = config["output"] ? File.open(config["output"], "w") : $stdout
@@ -6,21 +6,21 @@ require "set"
6
6
  module Calltally
7
7
  class Config
8
8
  DEFAULTS = {
9
- "profile" => "auto", # auto|rails|default
10
- "dirs" => %w[.],
11
- "exclude" => %w[spec test vendor node_modules tmp log .git .bundle],
12
- "top" => 100,
13
- "verbose" => false,
14
- "include_erb" => false,
15
- "mode" => "pairs", # pairs|methods|receivers
16
- "receivers" => nil, # ["User","Group"]
17
- "methods" => nil, # ["where","find"]
9
+ "profile" => "auto", # auto|rails|default
10
+ "dirs" => %w[.],
11
+ "exclude" => %w[spec test vendor node_modules tmp log .git .bundle],
12
+ "top" => 100,
13
+ "verbose" => false,
14
+ "mode" => "pairs", # pairs|methods|receivers
15
+ "receivers" => nil, # ["User","Group"]
16
+ "methods" => nil, # ["where","find"]
18
17
  "include_nil_receiver" => false,
19
- "split_variables" => false, # Show variable names vs grouping
20
- "receiver_types" => nil, # ["locals", "ivars", "constants"] etc.
21
- "skip_operators" => true,
22
- "format" => "table", # table|json|csv
23
- "output" => nil
18
+ "split_variables" => false, # Show variable names vs grouping
19
+ "receiver_types" => nil, # ["locals", "ivars", "constants"] etc.
20
+ "skip_operators" => true,
21
+ "format" => "table", # table|json|csv
22
+ "output" => nil,
23
+ "plugins" => [] # Enable plugins (e.g., ["erb"])
24
24
  }.freeze
25
25
 
26
26
  RAILS_DIR_PRESET = %w[app lib config].freeze
@@ -40,7 +40,6 @@ module Calltally
40
40
 
41
41
  if config["profile"] == "rails"
42
42
  config["dirs"] = RAILS_DIR_PRESET if config["dirs"] == DEFAULTS["dirs"]
43
- config["include_erb"] = config["include_erb"] || erubi_available?
44
43
  end
45
44
 
46
45
  config["receivers"] = to_set_or_nil(config["receivers"])
@@ -59,13 +58,6 @@ module Calltally
59
58
  end
60
59
  end
61
60
 
62
- def self.erubi_available?
63
- require "erubi"
64
- true
65
- rescue LoadError
66
- false
67
- end
68
-
69
61
  def self.to_set_or_nil(v)
70
62
  case v
71
63
  when nil then nil
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Calltally
4
+ module Plugin
5
+ @handlers = {}
6
+
7
+ class << self
8
+ def register(ext, &block)
9
+ @handlers[ext] = block
10
+ end
11
+
12
+ def handle(path, src, cfg)
13
+ if (handler = @handlers[File.extname(path)])
14
+ handler.call(path, src, cfg)
15
+ end
16
+ end
17
+
18
+ def registered_exts
19
+ @handlers.keys
20
+ end
21
+ end
22
+ end
23
+ end
@@ -2,12 +2,14 @@
2
2
 
3
3
  require "find"
4
4
  require "calltally/prism_visitor"
5
+ require "calltally/plugin"
5
6
 
6
7
  module Calltally
7
8
  class Scanner
8
9
  def initialize(base_dir:, config:)
9
10
  @base_dir = File.expand_path(base_dir)
10
11
  @config = config
12
+ load_plugins(@config["plugins"])
11
13
  end
12
14
 
13
15
  def scan
@@ -61,7 +63,7 @@ module Calltally
61
63
 
62
64
  def collect_paths
63
65
  exts = %w[.rb .ru .rake]
64
- exts << ".erb" if @config["include_erb"]
66
+ exts.concat(Calltally::Plugin.registered_exts)
65
67
 
66
68
  files = []
67
69
  @config["dirs"].each do |dir|
@@ -86,19 +88,25 @@ module Calltally
86
88
  end
87
89
 
88
90
  def read_source(path)
89
- src = File.binread(path)
90
- src = src.encode(Encoding::UTF_8, invalid: :replace, undef: :replace, replace: "")
91
+ src = File.binread(path).encode(Encoding::UTF_8, invalid: :replace, undef: :replace, replace: "")
91
92
 
92
- if @config["include_erb"] && File.extname(path) == ".erb"
93
- begin
94
- require "erubi"
95
- src = Erubi::Engine.new(src).src
96
- rescue LoadError
97
- warn "ERB requested but 'erubi' not installed. Skipping ERB compilation for #{path}."
98
- end
93
+ if (plugin_result = Calltally::Plugin.handle(path, src, @config))
94
+ return plugin_result
99
95
  end
100
96
 
101
97
  src
102
98
  end
99
+
100
+ def load_plugins(plugin_names = [])
101
+ return if plugin_names.empty?
102
+
103
+ plugin_names.each do |plugin_name|
104
+ require "calltally/#{plugin_name}"
105
+
106
+ warn_verbose "Loaded plugin: calltally-#{plugin_name}"
107
+ rescue LoadError
108
+ warn "Plugin 'calltally-#{plugin_name}' not found. Install with: gem install calltally-#{plugin_name}"
109
+ end
110
+ end
103
111
  end
104
112
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Calltally
4
- VERSION = "0.1.0"
4
+ VERSION = "0.3.0"
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: calltally
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Naoki Nishiguchi
@@ -51,20 +51,6 @@ dependencies:
51
51
  - - "~>"
52
52
  - !ruby/object:Gem::Version
53
53
  version: '13.0'
54
- - !ruby/object:Gem::Dependency
55
- name: erubi
56
- requirement: !ruby/object:Gem::Requirement
57
- requirements:
58
- - - ">="
59
- - !ruby/object:Gem::Version
60
- version: '0'
61
- type: :development
62
- prerelease: false
63
- version_requirements: !ruby/object:Gem::Requirement
64
- requirements:
65
- - - ">="
66
- - !ruby/object:Gem::Version
67
- version: '0'
68
54
  description: A simple yet powerful tool to analyze method usage in Ruby/Rails codebases.
69
55
  Track which methods are called most frequently, filter by receivers or method names,
70
56
  and export results in table, JSON, or CSV format. Perfect for understanding code
@@ -74,6 +60,7 @@ executables:
74
60
  extensions: []
75
61
  extra_rdoc_files: []
76
62
  files:
63
+ - CHANGELOG.md
77
64
  - CODE_OF_CONDUCT.md
78
65
  - LICENSE.txt
79
66
  - README.md
@@ -83,6 +70,7 @@ files:
83
70
  - lib/calltally/cli.rb
84
71
  - lib/calltally/config.rb
85
72
  - lib/calltally/formatter.rb
73
+ - lib/calltally/plugin.rb
86
74
  - lib/calltally/prism_visitor.rb
87
75
  - lib/calltally/scanner.rb
88
76
  - lib/calltally/version.rb