zen_apropos 0.2.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 +7 -0
- data/.gitignore +2 -0
- data/CHANGELOG.md +25 -0
- data/CODE_OF_CONDUCT.md +74 -0
- data/Gemfile +3 -0
- data/Gemfile.lock +21 -0
- data/LICENSE.txt +21 -0
- data/README.md +262 -0
- data/Rakefile +7 -0
- data/bin/zen_apropos +181 -0
- data/lib/zen_apropos/annotations_parser.rb +38 -0
- data/lib/zen_apropos/configuration.rb +14 -0
- data/lib/zen_apropos/engine.rb +156 -0
- data/lib/zen_apropos/entry.rb +38 -0
- data/lib/zen_apropos/index.rb +56 -0
- data/lib/zen_apropos/linter.rb +84 -0
- data/lib/zen_apropos/query_parser.rb +38 -0
- data/lib/zen_apropos/result_formatter.rb +122 -0
- data/lib/zen_apropos/result_grouper.rb +58 -0
- data/lib/zen_apropos/source.rb +21 -0
- data/lib/zen_apropos/sources/rake_source.rb +156 -0
- data/lib/zen_apropos/version.rb +3 -0
- data/lib/zen_apropos/zen_desc.rb +38 -0
- data/lib/zen_apropos.rb +25 -0
- data/zen_apropos.gemspec +33 -0
- metadata +103 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 63464f8005004d28dee7b44cd5851420b6d4e1ca757639e1b0576f0d59ec772b
|
|
4
|
+
data.tar.gz: 5626472b4d781eb408335060267ed96c1a5de249301ab5e007f10c87f2adfe02
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 884a57e12504d4df0f7e414c950ce50d28f51c6f025c5cca99f02ac1ad80157498ebd3aad50db029efb9bfd7bec9848e063308555070860ca47f5454e008cd36
|
|
7
|
+
data.tar.gz: 0c2a616987e6b4fbe517d9e59c9fa8b025a8688c0ffa8939a47e7b15185cc45d5f5e9cb545601646e9fe61811e8112fe2fca3a4603d20e7038c6ecb38aaf7542
|
data/.gitignore
ADDED
data/CHANGELOG.md
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 0.2.0
|
|
4
|
+
|
|
5
|
+
### Breaking Changes
|
|
6
|
+
|
|
7
|
+
- **Default tag renamed from `@zen` to `@zen_desc`.** Rename your annotations or set `config.tag = 'zen'` to keep the old prefix.
|
|
8
|
+
|
|
9
|
+
### Improvements
|
|
10
|
+
|
|
11
|
+
- `zen_desc` DSL now uses a pending-consumption pattern instead of a description-keyed registry, preventing collisions when two tasks share the same description
|
|
12
|
+
- Index cache no longer deserializes the cache file twice on cache hits
|
|
13
|
+
- Cache writing gracefully handles permission errors instead of raising
|
|
14
|
+
|
|
15
|
+
## 0.1.0
|
|
16
|
+
|
|
17
|
+
- Initial release
|
|
18
|
+
- CLI search with free text and structured filters (`team:`, `safety:`, `namespace:`, `keyword:`)
|
|
19
|
+
- `# @zen_desc` comment annotations and `zen_desc` DSL
|
|
20
|
+
- Interactive result viewer with source inspection
|
|
21
|
+
- Plain mode (`--plain`) for machine-readable output
|
|
22
|
+
- Annotation linter with `--changed-only` support
|
|
23
|
+
- Configurable tag prefix via `ZenApropos.configure`
|
|
24
|
+
- Custom glob patterns for monorepo support
|
|
25
|
+
- Mtime-based index caching
|
data/CODE_OF_CONDUCT.md
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
# Contributor Covenant Code of Conduct
|
|
2
|
+
|
|
3
|
+
## Our Pledge
|
|
4
|
+
|
|
5
|
+
In the interest of fostering an open and welcoming environment, we as
|
|
6
|
+
contributors and maintainers pledge to making participation in our project and
|
|
7
|
+
our community a harassment-free experience for everyone, regardless of age, body
|
|
8
|
+
size, disability, ethnicity, gender identity and expression, level of experience,
|
|
9
|
+
nationality, personal appearance, race, religion, or sexual identity and
|
|
10
|
+
orientation.
|
|
11
|
+
|
|
12
|
+
## Our Standards
|
|
13
|
+
|
|
14
|
+
Examples of behavior that contributes to creating a positive environment
|
|
15
|
+
include:
|
|
16
|
+
|
|
17
|
+
* Using welcoming and inclusive language
|
|
18
|
+
* Being respectful of differing viewpoints and experiences
|
|
19
|
+
* Gracefully accepting constructive criticism
|
|
20
|
+
* Focusing on what is best for the community
|
|
21
|
+
* Showing empathy towards other community members
|
|
22
|
+
|
|
23
|
+
Examples of unacceptable behavior by participants include:
|
|
24
|
+
|
|
25
|
+
* The use of sexualized language or imagery and unwelcome sexual attention or
|
|
26
|
+
advances
|
|
27
|
+
* Trolling, insulting/derogatory comments, and personal or political attacks
|
|
28
|
+
* Public or private harassment
|
|
29
|
+
* Publishing others' private information, such as a physical or electronic
|
|
30
|
+
address, without explicit permission
|
|
31
|
+
* Other conduct which could reasonably be considered inappropriate in a
|
|
32
|
+
professional setting
|
|
33
|
+
|
|
34
|
+
## Our Responsibilities
|
|
35
|
+
|
|
36
|
+
Project maintainers are responsible for clarifying the standards of acceptable
|
|
37
|
+
behavior and are expected to take appropriate and fair corrective action in
|
|
38
|
+
response to any instances of unacceptable behavior.
|
|
39
|
+
|
|
40
|
+
Project maintainers have the right and responsibility to remove, edit, or
|
|
41
|
+
reject comments, commits, code, wiki edits, issues, and other contributions
|
|
42
|
+
that are not aligned to this Code of Conduct, or to ban temporarily or
|
|
43
|
+
permanently any contributor for other behaviors that they deem inappropriate,
|
|
44
|
+
threatening, offensive, or harmful.
|
|
45
|
+
|
|
46
|
+
## Scope
|
|
47
|
+
|
|
48
|
+
This Code of Conduct applies both within project spaces and in public spaces
|
|
49
|
+
when an individual is representing the project or its community. Examples of
|
|
50
|
+
representing a project or community include using an official project e-mail
|
|
51
|
+
address, posting via an official social media account, or acting as an appointed
|
|
52
|
+
representative at an online or offline event. Representation of a project may be
|
|
53
|
+
further defined and clarified by project maintainers.
|
|
54
|
+
|
|
55
|
+
## Enforcement
|
|
56
|
+
|
|
57
|
+
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
|
58
|
+
reported by contacting the project team at a.keewan@zenhr.com. All
|
|
59
|
+
complaints will be reviewed and investigated and will result in a response that
|
|
60
|
+
is deemed necessary and appropriate to the circumstances. The project team is
|
|
61
|
+
obligated to maintain confidentiality with regard to the reporter of an incident.
|
|
62
|
+
Further details of specific enforcement policies may be posted separately.
|
|
63
|
+
|
|
64
|
+
Project maintainers who do not follow or enforce the Code of Conduct in good
|
|
65
|
+
faith may face temporary or permanent repercussions as determined by other
|
|
66
|
+
members of the project's leadership.
|
|
67
|
+
|
|
68
|
+
## Attribution
|
|
69
|
+
|
|
70
|
+
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
|
|
71
|
+
available at [https://contributor-covenant.org/version/1/4][version]
|
|
72
|
+
|
|
73
|
+
[homepage]: https://contributor-covenant.org
|
|
74
|
+
[version]: https://contributor-covenant.org/version/1/4/
|
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
PATH
|
|
2
|
+
remote: .
|
|
3
|
+
specs:
|
|
4
|
+
zen_apropos (0.2.0)
|
|
5
|
+
|
|
6
|
+
GEM
|
|
7
|
+
remote: https://rubygems.org/
|
|
8
|
+
specs:
|
|
9
|
+
minitest (5.25.1)
|
|
10
|
+
rake (13.2.1)
|
|
11
|
+
|
|
12
|
+
PLATFORMS
|
|
13
|
+
ruby
|
|
14
|
+
|
|
15
|
+
DEPENDENCIES
|
|
16
|
+
minitest (~> 5.0)
|
|
17
|
+
rake (~> 13.0)
|
|
18
|
+
zen_apropos!
|
|
19
|
+
|
|
20
|
+
BUNDLED WITH
|
|
21
|
+
2.1.4
|
data/LICENSE.txt
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
The MIT License (MIT)
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 ZenHR Engineering
|
|
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
|
|
13
|
+
all 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
|
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
# zen_apropos
|
|
2
|
+
|
|
3
|
+
[](https://badge.fury.io/rb/zen_apropos)
|
|
4
|
+
|
|
5
|
+
Apropos-style search engine for rake tasks.
|
|
6
|
+
|
|
7
|
+
`zen_apropos` scans your `.rake` files, indexes task names, descriptions, source code, and structured annotations, then lets you search across all of them from the command line or Rails console.
|
|
8
|
+
|
|
9
|
+
## Installation
|
|
10
|
+
|
|
11
|
+
Add to your Gemfile:
|
|
12
|
+
|
|
13
|
+
```ruby
|
|
14
|
+
gem 'zen_apropos'
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
Or install directly:
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
gem install zen_apropos
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
Then run `bundle install`.
|
|
24
|
+
|
|
25
|
+
## CLI Usage
|
|
26
|
+
|
|
27
|
+
### Search
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
zen_apropos <query> # interactive rich output
|
|
31
|
+
zen_apropos <query> --plain # machine-readable, no colors
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
In rich mode, results are numbered. You can type a number to view the full source of that task, enter a new query to search again, or `q` to quit.
|
|
35
|
+
|
|
36
|
+
Plain mode outputs pipe-separated columns (`name | description | safety | team`) suitable for piping to other tools.
|
|
37
|
+
|
|
38
|
+
### Lint
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
zen_apropos lint # check all rake files
|
|
42
|
+
zen_apropos lint --changed-only # only files changed in the current branch
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
The linter checks that every `desc`/`task` pair has annotation tags or uses `zen_desc`. Exits with code `0` when all tasks are annotated, `1` when any are missing.
|
|
46
|
+
|
|
47
|
+
Output looks like:
|
|
48
|
+
|
|
49
|
+
```
|
|
50
|
+
⚠ lib/tasks/new_feature.rake:12 -- task "new_feature:run" has no @zen_desc annotations
|
|
51
|
+
|
|
52
|
+
2 task(s) missing annotations. Consider adding @zen_desc tags or using zen_desc.
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
### Help
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
zen_apropos --help
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
## Query Syntax
|
|
62
|
+
|
|
63
|
+
Free text matches against task name, description, keywords, and source code (case-insensitive substring match). You can also use structured filters:
|
|
64
|
+
|
|
65
|
+
| Filter | Example | Matches |
|
|
66
|
+
|--------|---------|---------|
|
|
67
|
+
| `team:` | `team:search` | Tasks owned by the "search" team |
|
|
68
|
+
| `safety:` | `safety:destructive` | Tasks marked as destructive |
|
|
69
|
+
| `namespace:` | `namespace:indexer` | Tasks in the `indexer` namespace |
|
|
70
|
+
| `keyword:` | `keyword:elasticsearch` | Tasks tagged with that keyword |
|
|
71
|
+
|
|
72
|
+
Filters can be combined with each other and with free text. Multiple filters use implicit AND:
|
|
73
|
+
|
|
74
|
+
```bash
|
|
75
|
+
zen_apropos "employee team:search"
|
|
76
|
+
zen_apropos "team:finance safety:destructive"
|
|
77
|
+
zen_apropos "keyword:elasticsearch"
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
## Annotation System
|
|
81
|
+
|
|
82
|
+
Every rake task should be annotated with team ownership, safety level, and keywords. There are two equivalent ways to do this.
|
|
83
|
+
|
|
84
|
+
### Option A: Comment Tags
|
|
85
|
+
|
|
86
|
+
Place annotation lines immediately before the `desc` line. The default tag is `@zen_desc`:
|
|
87
|
+
|
|
88
|
+
```ruby
|
|
89
|
+
namespace :indexer do
|
|
90
|
+
# @zen_desc team: search
|
|
91
|
+
# @zen_desc safety: safe
|
|
92
|
+
# @zen_desc keywords: elasticsearch, reindex
|
|
93
|
+
desc 'Reindex all employees'
|
|
94
|
+
task employees: :environment do
|
|
95
|
+
Employee.reindex
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
### Option B: `zen_desc` DSL
|
|
101
|
+
|
|
102
|
+
Replace `desc` with `zen_desc` and pass metadata as keyword arguments:
|
|
103
|
+
|
|
104
|
+
```ruby
|
|
105
|
+
zen_desc 'Run database backup',
|
|
106
|
+
team: 'devops',
|
|
107
|
+
safety: :safe,
|
|
108
|
+
keywords: %w[backup database postgres]
|
|
109
|
+
|
|
110
|
+
task db_backup: :environment do
|
|
111
|
+
system('pg_dump ...')
|
|
112
|
+
end
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
`zen_desc` calls `desc` under the hood, so `rake -T` keeps working.
|
|
116
|
+
|
|
117
|
+
**Note:** `zen_desc` is opt-in. You must explicitly require it:
|
|
118
|
+
|
|
119
|
+
```ruby
|
|
120
|
+
require 'zen_apropos/zen_desc'
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
### Supported Fields
|
|
124
|
+
|
|
125
|
+
| Field | Type | Values |
|
|
126
|
+
|-------|------|--------|
|
|
127
|
+
| `team` | String | Any team name (e.g. `search`, `hr-platform`, `devops`) |
|
|
128
|
+
| `safety` | String/Symbol | `safe`, `caution`, `destructive` |
|
|
129
|
+
| `keywords` | Array/CSV | Comma-separated in comment tags, `%w[]` array in `zen_desc` |
|
|
130
|
+
|
|
131
|
+
## Custom Tag Prefix
|
|
132
|
+
|
|
133
|
+
By default, zen_apropos recognizes `# @zen_desc` comment tags. If you want a different prefix for your organization:
|
|
134
|
+
|
|
135
|
+
### Project setup (recommended)
|
|
136
|
+
|
|
137
|
+
Run `init` to generate a binstub with your tag pre-configured:
|
|
138
|
+
|
|
139
|
+
```bash
|
|
140
|
+
bundle exec zen_apropos init --tag myapp
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
This creates `bin/zen_apropos` in your project. From then on:
|
|
144
|
+
|
|
145
|
+
```bash
|
|
146
|
+
bin/zen_apropos "employee" # uses @myapp automatically
|
|
147
|
+
bin/zen_apropos lint # lints for @myapp tags
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
### Per-command flag
|
|
151
|
+
|
|
152
|
+
Pass `--tag` to any command:
|
|
153
|
+
|
|
154
|
+
```bash
|
|
155
|
+
zen_apropos --tag myapp "employee"
|
|
156
|
+
zen_apropos lint --tag myapp --changed-only
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
### Ruby configuration
|
|
160
|
+
|
|
161
|
+
In an initializer or setup file:
|
|
162
|
+
|
|
163
|
+
```ruby
|
|
164
|
+
ZenApropos.configure do |config|
|
|
165
|
+
config.tag = 'myapp'
|
|
166
|
+
config.glob_patterns = ['lib/tasks/**/*.rake', 'packages/**/lib/tasks/**/*.rake']
|
|
167
|
+
end
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
### Then annotate with your custom tag
|
|
171
|
+
|
|
172
|
+
```ruby
|
|
173
|
+
# @myapp team: payments
|
|
174
|
+
# @myapp safety: destructive
|
|
175
|
+
# @myapp keywords: refund, stripe
|
|
176
|
+
desc 'Process pending refunds'
|
|
177
|
+
task process_refunds: :environment do
|
|
178
|
+
# ...
|
|
179
|
+
end
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
The linter, CLI, and console helper all respect the configured tag.
|
|
183
|
+
|
|
184
|
+
> **Migrating from `@zen`:** If you previously used `# @zen` tags, either rename them to `# @zen_desc` or set `config.tag = 'zen'` to keep the old prefix.
|
|
185
|
+
|
|
186
|
+
## Rails Console Integration
|
|
187
|
+
|
|
188
|
+
You can define an `apropos` helper for quick searching in the Rails console. Example initializer:
|
|
189
|
+
|
|
190
|
+
```ruby
|
|
191
|
+
# config/initializers/zen_apropos.rb
|
|
192
|
+
return unless Rails.env.development?
|
|
193
|
+
|
|
194
|
+
require 'zen_apropos'
|
|
195
|
+
require 'zen_apropos/zen_desc'
|
|
196
|
+
|
|
197
|
+
ZenApropos.configure do |config|
|
|
198
|
+
config.tag = 'myapp' # recognizes # @myapp instead of # @zen_desc
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
RAKE_PATHS = ['lib/tasks/**/*.rake'].freeze
|
|
202
|
+
|
|
203
|
+
def apropos(query = nil, plain: false)
|
|
204
|
+
engine = ZenApropos::Engine.new(plain: plain, glob_patterns: RAKE_PATHS)
|
|
205
|
+
|
|
206
|
+
if query.nil? || query.strip.empty?
|
|
207
|
+
puts engine.help
|
|
208
|
+
return
|
|
209
|
+
end
|
|
210
|
+
|
|
211
|
+
puts engine.search(query)
|
|
212
|
+
end
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
Then in the console:
|
|
216
|
+
|
|
217
|
+
```ruby
|
|
218
|
+
apropos "employee"
|
|
219
|
+
apropos "team:finance safety:destructive"
|
|
220
|
+
apropos "reindex", plain: true
|
|
221
|
+
apropos # shows help
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
## Custom Glob Patterns
|
|
225
|
+
|
|
226
|
+
By default, zen_apropos scans `lib/tasks/**/*.rake`. For monorepos or apps with tasks in non-standard locations, configure custom patterns:
|
|
227
|
+
|
|
228
|
+
```ruby
|
|
229
|
+
ZenApropos.configure do |config|
|
|
230
|
+
config.glob_patterns = ['lib/tasks/**/*.rake', 'packages/**/lib/tasks/**/*.rake']
|
|
231
|
+
end
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
This works in binstubs, initializers, and anywhere you call `ZenApropos.configure`. The CLI and linter both respect it. You can also pass patterns directly to the engine:
|
|
235
|
+
|
|
236
|
+
```ruby
|
|
237
|
+
engine = ZenApropos::Engine.new(
|
|
238
|
+
glob_patterns: ['lib/tasks/**/*.rake', 'packages/**/lib/tasks/**/*.rake']
|
|
239
|
+
)
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
## Running Tests
|
|
243
|
+
|
|
244
|
+
```bash
|
|
245
|
+
bundle exec rake test
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
Or simply:
|
|
249
|
+
|
|
250
|
+
```bash
|
|
251
|
+
bundle exec rake
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
Tests use Minitest and run against fixture rake files in `test/fixtures/`.
|
|
255
|
+
|
|
256
|
+
## Contributing
|
|
257
|
+
|
|
258
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/ZenHR/zen_apropos. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [code of conduct](CODE_OF_CONDUCT.md).
|
|
259
|
+
|
|
260
|
+
## License
|
|
261
|
+
|
|
262
|
+
The gem is available as open source under the terms of the [MIT License](LICENSE.txt).
|
data/Rakefile
ADDED
data/bin/zen_apropos
ADDED
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# ZenApropos CLI — search rake tasks and lint annotations
|
|
3
|
+
#
|
|
4
|
+
# Usage:
|
|
5
|
+
# zen_apropos <query> [--plain] [--tag TAG] Search rake tasks
|
|
6
|
+
# zen_apropos lint [--changed-only] [--tag TAG] Check for missing annotations
|
|
7
|
+
# zen_apropos init [--tag TAG] Generate a project binstub
|
|
8
|
+
# zen_apropos --help Show help
|
|
9
|
+
|
|
10
|
+
require 'fileutils'
|
|
11
|
+
require 'zen_apropos'
|
|
12
|
+
|
|
13
|
+
def extract_tag!(args)
|
|
14
|
+
index = args.index('--tag')
|
|
15
|
+
return nil unless index
|
|
16
|
+
|
|
17
|
+
args.delete_at(index) # remove --tag
|
|
18
|
+
args.delete_at(index) # remove the value
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def apply_tag!(args)
|
|
22
|
+
tag = extract_tag!(args)
|
|
23
|
+
ZenApropos.configure { |c| c.tag = tag } if tag
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def run_search(args)
|
|
27
|
+
apply_tag!(args)
|
|
28
|
+
plain = args.delete('--plain')
|
|
29
|
+
query = args.join(' ')
|
|
30
|
+
|
|
31
|
+
engine = ZenApropos::Engine.new(root_path: Dir.pwd, plain: plain, glob_patterns: ZenApropos.configuration.glob_patterns)
|
|
32
|
+
|
|
33
|
+
if query.empty? || query == '--help' || query == '-h'
|
|
34
|
+
puts engine.help
|
|
35
|
+
exit 0
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
output = engine.search(query)
|
|
39
|
+
|
|
40
|
+
if output.strip.empty?
|
|
41
|
+
puts "\n❌ No rake tasks found matching: '#{query}'"
|
|
42
|
+
puts "\nTry:"
|
|
43
|
+
puts " • zen_apropos --help"
|
|
44
|
+
exit 0
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
puts "\n#{output}\n"
|
|
48
|
+
|
|
49
|
+
# Interactive mode (rich mode only)
|
|
50
|
+
exit 0 if plain
|
|
51
|
+
|
|
52
|
+
loop do
|
|
53
|
+
print "\nEnter a number, new query, or q to quit: "
|
|
54
|
+
input = $stdin.gets&.strip
|
|
55
|
+
break if input.nil? || input == 'q' || input.empty?
|
|
56
|
+
|
|
57
|
+
if input.match?(/^\d+$/) && engine.last_results&.any?
|
|
58
|
+
index = input.to_i - 1
|
|
59
|
+
if index < 0 || index >= engine.last_results.length
|
|
60
|
+
puts "Invalid number. Enter 1-#{engine.last_results.length}."
|
|
61
|
+
next
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
entry = engine.last_results[index]
|
|
65
|
+
puts "\n\e[36m━━━ rake #{entry.name} \e[0m#{'━' * [0, 50 - entry.name.length].max}"
|
|
66
|
+
puts " \e[2m#{entry.relative_path}:#{entry.line_number}\e[0m\n\n"
|
|
67
|
+
puts entry.source
|
|
68
|
+
puts "\e[36m#{'━' * 58}\e[0m"
|
|
69
|
+
else
|
|
70
|
+
output = engine.search(input)
|
|
71
|
+
if output.strip.empty?
|
|
72
|
+
puts "\n❌ No rake tasks found matching: '#{input}'"
|
|
73
|
+
else
|
|
74
|
+
puts "\n#{output}\n"
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def run_lint(args)
|
|
81
|
+
if args.include?('--help') || args.include?('-h')
|
|
82
|
+
tag = ZenApropos.configuration.tag
|
|
83
|
+
puts <<~HELP
|
|
84
|
+
|
|
85
|
+
🔍 zen_apropos lint - Rake Task Annotation Linter
|
|
86
|
+
|
|
87
|
+
Warns when rake tasks are missing @#{tag} annotations.
|
|
88
|
+
|
|
89
|
+
Usage:
|
|
90
|
+
zen_apropos lint # Check all rake files
|
|
91
|
+
zen_apropos lint --changed-only # Check only files changed in current branch
|
|
92
|
+
zen_apropos lint --tag myapp # Use a custom annotation tag
|
|
93
|
+
|
|
94
|
+
Exit codes:
|
|
95
|
+
0 All tasks have annotations
|
|
96
|
+
1 Some tasks are missing annotations
|
|
97
|
+
|
|
98
|
+
HELP
|
|
99
|
+
exit 0
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
apply_tag!(args)
|
|
103
|
+
changed_only = args.include?('--changed-only')
|
|
104
|
+
exit ZenApropos::Linter.new(changed_only: changed_only).run
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def run_init(args)
|
|
108
|
+
tag = extract_tag!(args) || 'zen'
|
|
109
|
+
binstub_path = File.join(Dir.pwd, 'bin', 'zen_apropos')
|
|
110
|
+
|
|
111
|
+
if File.exist?(binstub_path)
|
|
112
|
+
puts "#{binstub_path} already exists. Overwrite? [y/N] "
|
|
113
|
+
answer = $stdin.gets&.strip
|
|
114
|
+
unless answer&.downcase == 'y'
|
|
115
|
+
puts 'Aborted.'
|
|
116
|
+
exit 0
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
FileUtils.mkdir_p(File.dirname(binstub_path))
|
|
121
|
+
|
|
122
|
+
content = <<~RUBY
|
|
123
|
+
#!/usr/bin/env ruby
|
|
124
|
+
require 'bundler/setup'
|
|
125
|
+
require 'zen_apropos'
|
|
126
|
+
|
|
127
|
+
ZenApropos.configure do |config|
|
|
128
|
+
config.tag = '#{tag}'
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
load Gem.bin_path('zen_apropos', 'zen_apropos')
|
|
132
|
+
RUBY
|
|
133
|
+
|
|
134
|
+
File.write(binstub_path, content)
|
|
135
|
+
File.chmod(0o755, binstub_path)
|
|
136
|
+
|
|
137
|
+
puts "Created #{binstub_path} with tag '#{tag}'"
|
|
138
|
+
puts "Run: bin/zen_apropos <query>"
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
def show_help
|
|
142
|
+
puts <<~HELP
|
|
143
|
+
|
|
144
|
+
🔍 ZenApropos - Rake Task Search & Lint
|
|
145
|
+
|
|
146
|
+
Usage:
|
|
147
|
+
zen_apropos <query> [--plain] [--tag TAG] Search rake tasks
|
|
148
|
+
zen_apropos lint [--changed-only] [--tag TAG] Check for missing annotations
|
|
149
|
+
zen_apropos init [--tag TAG] Generate a project binstub
|
|
150
|
+
zen_apropos --help Show this help
|
|
151
|
+
|
|
152
|
+
Search examples:
|
|
153
|
+
zen_apropos employee
|
|
154
|
+
zen_apropos "team:finance safety:destructive"
|
|
155
|
+
zen_apropos reindex --plain
|
|
156
|
+
zen_apropos --tag myapp employee
|
|
157
|
+
|
|
158
|
+
Lint examples:
|
|
159
|
+
zen_apropos lint
|
|
160
|
+
zen_apropos lint --changed-only
|
|
161
|
+
zen_apropos lint --tag myapp
|
|
162
|
+
|
|
163
|
+
Setup:
|
|
164
|
+
zen_apropos init --tag myapp # creates bin/zen_apropos with tag pre-configured
|
|
165
|
+
|
|
166
|
+
HELP
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
# Route to subcommand
|
|
170
|
+
case ARGV.first
|
|
171
|
+
when 'lint'
|
|
172
|
+
ARGV.shift
|
|
173
|
+
run_lint(ARGV)
|
|
174
|
+
when 'init'
|
|
175
|
+
ARGV.shift
|
|
176
|
+
run_init(ARGV)
|
|
177
|
+
when '--help', '-h', nil
|
|
178
|
+
show_help
|
|
179
|
+
else
|
|
180
|
+
run_search(ARGV.dup)
|
|
181
|
+
end
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# Parses annotation comment tags from lines preceding a task definition
|
|
2
|
+
#
|
|
3
|
+
# By default, recognizes # @zen_desc tags. Configure with:
|
|
4
|
+
# ZenApropos.configure { |c| c.tag = 'myapp' }
|
|
5
|
+
#
|
|
6
|
+
# Supported format:
|
|
7
|
+
# # @zen_desc team: hr-platform
|
|
8
|
+
# # @zen_desc safety: caution
|
|
9
|
+
# # @zen_desc keywords: sync, external, api
|
|
10
|
+
module ZenApropos
|
|
11
|
+
class AnnotationsParser
|
|
12
|
+
def initialize(lines)
|
|
13
|
+
@lines = lines
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def parse
|
|
17
|
+
annotations = {}
|
|
18
|
+
|
|
19
|
+
@lines.each do |line|
|
|
20
|
+
next unless ZenApropos.configuration.tag_pattern.match?(line)
|
|
21
|
+
next unless line =~ /@\w+\s+(\w+):\s*(.+)$/
|
|
22
|
+
|
|
23
|
+
key = Regexp.last_match(1).strip.to_sym
|
|
24
|
+
value = Regexp.last_match(2).strip
|
|
25
|
+
|
|
26
|
+
annotations[key] = key == :keywords ? value.split(/,\s*/) : value
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
annotations
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
class << self
|
|
33
|
+
def parse(lines)
|
|
34
|
+
new(lines).parse
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|