From be93828ce2e95645a7f7be83bc7ac8865fd7e478 Mon Sep 17 00:00:00 2001 From: Ary Borenszweig Date: Fri, 16 Oct 2015 16:26:02 -0300 Subject: [PATCH] `json_mapping` and `yaml_mapping` are now `JSON.mapping` and `YAML.mapping` --- CHANGELOG.md | 1 + spec/std/json/mapping_spec.cr | 22 ++-- spec/std/yaml/mapping_spec.cr | 10 +- src/compiler/crystal/tools/context.cr | 2 +- src/compiler/crystal/tools/implementations.cr | 4 +- src/json/any.cr | 2 +- src/json/json.cr | 6 +- src/json/mapping.cr | 114 ++++++++-------- src/yaml/mapping.cr | 123 +++++++++--------- src/yaml/yaml.cr | 16 +-- 10 files changed, 144 insertions(+), 156 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fa1a3b95700f..8e08d9d86e7d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ * **(breaking change)** The `CGI` module's funcionality has been moved to `URI` and `HTTP::Params` * **(breaking change)** `IO#read()` is now `IO#gets_to_end`. Removed `IO#read(count)`, added `IO#skip(count)` +* **(breaking change)** `json_mapping` is now `JSON.mapping`. `yaml_mapping` is now `YAML.mapping`. * Added `crystal tool format` that automatically formats your code * `protected` methods can now be invoked between types inside a same namespace * Removed `curses`, you can use `https://github.com/jreinert/ncurses-crystal` diff --git a/spec/std/json/mapping_spec.cr b/spec/std/json/mapping_spec.cr index 73b9a8b2d3a8..63c6a7f080f7 100644 --- a/spec/std/json/mapping_spec.cr +++ b/spec/std/json/mapping_spec.cr @@ -2,7 +2,7 @@ require "spec" require "json" class JSONPerson - json_mapping({ + JSON.mapping({ name: {type: String}, age: {type: Int32, nilable: true}, }) @@ -14,33 +14,33 @@ class JSONPerson end class StrictJSONPerson - json_mapping({ + JSON.mapping({ name: {type: String}, age: {type: Int32, nilable: true}, }, true) end class JSONPersonEmittingNull - json_mapping({ + JSON.mapping({ name: {type: String}, age: {type: Int32, nilable: true, emit_null: true}, }) end class JSONWithBool - json_mapping({ + JSON.mapping({ value: {type: Bool}, }) end class JSONWithTime - json_mapping({ + JSON.mapping({ value: {type: Time, converter: Time::Format.new("%F %T")}, }) end class JSONWithNilableTime - json_mapping({ + JSON.mapping({ value: {type: Time, converter: Time::Format.new("%F")}, }) @@ -49,7 +49,7 @@ class JSONWithNilableTime end class JSONWithNilableTimeEmittingNull - json_mapping({ + JSON.mapping({ value: {type: Time, converter: Time::Format.new("%F"), emit_null: true}, }) @@ -58,19 +58,19 @@ class JSONWithNilableTimeEmittingNull end class JSONWithSimpleMapping - json_mapping({name: String, age: Int32}) + JSON.mapping({name: String, age: Int32}) end class JSONWithKeywordsMapping - json_mapping({end: Int32, abstract: Int32}) + JSON.mapping({end: Int32, abstract: Int32}) end class JSONWithAny - json_mapping({name: String, any: JSON::Any}) + JSON.mapping({name: String, any: JSON::Any}) end class JsonWithProblematicKeys - json_mapping({ + JSON.mapping({ key: Int32, pull: Int32, }) diff --git a/spec/std/yaml/mapping_spec.cr b/spec/std/yaml/mapping_spec.cr index cd1b75e748a4..fbf3757a0fee 100644 --- a/spec/std/yaml/mapping_spec.cr +++ b/spec/std/yaml/mapping_spec.cr @@ -2,7 +2,7 @@ require "spec" require "yaml" class YAMLPerson - yaml_mapping({ + YAML.mapping({ name: String, age: {type: Int32, nilable: true}, }) @@ -14,26 +14,26 @@ class YAMLPerson end class StrictYAMLPerson - yaml_mapping({ + YAML.mapping({ name: {type: String}, age: {type: Int32, nilable: true}, }, true) end class YAMLWithBool - yaml_mapping({ + YAML.mapping({ value: {type: Bool}, }) end class YAMLWithTime - yaml_mapping({ + YAML.mapping({ value: {type: Time, converter: Time::Format.new("%F %T")}, }) end class YAMLWithKey - yaml_mapping({ + YAML.mapping({ key: String, value: Int32, pull: Int32, diff --git a/src/compiler/crystal/tools/context.cr b/src/compiler/crystal/tools/context.cr index 02797316363b..90642a0052f5 100644 --- a/src/compiler/crystal/tools/context.cr +++ b/src/compiler/crystal/tools/context.cr @@ -29,7 +29,7 @@ module Crystal end class ContextResult - json_mapping({ + JSON.mapping({ status: {type: String}, message: {type: String}, contexts: {type: Array(HashStringType), nilable: true}, diff --git a/src/compiler/crystal/tools/implementations.cr b/src/compiler/crystal/tools/implementations.cr index f6065344054c..f5b750fa48ba 100644 --- a/src/compiler/crystal/tools/implementations.cr +++ b/src/compiler/crystal/tools/implementations.cr @@ -4,7 +4,7 @@ require "json" module Crystal class ImplementationResult - json_mapping({ + JSON.mapping({ status: {type: String}, message: {type: String}, implementations: {type: Array(ImplementationTrace), nilable: true}, @@ -32,7 +32,7 @@ module Crystal # It keeps track of macro expansion in a human friendly way and # pointing to the exact line an expansion and method definition occurs. class ImplementationTrace - json_mapping({ + JSON.mapping({ line: {type: Int32}, column: {type: Int32}, filename: {type: String}, diff --git a/src/json/any.cr b/src/json/any.cr index 70468bf3bf8d..75d79463ba75 100644 --- a/src/json/any.cr +++ b/src/json/any.cr @@ -1,4 +1,4 @@ -# You can use `JSON::Any` inside a `JSON::Mapping` to make a property be parsed +# You can use `JSON::Any` inside a `JSON#mapping` to make a property be parsed # to a `JSON::Type`. This is useful if you have properties with dynamic content # that must later be inspected at runtime. module JSON::Any diff --git a/src/json/json.cr b/src/json/json.cr index 9f617311d7e9..dbd873083c9c 100644 --- a/src/json/json.cr +++ b/src/json/json.cr @@ -1,8 +1,8 @@ # The JSON module allows parsing and generating [JSON](http://json.org/) documents. # -# ### Parsing and generating with `JSON::Mapping` +# ### Parsing and generating with `JSON#mapping` # -# Use `JSON::Mapping` to define how an object is mapped to JSON, making it +# Use `JSON#mapping` to define how an object is mapped to JSON, making it # the recommended easy, type-safe and efficient option for parsing and generating # JSON. Refer to that module's documentation to learn about it. # @@ -32,7 +32,7 @@ # # `to_json` and `to_json(IO)` methods are provided for primitive types, but you # need to define `to_json(IO)` for custom objects, either manually or using -# `JSON::Mapping`. +# `JSON#mapping`. module JSON # Exception thrown on a JSON parse error. class ParseException < Exception diff --git a/src/json/mapping.cr b/src/json/mapping.cr index 0bbc292bd19e..5011fb1d18d0 100644 --- a/src/json/mapping.cr +++ b/src/json/mapping.cr @@ -1,61 +1,59 @@ -# The `JSON::Mapping` module defines a single macro, `json_mapping`, that -# defines how an object is mapped to JSON. -# -# This module is automatically included by `Object` when you `require "json"`. -# -# ### Example -# -# ``` -# require "json" -# -# class Location -# json_mapping({ -# lat: Float64, -# lng: Float64, -# }) -# end -# -# class House -# json_mapping({ -# address: String, -# location: {type: Location, nilable: true}, -# }) -# end -# -# house = House.from_json(%({"address": "Crystal Road 1234", "location": {"lat": 12.3, "lng": 34.5}})) -# house.address #=> "Crystal Road 1234" -# house.location #=> #<Location:0x10cd93d80 @lat=12.3, @lng=34.5> -# house.to_json #=> %({"address":"Crystal Road 1234","location":{"lat":12.3,"lng":34.5}}) -# ``` -# -# ### Usage -# -# `json_mapping` must receive a hash literal whose keys will define Crystal properties. -# -# The value of each key can be a single type (not a union type). Primitive types (numbers, string, boolean and nil) -# are supported, as well as custom objects which use `json_mapping` or define a `new` method -# that accepts a `JSON::PullParser` and returns an object from it. -# -# The value can also be another hash literal with the following options: -# * type: (required) the single type described above -# * key: the property name in the JSON document (as opposed to the property name in the Crystal code) -# * nilable: if true, the property can be `Nil` -# * emit_null: if true, emits a `null` value for nilable properties (by default nulls are not emitted) -# * converter: specify an alternate type for parsing and generation. The converter must define `from_json(JSON::PullParser)` and `to_json(value, IO)` as class methods. -# -# The mapping also automatically defines Crystal properties (getters and setters) for each -# of the keys. It doesn't define a constructor accepting those arguments, but you can provide -# an overload. -# -# The macro basically defines a constructor accepting a `JSON::PullParser` that reads from -# it and initializes this type's instance variables. It also defines a `to_json(IO)` method -# by invoking `to_json(IO)` on each of the properties (unless a converter is specified, in -# which case `to_json(value, IO)` is invoked). -module JSON::Mapping - # Defines a JSON mapping. If `strict` is true, unknown properties in the JSON +module JSON + # The `JSON.mapping` macro defines how an object is mapped to JSON. + # + # ### Example + # + # ``` + # require "json" + # + # class Location + # JSON.mapping({ + # lat: Float64, + # lng: Float64, + # }) + # end + # + # class House + # JSON.mapping({ + # address: String, + # location: {type: Location, nilable: true}, + # }) + # end + # + # house = House.from_json(%({"address": "Crystal Road 1234", "location": {"lat": 12.3, "lng": 34.5}})) + # house.address #=> "Crystal Road 1234" + # house.location #=> #<Location:0x10cd93d80 @lat=12.3, @lng=34.5> + # house.to_json #=> %({"address":"Crystal Road 1234","location":{"lat":12.3,"lng":34.5}}) + # ``` + # + # ### Usage + # + # `JSON.mapping` must receive a hash literal whose keys will define Crystal properties. + # + # The value of each key can be a single type (not a union type). Primitive types (numbers, string, boolean and nil) + # are supported, as well as custom objects which use `JSON.mapping` or define a `new` method + # that accepts a `JSON::PullParser` and returns an object from it. + # + # The value can also be another hash literal with the following options: + # * type: (required) the single type described above + # * key: the property name in the JSON document (as opposed to the property name in the Crystal code) + # * nilable: if true, the property can be `Nil` + # * emit_null: if true, emits a `null` value for nilable properties (by default nulls are not emitted) + # * converter: specify an alternate type for parsing and generation. The converter must define `from_json(JSON::PullParser)` and `to_json(value, IO)` as class methods. + # + # The mapping also automatically defines Crystal properties (getters and setters) for each + # of the keys. It doesn't define a constructor accepting those arguments, but you can provide + # an overload. + # + # The macro basically defines a constructor accepting a `JSON::PullParser` that reads from + # it and initializes this type's instance variables. It also defines a `to_json(IO)` method + # by invoking `to_json(IO)` on each of the properties (unless a converter is specified, in + # which case `to_json(value, IO)` is invoked). + # + # If `strict` is true, unknown properties in the JSON # document will raise a parse exception. The default is `false`, so unknown properties # are silently ignored. - macro json_mapping(properties, strict = false) + macro mapping(properties, strict = false) {% for key, value in properties %} {% properties[key] = {type: value} unless value.is_a?(HashLiteral) %} {% end %} @@ -141,7 +139,3 @@ module JSON::Mapping end end end - -class Object - include JSON::Mapping -end diff --git a/src/yaml/mapping.cr b/src/yaml/mapping.cr index aacb36af2005..9628795988b0 100644 --- a/src/yaml/mapping.cr +++ b/src/yaml/mapping.cr @@ -1,64 +1,61 @@ -# The `#yaml_mapping` macro defines how an object is mapped to YAML. -# -# Once "yaml" is required, `#yaml_mapping` macro is included into `Object`. -# It takes hash literal as argument, in which attributes and types are defined. -# Once defined, `Object#from_yaml` populates properties of the class from the -# YAML document. -# -# ```crystal -# require "yaml" -# -# class Employee -# yaml_mapping({ -# title: String, -# name: String, -# }) -# end -# -# employee = Employee.from_yaml("title: Manager\nname: John") -# employee.title # => "Manager" -# employee.name # => "John" -# -# employee.name = "Jenny" -# employee.name # => "Jenny" -# ``` -# -# Attributes not mapped with `#yaml_mapping` are not defined as properties. -# Also, missing attributes raise a `ParseException`. -# -# ```crystal -# employee = Employee.from_yaml("title: Manager\nname: John\nage: 30") -# employee.age # => undefined method 'age'. -# -# employee = Employee.from_yaml("title: Manager") -# # => ParseException: missing yaml attribute: name -# ``` -# -# You can also define attributes for each property. -# -# ```crystal -# class Employee -# yaml_mapping({ -# title: String, -# name: { -# type: String, -# nilable: true, -# key: "firstname", -# }, -# }) -# end -# ``` -# -# Available attributes: -# -# * *type* (required) defines its type. In the example above, *title: String* is a shortcut to *title: {type: String}*. -# * *nilable* defines if a property can be a `Nil`. -# * *key* defines whick key to read from a YAML document. It defaults to the name of the property. -# * *converter* takes an alternate type for parsing. It requires a `#from_yaml` method in that class, and returns an instance of the given type. -# -module YAML::Mapping - # Defines a YAML mapping. - macro yaml_mapping(properties, strict = false) +module YAML + # The `YAML.mapping` macro defines how an object is mapped to YAML. + # + # It takes hash literal as argument, in which attributes and types are defined. + # Once defined, `Object#from_yaml` populates properties of the class from the + # YAML document. + # + # ```crystal + # require "yaml" + # + # class Employee + # YAML.mapping({ + # title: String, + # name: String, + # }) + # end + # + # employee = Employee.from_yaml("title: Manager\nname: John") + # employee.title # => "Manager" + # employee.name # => "John" + # + # employee.name = "Jenny" + # employee.name # => "Jenny" + # ``` + # + # Attributes not mapped with `YAML.mapping` are not defined as properties. + # Also, missing attributes raise a `ParseException`. + # + # ```crystal + # employee = Employee.from_yaml("title: Manager\nname: John\nage: 30") + # employee.age # => undefined method 'age'. + # + # employee = Employee.from_yaml("title: Manager") + # # => ParseException: missing yaml attribute: name + # ``` + # + # You can also define attributes for each property. + # + # ```crystal + # class Employee + # YAML.mapping({ + # title: String, + # name: { + # type: String, + # nilable: true, + # key: "firstname", + # }, + # }) + # end + # ``` + # + # Available attributes: + # + # * *type* (required) defines its type. In the example above, *title: String* is a shortcut to *title: {type: String}*. + # * *nilable* defines if a property can be a `Nil`. + # * *key* defines whick key to read from a YAML document. It defaults to the name of the property. + # * *converter* takes an alternate type for parsing. It requires a `#from_yaml` method in that class, and returns an instance of the given type. + macro mapping(properties, strict = false) {% for key, value in properties %} {% properties[key] = {type: value} unless value.is_a?(HashLiteral) %} {% end %} @@ -119,7 +116,3 @@ module YAML::Mapping end end end - -class Object - include YAML::Mapping -end diff --git a/src/yaml/yaml.cr b/src/yaml/yaml.cr index 383f1d2898a1..f0947c50a7ca 100644 --- a/src/yaml/yaml.cr +++ b/src/yaml/yaml.cr @@ -7,17 +7,17 @@ require "./*" # Deserializes a YAML document into a `Type`. # A `Type` is a union of all possible YAML types, so casting to a specific type is necessary # before the value is practically usable. -# +# # ```crystal # require "yaml" # -# data = YAML.load("foo: bar") +# data = YAML.load("foo: bar") # (data as Hash)["foo"] #=> "bar" # ``` # -# ### Parsing with `Mapping` +# ### Parsing with `YAML#mapping` # -# `Mapping` defines how an object is mapped to YAML. Mapped data is accessible +# `YAML#mapping` defines how an object is mapped to YAML. Mapped data is accessible # through generated properties like *Foo#bar*. It is more type-safe and efficient. # module YAML @@ -50,14 +50,14 @@ module YAML # bar # ``` # - # ```crystal + # ```crystal # require "yaml" # YAML.load(File.read("./foo.yml")) # #=> { # #=> "data" => { - # #=> "string" => "foobar", - # #=> "array" => ["John", "Sarah"], - # #=> "hash" => {"key" => "value"}, + # #=> "string" => "foobar", + # #=> "array" => ["John", "Sarah"], + # #=> "hash" => {"key" => "value"}, # #=> "paragraph" => "foo\nbar\n" # #=> } # ```