xronor 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: b9da6e45fffe237ba7dc775a577a5b13aaccf6ed
4
+ data.tar.gz: 1fb3ef2dfdd321ee7462ca6c9120ee749d9e8e88
5
+ SHA512:
6
+ metadata.gz: dbe0036b34de528d5cccbd770243333eb9bf91ae8441f88023eea7611636680ef273c75e96df45624e443e1f0c70fa3981c09291cdfbe04f697cb036d3f700e2
7
+ data.tar.gz: 062ca0625de294f93a11ffc572a09d0be09e50e98fa43521157f37625a137add6819c653243a9249cb8ae394e82a5c7c74dde8b7e6953277494226414b65617b
data/.gitignore ADDED
@@ -0,0 +1,12 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
10
+
11
+ # rspec failure tracking
12
+ .rspec_status
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --format documentation
2
+ --color
data/.travis.yml ADDED
@@ -0,0 +1,7 @@
1
+ sudo: false
2
+ language: ruby
3
+ rvm:
4
+ - 2.2.6
5
+ - 2.3.3
6
+ - 2.4.0
7
+ before_install: gem install bundler -v 1.14.6
data/CHANGELOG.md ADDED
@@ -0,0 +1,3 @@
1
+ # [v0.1.0](https://github.com/dtan4/xronor/releases/tag/v0.1.0) (2017-03-24)
2
+
3
+ Initial release.
data/Gemfile ADDED
@@ -0,0 +1,13 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in xronor.gemspec
4
+ gemspec
5
+
6
+ group :development do
7
+ gem "guard", "~> 2.14", require: false
8
+ gem "guard-rspec", "~> 4.7", require: false
9
+ end
10
+
11
+ group :test do
12
+ gem "codecov", "~> 0.1", require: false
13
+ end
data/Guardfile ADDED
@@ -0,0 +1,16 @@
1
+ guard :rspec, cmd: "bundle exec rspec" do
2
+ require "guard/rspec/dsl"
3
+ dsl = Guard::RSpec::Dsl.new(self)
4
+
5
+ # Feel free to open issues for suggestions and improvements
6
+
7
+ # RSpec files
8
+ rspec = dsl.rspec
9
+ watch(rspec.spec_helper) { rspec.spec_dir }
10
+ watch(rspec.spec_support) { rspec.spec_dir }
11
+ watch(rspec.spec_files)
12
+
13
+ # Ruby files
14
+ ruby = dsl.ruby
15
+ dsl.watch_spec_files_for(ruby.lib_files)
16
+ end
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2017 Daisuke Fujita
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,140 @@
1
+ # Xronor
2
+
3
+ [![Build Status](https://travis-ci.org/dtan4/xronor.svg?branch=master)](https://travis-ci.org/dtan4/xronor)
4
+ [![codecov](https://codecov.io/gh/dtan4/xronor/branch/master/graph/badge.svg)](https://codecov.io/gh/dtan4/xronor)
5
+
6
+ Timezone-aware Job Scheduler DSL and Converter
7
+
8
+ ```ruby
9
+ job_template "/bin/bash -l -c ':job'"
10
+
11
+ job_type :rake, "bundle exec rake :task RAILS_ENV=production"
12
+
13
+ default do
14
+ timezone "Asia/Tokyo" # UTC+9
15
+ end
16
+
17
+ every 1.hour, at: 15 do
18
+ name "Send awesome mails"
19
+ rake "send_awesome_mail"
20
+ end
21
+
22
+ every :day, at: '0:00 am' do
23
+ name "Send greeting notifications"
24
+ description "Send greeting notifications for all users"
25
+ rake "send_greeting_notification"
26
+ end
27
+
28
+ every :day, at: '0:00 am', timezone: "Europe/Berlin" do # UTC+1
29
+ name "Send notifications for Berlin"
30
+ description "Send notifications for Berlin"
31
+ rake "send_notification[Europe/Berlin]"
32
+ end
33
+
34
+ every :wednesday, at: '0:10 am' do
35
+ name "Create new companies"
36
+ rake "create_new_companies"
37
+ end
38
+
39
+ every "0 10 10,20 * *" do
40
+ name "Healthcheck"
41
+ rake "ping"
42
+ end
43
+ ```
44
+
45
+ ## Table of contents
46
+
47
+ - [Why](#why)
48
+ * [Scheduled job execution system](#scheduled-job-execution-system)
49
+ * [Scheduler DSL](#scheduler-dsl)
50
+ * [:point_right:](#point_right)
51
+ - [Installation](#installation)
52
+ - [Usage](#usage)
53
+ - [Xronor DSL](#xronor-dsl)
54
+ - [Development](#development)
55
+ - [Contributing](#contributing)
56
+ - [Author](#author)
57
+ - [License](#license)
58
+
59
+ ## Why
60
+
61
+ ### Scheduled job execution system
62
+
63
+ As you know, Cron is commonly used for scheduled jobs.
64
+ However, Cron has some difficulties:
65
+
66
+ - Does not consider timezone. It depends on machine environment where cron daemon runs.
67
+ - Does not contain job metadata (name, description, ...).
68
+ - Cron daemon cannot be distributed. Machine where cron daemon runs can be SPOF.
69
+
70
+ Recently there are solutions for the last point, e.g. [Azure Scheduler](https://azure.microsoft.com/en-us/services/scheduler/), [CloudWatch Events](http://docs.aws.amazon.com/AmazonCloudWatch/latest/events/WhatIsCloudWatchEvents.html) and [Kubernetes Cron Job](https://kubernetes.io/docs/user-guide/cron-jobs/), but those services still have fixed timezone to UTC.
71
+
72
+ ### Scheduler DSL
73
+
74
+ [Whenever gem](https://github.com/javan/whenever) is very useful to describe scheduled jobs in human-friendly format.
75
+ However, Whenever cannot treat timezone and metadata so that it is just a wrapper of Cron expression.
76
+
77
+ ### :point_right:
78
+
79
+ To resolve above problems, we need:
80
+
81
+ - _timezone-aware_ job scheduler DSL
82
+ - Just like an enhance of Whenever
83
+ - a DSL converter which is easy to register CloudWatch Events rule
84
+
85
+
86
+ ## Installation
87
+
88
+ Add this line to your application's Gemfile:
89
+
90
+ ```ruby
91
+ gem 'xronor'
92
+ ```
93
+
94
+ And then execute:
95
+
96
+ $ bundle
97
+
98
+ Or install it yourself as:
99
+
100
+ $ gem install xronor
101
+
102
+ ## Usage
103
+
104
+ ```
105
+ Commands:
106
+ xronor crontab SCHEDULEFILE
107
+ xronor cwa SCHEDULEFILE --cluster=CLUSTER --container=CONTAINER --function=FUNCTION --table=TABLE --task-definition=TASK_DEFINITION
108
+ xronor template SCHEDULEFILE --template=TEMPLATE
109
+ xronor template_per_job SCHEDULERFILE --outdir=OUTDIR --template=TEMPLATE
110
+ ```
111
+
112
+ Xronor CLI converts DSL file to:
113
+
114
+ - `xronor cwa` CloudWatch Events Rule (requires `AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY` and `AWS_REGION` environment variables)
115
+ - `xronor crontab` crontab file
116
+ - file(s) from ERB template
117
+ - `xronor template` write all jobs in one file
118
+ - `xronor template_per_job` generate files per job
119
+
120
+ ## Xronor DSL
121
+
122
+ :point_right: [docs/dsl](docs/dsl.md)
123
+
124
+ ## Development
125
+
126
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
127
+
128
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
129
+
130
+ ## Contributing
131
+
132
+ Bug reports and pull requests are welcome on GitHub at https://github.com/dtan4/xronor.
133
+
134
+ ## Author
135
+
136
+ Daisuke Fujita ([@dtan4](https://github.com/dtan4))
137
+
138
+ ## License
139
+
140
+ The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
data/Rakefile ADDED
@@ -0,0 +1,6 @@
1
+ require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task :default => :spec
data/bin/console ADDED
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "xronor"
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ # (If you use this, don't forget to add pry to your Gemfile!)
10
+ # require "pry"
11
+ # Pry.start
12
+
13
+ require "irb"
14
+ IRB.start(__FILE__)
data/bin/setup ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
data/docs/README.md ADDED
@@ -0,0 +1,3 @@
1
+ # Xronor documents
2
+
3
+ - [Xronor DSL](dsl.md)
data/docs/dsl.md ADDED
@@ -0,0 +1,179 @@
1
+ # Xronor DSL
2
+
3
+ Xronor DSL is heavily inspired by [Whenever](https://github.com/javan/whenever) DSL.
4
+
5
+ For example,
6
+
7
+ ```ruby
8
+ job_template "/bin/bash -l -c ':job'"
9
+
10
+ job_type :rake, "bundle exec rake :task RAILS_ENV=production"
11
+
12
+ default do
13
+ timezone "Asia/Tokyo" # UTC+9
14
+ end
15
+
16
+ every 1.hour, at: 15 do
17
+ name "Send awesome mails"
18
+ rake "send_awesome_mail"
19
+ end
20
+
21
+ every :day, at: '0:00 am' do
22
+ name "Send greeting notifications"
23
+ description "Send greeting notifications for all users"
24
+ rake "send_greeting_notification"
25
+ end
26
+
27
+ every :day, at: '0:00 am', timezone: "Europe/Berlin" do # UTC+1
28
+ name "Send notifications for Berlin"
29
+ description "Send notifications for Berlin"
30
+ rake "send_notification[Europe/Berlin]"
31
+ end
32
+
33
+ every :wednesday, at: '0:10 am' do
34
+ name "Create new companies"
35
+ rake "create_new_companies"
36
+ end
37
+
38
+ every "0 10 10,20 * *" do
39
+ name "Healthcheck"
40
+ rake "ping"
41
+ end
42
+ ```
43
+
44
+ will be converted to the following crontab:
45
+
46
+ ```
47
+ # Send awesome mails - Send awesome mails
48
+ 15 * * * * /bin/bash -l -c 'bundle exec rake send_awesome_mail RAILS_ENV=production'
49
+
50
+ # Update Elasticsearch indices - Update Elasticsearch indices
51
+ 10 * * * * /bin/bash -l -c 'bundle exec rake update_elasticsearch RAILS_ENV=production'
52
+
53
+ # Send greeting notifications - Send greeting notifications for all users
54
+ 0 15 * * * /bin/bash -l -c 'bundle exec rake send_greeting_notification RAILS_ENV=production'
55
+
56
+ # Send notifications for Berlin - Send notifications for Berlin
57
+ 0 23 * * * /bin/bash -l -c 'bundle exec rake send_notification[Europe/Berlin] RAILS_ENV=production'
58
+
59
+ # Create new companies - Create new companies
60
+ 10 15 * * 2 /bin/bash -l -c 'bundle exec rake create_new_companies RAILS_ENV=production'
61
+
62
+ # Healthcheck - Healthcheck
63
+ 0 10 10,20 * * /bin/bash -l -c 'bundle exec rake ping RAILS_ENV=production'
64
+ ```
65
+
66
+ ## Table of contents
67
+
68
+ - [Configuration](#configuration)
69
+ * [`job_template`](#job_template)
70
+ * [`job_type` (Required at least one)](#job_type-required-at-least-one)
71
+ * [`default`](#default)
72
+ - [Job definition](#job-definition)
73
+ * [`every do ... end`](#every---do--end)
74
+ * [`name` (Required)](#name-required)
75
+ * [`description`](#description)
76
+ * [`job_type` (e.g. `rake`)](#job_type-eg-rake)
77
+
78
+ ## Configuration
79
+
80
+ ```ruby
81
+ job_template "/bin/bash -l -c ':job'"
82
+
83
+ job_type :rake, "bundle exec rake :task RAILS_ENV=production"
84
+
85
+ default do
86
+ timezone "Asia/Tokyo" # UTC+9
87
+ end
88
+ ```
89
+
90
+ ### `job_template`
91
+
92
+ Define common job command template.
93
+ `:job` will be replaced to per-job command.
94
+
95
+ Default: `:job`
96
+
97
+ ### `job_type` (Required at least one)
98
+
99
+ Define job type and per-job command template.
100
+ `:task` will be replaced to the first argument of job.
101
+
102
+ For example, the following configuration will generate `/bin/bash -l -c 'bundle exec rake update_elasticsearch'`.
103
+
104
+ ```ruby
105
+ job_template "/bin/bash -l -c ':job'"
106
+
107
+ job_type :rake, "bundle exec rake :task"
108
+
109
+ every 1.minute do
110
+ rake "update_elasticsearch"
111
+ end
112
+ ```
113
+
114
+ ### `default`
115
+
116
+ Set default timezone.
117
+ Timezone format follows [tz database](https://en.wikipedia.org/wiki/List_of_tz_database_time_zones) (e.g. `UTC`, `Asia/Tokyo`).
118
+
119
+ |key|description|default|
120
+ |---|---|---|
121
+ |`timezone`|Timezone of described time in DSL|`UTC`|
122
+ |`cron_timezone`|Timezone of the machine where schedule engine runs|`UTC`|
123
+
124
+ For example, the following configuration will parse `10:30 am` as `10:30 am UTC+9` then convert to `30 1 * * *` (`1:30 am UTC`).
125
+
126
+ ```ruby
127
+ default do
128
+ timezone "Asia/Tokyo" # UTC+9
129
+ cron_timezone "UTC"
130
+ end
131
+ ```
132
+
133
+ ## Job definition
134
+
135
+ ```ruby
136
+ every :day, at: '0:00 am', timezone: "Europe/Berlin" do # UTC+1
137
+ name "Send notifications for Berlin"
138
+ description "Send notifications for Berlin"
139
+ rake "send_notification[Europe/Berlin]"
140
+ end
141
+ ```
142
+
143
+ ### `every <frequency> <options> do ... end`
144
+
145
+ Define job schedule.
146
+
147
+ Available `<frequency>`:
148
+
149
+ |key|description|
150
+ |---|---|
151
+ |`:minute`|Invoke at every minute|
152
+ |`:hour`|Invoke at every hour|
153
+ |`:day`|Invoke at every day|
154
+ |`:sunday`, `:monday`, ..., `:saturday`|Invoke at every weekday|
155
+ |`N.minutes` (N = 1,2,3,...)|Invoke at every N minutes|
156
+ |`N.hours` (N = 1,2,3,...)|Invoke at every N hours|
157
+ |`N.days` (N = 1,2,3,...)|Invoke at every N days|
158
+ |`0 10 10,20 * *`|Cron expression in `cron_timezone`|
159
+
160
+ Available `<options>`:
161
+
162
+ |key|description|
163
+ |---|---|
164
+ |`at`|Invocation time|
165
+ |`timezone`|Timezone of described time in DSL|
166
+ |`cron_timezone`|Timezone of the machine where schedule engine runs|
167
+
168
+ ### `name` (Required)
169
+
170
+ Define job name
171
+
172
+ ### `description`
173
+
174
+ Define job description.
175
+ If `description` is not specified, job name will be used as description.
176
+
177
+ ### `job_type` (e.g. `rake`)
178
+
179
+ Define job command.
data/exe/xronor ADDED
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "xronor"
4
+
5
+ Xronor::CLI.start(ARGV)
@@ -0,0 +1,70 @@
1
+ module Xronor
2
+ module AWS
3
+ class CloudWatchEvents
4
+ def initialize(client: Aws::CloudWatchEvents::Client.new)
5
+ @client = client
6
+ end
7
+
8
+ def deregister_job(job_name)
9
+ targets = @client.list_targets_by_rule(rule: job_name).targets
10
+ @client.remove_targets(rule: job_name, ids: targets.map(&:id))
11
+ @client.delete_rule(name: job_name)
12
+ end
13
+
14
+ def list_jobs(prefix = "")
15
+ @client.list_rules.rules.select { |rule| rule.name.start_with?(prefix) }.map(&:name)
16
+ end
17
+
18
+ def register_job(job, prefix, cluster, task_definition, container, target_function_arn)
19
+ rule_name = job.cloud_watch_rule_name(prefix)
20
+ rule_arn = put_rule(rule_name, job.description, job.cloud_watch_schedule)
21
+ put_target(rule_name, cluster, task_definition, container, job.command, target_function_arn)
22
+ rule_arn
23
+ end
24
+
25
+ private
26
+
27
+ def put_rule(name, description, schedule)
28
+ @client.put_rule({
29
+ name: name,
30
+ description: description,
31
+ schedule_expression: schedule,
32
+ }).rule_arn
33
+ end
34
+
35
+ def put_target(name, cluster, task_definition, container, command, target_function_arn)
36
+ @client.put_targets({
37
+ rule: name,
38
+ targets: [
39
+ {
40
+ id: generate_id,
41
+ arn: target_function_arn,
42
+ input_transformer: {
43
+ input_paths_map: {
44
+ "resources" => "$.resources",
45
+ "time" => "$.time",
46
+ },
47
+ input_template: generate_input_template(cluster, task_definition, container, command),
48
+ },
49
+ },
50
+ ],
51
+ })
52
+ end
53
+
54
+ def generate_input_template(cluster, task_definition, container, command)
55
+ JSON.generate({
56
+ "resources" => '###$.resources###',
57
+ "time" => '###$.time###',
58
+ "cluster" => cluster,
59
+ "task_definition" => task_definition,
60
+ "container" => container,
61
+ "command" => Shellwords.split(command),
62
+ }).sub('"###$.resources###"', "<resources>").sub('"###$.time###"', "<time>")
63
+ end
64
+
65
+ def generate_id
66
+ SecureRandom.uuid
67
+ end
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,44 @@
1
+ module Xronor
2
+ module AWS
3
+ class DynamoDB
4
+ BATCH_WRITE_ITEM_MAX = 25
5
+
6
+ def initialize(client: Aws::DynamoDB::Client.new)
7
+ @client = client
8
+ end
9
+
10
+ def sync_rule_arns(table, add_rule_arns, delete_rule_arns)
11
+ put_requests = add_rule_arns.map do |arn|
12
+ {
13
+ put_request: {
14
+ item: {
15
+ "ARN" => arn,
16
+ "InvokedAt" => "0"
17
+ },
18
+ },
19
+ }
20
+ end
21
+
22
+ delete_requests = delete_rule_arns.map do |arn|
23
+ {
24
+ delete_request: {
25
+ key: {
26
+ "ARN" => arn,
27
+ },
28
+ },
29
+ }
30
+ end
31
+
32
+ requests = put_requests + delete_requests
33
+
34
+ requests.each_slice(BATCH_WRITE_ITEM_MAX) do |reqs|
35
+ @client.batch_write_item({
36
+ request_items: {
37
+ table => reqs,
38
+ },
39
+ })
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,14 @@
1
+ module Xronor
2
+ module AWS
3
+ class Lambda
4
+ def initialize(client: Aws::Lambda::Client.new)
5
+ @client = client
6
+ end
7
+
8
+ def retrieve_function_arn(name)
9
+ function = @client.list_functions.functions.find { |fn| fn.function_name == name }
10
+ function ? function.function_arn : ""
11
+ end
12
+ end
13
+ end
14
+ end
data/lib/xronor/cli.rb ADDED
@@ -0,0 +1,43 @@
1
+ module Xronor
2
+ class CLI < Thor
3
+ DEFAULT_JOB_PREFIX = "scheduler-"
4
+
5
+ desc "crontab SCHEDULEFILE", "Generate crontab file"
6
+ def crontab(filename)
7
+ body = Xronor::Generator::Crontab.generate(filename, options)
8
+ puts body
9
+ end
10
+
11
+ desc "cwa SCHEDULEFILE", "Register CloudWatch Events - Scheduler & ECS job runner"
12
+ option :prefix, default: DEFAULT_JOB_PREFIX
13
+ option :function, required: true
14
+ option :cluster, required: true
15
+ option :task_definition, required: true
16
+ option :container, required: true
17
+ option :table, required: true
18
+ option :dry_run, type: :boolean, default: false
19
+ def cwa(filename)
20
+ Xronor::Generator::CloudWatchEvents.generate(filename, options)
21
+ end
22
+
23
+ desc "template SCHEDULEFILE", "Generate single file"
24
+ option :template, required: true
25
+ def template(filename)
26
+ body = Xronor::Generator::ERB.generate_all_in_one(filename, options)
27
+ puts body
28
+ end
29
+
30
+ desc "template_per_job SCHEDULERFILE", "Generate files per job"
31
+ option :ext, default: nil
32
+ option :outdir, required: true
33
+ option :template, required: true
34
+ def template_per_job(filename)
35
+ contents = Xronor::Generator::ERB.generate_per_job(filename, options)
36
+
37
+ contents.each do |name, body|
38
+ path = File.join(options[:outdir], options[:ext] ? "#{name}.#{options[:ext]}" : name)
39
+ open(path, "w+") { |f| f.puts body }
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,13 @@
1
+ class Numeric
2
+ def respond_to?(method, include_private = false)
3
+ super || Xronor::DSL::NumericSeconds.public_method_defined?(method)
4
+ end
5
+
6
+ def method_missing(method, *args, &block)
7
+ if Xronor::DSL::NumericSeconds.public_method_defined?(method)
8
+ Xronor::DSL::NumericSeconds.new(self).send(method)
9
+ else
10
+ super
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,25 @@
1
+ module Xronor
2
+ class DSL
3
+ module Checker
4
+ class ValidationError < StandardError
5
+ end
6
+
7
+ def required(name, value)
8
+ invalid = false
9
+
10
+ if value
11
+ case value
12
+ when String
13
+ invalid = value.strip.empty?
14
+ when Array, Hash
15
+ invalid = value.empty?
16
+ end
17
+ else
18
+ invalid = true
19
+ end
20
+
21
+ raise ValidationError.new("'#{name}' is required") if invalid
22
+ end
23
+ end
24
+ end
25
+ end