ask-tools 0.1.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: 41146df4fa3ec2cec15d6085c09d14204cedba968ea1afab6afa72cadf2ec971
4
+ data.tar.gz: 7592284b53e742c81654504d8e9c4b25e8f93c7876cd2a6b6fad2cc7dc8aa0c3
5
+ SHA512:
6
+ metadata.gz: 8de6e0c216ab3432b59c016e0edcee9a2de8e303e83b73655e49d7b75a71c08deff8dc22ea917d2ebd9c6f30f667dc6aede63c3fce61bcbce30a10cc4e09c127
7
+ data.tar.gz: f7b0709e856f0f4e6eceaf21e7117bb59095fd6eebba267559fe0651468932b20e3d926feab4509145e4053ede86a41b70fcdc45deca176f2cb84dbfba9b0f84
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Kaka Ruto
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,205 @@
1
+ # ask-tools
2
+
3
+ The foundational gem for the ask-rb ecosystem. Defines `Ask::Tool` — the base class every tool inherits from — along with `Ask::Result` (standardized return value), tool discovery/registration, and a scaffold generator. **Zero external dependencies.**
4
+
5
+ This gem does **not** ship any executable tools. It only provides the contract that tool gems (e.g., `ask-tools-shell`, `ask-tools-filesystem`) implement.
6
+
7
+ ## Installation
8
+
9
+ Add this line to your `Gemfile`:
10
+
11
+ ```ruby
12
+ gem "ask-tools"
13
+ ```
14
+
15
+ Or install it directly:
16
+
17
+ ```bash
18
+ gem install ask-tools
19
+ ```
20
+
21
+ ## Quick Start
22
+
23
+ ```ruby
24
+ require "ask-tools"
25
+
26
+ class Greeter < Ask::Tool
27
+ description "Greets a person by name"
28
+ param :name, type: :string, desc: "The person's name", required: true
29
+
30
+ def execute(name:)
31
+ Ask::Result.ok(data: "Hello, #{name}!")
32
+ end
33
+ end
34
+
35
+ # Use it
36
+ tool = Greeter.new
37
+ tool.name # => "greeter"
38
+ tool.description # => "Greets a person by name"
39
+
40
+ result = tool.call(name: "World")
41
+ result.ok? # => true
42
+ result.output # => "Hello, World!"
43
+ ```
44
+
45
+ ## API Reference
46
+
47
+ ### `Ask::Tool` — Base Class
48
+
49
+ Subclass `Ask::Tool` to define a tool that an LLM can call.
50
+
51
+ #### Class DSL
52
+
53
+ | Method | Description |
54
+ |--------|-------------|
55
+ | `description(text)` | Sets or retrieves the tool's human-readable description. Alias: `desc` |
56
+ | `param(name, type:, desc:, required:)` | Declares a parameter. `type` must be a valid JSON Schema type (`:string`, `:integer`, `:number`, `:boolean`, `:array`, `:object`) |
57
+
58
+ #### Instance Methods
59
+
60
+ | Method | Returns | Description |
61
+ |--------|---------|-------------|
62
+ | `name` | `String` | Auto-derived from the class name: CamelCase → snake_case, strips `_tool` suffix |
63
+ | `description` | `String?, nil` | The tool's description |
64
+ | `parameters` | `Hash{Symbol => Parameter}` | Declared parameter definitions |
65
+ | `call(args = {})` | `Ask::Result` | Normalizes args (symbolizes keys), validates required params, delegates to `execute`. Catches `Halt` and `StandardError` |
66
+ | `execute(**args)` | `Ask::Result` | **Override this.** Implement the tool's logic. |
67
+ | `params_schema` | `Hash?, nil` | JSON Schema hash for LLM function-calling APIs. Returns `nil` when no params declared |
68
+ | `tool_definition` | `Hash` | Full tool definition hash with `:name`, `:description`, and `:input_schema` |
69
+
70
+ #### Error Handling
71
+
72
+ - **`Ask::Tool::Halt`** — Raise this inside `execute` to signal the conversation loop should stop after this tool's result. `call` returns an `Ask::Result` with `metadata[:halted] = true`.
73
+ - **`StandardError`** — Any other exception raised in `execute` is caught by `call` and returned as an error `Ask::Result`.
74
+
75
+ ### `Ask::Result` — Return Value
76
+
77
+ A value object representing the outcome of a tool execution.
78
+
79
+ #### Factory Methods
80
+
81
+ ```ruby
82
+ # Successful result
83
+ Ask::Result.ok(data: "output", metadata: { key: "val" })
84
+
85
+ # Failed result
86
+ Ask::Result.error(message: "Something went wrong", metadata: { code: 500 })
87
+ ```
88
+
89
+ #### Attributes
90
+
91
+ | Attribute | Type | Description |
92
+ |-----------|------|-------------|
93
+ | `ok?` / `ok` | `Boolean` | Whether the tool completed successfully |
94
+ | `output` | `Object?, nil` | Output data (success) |
95
+ | `error` | `String?, nil` | Error message (failure) |
96
+ | `metadata` | `Hash` | Arbitrary metadata |
97
+
98
+ #### Instance Methods
99
+
100
+ | Method | Returns | Description |
101
+ |--------|---------|-------------|
102
+ | `to_s` | `String` | Returns `output.to_s` for success, `error` for failure |
103
+ | `to_h` | `Hash` | Serialized hash with `:ok`, `:output`, `:error`, `:metadata` |
104
+ | `inspect` | `String` | Human-readable representation |
105
+
106
+ ### `Ask::Tool::Parameter` — Parameter Definition
107
+
108
+ Internal value object describing a declared parameter. Accessible via `Tool.parameters[name]`.
109
+
110
+ | Attribute | Type | Description |
111
+ |-----------|------|-------------|
112
+ | `name` | `Symbol` | Parameter name |
113
+ | `type` | `String` | JSON Schema type string |
114
+ | `description` | `String?, nil` | Human-readable description |
115
+ | `required` / `required?` | `Boolean` | Whether the parameter is mandatory |
116
+
117
+ ### `Ask::Tools` — Registry & Discovery
118
+
119
+ Central registry for tool classes.
120
+
121
+ | Method | Returns | Description |
122
+ |--------|---------|-------------|
123
+ | `.register(tool_class)` | `void` | Manually register a tool class |
124
+ | `.all` | `Array<Tool>` | Instantiated list of all registered tools |
125
+ | `.discover` | `Array<Class>` | Auto-discover loaded `Ask::Tool` subclasses via `ObjectSpace` |
126
+ | `.[](name)` | `Tool?, nil` | Find a registered tool by its derived name |
127
+ | `.clear` | `void` | Remove all registered tools |
128
+ | `.count` | `Integer` | Number of registered tool classes |
129
+
130
+ ```ruby
131
+ # Manual registration
132
+ Ask::Tools.register(MyTool)
133
+
134
+ # Auto-discover all loaded Ask::Tool subclasses
135
+ Ask::Tools.discover
136
+
137
+ # Find by name
138
+ tool = Ask::Tools["my_tool"]
139
+ tool.call(input: "hello")
140
+
141
+ # List all
142
+ Ask::Tools.all.each { |t| puts t.name }
143
+ ```
144
+
145
+ ## Defining a Custom Tool
146
+
147
+ ```ruby
148
+ class SearchTool < Ask::Tool
149
+ description "Searches a knowledge base"
150
+ param :query, type: :string, desc: "Search query", required: true
151
+ param :limit, type: :integer, desc: "Max results", required: false
152
+
153
+ def execute(query:, limit: 10)
154
+ results = perform_search(query, limit)
155
+ Ask::Result.ok(data: results)
156
+ rescue SearchError => e
157
+ Ask::Result.error(message: e.message)
158
+ end
159
+
160
+ private
161
+
162
+ def perform_search(query, limit)
163
+ # ... implementation
164
+ end
165
+ end
166
+ ```
167
+
168
+ ## Development
169
+
170
+ ```bash
171
+ # Install dependencies
172
+ bundle install
173
+
174
+ # Run tests
175
+ bundle exec rake test
176
+
177
+ # Build the gem
178
+ gem build ask-tools.gemspec
179
+ ```
180
+
181
+ ## Testing
182
+
183
+ ask-tools uses **Minitest** with **Mocha** for mocking.
184
+
185
+ ```bash
186
+ # Run the full test suite
187
+ bundle exec rake test
188
+ ```
189
+
190
+ ## Release Process
191
+
192
+ 1. Update `CHANGELOG.md`
193
+ 2. Update `lib/ask/version.rb` if needed
194
+ 3. Build the gem: `gem build ask-tools.gemspec`
195
+ 4. Push to GitHub Packages: `gem push ask-tools-*.gem`
196
+
197
+ ## License
198
+
199
+ MIT — see [LICENSE](LICENSE).
200
+
201
+ ## Links
202
+
203
+ - **Source:** https://github.com/ask-rb/ask-tools
204
+ - **Issues:** https://github.com/ask-rb/ask-tools/issues
205
+ - **Docs:** https://github.com/ask-rb/ask-docs
@@ -0,0 +1,81 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ask
4
+ # Standardized return value for tool execution.
5
+ #
6
+ # Every tool's #execute method should return an Ask::Result.
7
+ # Use the factory methods +.ok+ and +.error+ for common cases.
8
+ #
9
+ # Ask::Result.ok(data: "hello world")
10
+ # Ask::Result.error(message: "something went wrong")
11
+ #
12
+ class Result
13
+ # @return [Boolean] whether the tool completed successfully
14
+ attr_reader :ok
15
+
16
+ # @return [Object, nil] the output data when the tool succeeded
17
+ attr_reader :output
18
+
19
+ # @return [String, nil] the error message when the tool failed
20
+ attr_reader :error
21
+
22
+ # @return [Hash] arbitrary metadata attached to the result
23
+ attr_reader :metadata
24
+
25
+ alias ok? ok
26
+
27
+ def initialize(ok:, output: nil, error: nil, metadata: {})
28
+ @ok = ok
29
+ @output = output
30
+ @error = error
31
+ @metadata = metadata
32
+ end
33
+
34
+ # Create a successful result.
35
+ #
36
+ # @param data [Object] the tool's output
37
+ # @param metadata [Hash] optional metadata
38
+ # @return [Ask::Result]
39
+ def self.ok(data:, metadata: {})
40
+ new(ok: true, output: data, error: nil, metadata: metadata)
41
+ end
42
+
43
+ # Create a failed result.
44
+ #
45
+ # @param message [String] description of the failure
46
+ # @param metadata [Hash] optional metadata
47
+ # @return [Ask::Result]
48
+ def self.error(message:, metadata: {})
49
+ new(ok: false, output: nil, error: message, metadata: metadata)
50
+ end
51
+
52
+ # Human-readable representation.
53
+ # Returns the output for success or the error message for failure.
54
+ #
55
+ # @return [String]
56
+ def to_s
57
+ ok? ? output.to_s : error.to_s
58
+ end
59
+
60
+ # Hash representation suitable for serialization.
61
+ #
62
+ # @return [Hash]
63
+ def to_h
64
+ {
65
+ ok: ok,
66
+ output: output,
67
+ error: error,
68
+ metadata: metadata
69
+ }
70
+ end
71
+
72
+ # @return [String] inspect string
73
+ def inspect
74
+ if ok?
75
+ "#<Ask::Result ok=true output=#{output.inspect}>"
76
+ else
77
+ "#<Ask::Result ok=false error=#{error.inspect}>"
78
+ end
79
+ end
80
+ end
81
+ end
@@ -0,0 +1,273 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ask
4
+ # Base class for defining tools that LLMs can call.
5
+ #
6
+ # Subclass +Ask::Tool+, use the DSL to declare metadata and parameters,
7
+ # and implement +#execute+ to perform the work.
8
+ #
9
+ # class Greeter < Ask::Tool
10
+ # description "Greets a person by name"
11
+ # param :name, type: :string, desc: "The person's name", required: true
12
+ #
13
+ # def execute(name:)
14
+ # Ask::Result.ok(data: "Hello, #{name}!")
15
+ # end
16
+ # end
17
+ #
18
+ # Greeter.new.name # => "greeter"
19
+ # Greeter.new.call(name: "World")
20
+ # # => #<Ask::Result ok=true output="Hello, World!">
21
+ #
22
+ class Tool
23
+ # Raised (or returned from +#call+) to signal the conversation loop
24
+ # should stop rather than continuing after this tool's result.
25
+ class Halt < StandardError
26
+ attr_reader :content
27
+
28
+ def initialize(content)
29
+ @content = content
30
+ super(content.to_s)
31
+ end
32
+ end
33
+
34
+ class << self
35
+ # @api private
36
+ def inherited(subclass)
37
+ super
38
+ @parameters = {} if @parameters.nil?
39
+ subclass.instance_variable_set(:@description, nil)
40
+ subclass.instance_variable_set(:@parameters, {})
41
+ end
42
+
43
+ # Set or retrieve the tool's human-readable description.
44
+ #
45
+ # @param text [String, nil] when provided, sets the description
46
+ # @return [String, nil]
47
+ def description(text = nil)
48
+ return @description unless text
49
+
50
+ @description = text
51
+ end
52
+ alias desc description
53
+
54
+ # Declare a parameter the tool accepts.
55
+ #
56
+ # @param name [Symbol] parameter name
57
+ # @param type [Symbol] JSON Schema type (+:string+, +:integer+, +:number+,
58
+ # +:boolean+, +:array+, +:object+)
59
+ # @param desc [String] human-readable description of the parameter
60
+ # @param required [Boolean] whether the parameter is mandatory
61
+ # @return [void]
62
+ def param(name, type:, desc: nil, description: nil, required: true)
63
+ type = type.to_s.downcase.to_sym
64
+ validate_param_type!(type, name)
65
+ parameters[name] = Parameter.new(
66
+ name: name,
67
+ type: map_type(type),
68
+ description: desc || description,
69
+ required: required
70
+ )
71
+ end
72
+
73
+ # @api private
74
+ # @return [Hash{Symbol => Ask::Tool::Parameter}]
75
+ def parameters
76
+ @parameters ||= {}
77
+ end
78
+
79
+ # @api private
80
+ # Define tool parameters using the {Ask::Schema} DSL.
81
+ #
82
+ # When a block is provided, it takes precedence over individual
83
+ # +param+ declarations for schema generation.
84
+ #
85
+ # @example
86
+ # params do
87
+ # string :location, description: "City name"
88
+ # string :unit, enum: %w[celsius fahrenheit]
89
+ # end
90
+ #
91
+ # @param schema [Ask::Schema, Class<Ask::Schema>, Hash, nil] A pre-built schema
92
+ # @param block [Proc] DSL block evaluated by Ask::Schema
93
+ # @return [void]
94
+ def params(schema = nil, &block)
95
+ @params_schema_definition = schema || block
96
+ end
97
+
98
+ def provider_params
99
+ @provider_params ||= {}
100
+ end
101
+ end
102
+
103
+ # Auto-derive the tool name from the class name.
104
+ # Converts CamelCase to snake_case and strips a trailing +_tool+ suffix.
105
+ #
106
+ # @return [String]
107
+ def name
108
+ # Use only the class name (last segment), ignoring module nesting
109
+ klass_name = self.class.name.to_s.split("::").last || self.class.name.to_s
110
+ normalized = klass_name.dup.force_encoding("UTF-8").unicode_normalize(:nfkd)
111
+ normalized.encode("ASCII", replace: "")
112
+ .gsub(/[^a-zA-Z0-9_-]/, "-")
113
+ .gsub(/([A-Z]+)([A-Z][a-z])/, '\1_\2')
114
+ .gsub(/([a-z\d])([A-Z])/, '\1_\2')
115
+ .downcase
116
+ .delete_suffix("_tool")
117
+ end
118
+
119
+ # @return [String, nil] the tool's description
120
+ def description
121
+ self.class.description
122
+ end
123
+
124
+ # @return [Hash{Symbol => Ask::Tool::Parameter}]
125
+ def parameters
126
+ self.class.parameters
127
+ end
128
+
129
+ # Call the tool with the given arguments.
130
+ #
131
+ # Normalizes keys to symbols, validates required parameters,
132
+ # and delegates to +#execute+.
133
+ #
134
+ # @param args [Hash, nil] keyword arguments for the tool
135
+ # @return [Ask::Result] the tool's result
136
+ def call(args = {})
137
+ normalized = normalize_args(args)
138
+ validation = validate(normalized)
139
+ return Ask::Result.error(message: validation) if validation
140
+
141
+ execute(**normalized)
142
+ rescue Halt => e
143
+ Ask::Result.ok(data: e.content, metadata: { halted: true })
144
+ rescue StandardError => e
145
+ Ask::Result.error(message: "#{self.class.name.split('::').last} raised #{e.class}: #{e.message}")
146
+ end
147
+
148
+ # Subclasses must implement this method.
149
+ #
150
+ # @param args [Hash] normalized keyword arguments
151
+ # @return [Ask::Result] the tool's result
152
+ def execute(**)
153
+ raise NotImplementedError, "#{self.class} must implement #execute(**args)"
154
+ end
155
+
156
+ # Generate a JSON Schema hash describing this tool's parameters.
157
+ # Suitable for LLM function-calling APIs (OpenAI, Anthropic, etc.).
158
+ #
159
+ # @return [Hash]
160
+ def params_schema
161
+ return @params_schema if defined?(@params_schema)
162
+
163
+ @params_schema = begin
164
+ if parameters.empty?
165
+ nil
166
+ else
167
+ properties = parameters.to_h do |_name, param|
168
+ schema = { type: param.type }
169
+ schema[:description] = param.description if param.description
170
+ schema[:items] = { type: "string" } if param.type == "array"
171
+ [param.name.to_s, schema]
172
+ end
173
+
174
+ required = parameters.select { |_, p| p.required }.keys.map(&:to_s)
175
+
176
+ {
177
+ type: "object",
178
+ properties: properties,
179
+ required: required,
180
+ additionalProperties: false
181
+ }
182
+ end
183
+ end
184
+ end
185
+
186
+ # Full tool definition hash for LLM API calls.
187
+ #
188
+ # @return [Hash]
189
+ def tool_definition
190
+ defn = {
191
+ name: name,
192
+ description: description
193
+ }
194
+ defn[:input_schema] = params_schema if params_schema
195
+ defn
196
+ end
197
+
198
+ # @return [String] inspect string
199
+ def inspect
200
+ "#<#{self.class.name} name=#{name.inspect}>"
201
+ end
202
+
203
+ private
204
+
205
+ def normalize_args(args)
206
+ return {} if args.nil?
207
+
208
+ args.respond_to?(:transform_keys) ? args.transform_keys(&:to_sym) : {}
209
+ end
210
+
211
+ def validate(normalized)
212
+ missing = self.class.parameters.select { |_, p| p.required && !normalized.key?(p.name) }
213
+ return "missing required parameters: #{missing.keys.map(&:inspect).join(', ')}" unless missing.empty?
214
+
215
+ unknown = normalized.keys - self.class.parameters.keys
216
+ return "unknown parameters: #{unknown.map(&:inspect).join(', ')}" unless unknown.empty?
217
+
218
+ nil
219
+ end
220
+
221
+ VALID_JSON_SCHEMA_TYPES = %i[string integer number boolean array object].freeze
222
+
223
+ def self.validate_param_type!(type, name)
224
+ return if VALID_JSON_SCHEMA_TYPES.include?(type)
225
+
226
+ raise ArgumentError,
227
+ "Invalid type #{type.inspect} for parameter #{name.inspect}. " \
228
+ "Valid types: #{VALID_JSON_SCHEMA_TYPES.map(&:inspect).join(', ')}"
229
+ end
230
+
231
+ def self.map_type(type)
232
+ case type
233
+ when :int then "integer"
234
+ when :float, :double then "number"
235
+ else type.to_s
236
+ end
237
+ end
238
+
239
+ # Internal value object for parameter metadata.
240
+ class Parameter
241
+ # @return [Symbol]
242
+ attr_reader :name
243
+
244
+ # @return [String] JSON Schema type string
245
+ attr_reader :type
246
+
247
+ # @return [String, nil]
248
+ attr_reader :description
249
+
250
+ # @return [Boolean]
251
+ attr_reader :required
252
+
253
+ alias required? required
254
+
255
+ def initialize(name:, type:, description: nil, required: true)
256
+ @name = name
257
+ @type = type
258
+ @description = description
259
+ @required = required
260
+ end
261
+
262
+ # @return [Hash]
263
+ def to_h
264
+ {
265
+ name: name,
266
+ type: type,
267
+ description: description,
268
+ required: required
269
+ }
270
+ end
271
+ end
272
+ end
273
+ end
data/lib/ask/tools.rb ADDED
@@ -0,0 +1,87 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "monitor"
4
+
5
+ module Ask
6
+ # Tool registry and discovery.
7
+ #
8
+ # Provides a central registry for tool classes and auto-discovery
9
+ # of Ask::Tool subclasses via ObjectSpace. Thread-safe.
10
+ #
11
+ # Ask::Tools.register(MyTool)
12
+ # Ask::Tools.all # => [MyTool.new, ...]
13
+ # Ask::Tools["my_tool"] # => instance of MyTool
14
+ # Ask::Tools.discover # auto-register all loaded Ask::Tool subclasses
15
+ #
16
+ module Tools
17
+ class << self
18
+ # Register a tool class manually.
19
+ #
20
+ # @param tool_class [Class < Ask::Tool]
21
+ # @return [void]
22
+ def register(tool_class)
23
+ monitor.synchronize { registry[tool_class.name] = tool_class }
24
+ end
25
+
26
+ # Return an array of instantiated registered tools.
27
+ #
28
+ # @return [Array<Ask::Tool>]
29
+ def all
30
+ monitor.synchronize { registry.values.map(&:new) }
31
+ end
32
+
33
+ # Auto-discover loaded +Ask::Tool+ subclasses via +ObjectSpace+
34
+ # and register any that aren't already registered.
35
+ #
36
+ # @return [Array<Class>] the newly discovered classes
37
+ def discover
38
+ monitor.synchronize do
39
+ discovered = ObjectSpace.each_object(Class).select do |klass|
40
+ klass < Ask::Tool && !registry.value?(klass) && klass.name
41
+ end
42
+ discovered.each { |klass| register(klass) }
43
+ discovered
44
+ end
45
+ end
46
+
47
+ # Find a registered tool by its derived name.
48
+ #
49
+ # @param name [String, Symbol] the tool name to look up
50
+ # @return [Ask::Tool, nil] an instance of the matching tool, or nil
51
+ def [](name)
52
+ name_str = name.to_s
53
+ monitor.synchronize do
54
+ registry.each_value do |klass|
55
+ instance = klass.new
56
+ return instance if instance.name == name_str
57
+ end
58
+ nil
59
+ end
60
+ end
61
+
62
+ # Remove all registered tools.
63
+ #
64
+ # @return [void]
65
+ def clear
66
+ monitor.synchronize { registry.clear }
67
+ end
68
+
69
+ # Number of registered tool classes.
70
+ #
71
+ # @return [Integer]
72
+ def count
73
+ monitor.synchronize { registry.size }
74
+ end
75
+
76
+ private
77
+
78
+ def registry
79
+ @registry ||= {}
80
+ end
81
+
82
+ def monitor
83
+ @monitor ||= Monitor.new
84
+ end
85
+ end
86
+ end
87
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ask
4
+ VERSION = "0.1.0"
5
+ end
data/lib/ask-tools.rb ADDED
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "ask/version"
4
+ require_relative "ask/tools"
5
+ require_relative "ask/tools/result"
6
+ require_relative "ask/tools/tool"
7
+
8
+ module Ask
9
+ end
metadata ADDED
@@ -0,0 +1,90 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: ask-tools
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Kaka Ruto
8
+ bindir: bin
9
+ cert_chain: []
10
+ date: 1980-01-02 00:00:00.000000000 Z
11
+ dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: minitest
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - "~>"
17
+ - !ruby/object:Gem::Version
18
+ version: '5.25'
19
+ type: :development
20
+ prerelease: false
21
+ version_requirements: !ruby/object:Gem::Requirement
22
+ requirements:
23
+ - - "~>"
24
+ - !ruby/object:Gem::Version
25
+ version: '5.25'
26
+ - !ruby/object:Gem::Dependency
27
+ name: mocha
28
+ requirement: !ruby/object:Gem::Requirement
29
+ requirements:
30
+ - - "~>"
31
+ - !ruby/object:Gem::Version
32
+ version: '3.1'
33
+ type: :development
34
+ prerelease: false
35
+ version_requirements: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - "~>"
38
+ - !ruby/object:Gem::Version
39
+ version: '3.1'
40
+ - !ruby/object:Gem::Dependency
41
+ name: rake
42
+ requirement: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - "~>"
45
+ - !ruby/object:Gem::Version
46
+ version: '13.0'
47
+ type: :development
48
+ prerelease: false
49
+ version_requirements: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - "~>"
52
+ - !ruby/object:Gem::Version
53
+ version: '13.0'
54
+ description: Defines Ask::Tool (base class), Ask::Result, and tool discovery. Zero
55
+ dependencies.
56
+ email:
57
+ - kaka@myrrlabs.com
58
+ executables: []
59
+ extensions: []
60
+ extra_rdoc_files: []
61
+ files:
62
+ - LICENSE
63
+ - README.md
64
+ - lib/ask-tools.rb
65
+ - lib/ask/tools.rb
66
+ - lib/ask/tools/result.rb
67
+ - lib/ask/tools/tool.rb
68
+ - lib/ask/version.rb
69
+ homepage: https://github.com/ask-rb/ask-tools
70
+ licenses:
71
+ - MIT
72
+ metadata: {}
73
+ rdoc_options: []
74
+ require_paths:
75
+ - lib
76
+ required_ruby_version: !ruby/object:Gem::Requirement
77
+ requirements:
78
+ - - ">="
79
+ - !ruby/object:Gem::Version
80
+ version: '3.2'
81
+ required_rubygems_version: !ruby/object:Gem::Requirement
82
+ requirements:
83
+ - - ">="
84
+ - !ruby/object:Gem::Version
85
+ version: '0'
86
+ requirements: []
87
+ rubygems_version: 4.0.3
88
+ specification_version: 4
89
+ summary: Tool framework for the ask-rb ecosystem
90
+ test_files: []