lapis-common 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/.travis.yml ADDED
@@ -0,0 +1,5 @@
1
+ language: ruby
2
+ rvm:
3
+ - 2.2
4
+ - jruby-9
5
+ before_install: gem install bundler -v 1.10.6
data/Gemfile ADDED
@@ -0,0 +1,5 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Gem dependencies and information defined in lapis-common.gemspec
4
+ gemspec
5
+
data/LICENSE.md ADDED
@@ -0,0 +1,16 @@
1
+ The MIT License (MIT)
2
+ =====================
3
+
4
+ *Copyright (c) 2016 Lapis MC*
5
+
6
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
7
+ documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
8
+ rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit
9
+ persons to whom the Software is furnished to do so, subject to the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
12
+
13
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
14
+ WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
15
+ COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
16
+ OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,56 @@
1
+ Lapis Common
2
+ ============
3
+
4
+ [![Travis CI](https://travis-ci.org/lapis-mc/common.svg)](https://travis-ci.org/lapis-mc/common)
5
+ [![Code Climate](https://codeclimate.com/github/lapis-mc/common/badges/gpa.svg)](https://codeclimate.com/github/lapis-mc/common)
6
+ [![Test Coverage](https://codeclimate.com/github/lapis-mc/common/badges/coverage.svg)](https://codeclimate.com/github/lapis-mc/common/coverage)
7
+ [![Issue Count](https://codeclimate.com/github/lapis-mc/common/badges/issue_count.svg)](https://codeclimate.com/github/lapis-mc/common)
8
+ [![Documentation](https://inch-ci.org/github/lapis-mc/common.svg?branch=master)](http://www.rubydoc.info/github/lapis-mc/common/master)
9
+
10
+ Shared library and utilities for Lapis modules.
11
+
12
+ Installation
13
+ ------------
14
+
15
+ Add this line to your application's Gemfile:
16
+
17
+ ```ruby
18
+ gem 'lapis-common'
19
+ ```
20
+
21
+ Or to your gemspec:
22
+
23
+ ```ruby
24
+ spec.add_dependency 'lapis-common'
25
+ ```
26
+
27
+ And then execute:
28
+
29
+ $ bundle
30
+
31
+ Or install it yourself as:
32
+
33
+ $ gem install lapis-common
34
+
35
+ Usage
36
+ -----
37
+
38
+ TODO
39
+
40
+ Development
41
+ -----------
42
+
43
+ After checking out the repo, run `bin/setup` to install dependencies.
44
+ Then, run `bundle exec rake test` to run the tests.
45
+ It's recommended that you run `bundle exec rake inspect` to run inspections.
46
+ You can also run `bin/console` for an interactive prompt that will allow you to experiment.
47
+ `bundle exec rake doc` will generate documentation.
48
+
49
+ To install this gem onto your local machine, run `bundle exec rake install`.
50
+ To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`.
51
+ This will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
52
+
53
+ Contributing
54
+ ------------
55
+
56
+ Bug reports and pull requests are welcome on [GitHub](https://github.com/lapis-mc/common).
data/Rakefile ADDED
@@ -0,0 +1,28 @@
1
+ require 'bundler/gem_tasks'
2
+ require 'reek/rake/task'
3
+ require 'rubocop/rake_task'
4
+ require 'rubocop'
5
+ require 'rspec/core/rake_task'
6
+ require 'yard'
7
+
8
+ task :default => [:build, :test]
9
+ task :inspect => [:reek, :rubocop]
10
+
11
+ RSpec::Core::RakeTask.new(:spec)
12
+ task :test => :spec
13
+
14
+ Reek::Rake::Task.new do |task| # :reek
15
+ task.name = :reek
16
+ task.source_files = 'lib/**/*.rb'
17
+ task.fail_on_error = false
18
+ end
19
+
20
+ RuboCop::RakeTask.new(:rubocop) do |task|
21
+ task.patterns = ['lib/**/*.rb']
22
+ task.fail_on_error = false
23
+ end
24
+
25
+ YARD::Rake::YardocTask.new do |task| # :yard
26
+ task.files = %w(lib/**/*.rb - *.md)
27
+ end
28
+ task :doc => :yard
data/bin/console ADDED
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'bundler/setup'
4
+ require 'lapis'
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ # (If you use this, don't forget to add pry to your Gemfile!)
10
+ # require "pry"
11
+ # Pry.start
12
+
13
+ require 'irb'
14
+ IRB.start
data/bin/setup ADDED
@@ -0,0 +1,7 @@
1
+ #!/bin/bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+
5
+ bundle install --path=vendor/bundle
6
+
7
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,31 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'lapis/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = 'lapis-common'
8
+ spec.version = Lapis::VERSION
9
+ spec.authors = ['Michael Miller']
10
+ spec.email = ['bluepixelmike@gmail.com']
11
+
12
+ spec.summary = %q{Shared library and utilities for Lapis modules}
13
+ spec.description = %q{Core classes and methods used by various Lapis modules.}
14
+ spec.homepage = 'https://github.com/lapis-mc/common'
15
+ spec.license = 'MIT'
16
+
17
+ spec.files = `git ls-files -z`.split("\x0")
18
+ spec.bindir = 'bin'
19
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
20
+ spec.executables = spec.files.grep(%r{^bin/(lapis|minecraft)-}) { |f| File.basename(f) }
21
+ spec.require_paths = ['lib']
22
+
23
+ spec.add_development_dependency 'bundler'
24
+ spec.add_development_dependency 'rake', '~> 10.0'
25
+ spec.add_development_dependency 'rspec', '~> 3.0'
26
+ spec.add_development_dependency 'factory_girl', '~> 4.0'
27
+ spec.add_development_dependency 'reek', '~> 3.0'
28
+ spec.add_development_dependency 'rubocop', '~> 0.38.0'
29
+ spec.add_development_dependency 'codeclimate-test-reporter' unless RUBY_PLATFORM == 'java'
30
+ spec.add_development_dependency 'yard'
31
+ end
data/lib/lapis/uuid.rb ADDED
@@ -0,0 +1,255 @@
1
+ require 'securerandom' # For generating new UUIDs.
2
+
3
+ module Lapis
4
+
5
+ # Universally unique identifier.
6
+ # Items with distinct different UUIDs should be different entities.
7
+ # Items with the same UUID should be the same entity.
8
+ # @note The naming scheme used for this class is as follows:
9
+ # * +uuid+ - An instance of this class.
10
+ # * +value+ - Packed string, not human readable. 16 bytes in length.
11
+ # * +uuid_str+ - Formatted string, human readable. Formatted as xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx (8-4-4-4-12).
12
+ class Uuid
13
+ include Comparable
14
+
15
+ # Raw, packed byte string containing the UUID's value.
16
+ # @return [String] Packed string, 16 bytes in length.
17
+ attr_reader :value
18
+
19
+ # Creates a UUID or one from an existing value.
20
+ # @param value [String] Packed bytes with the UUID's value.
21
+ # The value must be a string of 16 bytes (128 bits).
22
+ # @see generate
23
+ def initialize(value)
24
+ value_str = value.to_s
25
+ fail ArgumentError, 'Packed UUID value must be 16 bytes.' unless value_str.length == 16
26
+
27
+ # Get a copy to prevent external processes modifying the value.
28
+ @value = value_str.dup
29
+
30
+ # Prevent modification.
31
+ @value.freeze
32
+ end
33
+
34
+ class << self
35
+
36
+ # Generates a new (and random) UUID
37
+ # @return [UUID] Newly generated UUID.
38
+ def generate
39
+ # A built-in method from Ruby to generate a valid UUID is SecureRandom.uuid.
40
+ # However, it returns it as a formatted string.
41
+ # The formatted string has to be converted to a packed string before it can be used.
42
+ uuid_str = SecureRandom.uuid
43
+ value = pack_uuid_str(uuid_str)
44
+ Uuid.new(value)
45
+ end
46
+
47
+ # Determines whether a string contains a valid formatted UUID.
48
+ # @param uuid_str [String] String to check.
49
+ # @return [true] The string contains a valid formatted UUID.
50
+ # @return [false] The string does not contain a valid UUID.
51
+ def valid_str?(uuid_str)
52
+ if uuid_str.is_a? String
53
+ # Check the formatting.
54
+ # Note that the validity of the UUID isn't checked.
55
+ !!/^[0-9a-f]{8}(-?)[0-9a-f]{4}\1[0-9a-f]{4}\1[0-9a-f]{4}\1[0-9a-f]{12}$/i.match(uuid_str)
56
+ else
57
+ # A string wasn't passed in.
58
+ fail ArgumentError
59
+ end
60
+ end
61
+
62
+ # Converts a UUID formatted as a string into its packed byte representation.
63
+ # @param uuid_str [String] UUID in string form.
64
+ # @return [String] Packed string, 16 bytes in length.
65
+ # @!visibility private
66
+ def pack_uuid_str(uuid_str)
67
+ # 1) Strip hyphens.
68
+ # 2) Collect 2 characters.
69
+ # 3) Convert 2 characters from hex to numeric.
70
+ # 4) Pack the numeric values into bytes in a string.
71
+ uuid_str.delete('-').scan(/../).map(&:hex).pack('C16')
72
+ end
73
+
74
+ # Creates a UUID object from its string representation.
75
+ # @param uuid_str [String] UUID in string form.
76
+ # The string must be in the form: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx (8-4-4-4-12)
77
+ # @note Casing does not matter for a-f.
78
+ # @return [Uuid] Parsed UUID from the string.
79
+ # @return [nil] The string doesn't have a properly formatted UUID.
80
+ # @note Dashes may be omitted from the string.
81
+ # This allows strings of 32 hexadecimal characters.
82
+ def parse(uuid_str)
83
+ if valid_str?(uuid_str)
84
+ # Properly formatted UUID string.
85
+ # Pack the UUID into a byte string and return a new instance.
86
+ value = pack_uuid_str(uuid_str)
87
+ Uuid.new(value)
88
+ else
89
+ # Not properly formatted.
90
+ nil
91
+ end
92
+ end
93
+ end
94
+
95
+ # Checks for hash equality of two UUIDs.
96
+ # @param other [Uuid] Other UUID to compare against.
97
+ # @return [true] The UUIDs are equal.
98
+ # @return [false] The UUIDs are different.
99
+ # @note This method compares only {Uuid} instances.
100
+ # +false+ will be returned if +other+ is not a {Uuid}.
101
+ def eql?(other)
102
+ other.is_a?(Uuid) && eq_packed(other.value)
103
+ end
104
+
105
+ # Checks if two UUIDs have the same value.
106
+ # @param other [Uuid, String] Other object to check against.
107
+ # @return [true] The UUIDs are equal.
108
+ # @return [false] The UUIDs are different.
109
+ def ==(other)
110
+ case other
111
+ when Uuid
112
+ # Compare two UUID instances.
113
+ eq_packed(other.value)
114
+
115
+ when String
116
+ if other.length == 16
117
+ # Compare against a packed string
118
+ eq_packed(other)
119
+
120
+ else
121
+ # Compare against a formatted string
122
+ eq_formatted(other)
123
+ end
124
+
125
+ else
126
+ # Everything else can't be equated.
127
+ false
128
+ end
129
+ end
130
+
131
+ # Compares two UUIDs to each other to determine which is lower.
132
+ # @param other [Uuid, String] Other object to compare against.
133
+ # @return [-1] The left UUID is smaller.
134
+ # @return [0] The UUIDs are the same.
135
+ # @return [1] The right UUID is smaller.
136
+ # @return [nil] The right side isn't a UUID.
137
+ def <=>(other)
138
+ case other
139
+ when Uuid
140
+ # Compare two UUID instances.
141
+ cmp_packed(other.value)
142
+
143
+ when String
144
+ if other.length == 16
145
+ # Compare against a packed string
146
+ cmp_packed(other)
147
+
148
+ else
149
+ # Compare against a formatted string
150
+ cmp_formatted(other)
151
+ end
152
+
153
+ else
154
+ # Everything else can't be compared.
155
+ nil
156
+ end
157
+ end
158
+
159
+ # Produces a hash value of the UUID.
160
+ # @return [Fixnum]
161
+ def hash
162
+ @value.hash
163
+ end
164
+
165
+ # Produces the string representation of the UUID.
166
+ # @param dashes [Boolean] +true+ to put dashes in the output (standard format),
167
+ # +false+ to not put dashes.
168
+ # @return [String] UUID in string form: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx (8-4-4-4-12)
169
+ def to_s(dashes = true)
170
+ bytes = @value.bytes
171
+ if dashes
172
+ sprintf('%02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-%02x%02x%02x%02x%02x%02x', *bytes)
173
+ else
174
+ sprintf('%02x' * 16, *bytes)
175
+ end
176
+ end
177
+
178
+ # Empty UUID.
179
+ # @return [Uuid] UUID with the value 00000000-0000-0000-0000-000000000000.
180
+ DEFAULT = Uuid.new("\x0" * 16).freeze
181
+
182
+ private
183
+
184
+ # Checks for equality with a packed string.
185
+ # @param value [String] Packed string.
186
+ # @return [true] The packed string contains an identical value.
187
+ # @return [false] The packed string contains a different value.
188
+ def eq_packed(value)
189
+ # Compare the bytes in each string.
190
+ # Byte comparison *must* be used, since == uses character comparison.
191
+ # Bytes != characters when encoding is involved.
192
+ @value.each_byte.with_index do |byte_a, index|
193
+ byte_b = value.getbyte(index)
194
+ break false if byte_a != byte_b # Abort enumeration with false value.
195
+ break true if index == 15 # Return true on last iteration when bytes are the same.
196
+ end
197
+ end
198
+
199
+ # Checks for equality with a UUID formatted as a string.
200
+ # @param uuid_str [String] UUID formatted as a string.
201
+ # @return [true] The formatted string contains an identical value.
202
+ # @return [false] The formatted string contains a different value.
203
+ def eq_formatted(uuid_str)
204
+ klass = self.class
205
+ if klass.valid_str?(uuid_str)
206
+ # Format is valid.
207
+ # Compare it to packed string.
208
+ value = klass.pack_uuid_str(uuid_str)
209
+ eq_packed(value)
210
+ else
211
+ # Invalid format can't be equal.
212
+ false
213
+ end
214
+ end
215
+
216
+ # Compares the ordering with a packed string.
217
+ # @param value [String] Packed string.
218
+ # @return [-1] The left UUID is smaller.
219
+ # @return [0] The UUIDs are the same.
220
+ # @return [1] The right UUID is smaller.
221
+ # @return [nil] The right side isn't a UUID.
222
+ def cmp_packed(value)
223
+ # Compare the bytes in each string.
224
+ # Byte comparison *must* be used, since <=> uses character comparison.
225
+ # Bytes != characters when encoding is involved.
226
+ @value.bytes.each_with_index do |byte_a, index|
227
+ byte_b = value.getbyte(index)
228
+ result = byte_a <=> byte_b
229
+ break result if result != 0 # Abort enumeration with result.
230
+ break 0 if index == 15 # Return 0 on last byte if they're the same.
231
+ end
232
+ end
233
+
234
+ # Checks for equality with a UUID formatted as a string.
235
+ # @param uuid_str [String] UUID formatted as a string.
236
+ # @return [-1] The left UUID is smaller.
237
+ # @return [0] The UUIDs are the same.
238
+ # @return [1] The right UUID is smaller.
239
+ # @return [nil] The right side isn't a UUID.
240
+ def cmp_formatted(uuid_str)
241
+ klass = self.class
242
+ if klass.valid_str?(uuid_str)
243
+ # Format is valid.
244
+ # Compare it to packed string.
245
+ value = klass.pack_uuid_str(uuid_str)
246
+ cmp_packed(value)
247
+ else
248
+ # Invalid format can't be equal.
249
+ nil
250
+ end
251
+ end
252
+
253
+ end
254
+
255
+ end
@@ -0,0 +1,3 @@
1
+ module Lapis
2
+ VERSION = '0.1.0'
3
+ end
data/lib/lapis.rb ADDED
@@ -0,0 +1,6 @@
1
+ # Custom Minecraft utilities.
2
+ module Lapis
3
+ end
4
+
5
+ require_relative 'lapis/version'
6
+ require_relative 'lapis/uuid'
@@ -0,0 +1,43 @@
1
+ require 'securerandom'
2
+ require 'lapis/uuid'
3
+
4
+ FactoryGirl.define do
5
+ factory :uuid_str, class: String do
6
+ trait :no_dashes do
7
+ after(:build) do |uuid_str|
8
+ uuid_str.delete!('-')
9
+ end
10
+ end
11
+
12
+ initialize_with { SecureRandom.uuid }
13
+ end
14
+
15
+ factory :uuid_value, class: String do
16
+ transient do
17
+ association :uuid_str, :strategy => :build
18
+ end
19
+
20
+ initialize_with { uuid_str.delete('-').scan(/../).map(&:hex).pack('C*') }
21
+ end
22
+
23
+ factory :uuid, class: Lapis::Uuid do
24
+ transient do
25
+ source nil
26
+ end
27
+
28
+ initialize_with do
29
+ case source
30
+ when nil
31
+ Lapis::Uuid.generate
32
+ when Lapis::Uuid
33
+ Lapis::Uuid.new(source.value)
34
+ else
35
+ if source.length == 16
36
+ Lapis::Uuid.new(source)
37
+ else
38
+ Lapis::Uuid.new(build(:uuid_value, :uuid_str => source))
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end