xkpassword 0.5.0 → 0.6.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: 9feced30408ade895b492ff621703dd5d6a1cb6f6d4aaa6fe195534a249cb519
4
- data.tar.gz: 8f7bde17464c36aba01028a650daaa3e37d357018e5892bd9331384f25068a6a
3
+ metadata.gz: a8679b970d4279654ff7aa2f1212267bd2874466af8adfb197d4005dd94fb62a
4
+ data.tar.gz: 46877ecf578b62ced17e1ba35c8463387c34676d15cf301e8de5188af7619a79
5
5
  SHA512:
6
- metadata.gz: 158d3daab0101987cb7062a907125d80858aa7dbfdea51c6448ade83d4e155e5af31a87e37715ebde7a1965572008d2f56e64d747e53eb21a9a51e66c948d95b
7
- data.tar.gz: cfa02254cb10af01efe5387605d9036750c419031e40bb47cb8a2b23f97c4364ba534f66e3629b3b335dc450fb99d965f5827b654e0e5556d81a2f202eae7664
6
+ metadata.gz: 512d12534d4a4d0072dfb2581f98c7b9e1464647739b7b508cea5e3fb4e9ab972158d3b4ffa58fafc12f1a904ddc369b308ec33d57a32355b29028e2424d5476
7
+ data.tar.gz: '0828510166c54f05316e349a0187aee1d9fd49acf19ee4d8fd970309c938e9a0cb2a06390436f32788a87dc2e71a3cbc23aaf31d82dff868340b45dbd3244a38'
data/.gitignore CHANGED
@@ -3,7 +3,6 @@
3
3
  /Gemfile.lock
4
4
  /_yardoc/
5
5
  /coverage/
6
- /doc/
7
6
  /pkg/
8
7
  /spec/reports/
9
8
  /tmp/
data/README.md CHANGED
@@ -29,7 +29,7 @@ it.
29
29
 
30
30
  ## Installation
31
31
 
32
- Developed in Linux/Ubuntu, this should work fine in a Linux machine. How ever I can't say
32
+ Developed in Linux/Ubuntu, this should work fine on Linux machines. I cannot say
33
33
  the same is true with Mac or Windows systems.
34
34
 
35
35
  Add this line to your application's Gemfile:
@@ -50,43 +50,90 @@ Or install it yourself as:
50
50
  ~# gem install xkpassword
51
51
  ```
52
52
 
53
+ Installing the gem also installs the `xkpassword` executable.
54
+
53
55
  ## Usage
54
56
 
55
- You can use this app stand-alone in the command line or include it in any of your Ruby
56
- applications. For a fuller guide to presets and examples, see [doc/README.md](doc/README.md).
57
+ You can use this app as a Ruby gem in your application or as a command line utility.
58
+ For a fuller guide to presets and examples, see [doc/README.md](doc/README.md).
59
+
60
+ ### Command Line
57
61
 
58
- ### Comamnd Line
59
- The commandline application accepts the same collection of configuration options as would
60
- the `XKPassword` module would. For more information use `xkpassword --help` to obtain a
61
- full list of options.
62
+ The command line app accepts the same generation options as `XKPassword.generate`.
63
+ If you installed the gem, use the executable directly. If you are working from a
64
+ checkout, run the executable through Bundler.
62
65
 
63
66
  ```bash
64
67
  ~# xkpassword
65
68
  ~# xkpassword --help
69
+ ~# bundle exec exe/xkpassword
66
70
  ```
67
71
 
72
+ #### Global CLI config
73
+
74
+ The CLI reads optional defaults from `~/.xkpassword`. The file uses YAML, so it can
75
+ include comments while you experiment with different settings. Use `xkpassword --init`
76
+ to create a commented starter file.
77
+
78
+ ```bash
79
+ ~# xkpassword --init
80
+ ~# xkpassword
81
+ ~# xkpassword --separator .
82
+ ```
83
+
84
+ ```yaml
85
+ # ~/.xkpassword
86
+ # preset: wifi
87
+ words: 5
88
+ min_length: 4
89
+ max_length: 8
90
+ separator: "-"
91
+ # case_transform: capitalize
92
+ ```
93
+
94
+ CLI flags always override values from `~/.xkpassword`, so you can keep preferred defaults
95
+ in the file and still override them per command.
96
+
68
97
  ### Ruby Apps
69
98
 
99
+ Use the library API inside your Ruby application:
100
+
70
101
  ```ruby
71
102
  require 'xkpassword'
72
103
 
73
104
  options = {
74
- preset: :security,
105
+ words: 5,
106
+ min_length: 5,
107
+ max_length: 8,
108
+ separator: '.',
109
+ case_transform: :capitalize,
75
110
  }
76
111
 
77
112
  XKPassword.generate(options)
78
113
  ```
79
114
 
80
- If you are generating multiple passwords at once, I recommend you use
81
- the following as then it will only load and parse the databse once.
115
+ You can still use presets when they fit your use case:
116
+
117
+ ```ruby
118
+ require 'xkpassword'
119
+
120
+ XKPassword.generate(preset: :security, separator: '.')
121
+ ```
122
+
123
+ If you are generating multiple passwords at once, use a single generator instance so
124
+ the dictionary only needs to be loaded once:
82
125
 
83
126
  ```ruby
84
127
  require 'xkpassword/generator'
85
128
 
86
129
  options = {
87
- preset: :wifi,
130
+ words: 4,
131
+ min_length: 4,
132
+ max_length: 6,
133
+ separator: '-',
134
+ case_transform: :downcase,
88
135
  }
89
-
136
+
90
137
  generator = XKPassword::Generator.new
91
138
  generator.generate(options)
92
139
 
@@ -116,16 +163,6 @@ a new version, update the version number in `version.rb`, and then run `bundle e
116
163
  release`, which will create a git tag for the version, push git commits and tags, and push
117
164
  the `.gem` file to [rubygems.org](https://rubygems.org).
118
165
 
119
- ## TODO
120
-
121
- Some of the things I am interested in doing in the near future.
122
-
123
- - More tests, didn't have the time to write it all
124
- - Local configuration file -> ex: `~/.xkpassword`
125
- - Check for a better dictionary
126
- - Ability to provide a dictionary (this should help languages other than English)
127
- - A black-list - words that will not show up in the generation
128
-
129
166
  ## Contributing
130
167
 
131
168
  Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/xkpassword.
data/doc/README.md ADDED
@@ -0,0 +1,9 @@
1
+ # XKPassword Docs
2
+
3
+ Additional documentation for this gem lives in this folder.
4
+
5
+ - [Preset guide](presets.md)
6
+ - [Project README](../README.md) for installation, Ruby gem usage, CLI usage, and `~/.xkpassword` defaults
7
+ - [GitHub repository](https://github.com/jdeen/xkpassword)
8
+
9
+ Use these docs for feature-specific details, then jump back to the main README or repository as needed.
data/doc/presets.md ADDED
@@ -0,0 +1,37 @@
1
+ # Presets
2
+
3
+ Back to the [docs index](README.md) or the [project README](../README.md).
4
+
5
+ `XKPassword.generate` and `XKPassword::Generator#generate` accept a `:preset` option.
6
+ If omitted, `:preset` defaults to `:xkcd`.
7
+
8
+ ## Supported presets
9
+
10
+ | Preset | Defaults |
11
+ | --- | --- |
12
+ | `:xkcd` | 4 words, 4-8 letters, `-` separator, random per-word uppercasing |
13
+ | `:web32` | 4 words, 4-5 letters, `-` separator, random per-word uppercasing |
14
+ | `:wifi` | 6 words, 4-8 letters, `-` separator, random per-word uppercasing |
15
+ | `:security` | 6 words, 4-8 letters, space separator, lowercase words |
16
+ | `:apple_id` | 3 words, 4-7 letters, `-` separator, random per-word uppercasing |
17
+
18
+ ## Examples
19
+
20
+ ```ruby
21
+ require 'xkpassword'
22
+
23
+ XKPassword.generate
24
+ XKPassword.generate(preset: :wifi)
25
+ XKPassword.generate(preset: :security, separator: '.')
26
+ ```
27
+
28
+ You can pass a preset as a symbol or string. String values like `"apple_id"` and `"apple-id"`
29
+ are normalized to the matching preset.
30
+
31
+ Explicit options always win over the preset defaults, so this is valid:
32
+
33
+ ```ruby
34
+ XKPassword.generate(preset: :xkcd, words: 6, case_transform: :capitalize)
35
+ ```
36
+
37
+ For the full source and latest updates, see the [GitHub repository](https://github.com/jdeen/xkpassword).
data/exe/xkpassword CHANGED
@@ -1,48 +1,6 @@
1
1
  #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
2
3
 
3
- require 'optparse'
4
- require 'artii'
4
+ require 'xkpassword/cli'
5
5
 
6
- require 'bundler/setup'
7
- require 'xkpassword'
8
-
9
- artii = Artii::Base.new font: 'standard'
10
- message = """
11
- #{ artii.asciify('XKPassword') }
12
- by Ziyan Junaideen
13
-
14
- How many times have you changed your password just because you forgot it?
15
- Well, you are not alone. In todays security requirements, passwords need
16
- to be secure and difficult to break. Passwords need to be secure, sure,
17
- but they can also be easy to remember. Follow up this XKCD article for more
18
- information - http://xkcd.com/936/
19
-
20
- This does exactly what the picture predicts. You can use this in your Ruby
21
- applications (Ex: Rails, Sinatra) or standalone if you install the gem (as
22
- you have done here).
23
-
24
- Wish you all the best keeping things secure.
25
-
26
- Ziyan Junaideen
27
- ziyan@jdeen.com
28
-
29
- """
30
-
31
- options = {}
32
- OptionParser.new do |opts|
33
- opts.banner = "Usage: ./exe/xkpassword [options]"
34
-
35
- opts.on('-v', '--version', 'Gem version') { options[:version] = true }
36
- opts.on('-i', '--info', 'Gem info') { options[:info] = true }
37
-
38
- opts.on('--words [INTEGER]', 'Number of wrods to be used in the generated password') { |words| options[:words] = words.to_i }
39
- opts.on('--min-length [INTEGER]', 'Minimum length of a word') { |min| options[:min_length] = min.to_i }
40
- opts.on('--max-length [INTEGER]', 'Maximum length of a word') { |max| options[:max_length] = max.to_i }
41
- opts.on('--separator [STRING]', 'The separator to separate password') { |separator| options[:separator] = separator }
42
- opts.on('--case-transform [STRING]', 'Transform each word using upcase, downcase, or capitalize') { |case_transform| options[:case_transform] = case_transform }
43
- opts.on('--preset [STRING]', 'Preset to use: xkcd, web32, wifi, security, or apple_id') { |preset| options[:preset] = preset }
44
- end.parse!
45
-
46
- puts message if options[:info]
47
- puts XKPassword::VERSION if options[:version]
48
- puts XKPassword.generate(options) if !options[:info] && !options[:version]
6
+ exit XKPassword::CLI.new.run
@@ -0,0 +1,125 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'optparse'
4
+ require 'artii'
5
+
6
+ require 'xkpassword'
7
+ require 'xkpassword/config_file'
8
+
9
+ module XKPassword
10
+ # Command-line entrypoint for the xkpassword executable.
11
+ class CLI
12
+ INFO_MESSAGE = <<~TEXT.freeze
13
+ #{Artii::Base.new(font: 'standard').asciify('XKPassword')}
14
+ by Ziyan Junaideen
15
+
16
+ How many times have you changed your password just because you forgot it?
17
+ Well, you are not alone. In todays security requirements, passwords need
18
+ to be secure and difficult to break. Passwords need to be secure, sure,
19
+ but they can also be easy to remember. Follow up this XKCD article for more
20
+ information - http://xkcd.com/936/
21
+
22
+ This does exactly what the picture predicts. You can use this in your Ruby
23
+ applications (Ex: Rails, Sinatra) or standalone if you install the gem (as
24
+ you have done here).
25
+
26
+ Wish you all the best keeping things secure.
27
+
28
+ Ziyan Junaideen
29
+ ziyan@jdeen.com
30
+ TEXT
31
+
32
+ def initialize(argv = ARGV, stdout: $stdout, stderr: $stderr, env: ENV)
33
+ @argv = argv.dup
34
+ @stdout = stdout
35
+ @stderr = stderr
36
+ @config_file = XKPassword::ConfigFile.new(XKPassword::ConfigFile.default_path(env))
37
+ @options = {}
38
+ end
39
+
40
+ def run
41
+ option_parser.parse!(@argv)
42
+
43
+ return initialize_config if @options[:init]
44
+ return write_special_output if special_output?
45
+
46
+ options = @config_file.load.merge(@options)
47
+ write_line(XKPassword.generate(options))
48
+ rescue OptionParser::ParseError, XKPassword::ConfigFile::Error, ArgumentError => e
49
+ @stderr.puts(e.message)
50
+ 1
51
+ end
52
+
53
+ private
54
+
55
+ def option_parser
56
+ @option_parser ||= OptionParser.new do |opts|
57
+ opts.banner = 'Usage: xkpassword [options]'
58
+
59
+ add_command_options(opts)
60
+ add_generation_options(opts)
61
+ end
62
+ end
63
+
64
+ def add_command_options(opts)
65
+ opts.on('-v', '--version', 'Gem version') { @options[:version] = true }
66
+ opts.on('-i', '--info', 'Gem info') { @options[:info] = true }
67
+ opts.on('--init', "Create #{@config_file.path} with commented defaults") { @options[:init] = true }
68
+ opts.on('-h', '--help', 'Show help') { @options[:help] = true }
69
+ end
70
+
71
+ def add_generation_options(opts)
72
+ add_numeric_generation_options(opts)
73
+ add_text_generation_options(opts)
74
+ end
75
+
76
+ def add_numeric_generation_options(opts)
77
+ opts.on(
78
+ '--words [INTEGER]',
79
+ 'Number of words to use in the generated password'
80
+ ) { |words| @options[:words] = words.to_i }
81
+ opts.on('--min-length [INTEGER]', 'Minimum length of a word') { |min| @options[:min_length] = min.to_i }
82
+ opts.on('--max-length [INTEGER]', 'Maximum length of a word') { |max| @options[:max_length] = max.to_i }
83
+ end
84
+
85
+ def add_text_generation_options(opts)
86
+ opts.on(
87
+ '--separator [STRING]',
88
+ 'The separator between generated words'
89
+ ) { |separator| @options[:separator] = separator }
90
+ opts.on(
91
+ '--case-transform [STRING]',
92
+ 'Transform each word using upcase, downcase, or capitalize'
93
+ ) { |case_transform| @options[:case_transform] = case_transform }
94
+ add_preset_option(opts)
95
+ end
96
+
97
+ def add_preset_option(opts)
98
+ opts.on(
99
+ '--preset [STRING]',
100
+ 'Preset to use: xkcd, web32, wifi, security, or apple_id'
101
+ ) { |preset| @options[:preset] = preset }
102
+ end
103
+
104
+ def special_output?
105
+ @options[:help] || @options[:info] || @options[:version]
106
+ end
107
+
108
+ def write_special_output
109
+ write_line(option_parser) if @options[:help]
110
+ write_line(INFO_MESSAGE) if @options[:info]
111
+ write_line(XKPassword::VERSION) if @options[:version]
112
+ 0
113
+ end
114
+
115
+ def initialize_config
116
+ path = @config_file.init!
117
+ write_line("Created #{path}")
118
+ end
119
+
120
+ def write_line(message)
121
+ @stdout.puts(message)
122
+ 0
123
+ end
124
+ end
125
+ end
@@ -0,0 +1,105 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'psych'
4
+
5
+ module XKPassword
6
+ # Loads and initializes the CLI config file stored at ~/.xkpassword.
7
+ class ConfigFile
8
+ Error = Class.new(StandardError)
9
+
10
+ ALLOWED_OPTIONS = %i[
11
+ preset
12
+ words
13
+ min_length
14
+ max_length
15
+ separator
16
+ case_transform
17
+ ].freeze
18
+ INTEGER_OPTIONS = %i[words min_length max_length].freeze
19
+ TEMPLATE = <<~YAML
20
+ # Global defaults for the xkpassword CLI.
21
+ # Remove the leading "#" from any setting you want to enable.
22
+ #
23
+ # Supported keys:
24
+ # preset: xkcd, web32, wifi, security, apple_id
25
+ # words: integer
26
+ # min_length: integer
27
+ # max_length: integer
28
+ # separator: string
29
+ # case_transform: upcase, downcase, capitalize
30
+ #
31
+ # Example:
32
+ # preset: wifi
33
+ # words: 6
34
+ # min_length: 4
35
+ # max_length: 8
36
+ # separator: "-"
37
+ # case_transform: capitalize
38
+ YAML
39
+
40
+ attr_reader :path
41
+
42
+ def initialize(path = self.class.default_path)
43
+ @path = path
44
+ end
45
+
46
+ def self.default_path(env = ENV)
47
+ home = env['HOME'] || Dir.home
48
+ File.expand_path('.xkpassword', home)
49
+ end
50
+
51
+ def load
52
+ return {} unless File.exist?(path)
53
+
54
+ data = Psych.safe_load(File.read(path), aliases: false)
55
+ return {} if data.nil?
56
+ raise Error, "#{path} must contain a YAML mapping of options" unless data.is_a?(Hash)
57
+
58
+ normalize_options(data)
59
+ rescue Psych::SyntaxError => e
60
+ raise Error, "Could not parse #{path}: #{e.message}"
61
+ end
62
+
63
+ def init!
64
+ raise Error, "#{path} already exists" if File.exist?(path)
65
+
66
+ File.write(path, TEMPLATE)
67
+ path
68
+ end
69
+
70
+ private
71
+
72
+ def normalize_options(data)
73
+ data.each_with_object({}) do |(key, value), normalized|
74
+ normalized_key = normalize_key(key)
75
+
76
+ unless ALLOWED_OPTIONS.include?(normalized_key)
77
+ raise Error, "Unsupported config option #{key.inspect} in #{path}"
78
+ end
79
+
80
+ normalized[normalized_key] = normalize_value(normalized_key, value)
81
+ end
82
+ end
83
+
84
+ def normalize_key(key)
85
+ key.to_s.strip.downcase.tr(' -', '_').to_sym
86
+ end
87
+
88
+ def normalize_value(key, value)
89
+ return normalize_integer(key, value) if INTEGER_OPTIONS.include?(key)
90
+
91
+ return value if value.nil? || value.is_a?(String) || value.is_a?(Symbol)
92
+
93
+ raise Error, "#{key} in #{path} should be a string or left unset"
94
+ end
95
+
96
+ def normalize_integer(key, value)
97
+ integer_value = value.is_a?(String) ? Integer(value, 10) : value
98
+ return integer_value if integer_value.is_a?(Integer)
99
+
100
+ raise Error, "#{key} in #{path} should be an integer"
101
+ rescue ArgumentError, TypeError
102
+ raise Error, "#{key} in #{path} should be an integer"
103
+ end
104
+ end
105
+ end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module XKPassword
2
- VERSION = '0.5.0'
4
+ VERSION = '0.6.0'
3
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: xkpassword
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.0
4
+ version: 0.6.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ziyan Junaideen
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2026-04-12 00:00:00.000000000 Z
11
+ date: 2026-04-17 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: artii
@@ -90,8 +90,12 @@ files:
90
90
  - bin/console
91
91
  - bin/setup
92
92
  - bin/xkpassword
93
+ - doc/README.md
94
+ - doc/presets.md
93
95
  - exe/xkpassword
94
96
  - lib/xkpassword.rb
97
+ - lib/xkpassword/cli.rb
98
+ - lib/xkpassword/config_file.rb
95
99
  - lib/xkpassword/data/google-10000-english-no-swears.txt
96
100
  - lib/xkpassword/generator.rb
97
101
  - lib/xkpassword/store.rb