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 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
@@ -0,0 +1,2 @@
1
+ tmp/
2
+ *.gem
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
@@ -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
@@ -0,0 +1,3 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
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
+ [![Gem Version](https://badge.fury.io/rb/zen_apropos.svg)](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
@@ -0,0 +1,7 @@
1
+ require 'rake/testtask'
2
+
3
+ Rake::TestTask.new do |t|
4
+ t.test_files = FileList['test/**/test_*.rb']
5
+ end
6
+
7
+ task default: :test
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
@@ -0,0 +1,14 @@
1
+ module ZenApropos
2
+ class Configuration
3
+ attr_accessor :tag, :glob_patterns
4
+
5
+ def initialize
6
+ @tag = 'zen_desc'
7
+ @glob_patterns = nil
8
+ end
9
+
10
+ def tag_pattern
11
+ /^\s*#\s*@#{Regexp.escape(tag)}\s+/
12
+ end
13
+ end
14
+ end