This commit is contained in:
James Harton 2020-03-29 20:09:00 +13:00
parent 307ea895c2
commit f5173e8f9e
54 changed files with 1038 additions and 8 deletions

29
.rubocop.yml Normal file
View file

@ -0,0 +1,29 @@
# The behavior of RuboCop can be controlled via the .rubocop.yml
# configuration file. It makes it possible to enable/disable
# certain cops (checks) and to alter their behavior if they accept
# any parameters. The file can be placed either in your home
# directory or in some project directory.
#
# RuboCop will start looking for the configuration file in the directory
# where the inspected file is and continue its way up to the root directory.
#
# See https://github.com/rubocop-hq/rubocop/blob/master/manual/configuration.md
Layout/LineLength:
Max: 120
Style/HashEachMethods:
Enabled: true
Style/HashTransformKeys:
Enabled: true
Style/HashTransformValues:
Enabled: true
Metrics/BlockLength:
Exclude:
- spec/**/*_spec.rb
Style/Documentation:
Enabled: false

View file

@ -5,6 +5,7 @@ source 'https://rubygems.org'
# Specify your gem's dependencies in wag.gemspec
gemspec
gem 'pry', '~> 0.13.0'
gem 'rake', '~> 12.0'
gem 'rspec', '~> 3.0'
gem 'rubocop', '~> 0.80.1'

View file

@ -2,16 +2,23 @@ PATH
remote: .
specs:
wag (0.1.0)
dry-inflector (~> 0.2.0)
GEM
remote: https://rubygems.org/
specs:
ast (2.4.0)
coderay (1.1.2)
diff-lcs (1.3)
dry-inflector (0.2.0)
jaro_winkler (1.5.4)
method_source (1.0.0)
parallel (1.19.1)
parser (2.7.0.5)
ast (~> 2.4.0)
pry (0.13.0)
coderay (~> 1.1)
method_source (~> 1.0)
rainbow (3.0.0)
rake (12.3.3)
rexml (3.2.4)
@ -43,6 +50,7 @@ PLATFORMS
ruby
DEPENDENCIES
pry (~> 0.13.0)
rake (~> 12.0)
rspec (~> 3.0)
rubocop (~> 0.80.1)

View file

@ -1,8 +1,24 @@
# Wag
# WAG - The WebAssembly Code Generator
Welcome to your new gem! In this directory, you'll find the files you need to be able to package up your Ruby library into a gem. Put your Ruby code in the file `lib/wag`. To experiment with that code, run `bin/console` for an interactive prompt.
This Ruby gem allows you to generate WebAssembly programs programatically.
TODO: Delete this and the text above, and describe your gem
## Example
```ruby
WAG::Module.new.build do
memory(1)
export('memory', :memory)
export('hello', :$hello)
func(:$hello) do
result(:i32)
i32.const(16)
end
data do
i32.const(16)
<< "Hello World\00"
end
end
```
## Installation

View file

@ -2,7 +2,25 @@
require 'wag/version'
module Wag
module WAG
class Error < StandardError; end
# Your code goes here...
require 'wag/wat'
require 'wag/data'
require 'wag/function'
require 'wag/function_type'
require 'wag/global'
require 'wag/element'
require 'wag/export'
require 'wag/import'
require 'wag/indices'
require 'wag/inflector'
require 'wag/instruction'
require 'wag/label'
require 'wag/memory'
require 'wag/module'
require 'wag/table'
require 'wag/type'
end

18
lib/wag/data.rb Normal file
View file

@ -0,0 +1,18 @@
# frozen_string_literal: true
module WAG
class Data
include WAG::WAT
attr_reader :offset, :value
def initialize(offset, value)
@offset = offset.to_i
@value = value
end
def to_sexpr
[:data, [:"i32.const", offset], value]
end
end
end

18
lib/wag/element.rb Normal file
View file

@ -0,0 +1,18 @@
# frozen_string_literal: true
module WAG
class Element
include WAG::WAT
attr_reader :table_id, :labels
def initialize(table_id, *labels)
@table_id = table_id.to_i
@labels = labels.map { |l| WAG::Label.from(l) }
end
def to_sexpr
[:elem, [:"i32.const", table_id]].concat(labels.map(&:to_sexpr))
end
end
end

39
lib/wag/export.rb Normal file
View file

@ -0,0 +1,39 @@
# frozen_string_literal: true
module WAG
class Export
include WAG::WAT
attr_reader :name, :desc
def initialize(name)
@name = name
end
def func(label, &block)
@desc = Function.new(label)
@desc.instance_exec(&block) if block
@desc
end
def global(label, type)
@desc = Global.new(label, type)
end
def memory(number, min: nil, max: nil, &block)
@desc = Memory.new(number, min: min, max: max)
@desc.instance_exec(&block) if block
@desc
end
def table(number, type = :funcref, min: nil, max: nil, &block)
@desc = Table.new(number, type, min: min, max: max)
@desc.instance_exec(&block) if block
@desc
end
def to_sexpr
[:export, name, desc.to_sexpr]
end
end
end

30
lib/wag/function.rb Normal file
View file

@ -0,0 +1,30 @@
# frozen_string_literal: true
module WAG
class Function
include WAG::WAT
attr_reader :label, :params, :results
def initialize(label)
@label = WAG::Label.from(label)
@params = []
@results = []
end
def param(*types)
@params.concat(types.map { |t| WAG::Type.from(t) })
end
def result(*types)
@results.concat(types.map { |t| WAG::Type.from(t) })
end
def to_sexpr
[:func, @label.to_sexpr].tap do |func|
func.push([:param].concat(@params.map(&:to_sexpr))) if @params
func.push([:result].concat(@results.map(&:to_sexpr))) if @results
end
end
end
end

31
lib/wag/function_type.rb Normal file
View file

@ -0,0 +1,31 @@
# frozen_string_literal: true
module WAG
class FunctionType
include WAG::WAT
attr_reader :label, :params, :results
def initialize(label)
@label = WAG::Label.from(label)
@params = []
@results = []
end
def param(*types)
@params.concat(types.map { |t| WAG::Type.from(t) })
end
def result(*types)
@results.concat(types.map { |t| WAG::Type.from(t) })
end
def to_sexpr
[:type, @label.to_sexpr,
[:func].tap do |func|
func.push([:param].concat(@params.map(&:to_sexpr))) if @params
func.push([:result].concat(@results.map(&:to_sexpr))) if @results
end]
end
end
end

18
lib/wag/global.rb Normal file
View file

@ -0,0 +1,18 @@
# frozen_string_literal: true
module WAG
class Global
include WAG::WAT
attr_reader :label, :type
def initialize(label, type)
@label = WAG::Label.from(label)
@type = WAG::Type.from(type)
end
def to_sexpr
[:global, label.to_sexpr, type.to_sexpr]
end
end
end

42
lib/wag/import.rb Normal file
View file

@ -0,0 +1,42 @@
# frozen_string_literal: true
module WAG
class Import
include WAG::WAT
attr_reader :module_name, :name, :desc
def initialize(module_name, name)
@module_name = module_name
@name = name
end
def func(label, &block)
@desc = Function.new(label)
@desc.instance_exec(&block) if block
@desc
end
def global(label, type, &block)
@desc = Global.new(label, type)
@desc.instance_exec(&block) if block
@desc
end
def memory(number, min: nil, max: nil, &block)
@desc = Memory.new(number, min: min, max: max)
@desc.instance_exec(&block) if block
@desc
end
def table(number, type = :funcref, min: nil, max: nil, &block)
@desc = Table.new(number, type, min: min, max: max)
@desc.instance_exec(&block) if block
@desc
end
def to_sexpr
[:import, module_name, name, desc.to_sexpr]
end
end
end

14
lib/wag/indices.rb Normal file
View file

@ -0,0 +1,14 @@
# frozen_string_literal: true
module WAG
module Indices
require 'wag/indices/base'
require 'wag/indices/func'
require 'wag/indices/global'
require 'wag/indices/label'
require 'wag/indices/local'
require 'wag/indices/mem'
require 'wag/indices/table'
require 'wag/indices/type'
end
end

16
lib/wag/indices/base.rb Normal file
View file

@ -0,0 +1,16 @@
# frozen_string_literal: true
module WAG
module Indices
class Base
RANGE = [0..0xFFFFFFFF].freeze
attr_reader :value
def initialize(value)
raise "Index 0x#{value.to_s(16)} does not fit in an i32" unless RANGE.cover?(value)
@value = value
end
end
end
end

8
lib/wag/indices/func.rb Normal file
View file

@ -0,0 +1,8 @@
# frozen_string_literal: true
module WAG
module Indices
class Func < Base
end
end
end

View file

@ -0,0 +1,8 @@
# frozen_string_literal: true
module WAG
module Indices
class Global < Base
end
end
end

8
lib/wag/indices/label.rb Normal file
View file

@ -0,0 +1,8 @@
# frozen_string_literal: true
module WAG
module Indices
class Label < Base
end
end
end

8
lib/wag/indices/local.rb Normal file
View file

@ -0,0 +1,8 @@
# frozen_string_literal: true
module WAG
module Indices
class Local < Base
end
end
end

8
lib/wag/indices/mem.rb Normal file
View file

@ -0,0 +1,8 @@
# frozen_string_literal: true
module WAG
module Indices
class Mem < Base
end
end
end

8
lib/wag/indices/table.rb Normal file
View file

@ -0,0 +1,8 @@
# frozen_string_literal: true
module WAG
module Indices
class Table < Base
end
end
end

8
lib/wag/indices/type.rb Normal file
View file

@ -0,0 +1,8 @@
# frozen_string_literal: true
module WAG
module Indices
class Type < Base
end
end
end

13
lib/wag/inflector.rb Normal file
View file

@ -0,0 +1,13 @@
# frozen_string_literal: true
require 'dry-inflector'
module WAG
module Inflector
module_function
def inflector
@inflector ||= Dry::Inflector.new
end
end
end

19
lib/wag/instruction.rb Normal file
View file

@ -0,0 +1,19 @@
# frozen_string_literal: true
module WAG
module Instruction
require 'wag/instructions/base'
require 'wag/instructions/block'
require 'wag/instructions/br_if'
require 'wag/instructions/br_table'
require 'wag/instructions/br'
require 'wag/instructions/else'
require 'wag/instructions/end'
require 'wag/instructions/if'
require 'wag/instructions/loop'
require 'wag/instructions/nop'
require 'wag/instructions/return'
require 'wag/instructions/unreachable'
end
end

View file

@ -0,0 +1,24 @@
# frozen_string_literal: true
module WAG
module Instruction
class Base
include WAG::WAT
def self.instruction(op_code)
klass = Class.new(WAG::Instruction::Base)
klass.define_method(:op_code) { op_code }
klass
end
def name
name = WAG::Inflector.inflector.demodulize(self.class.name.to_s)
WAG::Inflector.inflector.underscore(name).to_sym
end
def to_sexpr
name.to_sym
end
end
end
end

View file

@ -0,0 +1,8 @@
# frozen_string_literal: true
module WAG
module Instruction
class Block < Base.instruction(0x02)
end
end
end

View file

@ -0,0 +1,17 @@
# frozen_string_literal: true
module WAG
module Instruction
class Br < Base.instruction(0x0c)
attr_reader :label
def initialize(label)
@label = WAG::Label.from(label)
end
def to_sexpr
[name, label.to_sexpr]
end
end
end
end

View file

@ -0,0 +1,17 @@
# frozen_string_literal: true
module WAG
module Instruction
class BrIf < Base.instruction(0x0d)
attr_reader :label
def initialize(label)
@label = WAG::Label.from(label)
end
def to_sexpr
[name, label.to_sexpr]
end
end
end
end

View file

@ -0,0 +1,17 @@
# frozen_string_literal: true
module WAG
module Instruction
class BrTable < Base.instruction(0x0e)
attr_reader :label
def initialize(*labels)
@labels = labels.map { |label| WAG::Label.from(label) }
end
def to_sexpr
[name].concat(@labels.map(&:to_sexpr))
end
end
end
end

View file

@ -0,0 +1,8 @@
# frozen_string_literal: true
module WAG
module Instruction
class Else < Base.instruction(0x05)
end
end
end

View file

@ -0,0 +1,8 @@
# frozen_string_literal: true
module WAG
module Instruction
class End < Base.instruction(0x0b)
end
end
end

View file

@ -0,0 +1,8 @@
# frozen_string_literal: true
module WAG
module Instruction
class If < Base.instruction(0x04)
end
end
end

View file

@ -0,0 +1,8 @@
# frozen_string_literal: true
module WAG
module Instruction
class Loop < Base.instruction(0x03)
end
end
end

View file

@ -0,0 +1,8 @@
# frozen_string_literal: true
module WAG
module Instruction
class Nop < Base.instruction(0x01)
end
end
end

View file

@ -0,0 +1,8 @@
# frozen_string_literal: true
module WAG
module Instruction
class Return < Base.instruction(0x0f)
end
end
end

View file

@ -0,0 +1,8 @@
# frozen_string_literal: true
module WAG
module Instruction
class Unreachable < Base.instruction(0x00)
end
end
end

24
lib/wag/label.rb Normal file
View file

@ -0,0 +1,24 @@
# frozen_string_literal: true
module WAG
class Label
include WAG::WAT
attr_reader :value
def initialize(value)
@value = value.to_s
@value = @value[1..-1] if @value.start_with?('$')
end
def to_sexpr
"$#{value}".to_sym
end
def self.from(value)
return value if value.is_a?(WAG::Label)
WAG::Label.new(value)
end
end
end

24
lib/wag/memory.rb Normal file
View file

@ -0,0 +1,24 @@
# frozen_string_literal: true
module WAG
class Memory
include WAG::WAT
attr_reader :number, :min, :max
def initialize(number, min: nil, max: nil)
@number = number
@min = min
@max = max
end
def to_sexpr
wat = [:memory, number]
wat.concat([:min, min]) if min
wat.concat([:max, max]) if max
wat
end
end
end

87
lib/wag/module.rb Normal file
View file

@ -0,0 +1,87 @@
# frozen_string_literal: true
module WAG
class Module
include WAG::WAT
def initialize
@data = []
@elements = []
@exports = []
@functions = []
@globals = []
@imports = []
@memories = []
@types = []
end
def import(module_name, name, &block)
import = Import.new(module_name, name)
@imports << import
import.instance_exec(&block) if block
import
end
def export(name, &block)
export = Export.new(name)
@exports << export
export.instance_exec(&block) if block
export
end
def memory(number, min: nil, max: nil, &block)
mem = Memory.new(number, min: min, max: max)
@memories << mem
mem.instance_exec(&block) if block
mem
end
def global(label, type, &block)
global = Global.new(label, type)
@globals << global
global.instance_exec(&block) if block
global
end
def type(label, &block)
type = FunctionType.new(label)
@types << type
type.instance_exec(&block) if block
type
end
def elem(table_id, *labels, &block)
elem = Element.new(table_id, *labels)
@elements << elem
elem.instance_exec(&block) if block
elem
end
def data(offset, value, &block)
data = Data.new(offset, value)
@data << data
data.instance_exec(&block) if block
data
end
def func(label, &block)
func = Function.new(label)
@functions << func
func.instance_exec(&block) if block
func
end
def to_sexpr # rubocop:disable Metrics/AbcSize
mod = [:module]
mod.concat(@imports.map(&:to_sexpr))
mod.concat(@exports.map(&:to_sexpr))
mod.concat(@memories.map(&:to_sexpr))
mod.concat(@globals.map(&:to_sexpr))
mod.concat(@types.map(&:to_sexpr))
mod.concat(@elements.map(&:to_sexpr))
mod.concat(@data.map(&:to_sexpr))
mod.concat(@functions.map(&:to_sexpr))
mod
end
end
end

29
lib/wag/table.rb Normal file
View file

@ -0,0 +1,29 @@
# frozen_string_literal: true
module WAG
class Table
include WAG::WAT
RANGE = (0..0xFFFFFFFF).freeze
attr_reader :number, :type, :min, :max
def initialize(number, type = :funcref, min: nil, max: nil) # rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
raise '`min` must fit in u32 range' if min && !RANGE.cover?(min)
raise '`max` must fit in u32 range' if max && !RANGE.cover?(max)
raise '`max` must be greater than `min`' if min && max && min > max
@number = number
@type = type
@min = min
@max = max
end
def to_sexpr
expr = [:table, number, type]
expr.concat([:min, min]) if min
expr.concat([:max, max]) if max
expr
end
end
end

19
lib/wag/type.rb Normal file
View file

@ -0,0 +1,19 @@
# frozen_string_literal: true
module WAG
module Type
require 'wag/types/base'
require 'wag/types/i32'
require 'wag/types/i64'
require 'wag/types/f32'
require 'wag/types/f64'
module_function
def from(type)
return type if type.is_a?(WAG::Type::Base)
const_get(WAG::Inflector.inflector.camelize(type.to_s)).new
end
end
end

16
lib/wag/types/base.rb Normal file
View file

@ -0,0 +1,16 @@
# frozen_string_literal: true
module WAG
module Type
class Base
def to_sexpr
name = WAG::Inflector.inflector.demodulize(self.class.name.to_s)
WAG::Inflector.inflector.underscore(name).to_sym
end
def value?
true
end
end
end
end

8
lib/wag/types/f32.rb Normal file
View file

@ -0,0 +1,8 @@
# frozen_string_literal: true
module WAG
module Type
class F32 < Base
end
end
end

8
lib/wag/types/f64.rb Normal file
View file

@ -0,0 +1,8 @@
# frozen_string_literal: true
module WAG
module Type
class F64 < Base
end
end
end

28
lib/wag/types/function.rb Normal file
View file

@ -0,0 +1,28 @@
# frozen_string_literal: true
module WAG
module Type
# A WebAssembly function type.
class Function < Base
attr_reader :parameter_types, :return_types
def initialize(parameter_types, return_types)
parameter_types.map! { |t| Huia::Type.from(t) }
return_types.map! { |t| Huia::Type.from(t) }
raise 'There must only be one return type (at the moment)' unless return_types.one?
unless parameter_types.all?(&:value?) && return_types.all?(&:value?)
raise 'Function type parameters can only be value types'
end
@parameter_types = parameter_types
@return_types = return_types
end
def value?
false
end
end
end
end

8
lib/wag/types/i32.rb Normal file
View file

@ -0,0 +1,8 @@
# frozen_string_literal: true
module WAG
module Type
class I32 < Base
end
end
end

8
lib/wag/types/i64.rb Normal file
View file

@ -0,0 +1,8 @@
# frozen_string_literal: true
module WAG
module Type
class I64 < Base
end
end
end

View file

@ -1,5 +1,5 @@
# frozen_string_literal: true
module Wag
module WAG
VERSION = '0.1.0'
end

19
lib/wag/wat.rb Normal file
View file

@ -0,0 +1,19 @@
# frozen_string_literal: true
module WAG
# A simple mixin which formats s-expressions as WAT.
module WAT
def to_wat
wat_encode(to_sexpr)
end
private
def wat_encode(data)
return "(#{data.map { |e| wat_encode(e) }.join(' ')})" if data.is_a?(Enumerable)
return data.inspect if data.is_a?(String)
data.to_s
end
end
end

View file

@ -2,6 +2,7 @@
require 'bundler/setup'
require 'wag'
require 'pry'
RSpec.configure do |config|
# Enable flags like --only-failures and --next-failure

55
spec/wag/export_spec.rb Normal file
View file

@ -0,0 +1,55 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe WAG::Export do
let(:export) { described_class.new('marty') }
subject { export }
describe 'When exporting memory' do
before do
export.memory(1, min: 1, max: 64)
end
describe '#to_sexpr' do
subject { super().to_sexpr }
it { is_expected.to eq [:export, 'marty', [:memory, 1, :min, 1, :max, 64]] }
end
end
describe 'When exporting a table' do
before do
export.table(1, :funcref, min: 1, max: 64)
end
describe '#to_sexpr' do
subject { super().to_sexpr }
it { is_expected.to eq [:export, 'marty', [:table, 1, :funcref, :min, 1, :max, 64]] }
end
end
describe 'When exporting a function' do
before do
export.func(:foo) do
param(:f32, :f64)
result(:i32)
end
end
describe '#to_sexpr' do
subject { super().to_sexpr }
it { is_expected.to eq [:export, 'marty', [:func, :$foo, %i[param f32 f64], %i[result i32]]] }
end
end
describe 'When exporting a global' do
before do
export.global(:foo, :i32)
end
describe '#to_sexpr' do
subject { super().to_sexpr }
it { is_expected.to eq [:export, 'marty', %i[global $foo i32]] }
end
end
end

55
spec/wag/import_spec.rb Normal file
View file

@ -0,0 +1,55 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe WAG::Import do
let(:import) { described_class.new('marty', 'mcfly') }
subject { import }
describe 'When importing memory' do
before do
import.memory(1, min: 1, max: 64)
end
describe '#to_sexpr' do
subject { super().to_sexpr }
it { is_expected.to eq [:import, 'marty', 'mcfly', [:memory, 1, :min, 1, :max, 64]] }
end
end
describe 'When importing a table' do
before do
import.table(1, :funcref, min: 1, max: 64)
end
describe '#to_sexpr' do
subject { super().to_sexpr }
it { is_expected.to eq [:import, 'marty', 'mcfly', [:table, 1, :funcref, :min, 1, :max, 64]] }
end
end
describe 'When importing a function' do
before do
import.func(:foo) do
param(:f32, :f64)
result(:i32)
end
end
describe '#to_sexpr' do
subject { super().to_sexpr }
it { is_expected.to eq [:import, 'marty', 'mcfly', [:func, :$foo, %i[param f32 f64], %i[result i32]]] }
end
end
describe 'When importing a global' do
before do
import.global(:foo, :i32)
end
describe '#to_sexpr' do
subject { super().to_sexpr }
it { is_expected.to eq [:import, 'marty', 'mcfly', %i[global $foo i32]] }
end
end
end

114
spec/wag/module_spec.rb Normal file
View file

@ -0,0 +1,114 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe WAG::Module do
let(:mod) { described_class.new }
subject { mod }
describe 'When the module is empty' do
describe '#to_sexpr' do
subject { super().to_sexpr }
it { is_expected.to eq [:module] }
end
describe '#to_wat' do
subject { super().to_wat }
it { is_expected.to eq '(module)' }
end
end
describe 'When the module imports a memory' do
before do
mod.import('js', 'memory').memory(1)
end
describe '#to_sexpr' do
subject { super().to_sexpr }
it { is_expected.to eq [:module, [:import, 'js', 'memory', [:memory, 1]]] }
end
end
describe 'When the module exports a memory' do
before do
mod.export('memory').memory(1)
end
describe '#to_sexpr' do
subject { super().to_sexpr }
it { is_expected.to eq [:module, [:export, 'memory', [:memory, 1]]] }
end
end
describe 'When the module declares a memory' do
before do
mod.memory(2)
end
describe '#to_sexpr' do
subject { super().to_sexpr }
it { is_expected.to eq [:module, [:memory, 2]] }
end
end
describe 'When the module declares a global' do
before do
mod.global(:foo, :i32)
end
describe '#to_sexpr' do
subject { super().to_sexpr }
it { is_expected.to eq [:module, %i[global $foo i32]] }
end
end
describe 'When the module declares a type' do
before do
mod.type(:foo) do
param(:i32)
result(:i32)
end
end
describe '#to_sexpr' do
subject { super().to_sexpr }
it { is_expected.to eq [:module, [:type, :$foo, [:func, %i[param i32], %i[result i32]]]] }
end
end
describe 'When the module declares a table element' do
before do
mod.elem(1, :foo1, :foo2)
end
describe '#to_sexpr' do
subject { super().to_sexpr }
it { is_expected.to eq [:module, [:elem, [:"i32.const", 1], :$foo1, :$foo2]] }
end
end
describe 'When the module declares data' do
before do
mod.data(16, 'Hello World')
end
describe '#to_sexpr' do
subject { super().to_sexpr }
it { is_expected.to eq [:module, [:data, [:"i32.const", 16], 'Hello World']] }
end
end
describe 'When the module declares a function' do
before do
mod.func(:add) do
param(:i32, :i32)
result(:i32)
end
end
describe '#to_sexpr' do
subject { super().to_sexpr }
it { is_expected.to eq [:module, [:func, :$add, %i[param i32 i32], %i[result i32]]] }
end
end
end

View file

@ -1,7 +1,7 @@
# frozen_string_literal: true
RSpec.describe Wag do
RSpec.describe WAG do
it 'has a version number' do
expect(Wag::VERSION).not_to be nil
expect(WAG::VERSION).not_to be nil
end
end

View file

@ -4,7 +4,7 @@ require_relative 'lib/wag/version'
Gem::Specification.new do |spec|
spec.name = 'wag'
spec.version = Wag::VERSION
spec.version = WAG::VERSION
spec.authors = ['James Harton']
spec.email = ['james@automat.nz']
@ -30,4 +30,6 @@ Gem::Specification.new do |spec|
spec.bindir = 'exe'
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
spec.require_paths = ['lib']
spec.add_runtime_dependency('dry-inflector', '~> 0.2.0')
end