class Remap::Compiler
Constructs a {Rule} from the block passed to {Remap::Base.define}
Public Class Methods
Constructs a rule tree given block
@example Compiles two rules, [get] and [map]
rule = Remap::Compiler.call do get :name get :age end state = Remap::State.call({ name: "John", age: 50 }) error = -> failure { raise failure.exception } rule.call(state, &error).fetch(:value) # => { name: "John", age: 50 }
@return [Rule]
# File lib/remap/compiler.rb, line 34 def self.call(backtrace: caller, &block) unless block return Rule::VOID end rules = new([]).tap do |compiler| compiler.instance_exec(&block) end.rules Rule::Block.new(backtrace: backtrace, rules: rules) end
Public Instance Methods
Selects all elements
@example Select all keys in array
rule = Remap::Compiler.call do map all, :name, to: :names end state = Remap::State.call([ { name: "John" }, { name: "Jane" } ]) output = rule.call(state) do |failure| raise failure.exception end output.fetch(:value) # => { names: ["John", "Jane"] }
@return [Rule::Path::Segment::Quantifier::All]
# File lib/remap/compiler.rb, line 383 def all if block_given? raise ArgumentError, "all selector does not take a block" end Selector::All.new(EMPTY_HASH) end
Selects index element in input
@param index [Integer]
@example Select value at index
rule = Remap::Compiler.call do map :names, at(1), to: :name end state = Remap::State.call({ names: ["John", "Jane"] }) output = rule.call(state) do |failure| raise failure.exception end output.fetch(:value) # => { name: "Jane" }
@return [Path::Segment::Key] @raise [ArgumentError] if index is not an Integer
# File lib/remap/compiler.rb, line 464 def at(index) if block_given? raise ArgumentError, "first selector does not take a block" end Selector::Index.new(index: index) rescue Dry::Struct::Error raise ArgumentError, "Selector at(index) requires an integer argument, got [#{index}] (#{index.class})" end
Iterates over the input value, passes each value to its block and merges the result back together
@example Map an array of hashes
rule = Remap::Compiler.call do each do map :name end end state = Remap::State.call([{ name: "John" }, { name: "Jane" }]) output = rule.call(state) do |failure| raise failure.exception end output.fetch(:value) # => ["John", "Jane"]
@return [Rule::Each]] @raise [ArgumentError] if no block given
# File lib/remap/compiler.rb, line 319 def each(backtrace: caller, &block) unless block raise ArgumentError, "#each requires a block" end add rule(all, backtrace: backtrace, &block) end
Maps using mapper
@param mapper [Remap]
@example Embed mapper Car into a person
class Car < Remap::Base define do map :car do map :name, to: :car end end end rule = Remap::Compiler.call do map :person do embed Car end end state = Remap::State.call({ person: { car: { name: "Volvo" } } }) output = rule.call(state) do |failure| raise failure.exception end output.fetch(:value) # => { car: "Volvo" }
@return [Rule::Embed]
# File lib/remap/compiler.rb, line 181 def embed(mapper, backtrace: caller) if block_given? raise ArgumentError, "#embed does not take a block" end Types::Mapper[mapper] do raise ArgumentError, "Argument to #embed must be a mapper, got #{mapper.class}" end result = rule(backtrace: backtrace).add do |s0| build_embed(s0, mapper, backtrace) end add result end
Selects first element in input
@example Select first value in an array
rule = Remap::Compiler.call do map :names, first, to: :name end state = Remap::State.call({ names: ["John", "Jane"] }) output = rule.call(state) do |failure| raise failure.exception end output.fetch(:value) # => { name: "John" }
@return [Path::Segment::Key]]
# File lib/remap/compiler.rb, line 493 def first if block_given? raise ArgumentError, "first selector does not take a block" end at(0) end
Select a path and uses the same path as output
@param path ([]) [Array<Segment>, Segment]
@example Map from [:name] to [:name]
rule = Remap::Compiler.call do get :name end state = Remap::State.call({ name: "John" }) output = rule.call(state) do |failure| raise failure.exception end output.fetch(:value) # => { name: "John" }
@return [Rule::Map::Required]
# File lib/remap/compiler.rb, line 118 def get(*path, backtrace: caller, &block) add rule(path, to: path, backtrace: backtrace, &block) end
Optional version of {#get}
@example Map from [:name] to [:name]
rule = Remap::Compiler.call do get :name get? :age end state = Remap::State.call({ name: "John" }) output = rule.call(state) do |failure| raise failure.exception end output.fetch(:value) # => { name: "John" }
@see get
@return [Rule::Map::Optional]
# File lib/remap/compiler.rb, line 143 def get?(*path, backtrace: caller, &block) add rule?(path, to: path, backtrace: backtrace, &block) end
Selects last element in input
@example Select last value in an array
rule = Remap::Compiler.call do map :names, last, to: :name end state = Remap::State.call({ names: ["John", "Jane", "Linus"] }) output = rule.call(state) do |failure| raise failure.exception end output.fetch(:value) # => { name: "Linus" }
@return [Path::Segment::Key]
# File lib/remap/compiler.rb, line 520 def last if block_given? raise ArgumentError, "last selector does not take a block" end at(-1) end
Maps input path [input] to output path [to]
@param path ([]) [Array<Segment>, Segment] @param to ([]) [Array<Symbol>, Symbol]
@example From path [:name] to [:nickname]
rule = Remap::Compiler.call do map :name, to: :nickname end state = Remap::State.call({ name: "John" }) output = rule.call(state) do |failure| raise failure.exception end output.fetch(:value) # => { nickname: "John" }
@return [Rule::Map::Required]
# File lib/remap/compiler.rb, line 67 def map(*path, to: EMPTY_ARRAY, backtrace: caller, &block) add rule(*path, to: to, backtrace: backtrace, &block) end
Optional version of {#map}
@example Map an optional field
rule = Remap::Compiler.call do to :person do map? :age, to: :age map :name, to: :name end end state = Remap::State.call({ name: "John" }) output = rule.call(state) do |failure| raise failure.exception end output.fetch(:value) # => { person: { name: "John" } }
@see map
@return [Rule::Map::Optional]
# File lib/remap/compiler.rb, line 94 def map?(*path, to: EMPTY_ARRAY, backtrace: caller, &block) add rule?(*path, to: to, backtrace: backtrace, &block) end
Static
option to be selected
@example Set path to option
rule = Remap::Compiler.call do set :meaning_of_life, to: option(:number) end state = Remap::State.call({}, options: { number: 42 }) output = rule.call(state) do |failure| raise failure.exception end output.fetch(:value) # => { meaning_of_life: 42 }
@param id [Symbol]
@return [Rule::Static::Option]
# File lib/remap/compiler.rb, line 435 def option(id, backtrace: caller) if block_given? raise ArgumentError, "option selector does not take a block" end Static::Option.new(name: id, backtrace: backtrace) end
Set a static value
@param path ([]) [Symbol, Array<Symbol>] @option to [Remap::Static]
@example Set static value to { name: “John” }
rule = Remap::Compiler.call do set :name, to: value("John") end state = Remap::State.call({}) output = rule.call(state) do |failure| raise failure.exception end output.fetch(:value) # => { name: "John" }
@example Reference an option
rule = Remap::Compiler.call do set :name, to: option(:name) end state = Remap::State.call({}, options: { name: "John" }) output = rule.call(state) do |failure| raise failure.exception end output.fetch(:value) # => { name: "John" }
@return [Rule::Set] @raise [ArgumentError]
if no path given if path is not a Symbol or Array<Symbol>
# File lib/remap/compiler.rb, line 232 def set(*path, to:, backtrace: caller) if block_given? raise ArgumentError, "#set does not take a block" end unless to.is_a?(Static) raise ArgumentError, "Argument to #set must be a static value, got #{to.class}" end add rule(to: path, backtrace: backtrace).add { to.call(_1) } end
Maps to path from map with block in between
@param path [Array<Symbol>, Symbol] @param map [Array<Segment>, Segment]
@example From path [:name] to [:nickname]
rule = Remap::Compiler.call do to :nickname, map: :name end state = Remap::State.call({ name: "John" }) output = rule.call(state) do |failure| raise failure.exception end output.fetch(:value) # => { nickname: "John" }
@return [Rule::Map]
# File lib/remap/compiler.rb, line 265 def to(*path, map: EMPTY_ARRAY, backtrace: caller, &block) add rule(*map, to: path, backtrace: backtrace, &block) end
Optional version of {#to}
@example Map an optional field
rule = Remap::Compiler.call do to :person do to? :age, map: :age to :name, map: :name end end state = Remap::State.call({ name: "John" }) output = rule.call(state) do |failure| raise failure.exception end output.fetch(:value) # => { person: { name: "John" } }
@see to
@return [Rule::Map::Optional]
# File lib/remap/compiler.rb, line 291 def to?(*path, map: EMPTY_ARRAY, backtrace: caller, &block) add rule?(*map, to: path, backtrace: backtrace, &block) end
Static
value to be selected
@param value [Any]
@example Set path to static value
rule = Remap::Compiler.call do set :api_key, to: value("<SECRET>") end state = Remap::State.call({}) output = rule.call(state) do |failure| raise failure.exception end output.fetch(:value) # => { api_key: "<SECRET>" }
@return [Rule::Static::Fixed]
# File lib/remap/compiler.rb, line 409 def value(value, backtrace: caller) if block_given? raise ArgumentError, "option selector does not take a block" end Static::Fixed.new(value: value, backtrace: backtrace) end
Wraps output in type
@param type [:array]
@yieldreturn [Rule]
@example Wrap an output value in an array
rule = Remap::Compiler.call do wrap(:array) do map :name end end state = Remap::State.call({ name: "John" }) output = rule.call(state) do |failure| raise failure.exception end output.fetch(:value) # => ["John"]
@return [Rule::Wrap] @raise [ArgumentError] if type is not :array
# File lib/remap/compiler.rb, line 352 def wrap(type, backtrace: caller, &block) unless block raise ArgumentError, "#wrap requires a block" end unless type == :array raise ArgumentError, "Argument to #wrap must equal :array, got [#{type}] (#{type.class})" end add rule(backtrace: backtrace, &block).then { Array.wrap(_1) } end
Private Instance Methods
# File lib/remap/compiler.rb, line 530 def add(rule) rule.tap { rules << rule } end
# File lib/remap/compiler.rb, line 556 def build_embed(s0, mapper, backtrace) catch_fatal(s0, backtrace) do |s1| s2 = s1.set(mapper: mapper) old_mapper = s0.fetch(:mapper) return mapper.call!(s2) do |f1| s3 = s2.set(notices: f1.notices + f1.failures) s3.return! end.except(:scope).merge(mapper: old_mapper) end end
# File lib/remap/compiler.rb, line 534 def rule(*path, to: EMPTY_ARRAY, backtrace: caller, &block) Rule::Map::Required.call({ path: { output: [to].flatten, input: path.flatten }, backtrace: backtrace, rule: call(backtrace: backtrace, &block) }) end
# File lib/remap/compiler.rb, line 545 def rule?(*path, to: EMPTY_ARRAY, backtrace: caller, &block) Rule::Map::Optional.call({ path: { output: [to].flatten, input: path.flatten }, backtrace: backtrace, rule: call(backtrace: backtrace, &block) }) end