diva-0.3.1/0000755000004100000410000000000013224664463012500 5ustar www-datawww-datadiva-0.3.1/Rakefile0000644000004100000410000000030613224664463014144 0ustar www-datawww-datarequire "bundler/gem_tasks" require "rake/testtask" Rake::TestTask.new(:test) do |t| t.libs << "test" t.libs << "lib" t.test_files = FileList['test/**/*_test.rb'] end task :default => :test diva-0.3.1/bin/0000755000004100000410000000000013224664463013250 5ustar www-datawww-datadiva-0.3.1/bin/console0000755000004100000410000000051113224664463014635 0ustar www-datawww-data#!/usr/bin/env ruby require "bundler/setup" require "diva" # You can add fixtures and/or initialization code here to make experimenting # with your gem easier. You can also use a different console, if you like. # (If you use this, don't forget to add pry to your Gemfile!) # require "pry" # Pry.start require "irb" IRB.start diva-0.3.1/bin/setup0000755000004100000410000000020313224664463014331 0ustar www-datawww-data#!/usr/bin/env bash set -euo pipefail IFS=$'\n\t' set -vx bundle install # Do any other automated setup that you need to do here diva-0.3.1/Gemfile0000644000004100000410000000013113224664463013766 0ustar www-datawww-datasource 'https://rubygems.org' # Specify your gem's dependencies in diva.gemspec gemspec diva-0.3.1/LICENSE.txt0000644000004100000410000000207013224664463014322 0ustar www-datawww-dataThe MIT License (MIT) Copyright (c) 2017 Toshiaki Asai Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diva-0.3.1/.travis.yml0000644000004100000410000000013013224664463014603 0ustar www-datawww-datasudo: false language: ruby rvm: - 2.3.1 before_install: gem install bundler -v 1.12.5 diva-0.3.1/diva.gemspec0000644000004100000410000000203013224664463014763 0ustar www-datawww-data# coding: utf-8 lib = File.expand_path('../lib', __FILE__) $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) require 'diva/version' Gem::Specification.new do |spec| spec.name = "diva" spec.version = Diva::VERSION spec.authors = ["Toshiaki Asai"] spec.email = ["toshi.alternative@gmail.com"] spec.summary = %q{Implementation of expression for handling things.} spec.homepage = "https://github.com/toshia/diva" spec.license = "MIT" spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) } spec.bindir = "exe" spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) } spec.require_paths = ["lib"] spec.add_development_dependency "bundler", "~> 1.12" spec.add_development_dependency "rake", "~> 10.0" spec.add_development_dependency "minitest", "~> 5.0" spec.add_development_dependency "pry" spec.add_development_dependency "simplecov" spec.add_dependency "addressable", ">= 2.5", "< 2.6" end diva-0.3.1/lib/0000755000004100000410000000000013224664463013246 5ustar www-datawww-datadiva-0.3.1/lib/diva/0000755000004100000410000000000013224664463014171 5ustar www-datawww-datadiva-0.3.1/lib/diva/model.rb0000644000004100000410000000752213224664463015624 0ustar www-datawww-data# -*- coding: utf-8 -*- =begin rdoc いろんなリソースの基底クラス =end require 'diva/combinator' require 'diva/model_extend' require 'diva/uri' require 'diva/spec' require 'securerandom' class Diva::Model include Comparable include Diva::Combinable extend Diva::ModelExtend def initialize(args) @value = args.dup validate self.class.store_datum(self) end # データをマージする。 # selfにあってotherにもあるカラムはotherの内容で上書きされる。 # 上書き後、データはDataSourceに保存される def merge(other) @value.update(other.to_hash) validate self.class.store_datum(self) end # このModelのパーマリンクを返す。 # パーマリンクはWebのURLで、Web上のリソースでない場合はnilを返す。 # ==== Return # 次のいずれか # [URI::HTTP|Diva::URI] パーマリンク # [nil] パーマリンクが存在しない def perma_link nil end # このModelのURIを返す。 # ==== Return # [URI::Generic|Diva::URI] パーマリンク def uri perma_link || Diva::URI.new("#{self.class.scheme}://#{self.class.host}#{path}") end # このModelが、登録されているアカウントのうちいずれかが作成したものであれば true を返す # ==== Args # [service] Service | Enumerable 「自分」のService # ==== Return # [true] 自分のによって作られたオブジェクトである # [false] 自分のによって作られたオブジェクトではない def me?(service=nil) false end def hash @_hash ||= self.uri.to_s.hash ^ self.class.hash end def <=>(other) if other.is_a?(Diva::Model) created - other.created elsif other.respond_to?(:[]) and other[:created] created - other[:created] else id - other end end def ==(other) if other.is_a? Diva::Model self.class == other.class && uri == other.uri end end def eql?(other) self == other end def to_hash Hash[self.class.fields.map{|f| [f.name, fetch(f.name)] }] end def to_json(*rest, **kwrest) Hash[ self.class.fields.map{|f| [f.name, f.dump_for_json(fetch(f.name))] } ].to_json(*rest, **kwrest) end # カラムの生の内容を返す def fetch(key) @value[key.to_sym] end alias [] fetch # カラムに別の値を格納する。 # 格納後、データはDataSourceに保存される def []=(key, value) @value[key.to_sym] = value self.class.store_datum(self) value end # カラムと型が違うものがある場合、例外を発生させる。 def validate raise RuntimeError, "argument is #{@value}, not Hash" if not @value.is_a?(Hash) self.class.fields.each do |field| begin @value[field.name] = field.type.cast(@value[field.name]) rescue Diva::InvalidTypeError => err raise Diva::InvalidTypeError, "#{err} in field `#{field}'" end end end # キーとして定義されていない値を全て除外した配列を生成して返す。 # また、Modelを子に含んでいる場合、それを外部キーに変換する。 def filtering datum = self.to_hash result = Hash.new self.class.fields.each do |field| begin result[field.name] = field.type.cast(datum[field.name]) rescue Diva::InvalidTypeError => err raise Diva::InvalidTypeError, "#{err} in field `#{field}'" end end result end # このインスタンスのタイトル。 def title fields = self.class.fields.lazy.map(&:name) case when fields.include?(:name) name.gsub("\n", '') when fields.include?(:description) description.gsub("\n", '') else to_s.gsub("\n", '') end end private # URIがデフォルトで使うpath要素 def path @path ||= "/#{SecureRandom.uuid}" end end diva-0.3.1/lib/diva/model_extend.rb0000644000004100000410000000556513224664463017200 0ustar www-datawww-data# -*- coding: utf-8 -*- require 'diva/field' require 'diva/type' =begin rdoc Diva::Model のクラスメソッド =end module Diva::ModelExtend extend Gem::Deprecate attr_reader :slug, :spec # Modelのインスタンスのuriスキーム。オーバライドして適切な値にする # ==== Return # [String] URIスキーム def scheme @_scheme ||= self.to_s.split('::',2).first.gsub(/\W/,'').downcase.freeze end # Modelのインスタンスのホスト名。オーバライドして適切な値にする # ==== Return # [String] ホスト名 def host @_host ||= self.to_s.split('::',2).last.split('::').reverse.join('.').gsub(/[^\w\.]/,'').downcase.freeze end # Modelにフィールドを追加する。 # ==== Args # [field_name] Symbol フィールドの名前 # [type] Symbol フィールドのタイプ。:int, :string, :bool, :time のほか、Diva::Modelのサブクラスを指定する # [required] boolean _true_ なら、この項目を必須とする def add_field(field, type: nil, required: false) if field.is_a?(Symbol) field = Diva::Field.new(field, type, required: required) end (@fields ||= []) << field define_method(field.name) do @value[field.name] end define_method("#{field.name}?") do !!@value[field.name] end define_method("#{field.name}=") do |value| @value[field.name] = field.type.cast(value) self.class.store_datum(self) value end self end def fields @fields ||= [] end alias :keys :fields deprecate :keys, "fields", 2018, 02 # # プライベートクラスメソッド # def field Diva::FieldGenerator.new(self) end # URIに対応するリソースの内容を持ったModelを作成する。 # URIに対応する情報はネットワーク上などから取得される場合もある。そういった場合はこのメソッドは # Delayer::Deferred::Deferredable を返す可能性がある。 # とくにオーバライドしない場合、このメソッドは常に例外 Diva::NotImplementedError を投げる。 # ==== Args # [uri] _handle_ メソッドで指定したいずれかの条件に一致するURI # ==== Return # [Delayer::Deferred::Deferredable] # ネットワークアクセスを行って取得するなど取得に時間がかかる場合 # [self] # すぐにModelを生成できる場合、そのModel # ==== Raise # [Diva::NotImplementedError] # このModelでは、find_by_uriが実装されていない # [Diva::ModelNotFoundError] # _uri_ に対応するリソースが見つからなかった def find_by_uri(uri) raise Diva::NotImplementedError, "#{self}.find_by_uri does not implement." end # Modelが生成・更新された時に呼ばれるコールバックメソッドです def store_datum(retriever); end def container_class Array end end diva-0.3.1/lib/diva/uri.rb0000644000004100000410000000550513224664463015322 0ustar www-datawww-data# -*- coding: utf-8 -*- =begin rdoc =Model用のURIクラス mikutterでは、 URI や Addressable::URI の代わりに、このクラスを使用します。 URI や Addressable::URI に比べて、次のような特徴があります。 * コンストラクタに文字列を渡している場合、 _to_s_ がその文字列を返す。 * 正規化しないかわりに高速に動作します。 * Diva::URI のインスタンスは URI と同じように使える * unicode文字などが入っていて URI では表現できない場合、 Addressable::URI を使う * Addressable::URIでないと表現できないURIであればそちらを使うという判断を自動で行う == 使い方 Diva::URI() メソッドの引数にString, URI, Addressable::URI, Hash, Diva::URIのいずれかを与えます。 [String] uriの文字列(ex: "http://mikutter.hachune.net/") [URI] URI のインスタンス [Addressable::URI] Addressable::URI のインスタンス [Hash] これを引数にURI::Generic.build に渡すのと同じ形式の Hash [Diva::URI] 即座にこれ自身を返す == 例 Diva::URI("http://mikutter.hachune.net/") Diva::URI(URI::Generic.build(scheme: 'http', host: 'mikutter.hachune.net')) =end require 'uri' require 'addressable/uri' class Diva::URI def initialize(uri) case uri.freeze when URI, Addressable::URI @uri = uri when String @uri_string = uri when Hash @uri_hash = uri end end def ==(other) case other when URI, Addressable::URI other == to_uri when Diva::URI if has_string? or other.has_string? to_s == other.to_s else other.to_uri == to_uri end end end def hash to_s.hash ^ self.class.hash end def has_string? !!@uri_string end def has_uri? !!@uri end def to_s @uri_string ||= to_uri.to_s.freeze end def to_uri @uri ||= generate_uri.freeze end def scheme if has_string? and !has_uri? match = @uri_string.match(%r<\A(\w+):>) if match match[1] else to_uri.scheme end else to_uri.scheme end end def freeze unless frozen? to_uri to_s end super end def respond_to?(method) super or to_uri.respond_to?(method) end def method_missing(method, *rest, &block) to_uri.__send__(method, *rest, &block) end private def generate_uri if @uri @uri elsif @uri_string @uri = generate_uri_by_string elsif @uri_hash @uri = generate_uri_by_hash end @uri end def generate_uri_by_string URI.parse(@uri_string) rescue URI::InvalidComponentError Addressable::URI.parse(@uri_string) end def generate_uri_by_hash URI::Generic.build(@uri_hash) rescue URI::InvalidComponentError Addressable::URI.new(@uri_hash) end end diva-0.3.1/lib/diva/spec.rb0000644000004100000410000000015513224664463015451 0ustar www-datawww-data# -*- coding: utf-8 -*- Diva::ModelSpec = Struct.new( :slug, :name, :reply, :myself, :timeline, ) diva-0.3.1/lib/diva/version.rb0000644000004100000410000000004413224664463016201 0ustar www-datawww-datamodule Diva VERSION = "0.3.1" end diva-0.3.1/lib/diva/datasource.rb0000644000004100000410000000166713224664463016662 0ustar www-datawww-data# -*- coding: utf-8 -*- =begin rdoc データの保存/復元を実際に担当するデータソース。 データソースをモデルにModel::add_data_retrieverにて幾つでも参加させることが出来る。 =end module Diva::DataSource USE_ALL = -1 # findbyidの引数。全てのDataSourceから探索する USE_LOCAL_ONLY = -2 # findbyidの引数。ローカルにあるデータのみを使う attr_accessor :keys # idをもつデータを返す。 # もし返せない場合は、nilを返す def findbyid(id, policy) nil end # 取得できたらそのDivaのインスタンスをキーにして実行されるDeferredを返す def idof(id) Thread.new{ findbyid(id) } end alias [] idof # データの保存 # データ一件保存する。保存に成功したか否かを返す。 def store_datum(datum) false end def inspect self.class.to_s end end diva-0.3.1/lib/diva/field.rb0000644000004100000410000000165413224664463015607 0ustar www-datawww-data# -*- coding: utf-8 -*- require 'diva/type' =begin rdoc Modelのキーの情報を格納する。 キーひとつにつき1つのインスタンスが作られる。 =end module Diva class Field attr_reader :name, :type, :required # [name] Symbol フィールドの名前 # [type] Symbol フィールドのタイプ。:int, :string, :bool, :time のほか、Diva::Modelのサブクラスを指定する # [required] boolean _true_ なら、この項目を必須とする def initialize(name, type, required: false) @name = name.to_sym @type = Diva::Type.optional(Diva::Type(type)) @required = !!required end def dump_for_json(value) type.dump_for_json(value) end def required? required end def to_sym name end def to_s name.to_s end def inspect "#<#{self.class}: #{name}(#{type})#{required ? '*' : ''}>" end end end diva-0.3.1/lib/diva/combinator.rb0000644000004100000410000000473613224664463016665 0ustar www-datawww-data# -*- coding: utf-8 -*- module Diva::Combinable class Combinator def initialize(a, b) @a, @b = a, b end # コンストラクタに渡された二つの値が _behavior_ をサポートしていれば真を返す # ==== Args # [behavior] 想定する振る舞い(Symbol) # ==== Return # true or false def =~(behavior) fail 'Not implement' end def ===(behavior) self =~ behavior end # このCombinatorが持っている値のすべての組み合わせのうち、適切に _behavior_ # をサポートしている組み合わせについて繰り返すEnumeratorを返す # ==== Args # [behavior] 想定する振る舞い(Symbol) # ==== Return # [レシーバ, 引数] のペアを列挙するEnumerator def enum(behavior) Enumerator.new do |yielder| yielder << [@a, @b] if a_b(behavior) yielder << [@b, @a] if b_a(behavior) end end def method_missing(behavior, *rest) if rest.empty? return self =~ behavior end super end private def a_b(behavior) @a.respond_to?(behavior) && @a.__send__(behavior, @b) end def b_a(behavior) @b.respond_to?(behavior) && @b.__send__(behavior, @a) end end class PluralCombinator < Combinator def enum(behavior) Enumerator.new do |yielder| @b.each do |b| (b | @a).enum(behavior).each(&yielder.method(:<<)) end end end end class SingleCombinator < Combinator def =~(behavior) a_b(behavior) || b_a(behavior) end end class AnyCombinator < PluralCombinator def =~(behavior) @b.any?{|b| b | @a =~ behavior } end end class AllCombinator < PluralCombinator def =~(behavior) @b.all?{|b| b | @a =~ behavior } end end # _other_ との Diva::Conbinable::Combinator を生成する # ==== Args # [other] Diva::Modelか、Diva::Modelを列挙するEnumerable # ==== Return # Diva::Conbinable::Combinator def |(other) if other.is_a? Enumerable AnyCombinator.new(self, other) else SingleCombinator.new(self, other) end end # _other_ との Diva::Conbinable::Combinator を生成する # ==== Args # [other] Diva::Modelか、Diva::Modelを列挙するEnumerable # ==== Return # Diva::Conbinable::Combinator def &(other) if other.is_a? Enumerable AllCombinator.new(self, other) else SingleCombinator.new(self, other) end end end diva-0.3.1/lib/diva/type.rb0000644000004100000410000001207013224664463015477 0ustar www-datawww-data# -*- coding: utf-8 -*- =begin rdoc Modelの各キーに格納できる値の制約。 この制約に満たない場合は、アトミックな制約であれば値の変換が行われ、そうでない場合は Diva::InvalidTypeError 例外を投げる。 これは新しくインスタンスなどを作らず、INT、FLOATなどのプリセットを利用する。 == 設定できる制約 Modelフィールドの制約には以下のようなものがある。 === アトミックな制約 以下のような値は、DivaのModelフィールドにおいてはアトミックな制約と呼び、そのまま格納できる。 [INT] 数値(Integer) [FLOAT] 少数(Float) [BOOL] 真理値(true|false) [STRING] 文字列(String) [TIME] 時刻(Time) [URI] URI(Diva::URI|URI::Generic|Addressable::URI) === Model Diva::Modelのサブクラスであれば、それを制約とすることができる。 === 配列 アトミックな制約またはModel制約を満たした値の配列を格納することができる。 配列の全ての要素が設定された制約を満たしていれば、配列制約が満たされたことになる。 =end require "time" module Diva::Type extend self def model_of(model) ModelType.new(model) end def array_of(type) ArrayType.new(type) end def optional(type) OptionalType.new(type) end # 全てのType共通のスーパークラス class MetaType attr_reader :name def initialize(name, *rest, &cast) @name = name.to_sym if cast define_singleton_method :cast, &cast end end def cast(value) value end def to_s name.to_s end def dump_for_json(value) value end def inspect "Diva::Type(#{to_s})" end end class AtomicType < MetaType end INT = AtomicType.new(:int) do |v| case v when Integer v when Numeric, String, Time v.to_i when TrueClass 1 when FalseClass 0 else raise Diva::InvalidTypeError, "The value is not a `#{name}'." end end FLOAT = AtomicType.new(:float) do |v| case v when Float v when Numeric, String, Time v.to_f else raise Diva::InvalidTypeError, "The value is not a `#{name}'." end end BOOL = AtomicType.new(:bool) do |v| case v when TrueClass, FalseClass v when String !v.empty? when Integer v != 0 else raise Diva::InvalidTypeError, "The value is not a `#{name}'." end end STRING = AtomicType.new(:string) do |v| case v when Diva::Model, Enumerable raise Diva::InvalidTypeError, "The value is not a `#{name}'." else v.to_s end end class TimeType < AtomicType def dump_for_json(value) cast(value).iso8601 end end TIME = TimeType.new(:time) do |v| case v when Time v when Integer, Float Time.at(v) when String Time.iso8601(v) else raise Diva::InvalidTypeError, "The value is not a `#{name}'." end end URI = AtomicType.new(:uri) do |v| case v when Diva::URI, Addressable::URI, ::URI::Generic v when String Diva::URI.new(v) else raise Diva::InvalidTypeError, "The value is not a `#{name}'." end end class ModelType < MetaType attr_reader :model def initialize(model, *rest, &cast) super(:model, *rest) @model = model end def cast(value) case value when model value when Hash model.new(value) else raise Diva::InvalidTypeError, "The value #{value.inspect} is not a `#{model}'." end end def to_s "#{model} #{name}" end end class ArrayType < MetaType def initialize(type) type = Diva::Type(type) super("#{type.name}_array") @type = type end def cast(value) raise Diva::InvalidTypeError, "The value is not a `#{name}'." unless value.is_a?(Enumerable) value.to_a.map(&@type.method(:cast)) end def dump_for_json(value) value.to_a.map(&@type.method(:dump_for_json)) end def to_s "Array of #{@type.to_s}" end end class OptionalType < MetaType def initialize(type) super("optional_#{type.name}") @type = type end def cast(value) if value.nil? value else @type.cast(value) end end def dump_for_json(value) if value.nil? value else @type.dump_for_json(value) end end def to_s "#{@type.to_s}|nil" end end end module Diva def self.Type(type) case type when Diva::Type::MetaType type when :int Diva::Type::INT when :float Diva::Type::FLOAT when :bool Diva::Type::BOOL when :string Diva::Type::STRING when :time Diva::Type::TIME when :uri Diva::Type::URI when ->x{x.class == Class && x.ancestors.include?(Diva::Model) } Diva::Type.model_of(type) when Array Diva::Type.array_of(type.first) else fail "Invalid type #{type.inspect} (#{type.class})." end end end diva-0.3.1/lib/diva/error.rb0000644000004100000410000000123113224664463015644 0ustar www-datawww-data# -*- coding: utf-8 -*- module Diva class DivaError < StandardError; end class InvalidTypeError < DivaError; end class InvalidEntityError < DivaError; end # 実装してもしなくてもいいメソッドが実装されておらず、結果を得られない class NotImplementedError < DivaError; end # IDやURIなどの一意にリソースを特定する情報を使ってデータソースに問い合わせたが、 # 対応する情報が見つからず、Modelを作成できない class ModelNotFoundError < DivaError; end # URIとして受け付けられない値を渡された class InvalidURIError < InvalidTypeError; end end diva-0.3.1/lib/diva/field_generator.rb0000644000004100000410000000152113224664463017646 0ustar www-datawww-data# -*- coding: utf-8 -*- class Diva::FieldGenerator def initialize(model_klass) @model_klass = model_klass end def int(field_name, required: false) @model_klass.add_field(field_name, type: :int, required: required) end def string(field_name, required: false) @model_klass.add_field(field_name, type: :string, required: required) end def bool(field_name, required: false) @model_klass.add_field(field_name, type: :bool, required: required) end def time(field_name, required: false) @model_klass.add_field(field_name, type: :time, required: required) end def uri(field_name, required: false) @model_klass.add_field(field_name, type: :uri, required: required) end def has(field_name, type, required: false) @model_klass.add_field(field_name, type: type, required: required) end end diva-0.3.1/lib/diva.rb0000644000004100000410000000043613224664463014521 0ustar www-datawww-data# coding: utf-8 require 'diva/version' require 'diva/datasource' require 'diva/error' require 'diva/field_generator' require 'diva/field' require 'diva/model' require 'diva/spec' require 'diva/type' require 'diva/uri' require 'diva/version' module Diva # Your code goes here... end diva-0.3.1/.gitignore0000644000004100000410000000014013224664463014463 0ustar www-datawww-data/.bundle/ /.yardoc /Gemfile.lock /_yardoc/ /coverage/ /doc/ /pkg/ /spec/reports/ /tmp/ /vendor/ diva-0.3.1/README.md0000644000004100000410000000247513224664463013767 0ustar www-datawww-data# Diva This library is an implementation of expression for handling things. It replaces Retriever module of mikutter. Diva::Model is a common interface of all resources handled by mikutter. By handling data as a subclass of Diva::Model as necessary, you can obtain a common interface and it is useful for cooperation among mikutter plugins. ## Installation Add this line to your application's Gemfile: ```ruby gem 'diva' ``` And then execute: $ bundle Or install it yourself as: $ gem install diva ## Usage TODO: Write usage instructions here ## Development After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment. To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org). ## Contributing Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/diva. ## License The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).