kamerling 0.0.1

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.
Files changed (65) hide show
  1. checksums.yaml +7 -0
  2. data/.rubocop.yml +50 -0
  3. data/.ruby-version +1 -0
  4. data/Gemfile +2 -0
  5. data/Gemfile.lock +90 -0
  6. data/LICENCE +661 -0
  7. data/README.md +6 -0
  8. data/Rakefile +18 -0
  9. data/bin/kamerling +9 -0
  10. data/config/reek.yml +18 -0
  11. data/kamerling.gemspec +30 -0
  12. data/lib/kamerling/addr.rb +18 -0
  13. data/lib/kamerling/client.rb +2 -0
  14. data/lib/kamerling/core_extensions/main.rb +17 -0
  15. data/lib/kamerling/core_extensions.rb +1 -0
  16. data/lib/kamerling/handler.rb +24 -0
  17. data/lib/kamerling/http_api.rb +38 -0
  18. data/lib/kamerling/logging.rb +20 -0
  19. data/lib/kamerling/message.rb +47 -0
  20. data/lib/kamerling/migrations/1_basic_schema.rb +45 -0
  21. data/lib/kamerling/net_dispatcher.rb +8 -0
  22. data/lib/kamerling/project.rb +2 -0
  23. data/lib/kamerling/receiver.rb +11 -0
  24. data/lib/kamerling/registrar.rb +9 -0
  25. data/lib/kamerling/registration.rb +2 -0
  26. data/lib/kamerling/repo.rb +32 -0
  27. data/lib/kamerling/repos.rb +65 -0
  28. data/lib/kamerling/result.rb +2 -0
  29. data/lib/kamerling/server/http.rb +26 -0
  30. data/lib/kamerling/server/sock.rb +32 -0
  31. data/lib/kamerling/server/tcp.rb +19 -0
  32. data/lib/kamerling/server/udp.rb +20 -0
  33. data/lib/kamerling/server_runner.rb +50 -0
  34. data/lib/kamerling/task.rb +2 -0
  35. data/lib/kamerling/task_dispatcher.rb +29 -0
  36. data/lib/kamerling/uuid.rb +17 -0
  37. data/lib/kamerling/uuid_object.rb +79 -0
  38. data/lib/kamerling/views/clients.slim +6 -0
  39. data/lib/kamerling/views/layout.slim +6 -0
  40. data/lib/kamerling/views/project.slim +11 -0
  41. data/lib/kamerling/views/projects.slim +9 -0
  42. data/lib/kamerling/views/root.slim +6 -0
  43. data/lib/kamerling.rb +29 -0
  44. data/spec/kamerling/addr_spec.rb +27 -0
  45. data/spec/kamerling/client_spec.rb +9 -0
  46. data/spec/kamerling/core_extensions/main_spec.rb +18 -0
  47. data/spec/kamerling/handler_spec.rb +30 -0
  48. data/spec/kamerling/http_api_spec.rb +93 -0
  49. data/spec/kamerling/logging_spec.rb +73 -0
  50. data/spec/kamerling/message_spec.rb +70 -0
  51. data/spec/kamerling/net_dispatcher_spec.rb +21 -0
  52. data/spec/kamerling/receiver_spec.rb +22 -0
  53. data/spec/kamerling/registrar_spec.rb +16 -0
  54. data/spec/kamerling/repo_spec.rb +60 -0
  55. data/spec/kamerling/repos_spec.rb +136 -0
  56. data/spec/kamerling/server/http_spec.rb +22 -0
  57. data/spec/kamerling/server/tcp_spec.rb +46 -0
  58. data/spec/kamerling/server/udp_spec.rb +44 -0
  59. data/spec/kamerling/server_runner_spec.rb +65 -0
  60. data/spec/kamerling/task_dispatcher_spec.rb +23 -0
  61. data/spec/kamerling/task_spec.rb +9 -0
  62. data/spec/kamerling/uuid_object_spec.rb +101 -0
  63. data/spec/kamerling/uuid_spec.rb +24 -0
  64. data/spec/spec_helper.rb +26 -0
  65. metadata +325 -0
data/Rakefile ADDED
@@ -0,0 +1,18 @@
1
+ require 'rake/testtask'
2
+ require 'reek/rake/task'
3
+ require 'rubocop/rake_task'
4
+
5
+ task default: %i[spec rubocop reek]
6
+
7
+ Rake::TestTask.new :spec do |task|
8
+ task.test_files = FileList['spec/**/*_spec.rb']
9
+ task.warning = true
10
+ end
11
+
12
+ Reek::Rake::Task.new do |task|
13
+ task.config_files = 'config/reek.yml'
14
+ task.fail_on_error = false
15
+ task.reek_opts = '--quiet'
16
+ end
17
+
18
+ Rubocop::RakeTask.new
data/bin/kamerling ADDED
@@ -0,0 +1,9 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require_relative '../lib/kamerling'
4
+
5
+ Thread.abort_on_exception = true
6
+
7
+ Kamerling::Logging.log_to Logger.new $stdout
8
+
9
+ Kamerling::ServerRunner.new(ARGV).start.join
data/config/reek.yml ADDED
@@ -0,0 +1,18 @@
1
+ DuplicateMethodCall:
2
+ exclude:
3
+ - Kamerling#self.class_definition_from
4
+ - Kamerling::Handler#handle
5
+
6
+ IrresponsibleModule:
7
+ enabled: false
8
+
9
+ NestedIterators:
10
+ max_allowed_nesting: 2
11
+
12
+ UncommunicativeMethodName:
13
+ exclude:
14
+ - Kamerling#self.UUIDObject
15
+
16
+ UnusedParameters:
17
+ exclude:
18
+ - Kamerling::Server::HTTP#initialize
data/kamerling.gemspec ADDED
@@ -0,0 +1,30 @@
1
+ Gem::Specification.new do |gem|
2
+ gem.author = 'Piotr Szotkowski'
3
+ gem.description = 'A network server for distributing computations across different clients speaking TCP or UDP.'
4
+ gem.email = 'p.szotkowski@tele.pw.edu.pl'
5
+ gem.homepage = 'https://github.com/chastell/kamerling'
6
+ gem.license = 'AGPL-3.0'
7
+ gem.name = 'kamerling'
8
+ gem.summary = 'Kamerling: a computation network server'
9
+ gem.version = '0.0.1'
10
+
11
+ gem.files = `git ls-files -z`.split "\0"
12
+ gem.executables = gem.files.grep(%r{^bin/}).map { |path| File.basename path }
13
+ gem.test_files = gem.files.grep %r{^spec/.*\.rb$}
14
+
15
+ gem.add_dependency 'after_do', '~> 0.3.0'
16
+ gem.add_dependency 'sequel', '~> 4.4'
17
+ gem.add_dependency 'sinatra', '~> 1.4'
18
+ gem.add_dependency 'slim', '~> 2.0'
19
+ gem.add_dependency 'sqlite3', '~> 1.3'
20
+
21
+ gem.add_development_dependency 'bogus', '~> 0.1.3'
22
+ gem.add_development_dependency 'minitest', '~> 5.0'
23
+ gem.add_development_dependency 'minitest-focus', '~> 1.1'
24
+ gem.add_development_dependency 'nokogiri', '~> 1.6'
25
+ gem.add_development_dependency 'rack-test', '~> 0.6.2'
26
+ gem.add_development_dependency 'rake', '~> 10.1'
27
+ gem.add_development_dependency 'reek', '~> 1.3'
28
+ gem.add_development_dependency 'rerun', '~> 0.8.2'
29
+ gem.add_development_dependency 'rubocop', '~> 0.18.0'
30
+ end
@@ -0,0 +1,18 @@
1
+ module Kamerling
2
+ Addr = Struct.new :host, :port, :prot do
3
+ def connectable?
4
+ TCPSocket.open(*self).close
5
+ true
6
+ rescue Errno::ECONNREFUSED
7
+ false
8
+ end
9
+
10
+ def to_a
11
+ [host, port]
12
+ end
13
+
14
+ def to_s
15
+ "#{host}:#{port} (#{prot})"
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,2 @@
1
+ module Kamerling class Client < UUIDObject :addr, busy: false
2
+ end end
@@ -0,0 +1,17 @@
1
+ module Kamerling module CoreExtensions module Main
2
+ module_function
3
+
4
+ def req param
5
+ method = caller.first[/`(.*)'$/, 1]
6
+ callsite = Class === self ? "#{name}.#{method}" : "#{self.class}##{method}"
7
+ raise "#{callsite}: param #{param} is required"
8
+ end
9
+
10
+ def warn_off
11
+ verbose = $VERBOSE
12
+ $VERBOSE = false
13
+ yield
14
+ ensure
15
+ $VERBOSE = verbose
16
+ end
17
+ end end end
@@ -0,0 +1 @@
1
+ require_relative 'core_extensions/main'
@@ -0,0 +1,24 @@
1
+ module Kamerling class Handler
2
+ UnknownInput = Class.new RuntimeError
3
+
4
+ def initialize receiver: Receiver.new, registrar: Registrar.new
5
+ @receiver, @registrar = receiver, registrar
6
+ end
7
+
8
+ def handle input, addr
9
+ message = Message.new input
10
+ case message.type
11
+ when :RGST
12
+ registrar.register addr: addr, client_uuid: message.client_uuid,
13
+ project_uuid: message.project_uuid
14
+ when :RSLT
15
+ receiver.receive addr: addr, client_uuid: message.client_uuid,
16
+ data: message.payload, task_uuid: message.task_uuid
17
+ end
18
+ rescue Message::UnknownType => exception
19
+ raise UnknownInput, exception.message
20
+ end
21
+
22
+ attr_reader :receiver, :registrar
23
+ private :receiver, :registrar
24
+ end end
@@ -0,0 +1,38 @@
1
+ require 'forwardable'
2
+ require 'sinatra/base'
3
+ require 'slim'
4
+
5
+ module Kamerling class HTTPAPI < Sinatra::Base
6
+ extend Forwardable
7
+
8
+ configure { set repos: Repos }
9
+
10
+ get '/' do
11
+ slim :root
12
+ end
13
+
14
+ get '/clients' do
15
+ warn_off { slim :clients, locals: { clients: repos.clients } }
16
+ end
17
+
18
+ get '/projects' do
19
+ warn_off { slim :projects, locals: { projects: repos.projects } }
20
+ end
21
+
22
+ get '/projects/:project_uuid' do
23
+ project = repos.project params['project_uuid']
24
+ clients = repos.clients_for project
25
+ tasks = repos.tasks_for project
26
+ warn_off { slim :project, locals: { clients: clients, tasks: tasks } }
27
+ end
28
+
29
+ post '/projects' do
30
+ uuid = params.fetch('uuid') { UUID.new }
31
+ repos << Project.new(name: params['name'], uuid: uuid)
32
+ redirect '/projects'
33
+ end
34
+
35
+ private
36
+
37
+ delegate repos: :settings
38
+ end end
@@ -0,0 +1,20 @@
1
+ require 'after_do'
2
+ require 'logger'
3
+
4
+ module Kamerling module Logging
5
+ module_function
6
+
7
+ def log_to logger: Logger.new($stdout)
8
+ Server::Sock.extend AfterDo
9
+ Server::Sock.before :start do |*, server|
10
+ logger.info "start #{server.addr}"
11
+ end
12
+ Server::Sock.before :handle do |input, client_addr|
13
+ logger.info "connect #{client_addr}"
14
+ logger.debug "received #{client_addr} #{input}"
15
+ end
16
+ Server::Sock.after :stop do |*, server|
17
+ logger.info "stop #{server.addr}"
18
+ end
19
+ end
20
+ end end
@@ -0,0 +1,47 @@
1
+ module Kamerling class Message
2
+ KnownTypes = %w[DATA PING RGST RSLT]
3
+ UnknownType = Class.new RuntimeError
4
+
5
+ def self.[](client: req(:client), payload: req(:payload),
6
+ project: req(:project), task: req(:task), type: req(:type))
7
+ new "#{type}\0\0\0\0\0\0\0\0\0\0\0\0" + UUID.bin(client.uuid) +
8
+ UUID.bin(project.uuid) + UUID.bin(task.uuid) + payload
9
+ end
10
+
11
+ def initialize raw
12
+ @raw = raw
13
+ type = raw[0..3]
14
+ raise UnknownType, type unless KnownTypes.include? type or type.empty?
15
+ end
16
+
17
+ def == other
18
+ raw == other.raw
19
+ end
20
+
21
+ def client_uuid
22
+ UUID[raw[16..31]]
23
+ end
24
+
25
+ def payload
26
+ raw[64..-1]
27
+ end
28
+
29
+ def project_uuid
30
+ UUID[raw[32..47]]
31
+ end
32
+
33
+ def task_uuid
34
+ UUID[raw[48..63]]
35
+ end
36
+
37
+ def to_s
38
+ raw
39
+ end
40
+
41
+ def type
42
+ raw[0..3].to_sym
43
+ end
44
+
45
+ attr_reader :raw
46
+ private :raw
47
+ end end
@@ -0,0 +1,45 @@
1
+ Sequel.migration do
2
+ change do
3
+ create_table :clients do
4
+ uuid :uuid, primary_key: true
5
+ boolean :busy, null: false
6
+ inet :host, null: false
7
+ integer :port, null: false
8
+ string :prot, null: false
9
+ end
10
+
11
+ create_table :projects do
12
+ uuid :uuid, primary_key: true
13
+ string :name, null: false
14
+ end
15
+
16
+ create_table :registrations do
17
+ uuid :uuid, primary_key: true
18
+ inet :host, null: false
19
+ integer :port, null: false
20
+ string :prot, null: false
21
+ foreign_key :client_uuid, :clients, index: true, null: false,
22
+ type: :uuid
23
+ foreign_key :project_uuid, :projects, index: true, null: false,
24
+ type: :uuid
25
+ end
26
+
27
+ create_table :results do
28
+ uuid :uuid, primary_key: true
29
+ bytea :data, null: false
30
+ inet :host, null: false
31
+ integer :port, null: false
32
+ string :prot, null: false
33
+ foreign_key :client_uuid, :clients, index: true, null: false, type: :uuid
34
+ foreign_key :task_uuid, :tasks, index: true, null: false, type: :uuid
35
+ end
36
+
37
+ create_table :tasks do
38
+ uuid :uuid, primary_key: true
39
+ boolean :done, null: false
40
+ bytea :data, null: false
41
+ foreign_key :project_uuid, :projects, index: true, null: false,
42
+ type: :uuid
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,8 @@
1
+ module Kamerling class NetDispatcher
2
+ def dispatch addr, bytes
3
+ case addr.prot
4
+ when :TCP then TCPSocket.open(*addr) { |socket| socket << bytes }
5
+ when :UDP then UDPSocket.new.send bytes, 0, *addr
6
+ end
7
+ end
8
+ end end
@@ -0,0 +1,2 @@
1
+ module Kamerling class Project < UUIDObject :name
2
+ end end
@@ -0,0 +1,11 @@
1
+ module Kamerling class Receiver
2
+ def receive addr: req(:addr), client_uuid: req(:client_uuid),
3
+ data: req(:data), repos: Repos, task_uuid: req(:task_uuid)
4
+ client = repos[Client][client_uuid]
5
+ task = repos[Task][task_uuid]
6
+ result = Result.new addr: addr, client: client, data: data, task: task
7
+ client.busy = false
8
+ task.done = true
9
+ repos << result << client << task
10
+ end
11
+ end end
@@ -0,0 +1,9 @@
1
+ module Kamerling class Registrar
2
+ def register addr: req(:addr), client_uuid: req(:client_uuid),
3
+ project_uuid: req(:project_uuid), repos: Repos
4
+ client = repos[Client][client_uuid]
5
+ project = repos[Project][project_uuid]
6
+ reg = Registration.new addr: addr, client: client, project: project
7
+ repos[Registration] << reg
8
+ end
9
+ end end
@@ -0,0 +1,2 @@
1
+ module Kamerling class Registration < UUIDObject :addr, :client, :project
2
+ end end
@@ -0,0 +1,32 @@
1
+ module Kamerling class Repo
2
+ NotFound = Class.new RuntimeError
3
+
4
+ def initialize klass, source
5
+ @klass, @source = klass, source
6
+ end
7
+
8
+ def << object
9
+ hash = object.to_h
10
+ warn_off { source << hash }
11
+ rescue Sequel::UniqueConstraintViolation
12
+ warn_off { source.where(uuid: object.uuid).update hash }
13
+ end
14
+
15
+ def [] uuid
16
+ hash = warn_off { source[uuid: uuid] }
17
+ raise NotFound, "#{klass} with UUID #{uuid}" unless hash
18
+ klass.from_h hash
19
+ end
20
+
21
+ def all
22
+ source.all.map { |hash| klass.from_h hash }
23
+ end
24
+
25
+ def related_to object
26
+ key = "#{object.class.name.split('::').last.downcase}_uuid".to_sym
27
+ source.where(key => object.uuid).map { |hash| klass.from_h hash }
28
+ end
29
+
30
+ attr_reader :klass, :source
31
+ private :klass, :source
32
+ end end
@@ -0,0 +1,65 @@
1
+ warn_off { require 'sequel' }
2
+
3
+ Sequel.extension :migration
4
+
5
+ module Kamerling class Repos
6
+ class << self
7
+ attr_writer :repos
8
+
9
+ def << object
10
+ repos[object.class] << object
11
+ self
12
+ end
13
+
14
+ def [] klass
15
+ repos[klass]
16
+ end
17
+
18
+ def clients
19
+ repos[Client].all
20
+ end
21
+
22
+ def clients_for project
23
+ repos[Registration].related_to(project).map(&:client)
24
+ end
25
+
26
+ def db= db
27
+ warn_off { Sequel::Migrator.run db, "#{__dir__}/migrations" }
28
+ @repos = nil
29
+ @db = db
30
+ end
31
+
32
+ def free_clients_for project
33
+ clients_for(project).reject(&:busy)
34
+ end
35
+
36
+ def next_task_for project
37
+ repos[Task].related_to(project).reject(&:done).first
38
+ end
39
+
40
+ def project project_uuid
41
+ repos[Project][project_uuid]
42
+ end
43
+
44
+ def projects
45
+ repos[Project].all
46
+ end
47
+
48
+ def tasks_for project
49
+ repos[Task].related_to project
50
+ end
51
+
52
+ private
53
+
54
+ def db
55
+ @db ||= self.db = Sequel.sqlite
56
+ end
57
+
58
+ def repos
59
+ @repos ||= Hash.new do |repos, klass|
60
+ table = "#{klass.name.split('::').last.downcase}s".to_sym
61
+ repos[klass] = Repo.new klass, warn_off { db[table] }
62
+ end
63
+ end
64
+ end
65
+ end end
@@ -0,0 +1,2 @@
1
+ module Kamerling class Result < UUIDObject :addr, :client, :data, :task
2
+ end end
@@ -0,0 +1,26 @@
1
+ module Kamerling module Server class HTTP
2
+ attr_reader :addr
3
+
4
+ def initialize addr: req(:addr)
5
+ @addr = addr
6
+ end
7
+
8
+ def join
9
+ thread.join
10
+ end
11
+
12
+ def start
13
+ @thread = Thread.new do
14
+ Rack::Handler::WEBrick.run HTTPAPI, Host: addr.host, Port: addr.port
15
+ end
16
+ loop { break if addr.connectable? }
17
+ self
18
+ end
19
+
20
+ def stop
21
+ thread.exit.join
22
+ end
23
+
24
+ attr_reader :thread
25
+ private :thread
26
+ end end end
@@ -0,0 +1,32 @@
1
+ module Kamerling module Server class Sock
2
+ attr_reader :addr
3
+
4
+ def initialize addr: req(:addr), handler: Handler.new
5
+ @addr = addr
6
+ @handler = handler
7
+ end
8
+
9
+ def join
10
+ thread.join
11
+ end
12
+
13
+ def start
14
+ @thread = Thread.new { run_loop }
15
+ wait_till_started
16
+ self
17
+ end
18
+
19
+ def stop
20
+ thread.exit.join
21
+ end
22
+
23
+ attr_reader :handler, :thread
24
+ private :handler, :thread
25
+
26
+ private
27
+
28
+ def handle input, client_addr
29
+ handler.handle input, client_addr
30
+ rescue Handler::UnknownInput
31
+ end
32
+ end end end
@@ -0,0 +1,19 @@
1
+ module Kamerling module Server class TCP < Sock
2
+ private
3
+
4
+ def handle_connection socket
5
+ client_addr = Addr[*socket.remote_address.ip_unpack, :TCP]
6
+ input = socket.read
7
+ handle input, client_addr
8
+ ensure
9
+ socket.close
10
+ end
11
+
12
+ def run_loop
13
+ Socket.tcp_server_loop(*addr) { |socket| handle_connection socket }
14
+ end
15
+
16
+ def wait_till_started
17
+ loop { break if addr.connectable? }
18
+ end
19
+ end end end
@@ -0,0 +1,20 @@
1
+ module Kamerling module Server class UDP < Sock
2
+ private
3
+
4
+ def handle_connection socket
5
+ input, conn = socket.recvfrom 2**16
6
+ client_addr = Addr[conn[3], conn[1], :UDP]
7
+ handle input, client_addr
8
+ end
9
+
10
+ def run_loop
11
+ socket = UDPSocket.new.tap { |server| server.bind(*addr) }
12
+ loop { handle_connection socket if IO.select [socket] }
13
+ ensure
14
+ socket.close if socket
15
+ end
16
+
17
+ def wait_till_started
18
+ 200.times { thread.run }
19
+ end
20
+ end end end
@@ -0,0 +1,50 @@
1
+ require 'optparse'
2
+
3
+ module Kamerling class ServerRunner
4
+ Settings = Struct.new(*%i[db host http tcp udp])
5
+
6
+ def initialize args, classes: def_classes, orm: Sequel, repos: Repos
7
+ @args = args
8
+ repos.db = orm.connect settings.db
9
+ @servers = { http: :TCP, tcp: :TCP, udp: :UDP }.map do |type, prot|
10
+ addr = Addr[settings.host, settings.send(type), prot]
11
+ classes[type].new addr: addr if addr.port
12
+ end.compact
13
+ end
14
+
15
+ def join
16
+ servers.each(&:join)
17
+ end
18
+
19
+ def start
20
+ servers.each(&:start)
21
+ self
22
+ end
23
+
24
+ attr_reader :args, :servers
25
+ private :args, :servers
26
+
27
+ private
28
+
29
+ def def_classes
30
+ { http: Server::HTTP, tcp: Server::TCP, udp: Server::UDP }
31
+ end
32
+
33
+ def settings
34
+ @settings ||= Settings.new.tap do |sets|
35
+ sets.db = 'sqlite::memory:'
36
+ sets.host = '127.0.0.1'
37
+ OptionParser.new do |opts|
38
+ opts.on("--db #{sets.db}", String, 'database') do |db|
39
+ sets.db = db
40
+ end
41
+ opts.on("--host #{sets.host}", String, 'server host') do |host|
42
+ sets.host = host
43
+ end
44
+ opts.on('--http 0', Integer, 'HTTP port') { |http| sets.http = http }
45
+ opts.on('--tcp 0', Integer, 'TCP port') { |tcp| sets.tcp = tcp }
46
+ opts.on('--udp 0', Integer, 'UDP port') { |udp| sets.udp = udp }
47
+ end.parse! args
48
+ end
49
+ end
50
+ end end
@@ -0,0 +1,2 @@
1
+ module Kamerling class Task < UUIDObject :data, :project, done: false
2
+ end end
@@ -0,0 +1,29 @@
1
+ module Kamerling class TaskDispatcher
2
+ def initialize net_dispatcher: NetDispatcher.new, repos: Repos
3
+ @net_dispatcher = net_dispatcher
4
+ @repos = repos
5
+ end
6
+
7
+ def dispatch
8
+ repos.projects.each do |project|
9
+ repos.free_clients_for(project).each do |client|
10
+ task = repos.next_task_for(project)
11
+ dispatch_task client: client, project: project, task: task if task
12
+ end
13
+ end
14
+ end
15
+
16
+ attr_reader :net_dispatcher, :repos
17
+ private :net_dispatcher, :repos
18
+
19
+ private
20
+
21
+ def dispatch_task client: req(:client), project: req(:project),
22
+ task: req(:task)
23
+ message = Message[client: client, payload: task.data, project: project,
24
+ task: task, type: :DATA]
25
+ net_dispatcher.dispatch client.addr, message.to_s
26
+ client.busy = true
27
+ repos << client
28
+ end
29
+ end end
@@ -0,0 +1,17 @@
1
+ require 'securerandom'
2
+
3
+ module Kamerling module UUID
4
+ module_function
5
+
6
+ def [] bin
7
+ bin.unpack('H8H4H4H4H12').join '-'
8
+ end
9
+
10
+ def bin uuid
11
+ [uuid.tr('-', '')].pack 'H*'
12
+ end
13
+
14
+ def new
15
+ SecureRandom.uuid
16
+ end
17
+ end end