uxid 0.0.1 → 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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ed3424777407d1f5ba89a397eff5afef6856b76f2a297465a5f8f2e8bdb780ea
4
- data.tar.gz: 7da99c1c18ff54126c116c2cc8acc3ba8dccf3cff4e2c5de40fbdfd02b2f4ff6
3
+ metadata.gz: 1c5ebc6fc8f88263e841c870bb1191ac2769933134e68557bcf8c5472c4a9f3b
4
+ data.tar.gz: 0bea747a87b5ad069325004ea48d9b38e3bd7935e8769f3056e5ceea550b684d
5
5
  SHA512:
6
- metadata.gz: 7857a6c7493e2bf50428f87d806901e900a3772ed1bfa276c8e56feb0ea37715aac9836f64032c807f4e12bfa906e2b9cd49e521a144d0f2e23a5f82cb58c825
7
- data.tar.gz: f913e3af0c61847e0c8eca11497f1d7d51a928d6c666baea7762f73f2c9d14a32d18bb4db308b13b68ab3acda2e451eb597f90fca66e3ed9465007501f125812
6
+ metadata.gz: ebd95c6ed10c5bd1272c598bb7d0538fb72888e7e11842ab4aa30cecccb8a92d839d278fba4d5ec62f512df8a5509ac1f93dc7d4102e8262c7b3f0241a4c145a
7
+ data.tar.gz: 6e4bb57c8369218d3015cf81ec967625e33f51cf66e5641b4c63c4b32bba9dad424def094b86ba88464d906176c16d9c187fc4c89cb5caa85234be8d99c9d0f7
@@ -0,0 +1,103 @@
1
+ AllCops:
2
+ TargetRubyVersion: 2.5
3
+ DisabledByDefault: true
4
+ Exclude:
5
+ - pkg/**/*.rb
6
+ - test/spec/**/*.rb
7
+
8
+ Bundler/DuplicatedGem:
9
+ Enabled: true
10
+ Lint:
11
+ Enabled: true
12
+ Security:
13
+ Enabled: true
14
+
15
+ # Single quotes being faster is hardly measurable and only affects parse time.
16
+ # Enforcing double quotes reduces the times where you need to change them
17
+ # when introducing an interpolation. Use single quotes only if their semantics
18
+ # are needed.
19
+ Style/StringLiterals:
20
+ EnforcedStyle: double_quotes
21
+ Exclude:
22
+ - lib/riddler/protobuf/*_pb.rb
23
+
24
+ # Seattle style
25
+ Style/MethodDefParentheses:
26
+ EnforcedStyle: require_no_parentheses
27
+
28
+ # Seattle style
29
+ Style/MethodCallWithoutArgsParentheses:
30
+ Enabled: true
31
+
32
+ # Seattle style
33
+ Style/MethodCallWithArgsParentheses:
34
+ Enabled: true
35
+ EnforcedStyle: omit_parentheses
36
+ AllowParenthesesInChaining: true
37
+ IgnoredMethods:
38
+ - watch
39
+
40
+ Style/SpecialGlobalVars:
41
+ Enabled: false
42
+
43
+ Layout/FirstArrayElementIndentation:
44
+ Enabled: true
45
+ EnforcedStyle: consistent
46
+
47
+ Style/Lambda:
48
+ Enabled: true
49
+ EnforcedStyle: literal
50
+
51
+ # Allow regex argument in Seattle style
52
+ Lint/AmbiguousRegexpLiteral:
53
+ Enabled: false
54
+
55
+ # Allow block argument in Seattle style
56
+ Lint/AmbiguousOperator:
57
+ Enabled: false
58
+
59
+ Layout/SpaceInsideHashLiteralBraces:
60
+ Enabled: true
61
+ EnforcedStyleForEmptyBraces: no_space
62
+ EnforcedStyle: no_space
63
+
64
+ Layout/FirstHashElementIndentation:
65
+ Enabled: true
66
+ EnforcedStyle: consistent
67
+
68
+ Layout/CaseIndentation:
69
+ Enabled: true
70
+ EnforcedStyle: end
71
+
72
+ Layout/EndAlignment:
73
+ Enabled: true
74
+ EnforcedStyleAlignWith: variable
75
+
76
+ Layout/SpaceAroundBlockParameters:
77
+ Enabled: true
78
+
79
+ Layout/SpaceBeforeBlockBraces:
80
+ Enabled: true
81
+
82
+ Layout/SpaceInsideBlockBraces:
83
+ Enabled: true
84
+
85
+ Layout/LineLength:
86
+ Enabled: true
87
+ Max: 120
88
+ Exclude:
89
+ - lib/riddler/protobuf/*_pb.rb
90
+
91
+ Metrics/ClassLength:
92
+ Enabled: true
93
+ Max: 200
94
+ Exclude:
95
+ - test/**/*.rb
96
+
97
+ Lint/SuppressedException:
98
+ Exclude:
99
+ - lib/tasks/**/*.rake
100
+
101
+ Lint/Debugger:
102
+ Exclude:
103
+ - test/**/*
@@ -0,0 +1,8 @@
1
+ SimpleCov.start do
2
+ coverage_dir "reports/coverage"
3
+
4
+ add_filter "/config/"
5
+ add_filter "/lib/tasks/"
6
+
7
+ track_files "lib/**/*.rb"
8
+ end
@@ -1,3 +1,8 @@
1
- ### 0.0.1 / 2020-06-17
1
+ ## 0.1.0 / 2020-11-08
2
+
3
+ * Adds basic UXID generation
4
+
5
+
6
+ ## 0.0.1 / 2020-06-17
2
7
 
3
8
  * Birthday!
data/Gemfile CHANGED
@@ -3,5 +3,11 @@ source "https://rubygems.org"
3
3
  # Specify your gem's dependencies in uxid.gemspec
4
4
  gemspec
5
5
 
6
- gem "rake", "~> 12.0"
7
6
  gem "minitest", "~> 5.0"
7
+ gem "minitest-focus"
8
+ gem "minitest-reporters"
9
+ gem "pry-nav"
10
+ gem "rake", "~> 12.0"
11
+ gem "rubycritic", require: false
12
+ gem "rubocop"
13
+ gem "simplecov", require: false
@@ -6,15 +6,116 @@ PATH
6
6
  GEM
7
7
  remote: https://rubygems.org/
8
8
  specs:
9
+ addressable (2.7.0)
10
+ public_suffix (>= 2.0.2, < 5.0)
11
+ ansi (1.5.0)
12
+ ast (2.4.1)
13
+ axiom-types (0.1.1)
14
+ descendants_tracker (~> 0.0.4)
15
+ ice_nine (~> 0.11.0)
16
+ thread_safe (~> 0.3, >= 0.3.1)
17
+ builder (3.2.4)
18
+ coderay (1.1.3)
19
+ coercible (1.0.0)
20
+ descendants_tracker (~> 0.0.1)
21
+ descendants_tracker (0.0.4)
22
+ thread_safe (~> 0.3, >= 0.3.1)
23
+ docile (1.3.2)
24
+ equalizer (0.0.11)
25
+ erubis (2.7.0)
26
+ flay (2.12.1)
27
+ erubis (~> 2.7.0)
28
+ path_expander (~> 1.0)
29
+ ruby_parser (~> 3.0)
30
+ sexp_processor (~> 4.0)
31
+ flog (4.6.4)
32
+ path_expander (~> 1.0)
33
+ ruby_parser (~> 3.1, > 3.1.0)
34
+ sexp_processor (~> 4.8)
35
+ ice_nine (0.11.2)
36
+ kwalify (0.7.2)
37
+ launchy (2.5.0)
38
+ addressable (~> 2.7)
39
+ method_source (0.9.2)
9
40
  minitest (5.14.1)
41
+ minitest-focus (1.2.1)
42
+ minitest (>= 4, < 6)
43
+ minitest-reporters (1.4.2)
44
+ ansi
45
+ builder
46
+ minitest (>= 5.0)
47
+ ruby-progressbar
48
+ parallel (1.19.2)
49
+ parser (2.7.1.3)
50
+ ast (~> 2.4.0)
51
+ path_expander (1.1.0)
52
+ pry (0.12.2)
53
+ coderay (~> 1.1.0)
54
+ method_source (~> 0.9.0)
55
+ pry-nav (0.3.0)
56
+ pry (>= 0.9.10, < 0.13.0)
57
+ psych (3.1.0)
58
+ public_suffix (4.0.5)
59
+ rainbow (3.0.0)
10
60
  rake (12.3.3)
61
+ reek (6.0.1)
62
+ kwalify (~> 0.7.0)
63
+ parser (>= 2.5.0.0, < 2.8, != 2.5.1.1)
64
+ psych (~> 3.1.0)
65
+ rainbow (>= 2.0, < 4.0)
66
+ regexp_parser (1.7.1)
67
+ rexml (3.2.4)
68
+ rubocop (0.85.1)
69
+ parallel (~> 1.10)
70
+ parser (>= 2.7.0.1)
71
+ rainbow (>= 2.2.2, < 4.0)
72
+ regexp_parser (>= 1.7)
73
+ rexml
74
+ rubocop-ast (>= 0.0.3)
75
+ ruby-progressbar (~> 1.7)
76
+ unicode-display_width (>= 1.4.0, < 2.0)
77
+ rubocop-ast (0.0.3)
78
+ parser (>= 2.7.0.1)
79
+ ruby-progressbar (1.10.1)
80
+ ruby_parser (3.14.2)
81
+ sexp_processor (~> 4.9)
82
+ rubycritic (4.5.0)
83
+ flay (~> 2.8)
84
+ flog (~> 4.4)
85
+ launchy (>= 2.0.0)
86
+ parser (>= 2.6.0)
87
+ rainbow (~> 3.0)
88
+ reek (~> 6.0, < 7.0)
89
+ ruby_parser (~> 3.8)
90
+ simplecov (>= 0.17.0)
91
+ tty-which (~> 0.4.0)
92
+ virtus (~> 1.0)
93
+ sexp_processor (4.15.0)
94
+ simplecov (0.18.5)
95
+ docile (~> 1.1)
96
+ simplecov-html (~> 0.11)
97
+ simplecov-html (0.12.2)
98
+ thread_safe (0.3.6)
99
+ tty-which (0.4.2)
100
+ unicode-display_width (1.7.0)
101
+ virtus (1.0.5)
102
+ axiom-types (~> 0.1)
103
+ coercible (~> 1.0)
104
+ descendants_tracker (~> 0.0, >= 0.0.3)
105
+ equalizer (~> 0.0, >= 0.0.9)
11
106
 
12
107
  PLATFORMS
13
108
  ruby
14
109
 
15
110
  DEPENDENCIES
16
111
  minitest (~> 5.0)
112
+ minitest-focus
113
+ minitest-reporters
114
+ pry-nav
17
115
  rake (~> 12.0)
116
+ rubocop
117
+ rubycritic
118
+ simplecov
18
119
  uxid!
19
120
 
20
121
  BUNDLED WITH
data/README.md CHANGED
@@ -1,8 +1,37 @@
1
1
  # UXID
2
2
 
3
- 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/uxid`. To experiment with that code, run `bin/console` for an interactive prompt.
3
+ ![MIT License][badge_license_url]
4
+ ![Gem Version][badge_version_url]
5
+
6
+ UXIDs are identifiers which:
7
+
8
+ * Describe the resource (aid in debugging and investigation)
9
+ * Prononounceable - easily and accurately transmit characters to another human using a telephone
10
+ * Work well with copy and paste (double clicking selects the entire ID)
11
+ * Can be shortened for low cardinality resources
12
+ * Are very unlikely to collide (more likely with less randomness)
13
+ * Are secure against enumeration attacks
14
+ * Can be generated by application code (not tied to the datastore)
15
+ * Are K-sortable (lexicographically sortable by time - works well with datastore indexing)
16
+ * Do not require any coordination (human or automated) at startup, or generation
17
+
18
+ Many of the concepts of Stripe IDs have been used in this library.
19
+
20
+ ## Usage
21
+
22
+ #### Generating UXIDs
23
+
24
+ ```ruby
25
+ # No options generates a basic ULID
26
+ UXID.generate # "01EMDGJF0DQXQJ8FM78XE97Y3H"
27
+
28
+ # A prefix can be provided
29
+ UXID.generate prefix: "cus" # "cus_01EMDGJF0DQXQJ8FM78XE97Y3H"
30
+
31
+ # The amount of entropy can be decreased for smaller cardinality resources
32
+ UXID.generate prefix: "cus", size: 4 # "cus_01EMDGN5QAGPWSKN2"
33
+ ```
4
34
 
5
- TODO: Delete this and the text above, and describe your gem
6
35
 
7
36
  ## Installation
8
37
 
@@ -20,9 +49,6 @@ Or install it yourself as:
20
49
 
21
50
  $ gem install uxid
22
51
 
23
- ## Usage
24
-
25
- TODO: Write usage instructions here
26
52
 
27
53
  ## Development
28
54
 
@@ -38,3 +64,10 @@ Bug reports and pull requests are welcome on GitHub at https://github.com/riddle
38
64
  ## License
39
65
 
40
66
  The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
67
+
68
+ <!-- LINKS -->
69
+ [mit_license_url]: http://opensource.org/licenses/MIT
70
+
71
+ <!-- BADGES -->
72
+ [badge_license_url]: https://img.shields.io/badge/license-MIT-brightgreen.svg?cacheSeconds=3600?style=flat-square
73
+ [badge_version_url]: https://badge.fury.io/rb/uxid.svg
data/Rakefile CHANGED
@@ -1,10 +1,3 @@
1
- require "bundler/gem_tasks"
2
- require "rake/testtask"
1
+ Rake.add_rakelib "lib/tasks"
3
2
 
4
- Rake::TestTask.new(:test) do |t|
5
- t.libs << "test"
6
- t.libs << "lib"
7
- t.test_files = FileList["test/**/*_test.rb"]
8
- end
9
-
10
- task :default => :test
3
+ task default: :test
@@ -3,12 +3,5 @@
3
3
  require "bundler/setup"
4
4
  require "uxid"
5
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(__FILE__)
6
+ require "pry"
7
+ Pry.start
@@ -0,0 +1,4 @@
1
+ begin
2
+ require "bundler/gem_tasks"
3
+ rescue LoadError
4
+ end
@@ -0,0 +1,2 @@
1
+ desc "Runs tests, linting, audits, ..."
2
+ task check: %w[ test linting:dev metrics:quality ]
@@ -0,0 +1,19 @@
1
+ begin
2
+ require "rubocop/rake_task"
3
+
4
+ desc "Run Linting (RuboCop)"
5
+ task linting: "linting:dev"
6
+
7
+ namespace :linting do
8
+ common_options = %w[ --display-cop-names --extra-details --config=.rubocop.yml --parallel ]
9
+
10
+ RuboCop::RakeTask.new :dev do |t|
11
+ t.options = common_options
12
+ end
13
+
14
+ RuboCop::RakeTask.new :report do |t|
15
+ t.options = common_options + %w[ --format=html --out=reports/linting/index.html ]
16
+ end
17
+ end
18
+ rescue LoadError
19
+ end
@@ -0,0 +1,12 @@
1
+ begin
2
+ require "rubycritic/rake_task"
3
+
4
+ namespace :metrics do
5
+ RubyCritic::RakeTask.new do |task|
6
+ task.name = "quality"
7
+ task.paths = FileList["lib/**/*.rb"]
8
+ task.options = "--format html --no-browser --path reports/quality"
9
+ end
10
+ end
11
+ rescue LoadError
12
+ end
@@ -0,0 +1,8 @@
1
+ require "rake/testtask"
2
+
3
+ Rake::TestTask.new :test do |t|
4
+ t.libs << "lib"
5
+ t.libs << "test"
6
+ t.test_files = FileList["test/**/*_test.rb"]
7
+ t.warning = false
8
+ end
@@ -1,5 +1,33 @@
1
+ # frozen-string-literal: true
2
+
3
+ if RUBY_VERSION >= "2.5"
4
+ require "securerandom"
5
+ else
6
+ require "sysrandom/securerandom"
7
+ end
8
+
9
+ require "uxid/model"
10
+ require "uxid/encoder"
1
11
  require "uxid/version"
2
12
 
3
13
  module UXID
14
+ CROCKFORD_ENCODING = "0123456789ABCDEFGHJKMNPQRSTVWXYZ"
15
+ DELIMITER = "_"
16
+
4
17
  class Error < StandardError; end
18
+
19
+ def self.generate attrs = {}
20
+ model = new attrs
21
+ model.encode
22
+ end
23
+
24
+ def self.new attrs = {}
25
+ model = ::UXID::Model.new
26
+
27
+ model.time = attrs[:time] || Time.now
28
+ model.size = attrs[:size] || 10
29
+ model.prefix = attrs[:prefix] || ""
30
+
31
+ model
32
+ end
5
33
  end
@@ -0,0 +1,50 @@
1
+ module UXID
2
+ module Crockford
3
+ ALPHABET = "0123456789ABCDEFGHJKMNPQRSTVWXYZ".freeze
4
+
5
+ class Chunk
6
+ def initialize bytes
7
+ @bytes = bytes
8
+ end
9
+
10
+ def decode
11
+ bytes = @bytes.take_while {|c| c != 61} # strip padding
12
+ n = (bytes.length * 5.0 / 8.0).floor
13
+ p = bytes.length < 8 ? 5 - (n * 8) % 5 : 0
14
+ c = bytes.inject(0) do |m,o|
15
+ i = ALPHABET.index o.chr
16
+ raise ArgumentError, "invalid character '#{o.chr}'" if i.nil?
17
+ (m << 5) + i
18
+ end >> p
19
+ (0..n-1).to_a.reverse.collect {|i| ((c >> i * 8) & 0xff).chr}
20
+ end
21
+
22
+ def encode
23
+ n = (@bytes.length * 8.0 / 5.0).ceil
24
+ p = n < 8 ? 5 - (@bytes.length * 8) % 5 : 0
25
+ c = @bytes.inject(0) {|m,o| (m << 8) + o} << p
26
+ [(0..n-1).to_a.reverse.collect {|i| ALPHABET[(c >> i * 5) & 0x1f].chr}]
27
+ end
28
+ end
29
+
30
+ def self.chunks str, size
31
+ result = []
32
+ bytes = str.bytes
33
+ while bytes.any? do
34
+ chunk_bytes = bytes.take size
35
+ bytes = bytes.drop size
36
+ new_chunk = Chunk.new chunk_bytes
37
+ result << new_chunk
38
+ end
39
+ result
40
+ end
41
+
42
+ def self.encode str
43
+ chunks(str, 5).collect(&:encode).flatten.join
44
+ end
45
+
46
+ def self.decode str
47
+ chunks(str, 8).collect(&:decode).flatten.join
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,211 @@
1
+ module UXID
2
+ class Encoder
3
+ def self.encode model
4
+ new(model).encode
5
+ end
6
+
7
+ def initialize model
8
+ @model = model
9
+ end
10
+
11
+ def encode
12
+ @model.time_encoded = encode_time
13
+ @model.entropy_encoded = public_send "encode_entropy_#{@model.size}"
14
+
15
+ @model.encoded
16
+ end
17
+
18
+ def encode_entropy_0; ""; end
19
+
20
+ def encode_entropy_1
21
+ b0, _b1 = @model.entropy_bytes
22
+
23
+ UXID::CROCKFORD_ENCODING[(b0&248)>>3] +
24
+ UXID::CROCKFORD_ENCODING[((b0&7)<<2)]
25
+ end
26
+
27
+ def encode_entropy_2
28
+ b0, b1 = @model.entropy_bytes
29
+
30
+ UXID::CROCKFORD_ENCODING[(b0&248)>>3] +
31
+ UXID::CROCKFORD_ENCODING[((b0&7)<<2)] +
32
+ UXID::CROCKFORD_ENCODING[(b0&248)>>3] +
33
+ UXID::CROCKFORD_ENCODING[((b0&7)<<2) | ((b1&192)>>6)] +
34
+ UXID::CROCKFORD_ENCODING[(b1&62)>>1] +
35
+ UXID::CROCKFORD_ENCODING[((b1&1)<<4)]
36
+ end
37
+
38
+ def encode_entropy_2
39
+ b0, b1 = @model.entropy_bytes
40
+
41
+ UXID::CROCKFORD_ENCODING[(b0&248)>>3] +
42
+ UXID::CROCKFORD_ENCODING[((b0&7)<<2)] +
43
+ UXID::CROCKFORD_ENCODING[(b0&248)>>3] +
44
+ UXID::CROCKFORD_ENCODING[((b0&7)<<2) | ((b1&192)>>6)] +
45
+ UXID::CROCKFORD_ENCODING[(b1&62)>>1] +
46
+ UXID::CROCKFORD_ENCODING[((b1&1)<<4)]
47
+ end
48
+
49
+ def encode_entropy_3
50
+ b0, b1, b2 = @model.entropy_bytes
51
+
52
+ UXID::CROCKFORD_ENCODING[(b0&248)>>3] +
53
+ UXID::CROCKFORD_ENCODING[((b0&7)<<2)] +
54
+ UXID::CROCKFORD_ENCODING[(b0&248)>>3] +
55
+ UXID::CROCKFORD_ENCODING[((b0&7)<<2) | ((b1&192)>>6)] +
56
+ UXID::CROCKFORD_ENCODING[(b1&62)>>1] +
57
+ UXID::CROCKFORD_ENCODING[((b1&1)<<4) | ((b2&240)>>4)] +
58
+ UXID::CROCKFORD_ENCODING[((b2&15)<<1)]
59
+ end
60
+
61
+ def encode_entropy_4
62
+ b0, b1, b2, b3 = @model.entropy_bytes
63
+
64
+ UXID::CROCKFORD_ENCODING[(b0&248)>>3] +
65
+ UXID::CROCKFORD_ENCODING[((b0&7)<<2)] +
66
+ UXID::CROCKFORD_ENCODING[(b0&248)>>3] +
67
+ UXID::CROCKFORD_ENCODING[((b0&7)<<2) | ((b1&192)>>6)] +
68
+ UXID::CROCKFORD_ENCODING[(b1&62)>>1] +
69
+ UXID::CROCKFORD_ENCODING[((b1&1)<<4) | ((b2&240)>>4)] +
70
+ UXID::CROCKFORD_ENCODING[((b2&15)<<1) | ((b3&128)>>7)] +
71
+ UXID::CROCKFORD_ENCODING[(b3&124)>>2] +
72
+ UXID::CROCKFORD_ENCODING[((b3&3)<<3)]
73
+ end
74
+
75
+ def encode_entropy_5
76
+ b0, b1, b2, b3, b4 = @model.entropy_bytes
77
+
78
+ UXID::CROCKFORD_ENCODING[(b0&248)>>3] +
79
+ UXID::CROCKFORD_ENCODING[((b0&7)<<2)] +
80
+ UXID::CROCKFORD_ENCODING[(b0&248)>>3] +
81
+ UXID::CROCKFORD_ENCODING[((b0&7)<<2) | ((b1&192)>>6)] +
82
+ UXID::CROCKFORD_ENCODING[(b1&62)>>1] +
83
+ UXID::CROCKFORD_ENCODING[((b1&1)<<4) | ((b2&240)>>4)] +
84
+ UXID::CROCKFORD_ENCODING[((b2&15)<<1) | ((b3&128)>>7)] +
85
+ UXID::CROCKFORD_ENCODING[(b3&124)>>2] +
86
+ UXID::CROCKFORD_ENCODING[((b3&3)<<3) | ((b4&224)>>5)] +
87
+ UXID::CROCKFORD_ENCODING[b4&31]
88
+ end
89
+
90
+ def encode_entropy_6
91
+ b0, b1, b2, b3, b4, b5 = @model.entropy_bytes
92
+
93
+ UXID::CROCKFORD_ENCODING[(b0&248)>>3] +
94
+ UXID::CROCKFORD_ENCODING[((b0&7)<<2)] +
95
+ UXID::CROCKFORD_ENCODING[(b0&248)>>3] +
96
+ UXID::CROCKFORD_ENCODING[((b0&7)<<2) | ((b1&192)>>6)] +
97
+ UXID::CROCKFORD_ENCODING[(b1&62)>>1] +
98
+ UXID::CROCKFORD_ENCODING[((b1&1)<<4) | ((b2&240)>>4)] +
99
+ UXID::CROCKFORD_ENCODING[((b2&15)<<1) | ((b3&128)>>7)] +
100
+ UXID::CROCKFORD_ENCODING[(b3&124)>>2] +
101
+ UXID::CROCKFORD_ENCODING[((b3&3)<<3) | ((b4&224)>>5)] +
102
+ UXID::CROCKFORD_ENCODING[b4&31] +
103
+ UXID::CROCKFORD_ENCODING[(b5&248)>>3] +
104
+ UXID::CROCKFORD_ENCODING[((b5&7)<<2)]
105
+ end
106
+
107
+ def encode_entropy_7
108
+ b0, b1, b2, b3, b4, b5, b6 = @model.entropy_bytes
109
+
110
+ UXID::CROCKFORD_ENCODING[(b0&248)>>3] +
111
+ UXID::CROCKFORD_ENCODING[((b0&7)<<2)] +
112
+ UXID::CROCKFORD_ENCODING[(b0&248)>>3] +
113
+ UXID::CROCKFORD_ENCODING[((b0&7)<<2) | ((b1&192)>>6)] +
114
+ UXID::CROCKFORD_ENCODING[(b1&62)>>1] +
115
+ UXID::CROCKFORD_ENCODING[((b1&1)<<4) | ((b2&240)>>4)] +
116
+ UXID::CROCKFORD_ENCODING[((b2&15)<<1) | ((b3&128)>>7)] +
117
+ UXID::CROCKFORD_ENCODING[(b3&124)>>2] +
118
+ UXID::CROCKFORD_ENCODING[((b3&3)<<3) | ((b4&224)>>5)] +
119
+ UXID::CROCKFORD_ENCODING[b4&31] +
120
+ UXID::CROCKFORD_ENCODING[(b5&248)>>3] +
121
+ UXID::CROCKFORD_ENCODING[((b5&7)<<2) | ((b6&192)>>6)] +
122
+ UXID::CROCKFORD_ENCODING[(b6&62)>>1] +
123
+ UXID::CROCKFORD_ENCODING[((b6&1)<<4)]
124
+ end
125
+
126
+ def encode_entropy_8
127
+ b0, b1, b2, b3, b4, b5, b6, b7 = @model.entropy_bytes
128
+
129
+ UXID::CROCKFORD_ENCODING[(b0&248)>>3] +
130
+ UXID::CROCKFORD_ENCODING[((b0&7)<<2)] +
131
+ UXID::CROCKFORD_ENCODING[(b0&248)>>3] +
132
+ UXID::CROCKFORD_ENCODING[((b0&7)<<2) | ((b1&192)>>6)] +
133
+ UXID::CROCKFORD_ENCODING[(b1&62)>>1] +
134
+ UXID::CROCKFORD_ENCODING[((b1&1)<<4) | ((b2&240)>>4)] +
135
+ UXID::CROCKFORD_ENCODING[((b2&15)<<1) | ((b3&128)>>7)] +
136
+ UXID::CROCKFORD_ENCODING[(b3&124)>>2] +
137
+ UXID::CROCKFORD_ENCODING[((b3&3)<<3) | ((b4&224)>>5)] +
138
+ UXID::CROCKFORD_ENCODING[b4&31] +
139
+ UXID::CROCKFORD_ENCODING[(b5&248)>>3] +
140
+ UXID::CROCKFORD_ENCODING[((b5&7)<<2) | ((b6&192)>>6)] +
141
+ UXID::CROCKFORD_ENCODING[(b6&62)>>1] +
142
+ UXID::CROCKFORD_ENCODING[((b6&1)<<4) | ((b7&240)>>4)] +
143
+ UXID::CROCKFORD_ENCODING[((b7&15)<<1)]
144
+ end
145
+
146
+ def encode_entropy_9
147
+ b0, b1, b2, b3, b4, b5, b6, b7, b8 = @model.entropy_bytes
148
+
149
+ UXID::CROCKFORD_ENCODING[(b0&248)>>3] +
150
+ UXID::CROCKFORD_ENCODING[((b0&7)<<2)] +
151
+ UXID::CROCKFORD_ENCODING[(b0&248)>>3] +
152
+ UXID::CROCKFORD_ENCODING[((b0&7)<<2) | ((b1&192)>>6)] +
153
+ UXID::CROCKFORD_ENCODING[(b1&62)>>1] +
154
+ UXID::CROCKFORD_ENCODING[((b1&1)<<4) | ((b2&240)>>4)] +
155
+ UXID::CROCKFORD_ENCODING[((b2&15)<<1) | ((b3&128)>>7)] +
156
+ UXID::CROCKFORD_ENCODING[(b3&124)>>2] +
157
+ UXID::CROCKFORD_ENCODING[((b3&3)<<3) | ((b4&224)>>5)] +
158
+ UXID::CROCKFORD_ENCODING[b4&31] +
159
+ UXID::CROCKFORD_ENCODING[(b5&248)>>3] +
160
+ UXID::CROCKFORD_ENCODING[((b5&7)<<2) | ((b6&192)>>6)] +
161
+ UXID::CROCKFORD_ENCODING[(b6&62)>>1] +
162
+ UXID::CROCKFORD_ENCODING[((b6&1)<<4) | ((b7&240)>>4)] +
163
+ UXID::CROCKFORD_ENCODING[((b7&15)<<1)] +
164
+ UXID::CROCKFORD_ENCODING[((b7&15)<<1) | ((b8&128)>>7)] +
165
+ UXID::CROCKFORD_ENCODING[(b8&124)>>2] +
166
+ UXID::CROCKFORD_ENCODING[((b8&3)<<3)]
167
+ end
168
+
169
+ def encode_entropy_10
170
+ b0, b1, b2, b3, b4, b5, b6, b7, b8, b9 = @model.entropy_bytes
171
+
172
+ UXID::CROCKFORD_ENCODING[(b0&248)>>3] +
173
+ UXID::CROCKFORD_ENCODING[((b0&7)<<2)] +
174
+ UXID::CROCKFORD_ENCODING[(b0&248)>>3] +
175
+ UXID::CROCKFORD_ENCODING[((b0&7)<<2) | ((b1&192)>>6)] +
176
+ UXID::CROCKFORD_ENCODING[(b1&62)>>1] +
177
+ UXID::CROCKFORD_ENCODING[((b1&1)<<4) | ((b2&240)>>4)] +
178
+ UXID::CROCKFORD_ENCODING[((b2&15)<<1) | ((b3&128)>>7)] +
179
+ UXID::CROCKFORD_ENCODING[(b3&124)>>2] +
180
+ UXID::CROCKFORD_ENCODING[((b3&3)<<3) | ((b4&224)>>5)] +
181
+ UXID::CROCKFORD_ENCODING[b4&31] +
182
+ UXID::CROCKFORD_ENCODING[(b5&248)>>3] +
183
+ UXID::CROCKFORD_ENCODING[((b5&7)<<2) | ((b6&192)>>6)] +
184
+ UXID::CROCKFORD_ENCODING[(b6&62)>>1] +
185
+ UXID::CROCKFORD_ENCODING[((b6&1)<<4) | ((b7&240)>>4)] +
186
+ UXID::CROCKFORD_ENCODING[((b7&15)<<1)] +
187
+ UXID::CROCKFORD_ENCODING[((b7&15)<<1) | ((b8&128)>>7)] +
188
+ UXID::CROCKFORD_ENCODING[(b8&124)>>2] +
189
+ UXID::CROCKFORD_ENCODING[((b8&3)<<3) | ((b9&224)>>5)] +
190
+ UXID::CROCKFORD_ENCODING[b9&31]
191
+ end
192
+
193
+ def encode_time
194
+ # Get the 5 bytes of the 48bit time
195
+ b0, b1, b2, b3, b4, b5 = @model.time_bytes
196
+
197
+ # Split these 5 bytes up into 10 characters using the encoding
198
+ # and join them together as a string
199
+ UXID::CROCKFORD_ENCODING[(b0&224)>>5] +
200
+ UXID::CROCKFORD_ENCODING[b0&31] +
201
+ UXID::CROCKFORD_ENCODING[(b1&248)>>3] +
202
+ UXID::CROCKFORD_ENCODING[((b1&7)<<2) | ((b2&192)>>6)] +
203
+ UXID::CROCKFORD_ENCODING[(b2&62)>>1] +
204
+ UXID::CROCKFORD_ENCODING[((b2&1)<<4) | ((b3&240)>>4)] +
205
+ UXID::CROCKFORD_ENCODING[((b3&15)<<1) | ((b4&128)>>7)] +
206
+ UXID::CROCKFORD_ENCODING[(b4&124)>>2] +
207
+ UXID::CROCKFORD_ENCODING[((b4&3)<<3) | ((b5&224)>>5)] +
208
+ UXID::CROCKFORD_ENCODING[b5&31]
209
+ end
210
+ end
211
+ end
@@ -0,0 +1,44 @@
1
+ require "uxid/encoder"
2
+
3
+ module UXID
4
+ class Model
5
+ attr_accessor :time, :time_encoded, :entropy, :entropy_encoded, :prefix, :size
6
+
7
+ def encode
8
+ encoder = ::UXID::Encoder.new self
9
+ encoder.encode
10
+ end
11
+
12
+ # Returns time as 48 bits
13
+ def time_bytes
14
+ return @time_bytes if @time_bytes
15
+
16
+ time_ms = (@time.to_f * 1000).to_i
17
+ bytes = [time_ms].pack "Q>"
18
+ bytes_string = bytes[2..-1]
19
+ @time_bytes = bytes_string.bytes
20
+ end
21
+
22
+ def entropy_bytes
23
+ return @entropy_bytes if @entropy_bytes
24
+
25
+ bytes_string = SecureRandom.random_bytes @size
26
+ @entropy_bytes = bytes_string.bytes
27
+ return @entropy_bytes
28
+ end
29
+
30
+ def encoded
31
+ id = time_encoded + entropy_encoded
32
+
33
+ if has_prefix?
34
+ [prefix, id].join UXID::DELIMITER
35
+ else
36
+ id
37
+ end
38
+ end
39
+
40
+ def has_prefix?
41
+ !(prefix.nil? || prefix == "")
42
+ end
43
+ end
44
+ end
@@ -1,3 +1,3 @@
1
1
  module UXID
2
- VERSION = "0.0.1"
2
+ VERSION = "0.1.0"
3
3
  end
@@ -1,4 +1,5 @@
1
- require_relative 'lib/uxid/version'
1
+ require_relative "lib/uxid/version"
2
+ here = File.expand_path "..", __FILE__
2
3
 
3
4
  Gem::Specification.new do |spec|
4
5
  spec.name = "uxid"
@@ -6,21 +7,21 @@ Gem::Specification.new do |spec|
6
7
  spec.authors = ["JohnnyT"]
7
8
  spec.email = ["ubergeek3141@gmail.com"]
8
9
 
9
- spec.summary = "User eXperience focused IDentifiers"
10
+ spec.summary = "Generates IDs like: cus_01EPEY1JMKXVBT and txn_01EPEY2P06TR1RTV07XA82ZGJJ (similar to Stripe identifiers)."
10
11
  spec.description = spec.summary
11
12
  spec.homepage = "https://github.com/riddler/uxid"
12
13
  spec.license = "MIT"
13
- spec.required_ruby_version = Gem::Requirement.new(">= 2.3.0")
14
+ spec.required_ruby_version = Gem::Requirement.new ">= 2.3.0"
14
15
 
15
16
  spec.metadata["homepage_uri"] = spec.homepage
16
17
  spec.metadata["source_code_uri"] = spec.homepage
17
18
 
18
19
  # Specify which files should be added to the gem when it is released.
19
20
  # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
20
- spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do
21
- `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
21
+ spec.files = Dir.chdir here do
22
+ `git ls-files -z`.split("\x0").reject { |f| f.match %r{^(test|spec|features)/} }
22
23
  end
23
24
  spec.bindir = "exe"
24
- spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
25
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename f }
25
26
  spec.require_paths = ["lib"]
26
27
  end
metadata CHANGED
@@ -1,22 +1,25 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: uxid
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1
4
+ version: 0.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - JohnnyT
8
- autorequire:
8
+ autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2020-06-18 00:00:00.000000000 Z
11
+ date: 2020-11-08 00:00:00.000000000 Z
12
12
  dependencies: []
13
- description: User eXperience focused IDentifiers
13
+ description: 'Generates IDs like: cus_01EPEY1JMKXVBT and txn_01EPEY2P06TR1RTV07XA82ZGJJ
14
+ (similar to Stripe identifiers).'
14
15
  email:
15
16
  - ubergeek3141@gmail.com
16
17
  executables: []
17
18
  extensions: []
18
19
  extra_rdoc_files: []
19
20
  files:
21
+ - ".rubocop.yml"
22
+ - ".simplecov"
20
23
  - CHANGELOG.md
21
24
  - Gemfile
22
25
  - Gemfile.lock
@@ -25,7 +28,15 @@ files:
25
28
  - Rakefile
26
29
  - bin/console
27
30
  - bin/setup
31
+ - lib/tasks/bundler.rake
32
+ - lib/tasks/check.rake
33
+ - lib/tasks/linting.rake
34
+ - lib/tasks/quality.rake
35
+ - lib/tasks/test.rake
28
36
  - lib/uxid.rb
37
+ - lib/uxid/crockford.rb
38
+ - lib/uxid/encoder.rb
39
+ - lib/uxid/model.rb
29
40
  - lib/uxid/version.rb
30
41
  - uxid.gemspec
31
42
  homepage: https://github.com/riddler/uxid
@@ -34,7 +45,7 @@ licenses:
34
45
  metadata:
35
46
  homepage_uri: https://github.com/riddler/uxid
36
47
  source_code_uri: https://github.com/riddler/uxid
37
- post_install_message:
48
+ post_install_message:
38
49
  rdoc_options: []
39
50
  require_paths:
40
51
  - lib
@@ -49,8 +60,9 @@ required_rubygems_version: !ruby/object:Gem::Requirement
49
60
  - !ruby/object:Gem::Version
50
61
  version: '0'
51
62
  requirements: []
52
- rubygems_version: 3.1.2
53
- signing_key:
63
+ rubygems_version: 3.1.4
64
+ signing_key:
54
65
  specification_version: 4
55
- summary: User eXperience focused IDentifiers
66
+ summary: 'Generates IDs like: cus_01EPEY1JMKXVBT and txn_01EPEY2P06TR1RTV07XA82ZGJJ
67
+ (similar to Stripe identifiers).'
56
68
  test_files: []