class Remap::Compiler

Constructs a {Rule} from the block passed to {Remap::Base.define}

Public Class Methods

call(backtrace: caller, &block) click to toggle source

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

all() click to toggle source

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
any()
Alias for: first
at(index) click to toggle source

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
each(backtrace: caller, &block) click to toggle source

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
embed(mapper, backtrace: caller) click to toggle source

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
first() click to toggle source

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
Also aliased as: any
get(*path, backtrace: caller, &block) click to toggle source

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
get?(*path, backtrace: caller, &block) click to toggle source

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
last() click to toggle source

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
map(*path, to: EMPTY_ARRAY, backtrace: caller, &block) click to toggle source

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
map?(*path, to: EMPTY_ARRAY, backtrace: caller, &block) click to toggle source

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
option(id, backtrace: caller) click to toggle source

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(*path, to:, backtrace: caller) click to toggle source

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
to(*path, map: EMPTY_ARRAY, backtrace: caller, &block) click to toggle source

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
to?(*path, map: EMPTY_ARRAY, backtrace: caller, &block) click to toggle source

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
value(value, backtrace: caller) click to toggle source

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
wrap(type, backtrace: caller, &block) click to toggle source

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

add(rule) click to toggle source
# File lib/remap/compiler.rb, line 530
def add(rule)
  rule.tap { rules << rule }
end
build_embed(s0, mapper, backtrace) click to toggle source
# 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
rule(*path, to: EMPTY_ARRAY, backtrace: caller, &block) click to toggle source
# 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
rule?(*path, to: EMPTY_ARRAY, backtrace: caller, &block) click to toggle source
# 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