datadog-statsd-schema 0.1.2 → 0.2.1

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.
data/Rakefile CHANGED
@@ -2,6 +2,7 @@
2
2
 
3
3
  require "bundler/gem_tasks"
4
4
  require "rspec/core/rake_task"
5
+ require "rubocop/rake_task"
5
6
  require "timeout"
6
7
 
7
8
  def shell(*args)
@@ -26,4 +27,7 @@ task build: :permissions
26
27
 
27
28
  RSpec::Core::RakeTask.new(:spec)
28
29
 
29
- task default: :spec
30
+ RuboCop::RakeTask.new(:rubocop)
31
+
32
+ # Define the default task to include both
33
+ task default: %i[spec rubocop]
Binary file
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ # vim: ft=ruby
4
+
5
+ namespace "marathon" do
6
+ tags do
7
+ tag :course, values: %w[sf-marathon new-york austin]
8
+ tag :length, values: %w[full half]
9
+ end
10
+
11
+ namespace "started" do
12
+ metrics do
13
+ counter "total" do
14
+ description "Number of people who started the Marathon"
15
+ tags required: %i[course length]
16
+ end
17
+ end
18
+ end
19
+
20
+ namespace "finished" do
21
+ metrics do
22
+ counter "total" do
23
+ description "Number of people who finished the Marathon"
24
+ inherit_tags "marathon.started.total"
25
+ end
26
+
27
+ distribution "duration" do
28
+ description "Marathon duration"
29
+ inherit_tags "marathon.started.total"
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ # vim: ft=ruby
4
+
5
+ namespace :web do
6
+ tags do
7
+ tag :environment, values: %w[production staging development]
8
+ tag :service, values: %w[api web worker]
9
+ tag :region, values: %w[us-east-1 us-west-2 eu-west-1]
10
+ end
11
+
12
+ namespace :requests do
13
+ metrics do
14
+ counter :total do
15
+ description "Total HTTP requests"
16
+ tags required: %i[environment service], allowed: %i[region]
17
+ end
18
+
19
+ distribution :duration do
20
+ description "Request processing time in milliseconds"
21
+ inherit_tags "web.requests.total"
22
+ end
23
+ end
24
+ end
25
+
26
+ metrics do
27
+ gauge :memory_usage do
28
+ description "Memory usage in bytes"
29
+ tags required: %i[environment], allowed: %i[service]
30
+ end
31
+ end
32
+ end
data/exe/dss ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ $LOAD_PATH.unshift(File.expand_path("../lib", __dir__))
5
+
6
+ require "datadog/statsd/schema"
7
+
8
+ Dry::CLI.new(Datadog::Statsd::Schema::CLI).call
@@ -0,0 +1,477 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "stringio"
4
+ require "colored2"
5
+ require "forwardable"
6
+ require "json"
7
+ require "yaml"
8
+
9
+ module Datadog
10
+ class Statsd
11
+ module Schema
12
+ # Result structure for schema analysis
13
+ # @!attribute [r] total_unique_metrics
14
+ # @return [Integer] Total number of unique metric names (including expansions)
15
+ # @!attribute [r] metrics_analysis
16
+ # @return [Array<MetricAnalysis>] Analysis for each metric
17
+ # @!attribute [r] total_possible_custom_metrics
18
+ # @return [Integer] Total number of possible custom metric combinations
19
+ AnalysisResult = Data.define(
20
+ :total_unique_metrics,
21
+ :metrics_analysis,
22
+ :total_possible_custom_metrics
23
+ )
24
+
25
+ # Analysis data for individual metrics
26
+ # @!attribute [r] metric_name
27
+ # @return [String] Full metric name
28
+ # @!attribute [r] metric_type
29
+ # @return [Symbol] Type of metric (:counter, :gauge, etc.)
30
+ # @!attribute [r] expanded_names
31
+ # @return [Array<String>] All expanded metric names (for gauge/distribution/histogram)
32
+ # @!attribute [r] unique_tags
33
+ # @return [Integer] Number of unique tags for this metric
34
+ # @!attribute [r] unique_tag_values
35
+ # @return [Integer] Total number of unique tag values across all tags
36
+ # @!attribute [r] total_combinations
37
+ # @return [Integer] Total possible tag value combinations for this metric
38
+ MetricAnalysis = Data.define(
39
+ :metric_name,
40
+ :metric_type,
41
+ :expanded_names,
42
+ :unique_tags,
43
+ :unique_tag_values,
44
+ :total_combinations
45
+ )
46
+
47
+ # Analyzes schema instances to provide comprehensive metrics statistics
48
+ class Analyzer
49
+ # Metric suffixes for different metric types that create multiple metrics
50
+ METRIC_EXPANSIONS = {
51
+ gauge: %w[count min max sum avg],
52
+ distribution: %w[count min max sum avg p50 p75 p90 p95 p99],
53
+ histogram: %w[count min max sum avg]
54
+ }.freeze
55
+
56
+ attr_reader :schemas, :stdout, :stderr, :color, :format, :analysis_result
57
+
58
+ SUPPORTED_FORMATS = %i[text json yaml].freeze
59
+
60
+ # Initialize analyzer with schema(s)
61
+ # @param schemas [Datadog::Statsd::Schema::Namespace, Array<Datadog::Statsd::Schema::Namespace>]
62
+ # Single schema or array of schemas to analyze
63
+ def initialize(
64
+ schemas,
65
+ stdout: $stdout,
66
+ stderr: $stderr,
67
+ color: true,
68
+ format: SUPPORTED_FORMATS.first
69
+ )
70
+ @schemas = Array(schemas)
71
+ @stdout = stdout
72
+ @stderr = stderr
73
+ @color = color
74
+ @format = format.to_sym
75
+
76
+ raise ArgumentError, "Unsupported format: #{format}. Supported formats are: #{SUPPORTED_FORMATS.join(", ")}" unless SUPPORTED_FORMATS.include?(format)
77
+
78
+ if color
79
+ Colored2.enable!
80
+ else
81
+ Colored2.disable!
82
+ end
83
+
84
+ @analysis_result = analyze
85
+ end
86
+
87
+ # Perform comprehensive analysis of the schemas
88
+ # @return [AnalysisResult] Complete analysis results
89
+ def analyze
90
+ all_metrics = collect_all_metrics
91
+ metrics_analysis = analyze_metrics(all_metrics).map(&:to_h)
92
+
93
+ total_unique_metrics = metrics_analysis.sum { |analysis| analysis[:expanded_names].size }
94
+ total_possible_custom_metrics = metrics_analysis.sum { |e| e[:total_combinations] }
95
+
96
+ AnalysisResult.new(
97
+ total_unique_metrics:,
98
+ metrics_analysis:,
99
+ total_possible_custom_metrics:
100
+ )
101
+ end
102
+
103
+ def render
104
+ case format
105
+ when :text
106
+ TextFormatter.new(stdout:, stderr:, color:, analysis_result:).render
107
+ when :json
108
+ JSONFormatter.new(stdout:, stderr:, color:, analysis_result:).render
109
+ when :yaml
110
+ YAMLFormatter.new(stdout:, stderr:, color:, analysis_result:).render
111
+ else
112
+ raise ArgumentError, "Unsupported format: #{format}. Supported formats are: #{SUPPORTED_FORMATS.join(", ")}"
113
+ end
114
+ end
115
+
116
+ private
117
+
118
+ # Collect all metrics from all schemas with their context
119
+ # @return [Array<Hash>] Array of metric info hashes
120
+ def collect_all_metrics
121
+ all_metrics = []
122
+
123
+ @schemas.each do |schema|
124
+ schema_metrics = schema.all_metrics
125
+ schema_metrics.each do |metric_full_name, metric_info|
126
+ all_metrics << {
127
+ full_name: metric_full_name,
128
+ definition: metric_info[:definition],
129
+ namespace: metric_info[:namespace],
130
+ namespace_path: metric_info[:namespace_path]
131
+ }
132
+ end
133
+ end
134
+
135
+ all_metrics
136
+ end
137
+
138
+ # Analyze each metric for tags and combinations
139
+ # @param all_metrics [Array<Hash>] Collected metrics
140
+ # @return [Array<MetricAnalysis>] Analysis for each metric
141
+ def analyze_metrics(all_metrics)
142
+ all_metrics.map do |metric_info|
143
+ analyze_single_metric(metric_info)
144
+ end
145
+ end
146
+
147
+ # Analyze a single metric
148
+ # @param metric_info [Hash] Metric information
149
+ # @return [MetricAnalysis] Analysis for this metric
150
+ def analyze_single_metric(metric_info)
151
+ definition = metric_info[:definition]
152
+ namespace = metric_info[:namespace]
153
+ namespace_path = metric_info[:namespace_path]
154
+ full_name = metric_info[:full_name]
155
+
156
+ # Get expanded metric names based on type
157
+ expanded_names = get_expanded_metric_names(full_name, definition.type)
158
+
159
+ # Build effective tags including parent namespace tags
160
+ effective_tags = build_effective_tags_for_metric(namespace, namespace_path)
161
+ available_tag_definitions = collect_available_tags(definition, effective_tags)
162
+
163
+ # Calculate tag statistics
164
+ unique_tags = available_tag_definitions.size
165
+ unique_tag_values = available_tag_definitions.values.sum { |tag_def| count_tag_values(tag_def) }
166
+
167
+ # Calculate total combinations (cartesian product of all tag values)
168
+ total_combinations = calculate_tag_combinations(available_tag_definitions) * expanded_names.size
169
+
170
+ MetricAnalysis.new(
171
+ metric_name: full_name,
172
+ metric_type: definition.type,
173
+ expanded_names: expanded_names,
174
+ unique_tags: unique_tags,
175
+ unique_tag_values: unique_tag_values,
176
+ total_combinations: total_combinations
177
+ )
178
+ end
179
+
180
+ # Build effective tags for a metric including parent namespace tags
181
+ # @param namespace [Namespace] The immediate namespace containing the metric
182
+ # @param namespace_path [Array<Symbol>] Full path to the namespace
183
+ # @return [Hash] Hash of effective tag definitions
184
+ def build_effective_tags_for_metric(namespace, namespace_path)
185
+ effective_tags = {}
186
+
187
+ # Start from the root and build up tags through the hierarchy
188
+ current_path = []
189
+
190
+ # Find and traverse parent namespaces to collect their tags
191
+ @schemas.each do |schema|
192
+ # Traverse the namespace path to collect parent tags
193
+ namespace_path.each do |path_segment|
194
+ # Skip :root as it's just the schema root
195
+ next if path_segment == :root
196
+
197
+ current_path << path_segment
198
+
199
+ # Find the namespace at this path
200
+ path_str = current_path.join(".")
201
+ found_namespace = schema.find_namespace_by_path(path_str)
202
+
203
+ next unless found_namespace
204
+
205
+ # Add tags from this namespace level
206
+ found_namespace.tags.each do |tag_name, tag_def|
207
+ effective_tags[tag_name] = tag_def
208
+ end
209
+ end
210
+
211
+ break if effective_tags.any? # Found the schema with our namespaces
212
+ end
213
+
214
+ # Add the immediate namespace's tags (these take precedence)
215
+ namespace.tags.each do |tag_name, tag_def|
216
+ effective_tags[tag_name] = tag_def
217
+ end
218
+
219
+ effective_tags
220
+ end
221
+
222
+ # Get expanded metric names for types that create multiple metrics
223
+ # @param base_name [String] Base metric name
224
+ # @param metric_type [Symbol] Type of the metric
225
+ # @return [Array<String>] All metric names this creates
226
+ def get_expanded_metric_names(base_name, metric_type)
227
+ expansions = METRIC_EXPANSIONS[metric_type]
228
+
229
+ if expansions
230
+ # For metrics that expand, create name.suffix for each expansion
231
+ expansions.map { |suffix| "#{base_name}.#{suffix}" }
232
+ else
233
+ # For simple metrics, just return the base name
234
+ [base_name]
235
+ end
236
+ end
237
+
238
+ # Collect all tags available to a metric
239
+ # @param definition [MetricDefinition] The metric definition
240
+ # @param effective_tags [Hash] Tags available in the namespace
241
+ # @return [Hash] Hash of tag name to tag definition
242
+ def collect_available_tags(definition, effective_tags)
243
+ available_tags = {}
244
+
245
+ # Handle tag inheritance from other metrics first
246
+ if definition.inherit_tags
247
+ inherited_tags = resolve_inherited_tags(definition.inherit_tags)
248
+ inherited_tags.keys
249
+ inherited_tags.each do |tag_name, tag_def|
250
+ available_tags[tag_name] = tag_def
251
+ end
252
+ end
253
+
254
+ # Determine which additional tags to include based on metric's tag specification
255
+ if definition.allowed_tags.any? || definition.required_tags.any?
256
+ # If metric specifies allowed or required tags, only include those + inherited tags
257
+ additional_tag_names = (definition.allowed_tags + definition.required_tags).map(&:to_sym).uniq
258
+
259
+ additional_tag_names.each do |tag_name|
260
+ available_tags[tag_name] = effective_tags[tag_name] if effective_tags[tag_name]
261
+ end
262
+ else
263
+ # If no allowed or required tags specified, include all effective namespace tags
264
+ # (This is the case when a metric doesn't restrict its tags)
265
+ effective_tags.each do |tag_name, tag_def|
266
+ available_tags[tag_name] = tag_def unless available_tags[tag_name]
267
+ end
268
+ end
269
+
270
+ available_tags
271
+ end
272
+
273
+ # Resolve inherited tags from a parent metric path
274
+ # @param inherit_path [String] Dot-separated path to parent metric
275
+ # @return [Hash] Hash of inherited tag definitions
276
+ def resolve_inherited_tags(inherit_path)
277
+ inherited_tags = {}
278
+
279
+ @schemas.each do |schema|
280
+ # Find the parent metric in the schema
281
+ all_metrics = schema.all_metrics
282
+ parent_metric_info = all_metrics[inherit_path]
283
+
284
+ next unless parent_metric_info
285
+
286
+ parent_definition = parent_metric_info[:definition]
287
+ parent_namespace = parent_metric_info[:namespace]
288
+ parent_namespace_path = parent_metric_info[:namespace_path]
289
+
290
+ # Build effective tags for the parent metric (including its own parent namespace tags)
291
+ parent_effective_tags = build_effective_tags_for_metric(parent_namespace, parent_namespace_path)
292
+
293
+ # Recursively resolve parent's inherited tags first
294
+ if parent_definition.inherit_tags
295
+ parent_inherited = resolve_inherited_tags(parent_definition.inherit_tags)
296
+ inherited_tags.merge!(parent_inherited)
297
+ end
298
+
299
+ # Get the tags that are actually available to the parent metric
300
+ parent_available_tags = collect_parent_available_tags(parent_definition, parent_effective_tags)
301
+ inherited_tags.merge!(parent_available_tags)
302
+
303
+ break # Found the parent metric, stop searching
304
+ end
305
+
306
+ inherited_tags
307
+ end
308
+
309
+ # Collect available tags for a parent metric (without recursion to avoid infinite loops)
310
+ # @param definition [MetricDefinition] The parent metric definition
311
+ # @param effective_tags [Hash] Tags available in the parent's namespace
312
+ # @return [Hash] Hash of tag name to tag definition
313
+ def collect_parent_available_tags(definition, effective_tags)
314
+ available_tags = {}
315
+
316
+ # Start with all effective tags from namespace
317
+ effective_tags.each do |tag_name, tag_def|
318
+ available_tags[tag_name] = tag_def
319
+ end
320
+
321
+ # Apply parent metric's tag restrictions
322
+ if definition.allowed_tags.any?
323
+ allowed_and_required_tags = (definition.allowed_tags + definition.required_tags).map(&:to_sym).uniq
324
+ available_tags.select! { |tag_name, _| allowed_and_required_tags.include?(tag_name) }
325
+ end
326
+
327
+ available_tags
328
+ end
329
+
330
+ # Count the number of possible values for a tag
331
+ # @param tag_definition [TagDefinition] The tag definition
332
+ # @return [Integer] Number of possible values
333
+ def count_tag_values(tag_definition)
334
+ if tag_definition.values.nil?
335
+ # If no values specified, assume it can have any value (estimate)
336
+ 100 # Conservative estimate for open-ended tags
337
+ elsif tag_definition.values.is_a?(Array)
338
+ tag_definition.values.size
339
+ elsif tag_definition.values.is_a?(Regexp)
340
+ # For regex, we can't know exact count, use estimate
341
+ 50 # Conservative estimate for regex patterns
342
+ else
343
+ 1 # Single value
344
+ end
345
+ end
346
+
347
+ # Calculate total possible combinations of tag values
348
+ # @param tag_definitions [Hash] Hash of tag name to definition
349
+ # @return [Integer] Total combinations possible
350
+ def calculate_tag_combinations(tag_definitions)
351
+ return 1 if tag_definitions.empty?
352
+
353
+ # Multiply the number of possible values for each tag
354
+ tag_definitions.values.reduce(1) do |total, tag_def|
355
+ total * count_tag_values(tag_def)
356
+ end
357
+ end
358
+
359
+ # ——————————————————————————————————————————————————————————————————————————————————————————————————————————————-
360
+ # Formatter classes
361
+ # ——————————————————————————————————————————————————————————————————————————————————————————————————————————————-
362
+ class BaseFormatter
363
+ attr_reader :stdout, :stderr, :color,
364
+ :analysis_result,
365
+ :total_unique_metrics,
366
+ :metrics_analysis,
367
+ :total_possible_custom_metrics
368
+
369
+ def initialize(stdout:, stderr:, color:, analysis_result:)
370
+ @stdout = stdout
371
+ @stderr = stderr
372
+ @color = color
373
+ @analysis_result = analysis_result.to_h.transform_values { |v| v.is_a?(Data) ? v.to_h : v }
374
+ @total_unique_metrics = @analysis_result[:total_unique_metrics]
375
+ @metrics_analysis = @analysis_result[:metrics_analysis]
376
+ @total_possible_custom_metrics = @analysis_result[:total_possible_custom_metrics]
377
+ end
378
+
379
+ def render
380
+ raise NotImplementedError, "Subclasses must implement this method"
381
+ end
382
+ end
383
+
384
+ class TextFormatter < BaseFormatter
385
+ attr_reader :output
386
+
387
+ def render
388
+ @output = StringIO.new
389
+ format_analysis_output
390
+ @output.string
391
+ end
392
+
393
+ private
394
+
395
+ # Format the analysis output for display
396
+ def format_analysis_output
397
+ format_metric_analysis_header(output)
398
+
399
+ analysis_result[:metrics_analysis].each do |analysis|
400
+ output.puts
401
+ format_metric_analysis(output, analysis)
402
+ line(output, placement: :flat)
403
+ end
404
+
405
+ summary(
406
+ output,
407
+ analysis_result[:total_unique_metrics],
408
+ analysis_result[:total_possible_custom_metrics]
409
+ )
410
+ output.string
411
+ end
412
+
413
+ def line(output, placement: :top)
414
+ if placement == :top
415
+ output.puts "┌──────────────────────────────────────────────────────────────────────────────────────────────┐".white.on.blue
416
+ elsif placement == :bottom
417
+ output.puts "└──────────────────────────────────────────────────────────────────────────────────────────────┘".white.on.blue
418
+ elsif placement == :middle
419
+ output.puts "├──────────────────────────────────────────────────────────────────────────────────────────────┤".white.on.blue
420
+ elsif placement == :flat
421
+ output.puts " ──────────────────────────────────────────────────────────────────────────────────────────────".white.bold
422
+ end
423
+ end
424
+
425
+ def summary(output, total_unique_metrics, total_possible_custom_metrics)
426
+ line(output)
427
+ output.puts "│ Schema Analysis Results: │".yellow.bold.on.blue
428
+ output.puts "│ SUMMARY │".white.on.blue
429
+ line(output, placement: :bottom)
430
+ output.puts
431
+ output.puts " Total unique metrics: #{("%3d" % total_unique_metrics).bold.green}"
432
+ output.puts "Total possible custom metric combinations: #{("%3d" % total_possible_custom_metrics).bold.green}"
433
+ output.puts
434
+ end
435
+
436
+ def format_metric_analysis_header(output)
437
+ line(output)
438
+ output.puts "│ Detailed Metric Analysis: │".white.on.blue
439
+ line(output, placement: :bottom)
440
+ end
441
+
442
+ def format_metric_analysis(output, analysis)
443
+ output.puts " • #{analysis[:metric_type].to_s.cyan}('#{analysis[:metric_name].yellow.bold}')"
444
+ if analysis[:expanded_names].size > 1
445
+ output.puts " Expanded names:"
446
+ output.print " • ".yellow
447
+ output.puts analysis[:expanded_names].join("\n • ").yellow
448
+ end
449
+ output.puts
450
+ output.puts " Unique tags: #{("%3d" % analysis[:unique_tags]).bold.green}"
451
+ output.puts " Total tag values: #{("%3d" % analysis[:unique_tag_values]).bold.green}"
452
+ output.puts " Possible combinations: #{("%3d" % analysis[:total_combinations]).bold.green}"
453
+ output.puts
454
+ end
455
+ end
456
+
457
+ # ——————————————————————————————————————————————————————————————————————————————————————————————————————————————-
458
+ # JSON Formatter classes
459
+ # ——————————————————————————————————————————————————————————————————————————————————————————————————————————————-
460
+ class JSONFormatter < BaseFormatter
461
+ def render
462
+ JSON.pretty_generate(analysis_result.to_h)
463
+ end
464
+ end
465
+
466
+ # ——————————————————————————————————————————————————————————————————————————————————————————————————————————————-
467
+ # YAML Formatter classes
468
+ # ——————————————————————————————————————————————————————————————————————————————————————————————————————————————-
469
+ class YAMLFormatter < BaseFormatter
470
+ def render
471
+ YAML.dump(analysis_result.to_h)
472
+ end
473
+ end
474
+ end
475
+ end
476
+ end
477
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "dry/cli"
4
+ require_relative "commands"
5
+
6
+ module Datadog
7
+ class Statsd
8
+ module Schema
9
+ module CLI
10
+ extend Dry::CLI::Registry
11
+
12
+ register "analyze", Commands::Analyze
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,63 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "dry/cli"
4
+
5
+ module Datadog
6
+ class Statsd
7
+ module Schema
8
+ module Commands
9
+ # @description Analyze a schema file for metrics and validation
10
+ class Analyze < Dry::CLI::Command
11
+ class << self
12
+ attr_accessor :stdout, :stderr
13
+ end
14
+
15
+ self.stdout = $stdout
16
+ self.stderr = $stderr
17
+
18
+ desc "Analyze a schema file for metrics and validation"
19
+
20
+ option :file, aliases: %w[-f], type: :string, required: true, desc: "Path to the schema file to analyze"
21
+ option :color, aliases: %w[-c], type: :boolean, required: false, desc: "Enable/Disable color output", default: true
22
+ option :format, aliases: %w[-o], type: :string, required: false, desc: "Output format, supports: json, yaml, text", default: :text
23
+
24
+ # @description Analyze a schema file for metrics and validation
25
+ # @param options [Hash] The options for the command
26
+ # @option options [String] :file The path to the schema file to analyze
27
+ # @option options [Boolean] :color Enable/Disable color output
28
+ # @return [void]
29
+ def call(**options)
30
+ file = options[:file]
31
+
32
+ unless file
33
+ warn "Error: --file option is required"
34
+ warn "Usage: dss analyze --file <schema.rb>"
35
+ exit 1
36
+ end
37
+
38
+ warn "Analyzing Schema File:"
39
+ warn " • file #{file.green}"
40
+ warn " • color: #{(options[:color] ? "enabled" : "disabled").yellow}"
41
+ warn " • formar #{options[:format].to_s.red}"
42
+ @schema = ::Datadog::Statsd::Schema.load_file(file)
43
+ ::Datadog::Statsd::Schema::Analyzer.new([@schema],
44
+ format: (options[:format] || :text).to_sym,
45
+ stdout: self.class.stdout,
46
+ stderr: self.class.stderr,
47
+ color: options[:color]).tap do |analyzer|
48
+ puts analyzer.render
49
+ end.analyze
50
+ end
51
+
52
+ def warn(...)
53
+ self.class.stderr.puts(...)
54
+ end
55
+
56
+ def puts(...)
57
+ self.class.stdout.puts(...)
58
+ end
59
+ end
60
+ end
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Datadog
4
+ class Statsd
5
+ module Schema
6
+ module Commands
7
+ # CLI commands will be defined here
8
+ end
9
+ end
10
+ end
11
+ end
12
+
13
+ # Require all command files
14
+ require_relative "commands/analyze"
@@ -27,7 +27,7 @@ module Datadog
27
27
  # @author Datadog Team
28
28
  # @since 0.1.0
29
29
  class Namespace < Dry::Struct
30
- # Include the types module for easier access to Dry::Types
30
+ # Include the types module for easier access to Dry::Typesa
31
31
  module Types
32
32
  include Dry.Types()
33
33
  end
@@ -8,7 +8,7 @@ module Datadog
8
8
  module Schema
9
9
  # Current version of the datadog-statsd-schema gem
10
10
  # @return [String] The semantic version string
11
- VERSION = "0.1.2"
11
+ VERSION = "0.2.1"
12
12
  end
13
13
  end
14
14
  end