human_readable 0.8.0 → 0.9.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: 5307eeacfd4961a43325a641b37da77388aedcb419f1c2bd0ae1021eec63417b
4
- data.tar.gz: 44414551a3e4333ab02beb913c69bf8afddce406b2e1701c2f7d79e1150a464d
3
+ metadata.gz: c0c42d3ef19502845eba7a407bd1b341c10598cf4625a748c740eec35467e572
4
+ data.tar.gz: 3b1f3ea6ce717b68854f5f7c30e43a27c988fa6c4f863140c36f5cab66d962d7
5
5
  SHA512:
6
- metadata.gz: 1662e91b1671caf3ac01da210016eb0544d6b82a9eec0f93279eaa9460472bea09fa8ba078d5204551dbb948c81c0a9f874a62d4001c41cfe9cd8c7c2dda38a0
7
- data.tar.gz: b728158418c8b4ea875dc80cccc4055107fbf48014821c325b8e76dfbe6c63f38394d57544c8f13909c376e47f45b19e812fc3ca100994ca2a0a42a2d12eaef9
6
+ metadata.gz: 78ab8e290c1cf235581efb86d42357a55f1c8972fd5a1d642c908291dcd6b7f8656ec4988579017c13bb20bd7487a75e30b9963fdc2ffef83d80da30b40fa25b
7
+ data.tar.gz: bec6d603d7670e5fd47e5f8ccea02d5a65fc6fda31dbc78df0dd3d86302516cacb265ab8758505550b39654ae62f61bfe800d14d592dcb74a4c44cf3f313e2ac
@@ -1,5 +1,10 @@
1
1
  Release History
2
2
  ===============
3
+ # 0.9.0
4
+ * Emoji support!!
5
+ * Minimum Ruby version bumped to 2.5.0 for Emoji support
6
+ * Extend or exclude available characters using config arrays
7
+ * Allow arrays in substitution keys
3
8
 
4
9
  # 0.8.0
5
10
  * Initial release
data/README.md CHANGED
@@ -1,6 +1,8 @@
1
+ [![Gem Version](https://badge.fury.io/rb/human_readable.svg)](https://badge.fury.io/rb/human_readable)
2
+
1
3
  # HumanReadable
2
4
 
3
- Human readable random tokens with limited ambiguous characters.
5
+ Human readable random tokens without ambiguous characters, and optional Emoji support.
4
6
 
5
7
  Focus is readability in poor conditions or from potentially damaged printed documents rather than cryptographic uses.
6
8
  Despite this focus, SecureRandom is used to help avoid collisions.
@@ -28,27 +30,45 @@ Or install it yourself as:
28
30
  ## Usage
29
31
 
30
32
  For 10 characters of the default character set, use `HumanReadable.generate`.
31
- For other lengths (2..x), use `HumanReadable.generate(output_size: 50)`.
33
+ For other lengths (2..x), use `HumanReadable.generate(output_size: 50)`, or change `output_size` in the configuration.
32
34
 
33
35
  ## Configuration
34
36
 
35
- * Change available characters and substitution by manipulating `substitution_hash`
36
- * To include non-default characters, add a self-reference to the hash
37
+ * Add or change substitutions by configuring `substitution_hash`
38
+ * To include non-default characters without substitution, configure `extend_chars`
39
+ * To exclude default characters, configure `exclude_chars`
37
40
  * Inspect available characters using `HumanReadable.charset`
38
- * For convenience, numbers and symbols are allowed in the hash and are translated to characters during usage
41
+ * For convenience, numbers and symbols are allowed in `substitution_hash` and are translated to characters during usage
39
42
 
40
- **CAUTION:** Changing `substitution_hash` keys alters the check character, invalidating previous tokens.
43
+ **CAUTION:** Changing available characters alters the check character, invalidating previous tokens.
41
44
 
42
45
 
43
46
  HumanReadable.configure do |c|
44
- # Default: substitution_hash = { I: 1, L: 1, O: 0, U: :V }
47
+ c.substitution_hash = { %w[I L] => 1, O: 0, U: :V } # Default
48
+ c.output_size = 10 # Default
45
49
 
46
- # Modifications
50
+ # Add or change substitutions
47
51
  c.substitution_hash[:B] = 8
48
52
  c.substitution_hash[:U] = nil
49
- c.substitution_hash['$'] = '$'
50
53
  # or equivalently
51
- c.substitution_hash = { I: 1, L: 1, O: 0, U: nil, B: 8, '$' => '$'}
54
+ c.substitution_hash = { %w[I L] => 1, O: 0, U: nil, B: 8}
55
+
56
+ # Extend charset when no substitution is needed
57
+ c.extend_chars << %w[~ ! @ $]
58
+
59
+ # Exclude from charset
60
+ c.exclude_chars = %w[X Y Z]
61
+
62
+ # Supports Emoji!!
63
+ c.extend_chars << %w[⛰️ 🧻 ✂️ 🦎 🖖]
64
+ c.substitution_hash['🖤'] = '❤️'
65
+
66
+ # And understands skin tones
67
+ c.remove_skin_tones = false # Default
68
+ c.substitution_hash[%w[👍🏻 👍🏼 👍🏽 👍🏾 👍🏿]] = '👍'
69
+ # -or-
70
+ c.remove_skin_tones = true
71
+ c.extend_chars << '👍'
52
72
  end
53
73
 
54
74
  ## Development
@@ -59,7 +79,7 @@ To install this gem onto your local machine, run `bundle exec rake install`. To
59
79
 
60
80
  ## Contributing
61
81
 
62
- Bug reports and pull requests are welcome on GitHub at https://github.com/MacksMind/human_readable.
82
+ Bug reports and pull requests are welcome on GitHub at <https://github.com/MacksMind/human_readable>.
63
83
 
64
84
 
65
85
  ## License
@@ -2,11 +2,11 @@
2
2
 
3
3
  # Copyright 2020 Mack Earnhardt
4
4
 
5
- require 'human_readable/version'
5
+ require_relative 'human_readable/version'
6
6
  require 'ostruct'
7
7
  require 'securerandom'
8
8
 
9
- # Human readable random tokens with no ambiguous characters
9
+ # Human readable random tokens without ambiguous characters, and optional Emoji support
10
10
  module HumanReadable
11
11
  # +#generate+ output_size must be >= 2 due to check character
12
12
  class MinSizeTwo < StandardError; end
@@ -15,34 +15,54 @@ module HumanReadable
15
15
  # Yields block for configuration
16
16
  #
17
17
  # HumanReadable.configure do |c|
18
+ # c.substitution_hash = { %w[I L] => 1, O: 0, U: :V } # Default
19
+ # c.output_size = 10 # Default
20
+ #
21
+ # # Substitution hash
18
22
  # c.substitution_hash[:B] = 8
19
23
  # c.substitution_hash[:U] = nil
20
- # c.substitution_hash['$'] = '$'
21
24
  # # or equivalently
22
- # c.substitution_hash = { I: 1, L: 1, O: 0, U: nil, B: 8, '$' => '$'}
23
- # end
25
+ # c.substitution_hash = { %w[I L] => 1, O: 0, U: nil, B: 8}
26
+ #
27
+ # # Extend charset
28
+ # c.extend_chars = %w[~ ! @ $]
24
29
  #
25
- # DEFAULT:
26
- # substitution_hash: { I: 1, L: 1, O: 0, U: :V }
30
+ # # Exclude charset
31
+ # c.exclude_chars = %w[X Y Z]
32
+ #
33
+ # # Supports Emoji!!
34
+ # c.extend_chars << %w[⛰️ 🧻 ✂️ 🦎 🖖]
35
+ # c.substitution_hash['🖤'] = '❤️'
36
+ #
37
+ # # And understands skin tones
38
+ # c.remove_skin_tones = false # Default
39
+ # c.substitution_hash[%w[👍🏻 👍🏼 👍🏽 👍🏾 👍🏿]] = '👍'
40
+ # # -or-
41
+ # c.remove_skin_tones = true
42
+ # c.extend_chars << '👍'
43
+ # end
27
44
  #
28
45
  # Specified keys won't be used during generation, and values will be substituted during
29
46
  # validation, increasing the likelihood that a misread character can be restored. Extend
30
- # or replace the substitutions to use a different character set. For convenience, numbers
47
+ # or replace the substitutions to alter the character set. For convenience, digits
31
48
  # and symbols are allowed in the hash and are translated to characters during usage.
32
- # Alter as needed per examples below.
33
49
  #
34
- # *CAUTION:* Changing substitution_hash keys alters the check character, invalidating previous tokens.
50
+ # @note Changing substitution_hash keys alters the check character, invalidating previous tokens.
51
+ # @return [nil]
35
52
  def configure
36
53
  yield(configuration)
54
+ nil
37
55
  end
38
56
 
39
57
  # Generates a random token of the requested size
40
58
  #
41
- # Minimum size is 2 since the last character is a check character
42
- def generate(output_size: 10)
59
+ # @note Minimum size is 2 since the last character is a check character
60
+ # @param output_size [Integer] desired number of printable characters
61
+ # @return [String] random token with check character
62
+ def generate(output_size: configuration.output_size)
43
63
  raise(MinSizeTwo) if output_size < 2
44
64
 
45
- (token = generate_random(output_size - 1)) + check_character(token)
65
+ "#{token = generate_random(output_size - 1)}#{check_character(token)}"
46
66
  end
47
67
 
48
68
  # Clean and validate a candidate token
@@ -52,11 +72,16 @@ module HumanReadable
52
72
  # * Remove characters not in available character set
53
73
  # * Validates the check character
54
74
  #
55
- # Return value: Valid token or nil
75
+ # @param input [String] the candidate token
76
+ # @return [String, nil] possibly modified token if valid, else nil
56
77
  def valid_token?(input)
57
78
  return unless input.is_a?(String)
58
79
 
59
- codepoints = input.upcase.tr(trans_from, trans_to).chars.map! { |c| charset.index(c) }
80
+ codepoints =
81
+ input.upcase.each_grapheme_cluster.map do |c|
82
+ c.gsub!(SKIN_TONE_REGEXP, '') if configuration.remove_skin_tones
83
+ charset_hash[validation_hash[c] || c]
84
+ end
60
85
  codepoints.compact!
61
86
 
62
87
  return if codepoints.size < 2
@@ -72,18 +97,53 @@ module HumanReadable
72
97
 
73
98
  # Characters available for token generation
74
99
  #
75
- # Manipulate by configuring +substitution_hash+
100
+ # DEFAULT: Digits 0-9 and uppercase letters A-Z except for ILOU
76
101
  #
77
- # DEFAULT: All number and uppercase letters except for ILOU
102
+ # @note Manipulate via {#configure}
103
+ # @return [Array] of available characters
78
104
  def charset
79
- @charset ||= (('0'..'9').to_a + ('A'..'Z').to_a - trans_from.chars + trans_to.chars - nil_substitutions).uniq
105
+ @charset ||=
106
+ begin
107
+ array = (
108
+ ('0'..'9').to_a +
109
+ ('A'..'Z').to_a +
110
+ extend_chars -
111
+ exclude_chars -
112
+ validation_hash.keys +
113
+ validation_hash.values
114
+ )
115
+ array.uniq!
116
+ array.sort!
117
+ end
118
+ end
119
+
120
+ # Reset configuration and memoizations
121
+ #
122
+ # @return [Array] list of variables reset
123
+ def reset
124
+ instance_variables.each { |sym| remove_instance_variable(sym) }
80
125
  end
81
126
 
82
127
  private
83
128
 
129
+ SKIN_TONE_REGEXP = /[🏻🏼🏽🏾🏿]/.freeze
130
+
131
+ # HumanReadable configuration
132
+ Configuration = Struct.new(
133
+ :substitution_hash,
134
+ :extend_chars,
135
+ :exclude_chars,
136
+ :output_size,
137
+ :remove_skin_tones
138
+ )
139
+
84
140
  def configuration
85
- @configuration ||= OpenStruct.new(
86
- substitution_hash: { I: 1, L: 1, O: 0, U: :V }
141
+ @configuration ||= Configuration.new(
142
+ { %w[I L] => 1, O: 0, U: :V },
143
+ [],
144
+ [],
145
+ 10,
146
+ false
87
147
  )
88
148
  end
89
149
 
@@ -99,8 +159,6 @@ module HumanReadable
99
159
  # Instead we attempt to optimize the number of bytes generated with each
100
160
  # call to SecureRandom.
101
161
  def generate_random(random_size)
102
- return '' unless random_size.positive?
103
-
104
162
  codepoints = []
105
163
 
106
164
  while codepoints.size < random_size
@@ -123,11 +181,11 @@ module HumanReadable
123
181
 
124
182
  # Compute check character using Luhn mod N algorithm
125
183
  #
126
- # *CAUTION:* Changing substitution_hash keys alters the output
184
+ # CAUTION: Changing charset alters the output
127
185
  def check_character(input)
128
186
  array =
129
- input.chars.reverse.each_with_index.map do |c, i|
130
- d = charset.index(c)
187
+ input.each_grapheme_cluster.to_a.reverse.each_with_index.map do |c, i|
188
+ d = charset_hash[c]
131
189
  d *= 2 if i.even?
132
190
  d / charset_size + d % charset_size
133
191
  end
@@ -159,36 +217,39 @@ module HumanReadable
159
217
  @charset_size ||= charset.size
160
218
  end
161
219
 
162
- def nil_substitutions
163
- @nil_substitutions ||=
164
- begin
165
- array = configuration.substitution_hash.each.map { |k, v| k if v.nil? }
166
- array.compact!
167
- array.map!(&:to_s)
168
- array.map!(&:upcase)
169
- end
220
+ def char_cleanup(array)
221
+ array.compact!
222
+ array.flatten!
223
+ array.map!(&:to_s)
224
+ array.map! { |element| element.gsub(SKIN_TONE_REGEXP, '') } if configuration.remove_skin_tones
225
+ array.map!(&:upcase)
170
226
  end
171
227
 
172
- def trans_from
173
- @trans_from ||=
174
- begin
175
- array = configuration.substitution_hash.each.map { |k, v| k unless v.nil? }
176
- array.compact!
177
- array.map!(&:to_s)
178
- array.map!(&:upcase)
179
- array.join
180
- end
228
+ def extend_chars
229
+ @extend_chars ||= char_cleanup(configuration.extend_chars)
230
+ end
231
+
232
+ def exclude_chars
233
+ @exclude_chars ||= char_cleanup(
234
+ configuration.exclude_chars + configuration.substitution_hash.each.map { |k, v| k if v.nil? }
235
+ )
181
236
  end
182
237
 
183
- def trans_to
184
- @trans_to ||=
238
+ # Flattened version of substitution_hash
239
+ def validation_hash
240
+ @validation_hash ||=
185
241
  begin
186
- array = configuration.substitution_hash.values
187
- array.compact!
188
- array.map!(&:to_s)
189
- array.map!(&:upcase)
190
- array.join
242
+ array =
243
+ configuration.substitution_hash.map do |k, v|
244
+ (k.is_a?(Array) ? k.map { |k1| [k1, v] } : [k, v]) unless v.nil?
245
+ end
246
+ array = char_cleanup(array)
247
+ Hash[*array]
191
248
  end
192
249
  end
250
+
251
+ def charset_hash
252
+ @charset_hash ||= Hash[charset.each_with_index.map { |char, i| [char, i] }]
253
+ end
193
254
  end
194
255
  end
@@ -4,6 +4,6 @@
4
4
 
5
5
  module HumanReadable
6
6
  # Gem version
7
- VERSION = '0.8.0'
7
+ VERSION = '0.9.0'
8
8
  public_constant :VERSION
9
9
  end
metadata CHANGED
@@ -1,20 +1,19 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: human_readable
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.8.0
4
+ version: 0.9.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Mack Earnhardt
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-08-15 00:00:00.000000000 Z
11
+ date: 2020-08-18 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: |
14
- Human readable random tokens with no ambiguous characters
15
-
16
- Tranlates invalid characters to their most likely original value
17
- and validates using a checksum.
14
+ Human readable random tokens without ambiguous characters, and optional Emoji support.
15
+ Translates invalid characters to their most likely original value
16
+ and validates using a check character.
18
17
  email:
19
18
  - mack@agilereasoning.com
20
19
  executables: []
@@ -41,15 +40,16 @@ required_ruby_version: !ruby/object:Gem::Requirement
41
40
  requirements:
42
41
  - - ">="
43
42
  - !ruby/object:Gem::Version
44
- version: 2.4.1
43
+ version: 2.5.0
45
44
  required_rubygems_version: !ruby/object:Gem::Requirement
46
45
  requirements:
47
46
  - - ">="
48
47
  - !ruby/object:Gem::Version
49
48
  version: '0'
50
49
  requirements: []
51
- rubygems_version: 3.1.2
50
+ rubygems_version: 3.0.3
52
51
  signing_key:
53
52
  specification_version: 4
54
- summary: Human readable random tokens with no ambiguous characters
53
+ summary: Human readable random tokens without ambiguous characters, and optional Emoji
54
+ support.
55
55
  test_files: []