ficha 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: 3ac8590b6ab46f565f13b1bb959e55c790f8ecebb62a7946b47be7e3acd94a88
4
- data.tar.gz: 6290916d3e12018298de9343a6d4803ce2b7beb5d983c19a9f26c1b32993426f
3
+ metadata.gz: e0ab263b57722fe59e5b216c33254355114fed0ade17c25b3b8864d5bbc75ea0
4
+ data.tar.gz: 829c0cf34924b04facbcb3c7b72bb352d321f5444ecb5b995adb121189186e33
5
5
  SHA512:
6
- metadata.gz: 68dbe337310fa333176a6b7341d479dec441af04de24dc1e811307631432c3d459b4aaf31fbbf5272148e2b9cef6971aeb9f43e6703f129b2c9314d66236a4d7
7
- data.tar.gz: b5216fd9da5ed299891069e10101af2598a3a68c36cdd31ef2b02b7d59e42be5a1d32dd596d8aaf963418c57e752718d44fa5167371362aa15b8c9f7a1c8fe07
6
+ metadata.gz: dabc9d504f43b6b8e7e1ff95764640dec5b3963dd44b78538a9599bb59f5f035731c65fac1bdffee0d8b62523e15a87ecaf8c7bd5e7d6956b4799570ff5cae60
7
+ data.tar.gz: d2fe651ad51b0e2d1db39318894836ca4a0d6a91f8df2c09e7827dfaac6757a9ca1d386867fd9a9dd740ebe0989f209a9ef335199c8ba9fe59a85b2488dab4f0
data/CHANGELOG.md CHANGED
@@ -1,23 +1,36 @@
1
1
  # Changelog
2
2
 
3
- All notable changes to `ficha` will be documented in this file.
4
-
3
+ All notable changes to `ficha` will be documented in this file.
5
4
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
5
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
6
 
8
- ## [Unreleased]
7
+ # 0.3.0 - 2025-12-11
8
+
9
+ ### Added: Non-dry-run mode: print file contents by default
10
+
11
+ ## [0.2.0] - 2025-12-09
12
+
13
+ ### Added
14
+
15
+ - Support for **global configuration** via `~/.ficha.yml` (used when no local `.ficha.yml` or `ficha.yml` is present).
16
+ - **Strategy composition**: define composite strategies using the `compose` key (e.g. `user-plus-session: { compose: [by-model:User, by-model:Session] }`).
17
+ - Built-in `Style/NoComments` RuboCop cop (disabled by default) for minimal-comment style enforcement.
9
18
 
10
19
  ### Changed
11
20
 
12
- - Refactored `Ficha::CLI#run` into smaller private methods to improve readability and comply with RuboCop metrics (ABC size, method length).
21
+ - Refactored `Ficha::CLI` and `Ficha::Engine` internals to improve readability and testability.
22
+ - Updated README examples to use correct config key `include_paths` (was incorrectly shown as `include`).
23
+ - Improved query parsing and strategy resolution logic.
13
24
 
14
25
  ### Fixed
15
26
 
16
- - Auto-corrected code style with RuboCop.
27
+ - Corrected GitHub repository URI in gemspec metadata.
17
28
 
18
29
  ### Internal
19
30
 
20
- - Added RuboCop linting to GitHub Actions CI.
31
+ - Added end-to-end tests for composed strategies and global config loading.
32
+ - Enforced RuboCop in GitHub Actions CI.
33
+ - Excluded test files from `Metrics/*` RuboCop checks.
21
34
 
22
35
  ## [0.1.0] - 2025-12-09
23
36
 
data/README.md CHANGED
@@ -3,8 +3,10 @@
3
3
  > **Feature-aware file dumper for Ruby projects**
4
4
  > Extract only the files you need — by model, endpoint, feature, or safe subset — without manual grepping.
5
5
 
6
- ![License: AGPL-3.0](https://img.shields.io/badge/license-AGPL--3.0-blue)
7
- ![Ruby](https://img.shields.io/badge/ruby-3.0%2B-red)
6
+ [![License: AGPL-3.0](https://img.shields.io/badge/license-AGPL--3.0-blue)](https://www.gnu.org/licenses/agpl-3.0.html)
7
+ [![Ruby](https://img.shields.io/badge/ruby-3.0%2B-red)](https://www.ruby-lang.org/)
8
+ [![CI](https://github.com/it1ro/ficha/actions/workflows/main.yml/badge.svg)](https://github.com/it1ro/ficha/actions)
9
+ [![Gem Version](https://badge.fury.io/rb/ficha.svg)](https://rubygems.org/gems/ficha)
8
10
 
9
11
  `ficha` helps you **dump relevant files** from large Ruby/Rails codebases based on **semantic strategies**, not just file extensions.
10
12
  Think of it as `grep` + `find` + domain knowledge — for developers who know *what* they need, but not *where* it is.
@@ -25,7 +27,7 @@ Perfect for:
25
27
  # Dump all files related to the `User` model
26
28
  ficha by-model:User
27
29
 
28
- # Dump a "safe" subset (models, serializers, controllers, specs)
30
+ # Dump a "safe" subset (models, controllers, specs, domain logic)
29
31
  ficha full-safe
30
32
 
31
33
  # Preview without writing
@@ -37,11 +39,14 @@ Output (dry-run):
37
39
  ```
38
40
  app/models/user.rb
39
41
  app/controllers/users_controller.rb
40
- app/serializers/user_serializer.rb
42
+ app/domain/user.rb
41
43
  spec/models/user_spec.rb
44
+ spec/domain/user_spec.rb
42
45
  ...
43
46
  ```
44
47
 
48
+ > ✅ Automatically **excludes sensitive files** like `master.key`, `.env*`, `credentials.yml.enc`.
49
+
45
50
  ---
46
51
 
47
52
  ## 🧩 Core Concepts
@@ -49,20 +54,22 @@ spec/models/user_spec.rb
49
54
  `ficha` works by combining:
50
55
 
51
56
  1. **Strategies** — rules that define *what to include* (e.g. `by-model`, `full-safe`)
52
- 2. **Config** — your project-specific paths, conventions, and overrides (`ficha.yml`)
53
- 3. **Engine** — the resolver that applies strategies to your codebase
54
- 4. **CLI** — your interface to run queries
57
+ 2. **Config** — your project-specific paths, conventions, and overrides (`ficha.yml` or `.ficha.yml`)
58
+ 3. **Template engine** — with filters like `underscore`, `pluralize` (via ActiveSupport)
59
+ 4. **Engine** — resolves paths, applies filters, excludes sensitive/ignored files
60
+ 5. **CLI** — your interface to run queries
55
61
 
56
62
  ```mermaid
57
63
  graph LR
58
64
  A[CLI: ficha by-model:User] --> B(Engine)
59
65
  B --> C{Strategy: by-model}
60
66
  B --> D[Config: ficha.yml]
61
- C --> E[Match model, controller, serializer...]
67
+ C --> E[Apply template filters:<br>underscore, pluralize]
62
68
  D --> F[Custom paths, excludes]
63
- E --> G[File list]
69
+ E --> G[Glob & match files]
64
70
  F --> G
65
- G --> H[Output or copy]
71
+ G --> H[Apply global filters]
72
+ H --> I[Output file list]
66
73
  ```
67
74
 
68
75
  ---
@@ -83,63 +90,107 @@ Then run:
83
90
  bundle install
84
91
  ```
85
92
 
86
- > 💡 `ficha` is designed as a **dev-only CLI tool** — it doesn’t affect your runtime.
93
+ > 💡 `ficha` is a **dev-only CLI tool** — zero runtime impact.
87
94
 
88
95
  ---
89
96
 
90
97
  ## ⚙️ Configuration
91
98
 
92
- By default, `ficha` looks for `config/ficha.yml` (in Rails) or `.ficha.yml` (in any project).
99
+ `ficha` looks for config in this order:
100
+
101
+ 1. `./.ficha.yml`
102
+ 2. `./ficha.yml`
103
+ 3. `~/.ficha.yml` (global fallback)
93
104
 
94
- ### Example `config/ficha.yml`
105
+ ### Example `ficha.yml`
95
106
 
96
107
  ```yaml
97
- # ficha.yml — declarative, extensible, no Ruby code!
108
+ version: "1"
109
+
110
+ defaults:
111
+ sensitive_names: ["*.env*", "master.key", "credentials.yml.enc"]
112
+ ignored_extensions: [".lock", ".jpg", ".png", ".gif", ".pdf"]
98
113
 
99
114
  strategies:
100
115
  by-model:
101
- include:
102
- - "app/models/{{name}}.rb"
103
- - "app/controllers/{{name.pluralize}}_controller.rb"
104
- - "app/serializers/{{name}}_serializer.rb"
105
- - "spec/models/{{name}}_spec.rb"
106
- - "spec/requests/{{name.pluralize}}_spec.rb"
107
- exclude:
108
- - "app/models/concerns/*"
116
+ params: [name]
117
+ include_paths:
118
+ - "app/models/{{name | underscore}}.rb"
119
+ - "app/controllers/{{name | pluralize | underscore}}_controller.rb"
120
+ - "app/domain/{{name | underscore}}.rb"
121
+ - "spec/models/{{name | underscore}}_spec.rb"
122
+ - "spec/domain/{{name | underscore}}_spec.rb"
109
123
 
110
124
  full-safe:
111
- include:
125
+ include_paths:
112
126
  - "app/models/**/*.rb"
113
127
  - "app/controllers/**/*.rb"
114
- - "app/serializers/**/*.rb"
115
- - "spec/{models,controllers,serializers}/**/*.rb"
128
+ - "app/domain/**/*.rb"
129
+ - "app/operations/**/*.rb"
130
+ - "app/services/**/*.rb"
131
+ - "spec/{models,controllers,domain,operations,services}/**/*.rb"
116
132
  exclude:
117
133
  - "spec/support/**/*"
134
+ ```
135
+
136
+ > ✅ **Philosophy**: Keep logic in config, not code.
137
+ > 🔒 Supports ERB if *really* needed (but discouraged for reproducibility).
138
+
139
+ ---
118
140
 
119
- paths:
120
- root: "." # project root
121
- output: "/tmp/ficha-out" # (future: where to copy files)
141
+ ## 🌐 Global Configuration
122
142
 
123
- excludes:
124
- - "tmp/**/*"
125
- - "log/**/*"
126
- - "vendor/**/*"
143
+ Create `~/.ficha.yml` to define **personal strategies** usable in any Ruby project:
144
+
145
+ ```yaml
146
+ version: "1"
147
+ strategies:
148
+ by-model:
149
+ params: [name]
150
+ include_paths:
151
+ - "app/models/{{name | underscore}}.rb"
152
+ - "app/controllers/{{name | pluralize | underscore}}_controller.rb"
153
+ by-job:
154
+ params: [name]
155
+ include_paths:
156
+ - "app/jobs/{{name | underscore}}.rb"
127
157
  ```
128
158
 
129
- > **Philosophy**: Keep logic in config, not code.
130
- > 🔒 Config supports ERB if you *really* need dynamic values (but avoid it for reproducibility).
159
+ Now `ficha by-model:User` works **even in projects without local config**.
160
+
161
+ > 🔒 Project-specific paths (output, excludes) should stay in local config.
131
162
 
132
163
  ---
133
164
 
134
165
  ## 🎯 Built-in Strategies
135
166
 
136
- | Strategy | Purpose |
137
- |---------------|--------|
138
- | `full-safe` | Dumps core app code: models, controllers, serializers, related specs |
139
- | `by-model:X` | Dumps everything related to model `X` (convention-based) |
167
+ | Strategy | Purpose |
168
+ |----------------|--------|
169
+ | `full-safe` | Dumps core app logic: models, controllers, domain, operations, services, specs |
170
+ | `by-model:X` | Dumps everything related to model `X` (with inflection support) |
140
171
  | *(more coming)* | — |
141
172
 
142
- You can **extend strategies** in your `ficha.yml` — no need to fork the gem.
173
+ You can **extend or override** strategies in your config — no gem forking needed.
174
+
175
+ ---
176
+
177
+ ## 🧩 Composition Support
178
+
179
+ Combine strategies using `compose`:
180
+
181
+ ```yaml
182
+ strategies:
183
+ user-plus-session:
184
+ compose:
185
+ - by-model:User
186
+ - by-model:Session
187
+ ```
188
+
189
+ Then run:
190
+
191
+ ```sh
192
+ ficha user-plus-session --dry-run
193
+ ```
143
194
 
144
195
  ---
145
196
 
@@ -152,15 +203,23 @@ Examples:
152
203
  ficha # runs 'full-safe'
153
204
  ficha by-model:User # model-aware dump
154
205
  ficha full-safe --dry-run # preview only
206
+ ficha by-model:User --exclude spec # exclude specs
155
207
 
156
208
  Options:
157
209
  --help Show help
158
210
  --dry-run Print matched files (default)
159
- --exclude GLOB Exclude additional paths (can be used multiple times)
211
+ --exclude GLOB Exclude paths (repeatable)
160
212
  ```
161
213
 
162
- > ⚠️ **Non-dry-run mode** (actual file copying) is **not implemented yet** coming soon!
163
- > You’ll be able to dump to a directory, zip, or tar.
214
+ > ⚠️ **File copying (non-dry-run)** is coming soon you’ll be able to dump to dir/zip/tar!
215
+
216
+ ---
217
+
218
+ ## 🛡️ Security & Privacy
219
+
220
+ - **Never includes** sensitive files (`master.key`, `.env`, etc.)
221
+ - **Declarative config** reduces risk of accidental data exposure
222
+ - **AGPL-3.0 licensed** — ensures freedom and transparency
164
223
 
165
224
  ---
166
225
 
@@ -177,9 +236,9 @@ Tests use real fixtures in `test/fixtures/rails_app/` — easy to extend.
177
236
 
178
237
  ## 📜 License
179
238
 
180
- AGPL-3.0 — because **privacy, self-hosting, and user freedom matter**.
239
+ [AGPL-3.0](./LICENSE) — because **privacy, self-hosting, and user freedom matter**.
181
240
 
182
- You’re free to use `ficha` in any project, but if you modify and redistribute it (e.g. as a hosted service), you must share the source.
241
+ > You’re free to use `ficha` in any project. If you modify and redistribute it (e.g. as a hosted service), you must share the source.
183
242
 
184
243
  ---
185
244
 
@@ -187,16 +246,19 @@ You’re free to use `ficha` in any project, but if you modify and redistribute
187
246
 
188
247
  PRs welcome! Especially:
189
248
 
190
- - New strategies
191
- - Non-dry-run output formats (dir, zip, tar)
192
- - Better Rails/7+ support
193
- - Performance optimizations
249
+ - 🧠 New strategies (`by-service`, `by-endpoint`, etc.)
250
+ - 💾 Non-dry-run output (dir, zip, tar)
251
+ - 🚀 Performance & memory optimizations
252
+ - 🧪 Better Rails 7+/Ruby 3.4+ support
194
253
 
195
254
  Please:
196
255
 
197
- - Write tests
198
- - Keep config declarative
199
- - Respect the AGPL spirit
256
+ - Write tests
257
+ - 📜 Keep config declarative
258
+ - 🔐 Respect the AGPL spirit
259
+
260
+ > 🐞 Found a bug? Use the [issue template](./.github/issue_template.md).
261
+ > 💡 Have an idea? Open a [feature request](./.github/issue_template.md).
200
262
 
201
263
  ---
202
264
 
data/bin/console CHANGED
@@ -4,8 +4,5 @@
4
4
  require "bundler/setup"
5
5
  require "ficha"
6
6
 
7
- # You can add fixtures and/or initialization code here to make experimenting
8
- # with your gem easier. You can also use a different console, if you like.
9
-
10
7
  require "irb"
11
8
  IRB.start(__FILE__)
data/bin/ficha CHANGED
@@ -1,8 +1,6 @@
1
1
  #!/usr/bin/env ruby
2
2
  # frozen_string_literal: true
3
3
 
4
- # bin/ficha
5
-
6
4
  $LOAD_PATH.unshift File.expand_path("../lib", __dir__)
7
5
  require "ficha/cli"
8
6
 
data/exe/ficha CHANGED
@@ -1,8 +1,6 @@
1
1
  #!/usr/bin/env ruby
2
2
  # frozen_string_literal: true
3
3
 
4
- # exe/ficha
5
-
6
4
  $LOAD_PATH.unshift File.expand_path("../lib", __dir__)
7
5
  require "ficha"
8
6
 
data/ficha.gemspec CHANGED
@@ -34,9 +34,9 @@ Gem::Specification.new do |spec|
34
34
 
35
35
  spec.metadata = {
36
36
  "homepage_uri" => spec.homepage,
37
- "source_code_uri" => "https://github.com/ilmir/ficha",
38
- "changelog_uri" => "https://github.com/ilmir/ficha/blob/main/CHANGELOG.md",
39
- "bug_tracker_uri" => "https://github.com/ilmir/ficha/issues",
37
+ "source_code_uri" => "https://github.com/it1ro/ficha",
38
+ "changelog_uri" => "https://github.com/it1ro/ficha/blob/main/CHANGELOG.md",
39
+ "bug_tracker_uri" => "https://github.com/it1ro/ficha/issues",
40
40
  "rubygems_mfa_required" => "true"
41
41
  }
42
42
  end
data/lib/ficha/config.rb CHANGED
@@ -6,6 +6,10 @@ module Ficha
6
6
  class Config
7
7
  DEFAULT_CONFIG_PATH = File.expand_path("../../data/base.ficha.yml", __dir__)
8
8
 
9
+ def initialize(data)
10
+ @data = data
11
+ end
12
+
9
13
  def self.load(config_path = nil)
10
14
  config_path ||= find_config
11
15
  if config_path && File.exist?(config_path)
@@ -17,8 +21,15 @@ module Ficha
17
21
  new(raw)
18
22
  end
19
23
 
20
- def initialize(data)
21
- @data = data
24
+ def self.find_config
25
+ local_candidates = ["./.ficha.yml", "./ficha.yml"]
26
+ local = local_candidates.find { |f| File.exist?(f) }
27
+ return local if local
28
+
29
+ global = File.expand_path("~/.ficha.yml")
30
+ return global if File.exist?(global)
31
+
32
+ nil
22
33
  end
23
34
 
24
35
  def strategies
@@ -28,9 +39,5 @@ module Ficha
28
39
  def defaults
29
40
  @data.fetch("defaults", {})
30
41
  end
31
-
32
- def self.find_config
33
- ["./.ficha.yml", "./ficha.yml"].find { |f| File.exist?(f) }
34
- end
35
42
  end
36
43
  end
data/lib/ficha/engine.rb CHANGED
@@ -31,12 +31,28 @@ module Ficha
31
31
  strategy_name = parsed[:name]
32
32
  params = parsed[:params]
33
33
 
34
- strategy = @config.strategies[strategy_name]
35
- raise "strategy '#{strategy_name}' not found in .ficha.yml" unless strategy
34
+ strategy = find_strategy!(strategy_name)
36
35
 
37
- param_hash = build_param_hash(strategy["params"] || [], params)
36
+ if strategy.key?("compose")
37
+ resolve_composed_query(strategy["compose"])
38
+ else
39
+ resolve_simple_query(strategy, params)
40
+ end
41
+ end
42
+
43
+ def find_strategy!(name)
44
+ strategy = @config.strategies[name]
45
+ raise "strategy '#{name}' not found in .ficha.yml" unless strategy
46
+
47
+ strategy
48
+ end
49
+
50
+ def resolve_composed_query(sub_queries)
51
+ sub_queries.flat_map { |sub_query| resolve_query(sub_query) }
52
+ end
38
53
 
39
- # Раскрываем include_paths через шаблоны и glob
54
+ def resolve_simple_query(strategy, given_params)
55
+ param_hash = build_param_hash(strategy["params"] || [], given_params)
40
56
  strategy["include_paths"].flat_map do |pattern|
41
57
  rendered = Template.render(pattern, param_hash)
42
58
  Dir.glob(rendered)
@@ -75,7 +91,6 @@ module Ficha
75
91
  excludes = @overrides[:exclude_paths] || []
76
92
  files.reject do |path|
77
93
  excludes.any? do |exclude_glob|
78
- # Если glob не содержит *, **, ? — считаем, что это директория → добавляем /**
79
94
  glob = exclude_glob.include?("*") || exclude_glob.include?("?") ? exclude_glob : "#{exclude_glob}/**/*"
80
95
  File.fnmatch(glob, path, File::FNM_PATHNAME | File::FNM_DOTMATCH)
81
96
  end
data/lib/ficha/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Ficha
4
- VERSION = "0.1.0"
4
+ VERSION = "0.3.0"
5
5
  end
data/lib/ficha.rb CHANGED
@@ -6,6 +6,8 @@ require_relative "ficha/template"
6
6
  require_relative "ficha/engine"
7
7
  require "optparse"
8
8
 
9
+ require "pry"
10
+
9
11
  module Ficha
10
12
  class CLI
11
13
  def self.start(argv)
@@ -31,6 +33,15 @@ module Ficha
31
33
  exit 1
32
34
  end
33
35
 
36
+ def print_files(files, show_line_numbers: true)
37
+ files.each do |path|
38
+ next unless File.file?(path) && readable_text_file?(path)
39
+
40
+ puts "\n=== #{path} ==="
41
+ print_file_content(path, show_line_numbers: show_line_numbers)
42
+ end
43
+ end
44
+
34
45
  private
35
46
 
36
47
  def handle_help!
@@ -46,11 +57,31 @@ module Ficha
46
57
  if @options[:dry_run]
47
58
  files.each { |f| puts f }
48
59
  else
49
- warn "Non-dry-run mode not implemented yet"
50
- exit 1
60
+ print_files(files, show_line_numbers: false)
51
61
  end
52
62
  end
53
63
 
64
+ def print_file_content(path, show_line_numbers:)
65
+ if show_line_numbers
66
+ File.readlines(path).each_with_index(1) do |line, lineno|
67
+ printf "%<lineno>4d | %<content>s\n", lineno: lineno, content: line.chomp
68
+ end
69
+ else
70
+ puts File.read(path)
71
+ end
72
+ end
73
+
74
+ def readable_text_file?(path)
75
+ return false if File.size(path) > 1_000_000
76
+
77
+ File.open(path, "rb") do |f|
78
+ chunk = f.read(1024)
79
+ !chunk&.include?("\x00")
80
+ end
81
+ rescue StandardError
82
+ false
83
+ end
84
+
54
85
  def execute_engine
55
86
  config = Config.load
56
87
  engine = Engine.new(
@@ -86,10 +117,13 @@ module Ficha
86
117
  Usage:
87
118
  ficha [STRATEGY:PARAMS...] [options]
88
119
 
120
+ By default, prints contents of matched files.
121
+ Use --dry-run to list paths only.
122
+
89
123
  Examples:
90
- ficha # full-safe dump
91
- ficha by-model:User # dump User-related files
92
- ficha full-safe --dry-run
124
+ ficha # print full-safe files
125
+ ficha by-model:User # print User-related files
126
+ ficha full-safe --dry-run # only list paths
93
127
 
94
128
  Options:
95
129
  HELP
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ficha
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
  - Ilmir Karimov
@@ -56,9 +56,9 @@ licenses:
56
56
  - AGPL-3.0-or-later
57
57
  metadata:
58
58
  homepage_uri: https://github.com/ilmir/ficha
59
- source_code_uri: https://github.com/ilmir/ficha
60
- changelog_uri: https://github.com/ilmir/ficha/blob/main/CHANGELOG.md
61
- bug_tracker_uri: https://github.com/ilmir/ficha/issues
59
+ source_code_uri: https://github.com/it1ro/ficha
60
+ changelog_uri: https://github.com/it1ro/ficha/blob/main/CHANGELOG.md
61
+ bug_tracker_uri: https://github.com/it1ro/ficha/issues
62
62
  rubygems_mfa_required: 'true'
63
63
  rdoc_options: []
64
64
  require_paths: