Skip to content

Instantly share code, notes, and snippets.

@wobh
Last active March 17, 2019 16:28
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save wobh/13fe76e7caa110e757d70d3f77190467 to your computer and use it in GitHub Desktop.
Save wobh/13fe76e7caa110e757d70d3f77190467 to your computer and use it in GitHub Desktop.
A draft convenience for making and checking human friendly random input strings for temporary passwords, secondary identifiers, etc.
class WordB20
Base20 = %w( B C D F G H J K L M N P Q R S T V W X Z )
def self.random_b20(word_size)
word_chars = []
word_size.times do
word_chars << Base20.sample
end
word_chars.join("")
end
def self.normalize(word, break_char:)
word.to_s.
upcase.
gsub(break_char, '')
end
def self.format(word, break_char:, chunk_size:)
normalize(word, break_char: break_char).
scan(Regexp.new ".{1,#{chunk_size}}").
join(break_char)
end
def self.validate(word, break_char:, word_size:)
normalized = normalize(word, break_char: break_char)
if (normalized.size == word_size &&
normalized.split('').all? { |c| Base20.include?(c) })
word
end
end
DefaultWordSize = 12
DefaultBreakChar = '-'
DefaultChunkSize = 4
def initialize(word=nil,
word_size: DefaultWordSize,
break_char: DefaultBreakChar,
chunk_size: DefaultChunkSize)
@word_size = word_size
@break_char = break_char
@chunk_size = chunk_size
if word
unless @word = self.class.validate(word,
break_char: @break_char,
word_size: @word_size)
raise "Invalid word #{word}"
end
else
@word = self.class.random_b20(@word_size)
end
@normalized = nil
@formatted = nil
end
def to_str
formatted
end
def to_s
to_str
end
def ==(other)
!!(normalized ==
self.class.normalize(other,
break_char: @break_char))
end
private
def normalized
@normalized ||=
self.class.normalize(@word,
break_char: @break_char)
end
def formatted
@formatted ||=
self.class.format(@word,
break_char: @break_char,
chunk_size: @chunk_size)
end
end
require 'minitest/autorun'
require 'minitest/pride'
require_relative 'word_b20'
describe "WordB20" do
describe ".random_b20" do
let(:size) { 16 }
subject { WordB20.random_b20(size) }
it "should have the specified size" do
subject.size.
must_equal size
end
end
describe ".normalize" do
let(:break_char) { "&" }
subject { "Foo&Bar&Baz&Qux" }
it "should be in all caps" do
WordB20.normalize(subject,
break_char: break_char).
must_match Regexp.new("\A[A-Z]+")
end
it "should not contain the break char" do
WordB20.normalize(subject,
break_char: break_char).
wont_match Regexp.new("&")
end
end
describe ".format" do
let(:break_char) { "+" }
let(:chunk_size) { 4 }
subject { "FOOBARBAZQUX" }
it "should match format" do
WordB20.format(subject,
break_char: break_char,
chunk_size: chunk_size).
must_match(Regexp.new "[A-Z]+#{break_char}{1,3}")
end
end
describe ".validate" do
let(:break_char) { "!" }
let(:word_size) { 8 }
subject { "FbRrbZqX" }
it "should approve valid words" do
WordB20.validate(subject,
break_char: break_char,
word_size: word_size).
must_equal(subject)
end
describe "with words of invalid length" do
subject { "FbRrrrrrbZqX" }
it "should reject word" do
WordB20.validate(subject,
break_char: break_char,
word_size: word_size).
must_be_nil
end
end
describe "with words with invalid characters" do
subject { "FOObRrbZ" }
it "should reject word" do
WordB20.validate(subject,
break_char: break_char,
word_size: word_size).
must_be_nil
end
end
end
describe ".new" do
describe "with a valid word argument" do
subject { "BBBB-MMMM-ZZZZ" }
it "should create a new object" do
WordB20.new(subject).
must_equal(subject)
end
end
end
describe "#to_str" do
subject { WordB20.new }
it "should be interpolated as it's formatted string" do
"#{subject}".
must_match(Regexp.new "[A-Z]+#{WordB20::DefaultBreakChar}{1,3}")
end
end
describe "#==" do
subject { WordB20.new }
it "should be equal to it's normalized string" do
subject.
must_equal(WordB20.normalize(subject.to_s,
break_char: WordB20::DefaultBreakChar))
end
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment