Kwalify User's Guide (for Ruby)
last update: $Date: 2008-01-28 15:27:12 +0900 (Mon, 28 Jan 2008) $
Preface
Kwalify(*1) is a parser, schema validator, and data binding tool for YAML and JSON. Kwalify enables you to handle YAML and JSON more easily and strictly.
Topics:
- Schema validation for YAML and JSON
- Class definition generation for Ruby, PHP, and Java
- Data binding
- Preceding alias
- (*1)
- Pronounce as 'Qualify'.
Table of Contents
Schema Definition
This section describes how to define schema definition of YAML.
Sequence
schema01.yaml
: sequence of stringtype: seq sequence: - type: str
document01a.yaml
: valid document example- foo - bar - baz
$ kwalify -lf schema01.yaml document01a.yaml document01a.yaml#0: valid.
document01b.yaml
: invalid document example- foo - 123 - baz
$ kwalify -lf schema01.yaml document01b.yaml document01b.yaml#0: INVALID - (line 2) [/1] '123': not a string.
Default 'type:
' is str
so you can omit 'type: str
'.
Mapping
schema02.yaml
: mapping of scalartype: map mapping: "name": type: str required: yes "email": type: str pattern: /@/ "age": type: int "birth": type: date
document02a.yaml
: valid document examplename: foo email: foo@mail.com age: 20 birth: 1985-01-01
$ kwalify -lf schema02.yaml document02a.yaml document02a.yaml#0: valid.
document02b.yaml
: invalid document examplename: foo email: foo(at)mail.com age: twenty birth: Jun 01, 1985
$ kwalify -lf schema02.yaml document02b.yaml document02b.yaml#0: INVALID - (line 2) [/email] 'foo(at)mail.com': not matched to pattern /@/. - (line 3) [/age] 'twenty': not a integer. - (line 4) [/birth] 'Jun 01, 1985': not a date.
Sequence of Mapping
schema03.yaml
: sequence of mappingtype: seq sequence: - type: map mapping: "name": type: str required: true "email": type: str
document03a.yaml
: valid document example- name: foo email: foo@mail.com - name: bar email: bar@mail.net - name: baz email: baz@mail.org
$ kwalify -lf schema03.yaml document03a.yaml document03a.yaml#0: valid.
document03b.yaml
: invalid document example- name: foo email: foo@mail.com - naem: bar email: bar@mail.net - name: baz mail: baz@mail.org
$ kwalify -lf schema03.yaml document03b.yaml document03b.yaml#0: INVALID - (line 3) [/1] key 'name:' is required. - (line 3) [/1/naem] key 'naem:' is undefined. - (line 6) [/2/mail] key 'mail:' is undefined.
Mapping of Sequence
schema04.yaml
: mapping of sequence of mappingtype: map mapping: "company": type: str required: yes "email": type: str "employees": type: seq sequence: - type: map mapping: "code": type: int required: yes "name": type: str required: yes "email": type: str
document04a.yaml
: valid document examplecompany: Kuwata lab. email: webmaster@kuwata-lab.com employees: - code: 101 name: foo email: foo@kuwata-lab.com - code: 102 name: bar email: bar@kuwata-lab.com
$ kwalify -lf schema04.yaml document04a.yaml document04a.yaml#0: valid.
document04b.yaml
: invalid document examplecompany: Kuwata Lab. email: webmaster@kuwata-lab.com employees: - code: A101 name: foo email: foo@kuwata-lab.com - code: 102 name: bar mail: bar@kuwata-lab.com
$ kwalify -lf schema04.yaml document04b.yaml document04b.yaml#0: INVALID - (line 4) [/employees/0/code] 'A101': not a integer. - (line 9) [/employees/1/mail] key 'mail:' is undefined.
Rule and Constraint
type:
, required:
, length
, ... are called constraint and set of constraints are called rule.
- Rule contains 'type:' constraint. If 'type:' is omitted, 'type: str' is used as default.
- 'sequence:' constraint takes a sequence of rule (the sequence can contain only a rule).
- 'mapping:' constraint takes a mapping which values are rules.

The following is a list of constraints.
-
required:
- Value is required when true (default is false). This is similar to not-null constraint in RDBMS.
-
enum:
- List of available values.
-
pattern:
- Specifies regular expression pattern of value.
-
type:
-
Type of value. The followings are available:
str
int
float
number
(== int or float)text
(== str or number)bool
date
time
timestamp
seq
map
scalar
(all but seq and map)any
(means any data)
-
range:
-
Range of value between max/max-ex and min/min-ex.
- 'max' means 'max-inclusive'.
- 'min' means 'min-inclusive'.
- 'max-ex' means 'max-exclusive'.
- 'min-ex' means 'min-exclusive'.
seq
,map
,bool
andany
are not available withrange:
. -
length:
-
Range of length of value between max/max-ex and min/min-ex.
Only type
str
andtext
are available withlength:
. -
assert:
-
String which represents validation expression.
String should contain variable name
val
which repsents value. (This is an experimental function and not supported in Kwartz-java). -
unique:
- Value is unique for mapping or sequence. This is similar to unique constraint of RDBMS. See the next subsection for detail.
-
name:
- Name of schema.
-
desc:
- Description. This is not used for validation.
-
class:
- Class name. This is for data-binding and is available only with type 'map'. This is also used in 'genclass' action.
-
default:
-
Default value.
This is only for 'genclass' action, and have no effect to validation and parsing.
Default value should be scalar and it is not available if
type:
ismap
orseq
, and also not available whenrequired:
is true.
schema05.yaml
: rule examplestype: seq sequence: - type: map mapping: "name": type: str required: yes "email": type: str required: yes pattern: /@/ "password": type: text length: { max: 16, min: 8 } "age": type: int range: { max: 30, min: 18 } # or assert: 18 <= val && val <= 30 "blood": type: str enum: [A, B, O, AB] "birth": type: date "memo": type: any "deleted": type: bool default: false
document05a.yaml
: valid document example- name: foo email: foo@mail.com password: xxx123456 age: 20 blood: A birth: 1985-01-01 - name: bar email: bar@mail.net age: 25 blood: AB birth: 1980-01-01
$ kwalify -lf schema05.yaml document05a.yaml document05a.yaml#0: valid.
document05b.yaml
: invalid document example- name: foo email: foo(at)mail.com password: xxx123 age: twenty blood: a birth: 1985-01-01 - given-name: bar family-name: Bar email: bar@mail.net age: 15 blood: AB birth: 1980/01/01
$ kwalify -lf schema05.yaml document05b.yaml document05b.yaml#0: INVALID - (line 2) [/0/email] 'foo(at)mail.com': not matched to pattern /@/. - (line 3) [/0/password] 'xxx123': too short (length 6 < min 8). - (line 4) [/0/age] 'twenty': not a integer. - (line 5) [/0/blood] 'a': invalid blood value. - (line 7) [/1] key 'name:' is required. - (line 7) [/1/given-name] key 'given-name:' is undefined. - (line 8) [/1/family-name] key 'family-name:' is undefined. - (line 10) [/1/age] '15': too small (< min 18). - (line 12) [/1/birth] '1980/01/01': not a date.
Unique constraint
'unique:
' constraint is available with elements of sequence or mapping.
This is equivalent to unique constraint of RDBMS.
- Type of rule which has '
unique:
' entry must be scalar (str, int, float, ...). - Type of parent rule must be sequence or mapping.
schema06.yaml
: unique constraint entry with mapping and sequencetype: seq sequence: - type: map required: yes mapping: "name": { type: str, required: yes, unique: yes } "email": { type: str } "groups": type: seq sequence: - { type: str, unique: yes }
document06a.yaml
: valid document example- name: foo email: admin@mail.com groups: - users - foo - admin - name: bar email: admin@mail.com groups: - users - admin - name: baz email: baz@mail.com groups: - users
$ kwalify -lf schema06.yaml document06a.yaml document06a.yaml#0: valid.
document06b.yaml
: invalid document example- name: foo email: admin@mail.com groups: - foo - users - admin - foo - name: bar email: admin@mail.com groups: - admin - users - name: bar email: baz@mail.com groups: - users
$ kwalify -lf schema06.yaml document06b.yaml document06b.yaml#0: INVALID - (line 7) [/0/groups/3] 'foo': is already used at '/0/groups/0'. - (line 13) [/2/name] 'bar': is already used at '/1/name'.
Tips
JSON
JSON is a lightweight data-interchange format, especially useful for JavaScript. JSON can be considered as a subset of YAML. It means that YAML parser can parse JSON and Kwalify can validate JSON document.
schema12.json
: an example schema definition written in JSON format{ "type": "map", "required": true, "mapping": { "name": { "type": "str", "required": true }, "email": { "type": "str" }, "age": { "type": "int" }, "gender": { "type": "str", "enum": ["M", "F"] }, "favorite": { "type": "seq", "sequence": [ { "type": "str" } ] } } }
document12a.json
: valid JSON document example{ "name": "Foo", "email": "foo@mail.com", "age": 20, "gender": "F", "favorite": [ "football", "basketball", "baseball" ] }
$ kwalify -lf schema12.json document12a.json document12a.json#0: valid.
document12b.json
: invalid JSON document example{ "mail": "foo@mail.com", "age": twenty, "gender": "X", "favorite": [ 123, 456 ] }
$ kwalify -lf schema12.json document12b.json document12b.json#0: INVALID - (line 1) [/] key 'name:' is required. - (line 2) [/mail] key 'mail:' is undefined. - (line 3) [/age] 'twenty': not a integer. - (line 4) [/gender] 'X': invalid gender value. - (line 5) [/favorite/0] '123': not a string. - (line 5) [/favorite/1] '456': not a string.
Anchor and Alias
You can share rules by YAML anchor and alias.
schema13.yaml
: anchor exampletype: seq sequence: - &employee type: map mapping: "given-name": &name type: str required: yes "family-name": *name "post": type: str enum: [exective, manager, clerk] "supervisor": *employee
Anchor and alias is also available in YAML document.
document13a.yaml
: valid document example- &foo given-name: foo family-name: Foo post: exective - &bar given-name: bar family-name: Bar post: manager supervisor: *foo - given-name: baz family-name: Baz post: clerk supervisor: *bar - given-name: zak family-name: Zak post: clerk supervisor: *bar
$ kwalify -lf schema13.yaml document13a.yaml document13a.yaml#0: valid.
Default of Mapping
YAML allows user to specify default value of mapping.
For example, the following YAML document uses default value of mapping.
A: 10 B: 20 =: -1 # default value
This is equal to the following Ruby code.
map = ["A"=>10, "B"=>20] map.default = -1 map
Kwalify allows user to specify default rule using default value of mapping. It is useful when key names are unknown.
schema14.yaml
: default rule exampletype: map mapping: =: # default rule type: number range: { max: 1, min: -1 }
document14a.yaml
: valid document examplevalue1: 0 value2: 0.5 value3: -0.9
$ kwalify -lf schema14.yaml document14a.yaml document14a.yaml#0: valid.
document14b.yaml
: invalid document examplevalue1: 0 value2: 1.1 value3: -2.0
$ kwalify -lf schema14.yaml document14b.yaml document14b.yaml#0: INVALID - (line 2) [/value2] '1.1': too large (> max 1). - (line 3) [/value3] '-2.0': too small (< min -1).
Merging Mappings
YAML allows user to merge mappings.
- &a1 A: 10 B: 20 - <<: *a1 # merge A: 15 # override C: 30 # add
This is equal to the following Ruby code.
a1 = {"A"=>10, "B"=>20} tmp = {} tmp.update(a1) # merge tmp["A"] = 15 # override tmp["C"] = 30 # add
This feature allows Kwalify to merge rule entries.
schema15.yaml
: merging rule entries exampletype: map mapping: "group": type: map mapping: "name": &name type: str required: yes "email": &email type: str pattern: /@/ required: no "user": type: map mapping: "name": <<: *name # merge length: { max: 16 } # add "email": <<: *email # merge required: yes # override
document15a.yaml
: valid document examplegroup: name: foo email: foo@mail.com user: name: bar email: bar@mail.com
$ kwalify -lf schema15.yaml document15a.yaml document15a.yaml#0: valid.
document15b.yaml
: invalid document examplegroup: name: foo email: foo@mail.com user: name: toooooo-looooong-name
$ kwalify -lf schema15.yaml document15b.yaml document15b.yaml#0: INVALID - (line 5) [/user] key 'email:' is required. - (line 5) [/user/name] 'toooooo-looooong-name': too long (length 21 > max 16).
How to in Ruby
This section describes how to use Kwalify in Ruby.
Validation
require 'kwalify' #require 'yaml' ## load schema data schema = Kwalify::Yaml.load_file('schema.yaml') ## or #schema = YAML.load_file('schema.yaml') ## create validator validator = Kwalify::Validator.new(schema) ## load document document = Kwalify::Yaml.load_file('document.yaml') ## or #document = YAML.load_file('document.yaml') ## validate errors = validator.validate(document) ## show errors if errors && !errors.empty? for e in errors puts "[#{e.path}] #{e.message}" end end
Parsing with Validation
From version 0.7, Kwalify supports parsing with validation.
require 'kwalify' #require 'yaml' ## load schema data schema = Kwalify::Yaml.load_file('schema.yaml') ## or #schema = YAML.load_file('schema.yaml') ## create validator validator = Kwalify::Validator.new(schema) ## create parser with validator ## (if validator is ommitted, no validation executed.) parser = Kwalify:::Yaml::Parser.new(validator) ## parse document with validation filename = 'document.yaml' document = parser.parse_file(filename) ## or #document = parser.parse(File.read(filename), filename) ## show errors if exist errors = parser.errors() if errors && !errors.empty? for e in errors puts "#{e.linenum}:#{e.column} [#{e.path}] #{e.message}" end end
Meta Validation
Meta validator is a validator which validates schema definition. The schema definition is placed at 'kwalify/kwalify.schema.yaml'.
Kwalify also provides Kwalify::MetaValidator class which validates schema defition.
require 'kwalify' ## meta validator metavalidator = Kwalify::MetaValidator.instance ## validate schema definition parser = Kwalify::Yaml::Parser.new(metavalidator) errors = parser.parse_file('schema.yaml') for e in errors puts "#{e.linenum}:#{e.column} [#{e.path}] #{e.message}" end if errors && !errors.empty?
Meta validation is also available with command-line option '-m'.
$ kwalify -m schema1.yaml schema2.yaml ...
Validator#validator_hook()
You can extend Kwalify::Validator and override Kwalify::Validator#validator_hook() method. This method is called by Kwalify::Validator#validate().
type: map mapping: "answers": type: seq sequence: - type: map name: Answer mapping: "name": { type: str, required: yes } "answer": { type: str, required: yes, enum: [good, not bad, bad] } "reason": { type: str }
#!/usr/bin/env ruby require 'kwalify' ## validator class for answers class AnswersValidator < Kwalify::Validator ## load schema definition @@schema = Kwalify::Yaml.load_file('answers-schema.yaml') ## or ## require 'yaml' ## @@schema = YAML.load_file('answers-schema.yaml') def initialize() super(@@schema) end ## hook method called by Validator#validate() def validate_hook(value, rule, path, errors) case rule.name when 'Answer' if value['answer'] == 'bad' reason = value['reason'] if !reason || reason.empty? msg = "reason is required when answer is 'bad'." errors << Kwalify::ValidationError.new(msg, path) end end end end end ## create validator validator = AnswersValidator.new ## parse and validate YAML document input = ARGF.read() parser = Kwalify::Yaml::Parser.new(validator) document = parser.parse(input) ## show errors errors = parser.errors() if !errors || errors.empty? puts "Valid." else puts "*** INVALID!" for e in errors # e.class == Kwalify::ValidationError puts "#{e.linenum}:#{e.column} [#{e.path}] #{e.message}" end end
document07a.yaml
: valid document exampleanswers: - name: Foo answer: good reason: I like this style. - name: Bar answer: not bad - name: Baz answer: bad reason: I don't like this style.
$ ruby answers-validator.rb document07a.yaml Valid.
document07b.yaml
: invalid document exampleanswers: - name: Foo answer: good - name: Bar answer: bad - name: Baz answer: not bad
$ ruby answers-validator.rb document07b.yaml *** INVALID! 4:3 [/answers/1] reason is required when answer is 'bad'.
You can validate some document by a Validator instance because Validator class and Validator#validate() method are stateless. If you use instance variables in custom validator_hook() method, it becomes to be stateful.
Preceding Alias
From version 0.7, Kwalify allows aliases to appear before corresponding anchors are now appeared. These aliases are called as 'preceding alias'.
- name: Foo parent: *bar # preceding alias - &bar name: Bar parent: *baz # preceding alias - &baz name: Baz parent: null
To enable preceding alias, set Kwalify::Yaml::Parser#preceding_alias to true.
require 'kwalify' parser = Kwalify::Yaml::Parser.new parser.preceding_alias = true # enable preceding alias ydoc = parser.parse_file('howto3.yaml') require 'pp' pp ydoc
$ ruby howto3.rb [{"name"=>"Foo", "parent"=>{"name"=>"Bar", "parent"=>{"name"=>"Baz", "parent"=>nil}}}, {"name"=>"Bar", "parent"=>{"name"=>"Baz", "parent"=>nil}}, {"name"=>"Baz", "parent"=>nil}]
Command-line option '-P' also enables preceding alias.
Preceding alias is very useful when document is complex.
Data Binding
From version 0.7, Kwalify supports data binding. * To enable data binding, set Kwlaify::Yaml::Parser#data_binding to true. * It is required to specify class name in schema definition. (Notice that 'class:' constraint is avaialbe only with rule which type is 'map'.) * Also instance methods '[]', '[]=', and 'keys?' must be defined in the classes. (Including Kwalify::Util::HashLike modules is easy way to define them.)
type: map class: Config mapping: "host": { type: str, required: true } "port": { type: int } "user": { type: str, required: true } "pass": { type: str, required: true }
host: localhost port: 8080 user: user1 pass: password1
## class definition require 'kwalify/util/hashlike' class Config include Kwalify::Util::HashLike # defines [], []=, and keys? attr_accessor :host, :posrt, :user, :pass end ## create validator object require 'kwalify' schema = Kwalify::Yaml.load_file('config.schema.yaml') validator = Kwalify::Validator.new(schema) ## parse configuration file with data binding parser = Kwalify::Yaml::Parser.new(validator) parser.data_binding = true # enable data binding config = parser.parse_file('config.yaml') require 'pp' pp config
$ ruby loadconfig.rb #<Config: @host="localhost", @pass="password1", @port=8080, @user="user1">
Data binding is available even when data is more complex. Preceding alias is also available.
For example, the following data is complex because it uses anchor and alias (including preceding alias).
teams: - &thechildren name: The Children desc: Level 7 ESPers chief: *minamoto # preceding alias members: [*kaoru, *aoi, *shiho] # preceding aliases members: - &minamoto name: Kohichi Minamoto desc: Scientist team: *thechildren - &kaoru name: Kaoru Akashi desc: Psychokino team: *thechildren - &aoi name: Aoi Nogami desc: Teleporter team: *thechildren - &shiho name: Shiho Sannomiya desc: Psycometrer team: *thechildren
Here is the schema definition. (Notice that 'class:' constraint is avaialbe only with rule which type is 'map'.)
type: map required: yes mapping: "teams": type: seq required: yes sequence: - &team type: map required: yes class: Team mapping: "name": {type: str, required: yes, unique: yes} "desc": {type: str} "chief": *member # preceding alias "members": type: seq sequence: [*member] # preceding alias "members": type: seq required: yes sequence: - &member type: map required: yes class: Member mapping: "name": {type: str, required: yes, unique: yes} "desc": {type: str} "team": *team
It is required to define class 'Team' and 'Member' for data-binding. Command-line option '-a genclass-ruby' will help you to generate class definitions from schema definition. Try 'kwalify -ha genclass-ruby' for more details about 'genclass-ruby' action.
$ kwalify -a genclass-ruby -P -f BABEL.schema.yaml \ --hashlike --initialize=false --module=Babel require 'kwalify/util/hashlike' module Babel ## class Team include Kwalify::Util::HashLike attr_accessor :name # str attr_accessor :desc # str attr_accessor :chief # map attr_accessor :members # seq end ## class Member include Kwalify::Util::HashLike attr_accessor :name # str attr_accessor :desc # str attr_accessor :team # map end end $ kwalify -a genclass-ruby -P -f BABEL.schema.yaml \ --hashlike --initialize=false --module=Babel > models.rb
Here is the ruby program.
require 'kwalify' require 'models' ## load schema definition schema = Kwalify::Yaml.load_file('BABEL.schema.yaml', :untabify=>true, :preceding_alias=>true) ## add module name to 'class:' Kwalify::Util.traverse_schema(schema) do |rulehash| if rulehash['class'] rulehash['class'] = 'Babel::' + rulehash['class'] end end ## create validator validator = Kwalify::Validator.new(schema) ## parse with data-binding parser = Kwalify::Yaml::Parser.new(validator) parser.preceding_alias = true parser.data_binding = true ydoc = parser.parse_file('BABEL.data.yaml', :untabify=>true) ## show document require 'pp' pp ydoc
$ ruby loadbabel.rb {"teams"=> [#<Babel::Team:0x53e0f8 @chief= #<Babel::Member:0x53d5e0 @desc="Scientist", @name="Kohichi Minamoto", @team=#<Babel::Team:0x53e0f8 ...>>, @desc="Level 7 ESPers", @members= [#<Babel::Member:0x53d018 @desc="Psychokino", @name="Kaoru Akashi", @team=#<Babel::Team:0x53e0f8 ...>>, #<Babel::Member:0x53ca50 @desc="Teleporter", @name="Aoi Nogami", @team=#<Babel::Team:0x53e0f8 ...>>, #<Babel::Member:0x53c488 @desc="Psycometrer", @name="Shiho Sannomiya", @team=#<Babel::Team:0x53e0f8 ...>>], @name="The Children">], "members"=> [#<Babel::Member:0x53d5e0 @desc="Scientist", @name="Kohichi Minamoto", @team= #<Babel::Team:0x53e0f8 @chief=#<Babel::Member:0x53d5e0 ...>, @desc="Level 7 ESPers", @members= [#<Babel::Member:0x53d018 @desc="Psychokino", @name="Kaoru Akashi", @team=#<Babel::Team:0x53e0f8 ...>>, #<Babel::Member:0x53ca50 @desc="Teleporter", @name="Aoi Nogami", @team=#<Babel::Team:0x53e0f8 ...>>, #<Babel::Member:0x53c488 @desc="Psycometrer", @name="Shiho Sannomiya", @team=#<Babel::Team:0x53e0f8 ...>>], @name="The Children">>, #<Babel::Member:0x53d018 @desc="Psychokino", @name="Kaoru Akashi", @team= #<Babel::Team:0x53e0f8 @chief= #<Babel::Member:0x53d5e0 @desc="Scientist", @name="Kohichi Minamoto", @team=#<Babel::Team:0x53e0f8 ...>>, @desc="Level 7 ESPers", @members= [#<Babel::Member:0x53d018 ...>, #<Babel::Member:0x53ca50 @desc="Teleporter", @name="Aoi Nogami", @team=#<Babel::Team:0x53e0f8 ...>>, #<Babel::Member:0x53c488 @desc="Psycometrer", @name="Shiho Sannomiya", @team=#<Babel::Team:0x53e0f8 ...>>], @name="The Children">>, #<Babel::Member:0x53ca50 @desc="Teleporter", @name="Aoi Nogami", @team= #<Babel::Team:0x53e0f8 @chief= #<Babel::Member:0x53d5e0 @desc="Scientist", @name="Kohichi Minamoto", @team=#<Babel::Team:0x53e0f8 ...>>, @desc="Level 7 ESPers", @members= [#<Babel::Member:0x53d018 @desc="Psychokino", @name="Kaoru Akashi", @team=#<Babel::Team:0x53e0f8 ...>>, #<Babel::Member:0x53ca50 ...>, #<Babel::Member:0x53c488 @desc="Psycometrer", @name="Shiho Sannomiya", @team=#<Babel::Team:0x53e0f8 ...>>], @name="The Children">>, #<Babel::Member:0x53c488 @desc="Psycometrer", @name="Shiho Sannomiya", @team= #<Babel::Team:0x53e0f8 @chief= #<Babel::Member:0x53d5e0 @desc="Scientist", @name="Kohichi Minamoto", @team=#<Babel::Team:0x53e0f8 ...>>, @desc="Level 7 ESPers", @members= [#<Babel::Member:0x53d018 @desc="Psychokino", @name="Kaoru Akashi", @team=#<Babel::Team:0x53e0f8 ...>>, #<Babel::Member:0x53ca50 @desc="Teleporter", @name="Aoi Nogami", @team=#<Babel::Team:0x53e0f8 ...>>, #<Babel::Member:0x53c488 ...>], @name="The Children">>]}
Actions
Kwalify has the command-line '-a action' which perform a certain action to schema definition. Currently only the following actions are provided.
- genclass-ruby
-
Generate class definitions in Ruby.
- genclass-java
-
Generate class definitions in Java.
- genclass-php
-
Generate class definitions in Ruby.
In fact action name represents template filename. For example, action 'genclass-ruby' invokes template file 'kwalify/templates/genclass-ruby.eruby'.
Each action can accept some command-line properties. For example, action 'genclass-ruby' can accept the command-line properties '--module=name', '--parent=name', and so on. Type 'kwalify -h -a action' to show the list of command-line properties the action can accept.
It is able to add your on action template file. The command-line option '-I' (template path) will help you.
Class Definition Generation
Command-line option '-a genclass-ruby' or '-a genclass-java' generates class definition automatically from schema definition in Ruby or Java.
Assume the following data file and schema definition.
address_book.yaml
: data filegroups: - name: family desc: my family - name: friend desc: my friends - name: business desc: those who works together people: - name: Sumire group: family birth: 2000-01-01 blood: A - name: Shiina group: friend birth: 1995-01-01 email: shiina@mail.org - name: Sakura group: business email: cherry@mail.net phone: 012-345-6789
address_book.schema.yaml
: schema definition filetype: map class: AddressBook desc: address-book class mapping: "groups": type: seq sequence: - type: map class: Group desc: group class mapping: "name": { type: str, required: yes } "desc": { type: str } "people": type: seq sequence: - type: map class: Person desc: person class mapping: "name": { type: str, required: yes } "desc": { type: str } "group": { type: str } "email": { type: str, pattern: '/@/' } "phone": { type: str } "birth": { type: date } "blood": { type: str, enum: [A, B, O, AB] } "deleted": { type: bool, default: false }
Ruby Class Definition
$ kwalify -a genclass-ruby -tf address_book.schema.yaml > address_book.rb
address_book.rb
: generated class definition## address-book class class AddressBook def initialize(hash=nil) if hash.nil? return end @groups = (v=hash['groups']) ? v.map!{|e| e.is_a?(Group) ? e : Group.new(e)} : v @people = (v=hash['people']) ? v.map!{|e| e.is_a?(Person) ? e : Person.new(e)} : v end attr_accessor :groups # seq attr_accessor :people # seq end ## group class class Group def initialize(hash=nil) if hash.nil? return end @name = hash['name'] @desc = hash['desc'] end attr_accessor :name # str attr_accessor :desc # str end ## person class class Person def initialize(hash=nil) if hash.nil? @deleted = false return end @name = hash['name'] @desc = hash['desc'] @group = hash['group'] @email = hash['email'] @phone = hash['phone'] @birth = hash['birth'] @blood = hash['blood'] @deleted = (v=hash['deleted']).nil? ? false : v end attr_accessor :name # str attr_accessor :desc # str attr_accessor :group # str attr_accessor :email # str attr_accessor :phone # str attr_accessor :birth # date attr_accessor :blood # str attr_accessor :deleted # bool def deleted? ; @deleted ; end end
example_address_book.rb
: example code of using address-book.rbrequire 'address_book' require 'yaml' require 'pp' str = File.read('address_book.yaml') ydoc = YAML.load(str) addrbook = AddressBook.new(ydoc) pp addrbook.groups pp addrbook.people
$ ruby example_address_book.rb [#<Group:0xddf24 @desc="my family", @name="family">, #<Group:0xddf10 @desc="my friends", @name="friend">, #<Group:0xdde84 @desc="those who works together", @name="business">] [#<Person:0xdefdc @birth=#<Date: 4903089/2,0,2299161>, @blood="A", @deleted=false, @desc=nil, @email=nil, @group="family", @name="Sumire", @phone=nil>, #<Person:0xdee9c @birth=#<Date: 4899437/2,0,2299161>, @blood=nil, @deleted=false, @desc=nil, @email="shiina@mail.org", @group="friend", @name="Shiina", @phone=nil>, #<Person:0xde8e8 @birth=nil, @blood=nil, @deleted=false, @desc=nil, @email="cherry@mail.net", @group="business", @name="Sakura", @phone="012-345-6789">]
Command-line option '-h -a genclass-ruby
' shows the commpand-line properties that template can accept.
$ kwalify -ha genclass-ruby --module=name : module name in which class defined --parent=name : parent class name --include=name : module name which all classes include --initialize=false : not print initialize() method --hashlike : include Kwalify::Util::HashLike module
$ kwalify -a genclass-ruby --module=My --hashlike
If command-line property '--hashlike' (== '--hashlike=true') is specified, module Kwalify::Util::HashLike is included for each classes generated. That module is defined in 'kwalify/util/hashlike.rb'
Java Class Definition
$ kwalify -a genclass-java -tf address_book.schema.yaml generating ./AddressBook.java...done. generating ./Group.java...done. generating ./Person.java...done.
AddressBook.java
: generated class definition// generated by kwalify from address_book.schema.yaml import java.util.*; /** * address-book class */ public class AddressBook { private List _groups; private List _people; public AddressBook() {} public AddressBook(Map map) { List seq; Object obj; if ((seq = (List)map.get("groups")) != null) { for (int i = 0; i < seq.size(); i++) { if ((obj = seq.get(i)) instanceof Map) { seq.set(i, new Group((Map)obj)); } } } _groups = seq; if ((seq = (List)map.get("people")) != null) { for (int i = 0; i < seq.size(); i++) { if ((obj = seq.get(i)) instanceof Map) { seq.set(i, new Person((Map)obj)); } } } _people = seq; } public List getGroups() { return _groups; } public void setGroups(List groups_) { _groups = groups_; } public List getPeople() { return _people; } public void setPeople(List people_) { _people = people_; } }
Group.java
: generated class definition// generated by kwalify from address_book.schema.yaml import java.util.*; /** * group class */ public class Group { private String _name; private String _desc; public Group() {} public Group(Map map) { _name = (String)map.get("name"); _desc = (String)map.get("desc"); } public String getName() { return _name; } public void setName(String name_) { _name = name_; } public String getDesc() { return _desc; } public void setDesc(String desc_) { _desc = desc_; } }
Person.java
: generated class definition// generated by kwalify from address_book.schema.yaml import java.util.*; /** * person class */ public class Person { private String _name; private String _desc; private String _group; private String _email; private String _phone; private Date _birth; private String _blood; public Person() {} public Person(Map map) { _name = (String)map.get("name"); _desc = (String)map.get("desc"); _group = (String)map.get("group"); _email = (String)map.get("email"); _phone = (String)map.get("phone"); _birth = (Date)map.get("birth"); _blood = (String)map.get("blood"); } public String getName() { return _name; } public void setName(String name_) { _name = name_; } public String getDesc() { return _desc; } public void setDesc(String desc_) { _desc = desc_; } public String getGroup() { return _group; } public void setGroup(String group_) { _group = group_; } public String getEmail() { return _email; } public void setEmail(String email_) { _email = email_; } public String getPhone() { return _phone; } public void setPhone(String phone_) { _phone = phone_; } public Date getBirth() { return _birth; } public void setBirth(Date birth_) { _birth = birth_; } public String getBlood() { return _blood; } public void setBlood(String blood_) { _blood = blood_; } }
ExampleAddressBook.java
: example code of using *.javaimport java.util.*; import kwalify.*; public class ExampleAddressBook { public static void main(String args[]) throws Exception { // read schema String schema_str = Util.readFile("address_book.schema.yaml"); schema_str = Util.untabify(schema_str); Object schema = new YamlParser(schema_str).parse(); // read document file String document_str = Util.readFile("address_book.yaml"); document_str = Util.untabify(document_str); YamlParser parser = new YamlParser(document_str); Object document = parser.parse(); // create address book object AddressBook addrbook = new AddressBook((Map)document); // show groups List groups = addrbook.getGroups(); if (groups != null) { for (Iterator it = groups.iterator(); it.hasNext(); ) { Group group = (Group)it.next(); System.out.println("group name: " + group.getName()); System.out.println("group desc: " + group.getDesc()); System.out.println(); } } // show people List people = addrbook.getPeople(); if (people != null) { for (Iterator it = people.iterator(); it.hasNext(); ) { Person person = (Person)it.next(); System.out.println("person name: " + person.getName()); System.out.println("person group: " + person.getGroup()); System.out.println("person email: " + person.getEmail()); System.out.println("person phone: " + person.getPhone()); System.out.println("person blood: " + person.getBlood()); System.out.println("person birth: " + person.getBirth()); System.out.println(); } } } }
$ javac -classpath '.:kwalify.jar' *.java $ java -classpath '.:kwalify.jar' ExampleAddressBook group name: family group desc: my family group name: friend group desc: my friends group name: business group desc: those who works together person name: Sumire person group: family person email: null person phone: null person blood: A person birth: Tue Feb 01 00:00:00 JST 2000 person name: Shiina person group: friend person email: shiina@mail.org person phone: null person blood: null person birth: Wed Feb 01 00:00:00 JST 1995 person name: Sakura person group: business person email: cherry@mail.net person phone: 012-345-6789 person blood: null person birth: null
Command-line option '-h -a genclass-java
' shows the commpand-line properties that template can accept.
$ kwalify -ha genclass-java --package=name : package name --extends=name : class name to extend --implements=name,... : interface names to implement --dir=path : directory to locate output file --basedir=path : base directory to locate output file --constructor=false : not print initialize() method
$ kwalify -a genclass-java --package=com.example.my --implements=Serializable --basedir=src
References
Usage in Command-Line
### usage1: validate YAML document in command-line $ kwalify -f schema.yaml document.yaml [document2.yaml ...] ### usage2: validate schema definition in command-line $ kwalify -m schema.yaml [schema2.yaml ...]
Command-line options:
-
-h
,--help
- Print help message.
-
-v
- Print version.
-
-q
- Quiet mode.
-
-s
- (Obsolete. Use '-q' instead.) Silent mode.
-
-f schema.yaml
- Specify schema definition file.
-
-m
- Meta-validation of schema definition.
-
-t
- Expand tab characters to spaces automatically.
-
-l
- Show linenumber on which error found.
-
-E
- Show errors in Emacs-compatible style (implies '-l' option).
-
-a action
- Do action. Currently supported action is 'genclass-ruby' and 'genclass-java'. Try '-ha action' to get help about the action.
-
-I path1,path2,...
- Template path (for '-a').
-
-P
- Enable preceding alias.