password_forge 0.0.2

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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: '04491df7a00f384358316e13cc7aefcfb779ff7b086fe0f5d4c1d8debc7cb11c'
4
+ data.tar.gz: 87e0e0c2b7965143d43d0e624fce83da06a591bc2c341e1dae8e280807a49af6
5
+ SHA512:
6
+ metadata.gz: ab01ba4518870bd2af66924298ba94a6509c703843216678556ba31ce1cdc31d22ecef7a5a996c0ef9cf3f2a8528130101813161d0aed0b0fe57c592082bc786
7
+ data.tar.gz: c573c0c3ec020471d63fb6bb3de55520bd0b5cdbd957e82755b638ea269fef69220db75a315a21e609b88492b9084fa5967a8456a580d8f74ac5ce0c777149ed
@@ -0,0 +1,46 @@
1
+ # Product
2
+
3
+ ## What this is
4
+
5
+ `password_forge` is a configurable password generator distributed as a Ruby gem.
6
+ It generates random passwords from four selectable character sets — uppercase,
7
+ lowercase, numeric and special — that can be switched on or off independently.
8
+ All sets are enabled by default, and an error is raised if every set is
9
+ disabled. Randomness comes from Ruby's `SecureRandom`.
10
+
11
+ The public API mirrors the design of an existing C# / NuGet package: a
12
+ constructor with four boolean flags plus a length, and a dedicated exception
13
+ when no character set is selected.
14
+
15
+ ## Goals
16
+
17
+ 1. **Learning vehicle.** This is the author's first Ruby gem. Code favours
18
+ clarity and idiomatic Ruby over cleverness, and everything is built with TDD.
19
+ 2. **Kiro evangelism in the Ruby world.** Beyond the gem itself, the project
20
+ ships a full "Kiro-native" experience for Ruby gem authors: Kiro skills,
21
+ project steering (this folder), hooks, and an MCP server. The repository is
22
+ meant to be a reference example of building and shipping a gem the Kiro way.
23
+
24
+ ## Audience
25
+
26
+ - Ruby developers who need a small, dependency-free password generator.
27
+ - Ruby developers curious about using Kiro to build and release gems.
28
+
29
+ ## Language
30
+
31
+ All repository content (code, comments, docs, commit messages) is in **English**.
32
+ The only exception is the author's personal development diary, which is kept
33
+ locally in `private-notes/` and excluded from the repository.
34
+
35
+ ## Release roadmap
36
+
37
+ The project ships in small, tagged increments. Each version is merged to `main`
38
+ before the next begins:
39
+
40
+ - **0.0.1** — Core generator, character sets, validation, tests, docs. (done)
41
+ - **0.1.0** — First public release on RubyGems.org via Trusted Publishing.
42
+ - **0.2.0** — Kiro skills for gem authors (feature TDD, version bump, release).
43
+ - **0.3.0** — Project `.kiro/` folder with full steering and conventions.
44
+ - **0.4.0** — Kiro hooks (run specs on save, changelog reminders, etc.).
45
+ - **0.5.0** — A Ruby MCP server exposing password generation as a tool.
46
+ - **0.6.0** — A fluent/builder API layered on top of the keyword-argument API.
@@ -0,0 +1,79 @@
1
+ # Structure
2
+
3
+ ## Directory layout
4
+
5
+ ```
6
+ password-forge-ruby-gem/ # repo root
7
+ ├── lib/
8
+ │ ├── password_forge.rb # Entry point: requires all components
9
+ │ └── password_forge/
10
+ │ ├── version.rb # VERSION constant
11
+ │ ├── errors.rb # Error, NoCharsetSelectedError
12
+ │ ├── charset.rb # Charset value object (character sets + build)
13
+ │ ├── validation.rb # Validation module (selection + length)
14
+ │ └── generator.rb # Generator class + PasswordForge.generate
15
+ ├── spec/
16
+ │ ├── spec_helper.rb
17
+ │ ├── password_forge_spec.rb # Top-level (version) spec
18
+ │ └── password_forge/
19
+ │ ├── charset_spec.rb
20
+ │ ├── validation_spec.rb
21
+ │ └── generator_spec.rb
22
+ ├── examples/ # Runnable local demos (not packaged)
23
+ │ ├── smoke_test.rb # Loads the gem from source and prints samples
24
+ │ └── local-consumer/ # Mini "external" project using the gem via path:
25
+ │ ├── Gemfile
26
+ │ └── run.rb
27
+ ├── docs/ # Extended documentation (not packaged)
28
+ │ ├── README.md # Docs index
29
+ │ └── local-testing.md # How to try the gem locally
30
+ ├── sig/ # RBS type signatures
31
+ ├── bin/ # setup + console helper scripts
32
+ ├── .github/workflows/
33
+ │ ├── main.yml # CI: RSpec matrix + RuboCop
34
+ │ └── release.yml # Trusted Publishing on v* tags
35
+ ├── .kiro/ # Kiro steering / skills / hooks
36
+ ├── private-notes/ # Local-only, git-ignored (dev diary)
37
+ ├── password_forge.gemspec
38
+ ├── Gemfile
39
+ ├── Rakefile
40
+ ├── README.md
41
+ ├── CHANGELOG.md
42
+ └── LICENSE.txt
43
+ ```
44
+
45
+ `Gemfile.lock` is generated per environment and git-ignored (see Tech), so it is
46
+ not part of the tracked tree above.
47
+
48
+ ## Architecture
49
+
50
+ The internal design mirrors the original C# separation of concerns while
51
+ staying idiomatic Ruby:
52
+
53
+ - **`PasswordForge::Charset`** — a module acting as a value object. Holds the
54
+ frozen `UPPER`, `LOWER`, `NUMERIC` and `SPECIAL` constants and a `build`
55
+ method that returns the pool of characters for the selected sets.
56
+ - **`PasswordForge::Validation`** — a module with `validate_charset_selection`
57
+ (raises when no set is selected) and `validate_length` (positive integer).
58
+ - **`PasswordForge::NoCharsetSelectedError`** — the equivalent of the C#
59
+ `InvalidCharSetException`; subclass of `PasswordForge::Error`.
60
+ - **`PasswordForge::Generator`** — the public class. The constructor validates
61
+ input and builds the pool; `#generate` returns a `SecureRandom`-backed
62
+ password.
63
+ - **`PasswordForge.generate(**options)`** — a top-level convenience wrapper.
64
+
65
+ ## Public API conventions
66
+
67
+ - The `Generator` constructor uses keyword arguments:
68
+ `upper_case:`, `lower_case:`, `numeric_case:`, `special_case:` (all `true`),
69
+ and `length:` (default 16). This matches the C# constructor parameter names.
70
+ - A fluent/builder API is planned for v0.6.0 and must be **additive**: the
71
+ keyword-argument API keeps working unchanged.
72
+
73
+ ## Naming
74
+
75
+ - **Gem name:** `password_forge` (underscore) — what users `gem install`.
76
+ - **GitHub repo:** `password-forge-ruby-gem` (hyphens) — more descriptive for
77
+ discovery. The two intentionally differ.
78
+ - Namespace all code under the `PasswordForge` module; one file per component
79
+ under `lib/password_forge/`.
@@ -0,0 +1,68 @@
1
+ # Tech
2
+
3
+ ## Stack
4
+
5
+ - **Language:** Ruby, `required_ruby_version >= 3.0.0`.
6
+ - **Test framework:** RSpec.
7
+ - **Linter:** RuboCop (config in `.rubocop.yml`, `TargetRubyVersion: 3.0`).
8
+ - **Randomness:** `SecureRandom` (standard library, no runtime dependencies).
9
+ - **Type signatures:** RBS stubs under `sig/`.
10
+
11
+ The gem has **no runtime dependencies**.
12
+
13
+ ## Common commands
14
+
15
+ Run from the repository root (`password-forge-ruby-gem/`):
16
+
17
+ ```bash
18
+ bundle install # install development dependencies
19
+ bundle exec rake # default task: RSpec + RuboCop
20
+ bundle exec rspec # run the test suite only
21
+ bundle exec rubocop # run the linter only
22
+ bundle exec rubocop -A # auto-correct safe offences
23
+ gem build password_forge.gemspec # build the gem locally
24
+ bin/console # interactive prompt with the gem loaded
25
+ ```
26
+
27
+ ## Development workflow
28
+
29
+ - **TDD.** Write the spec first, watch it fail, implement to green, then
30
+ refactor. Keep the suite and RuboCop green before every commit.
31
+ - **Branches.** Do feature work on a `feature/vX.Y.Z-*` branch and merge to
32
+ `main` per milestone. Never push directly to `main` for feature work.
33
+ - **Versioning.** Semantic Versioning. Bump `lib/password_forge/version.rb`,
34
+ update `CHANGELOG.md` (Keep a Changelog format), then tag `vX.Y.Z`.
35
+
36
+ ## Release process (Trusted Publishing)
37
+
38
+ Publishing is automated via OIDC — no API tokens are stored.
39
+
40
+ 1. A one-time setup on RubyGems.org registers a trusted publisher:
41
+ - RubyGem name: `password_forge`
42
+ - Repository owner: `devandreacarratta`
43
+ - Repository name: `password-forge-ruby-gem`
44
+ - Workflow filename: `release.yml`
45
+ - Environment: `release`
46
+ For the very first publish (gem not yet on RubyGems), a **pending** trusted
47
+ publisher is registered from the RubyGems profile before the gem exists.
48
+ 2. `.github/workflows/release.yml` triggers on `v*` tags, uses
49
+ `rubygems/release-gem@v1` with `contents: write` + `id-token: write` and the
50
+ `release` GitHub environment.
51
+ 3. Releasing = bump version, update CHANGELOG, merge to `main`, push the tag.
52
+
53
+ ## Key decisions (recorded so they are not re-litigated)
54
+
55
+ - **Gem email removed.** `spec.email` is intentionally omitted from the gemspec
56
+ (it is optional and would be public). The RubyGems account email is separate
57
+ and private.
58
+ - **`Gemfile.lock` is not committed.** Committing it pinned `BUNDLED WITH 4.x`,
59
+ which broke CI on Ruby < 3.2 (Bundler 4 requires Ruby 3.2+). Gems resolve the
60
+ lockfile per environment, so it is git-ignored.
61
+ - **Gem name vs repo name differ on purpose** (`password_forge` vs
62
+ `password-forge-ruby-gem`).
63
+ - **MFA required for pushes** via `rubygems_mfa_required = "true"` in the
64
+ gemspec.
65
+ - **CI splits test and lint:** RSpec runs across Ruby 3.0–3.4; RuboCop runs once
66
+ on 3.4 to avoid version-specific style noise.
67
+ - **Personal diary** lives in `private-notes/` (git-ignored), everything else is
68
+ English and tracked.
data/CHANGELOG.md ADDED
@@ -0,0 +1,48 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project are documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ ## [Unreleased]
9
+
10
+ ## [0.0.2] - 2026-09-05
11
+
12
+ ### Added
13
+
14
+ - `examples/` folder with runnable local demos: `smoke_test.rb` (loads the gem
15
+ from source and prints sample passwords) and `local-consumer/` (a minimal
16
+ external project that depends on the gem via a `path:` reference).
17
+ - `docs/` folder with a documentation index (`docs/README.md`) and a
18
+ local-testing guide (`docs/local-testing.md`).
19
+ - "Trying it locally" section in the README linking to the examples and docs.
20
+
21
+ ### Changed
22
+
23
+ - Excluded `examples/` and `docs/` from the packaged gem.
24
+
25
+ ### Fixed
26
+
27
+ - Corrected the directory tree and command paths in the Kiro steering docs
28
+ (`structure.md`, `tech.md`), which still referenced a nested `password_forge/`
29
+ root left over from an older project layout.
30
+
31
+ ## [0.0.1] - 2026-09-05
32
+
33
+ ### Added
34
+
35
+ - `PasswordForge::Generator` with keyword-argument constructor
36
+ (`upper_case:`, `lower_case:`, `numeric_case:`, `special_case:`, `length:`)
37
+ and a `SecureRandom`-backed `#generate`.
38
+ - `PasswordForge::Charset` value object exposing the `UPPER`, `LOWER`,
39
+ `NUMERIC` and `SPECIAL` character sets and a `build` method.
40
+ - `PasswordForge::Validation` for charset selection and length checks.
41
+ - `PasswordForge::NoCharsetSelectedError`, raised when no character set is
42
+ selected.
43
+ - `PasswordForge.generate` convenience wrapper.
44
+ - RSpec test suite, RuboCop configuration and README.
45
+
46
+ [Unreleased]: https://github.com/devandreacarratta/password-forge-ruby-gem/compare/v0.0.2...HEAD
47
+ [0.0.2]: https://github.com/devandreacarratta/password-forge-ruby-gem/compare/v0.0.1...v0.0.2
48
+ [0.0.1]: https://github.com/devandreacarratta/password-forge-ruby-gem/releases/tag/v0.0.1
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2026 Andrea Carratta
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,152 @@
1
+ # PasswordForge
2
+
3
+ [![Ruby](https://github.com/devandreacarratta/password-forge-ruby-gem/actions/workflows/main.yml/badge.svg)](https://github.com/devandreacarratta/password-forge-ruby-gem/actions/workflows/main.yml)
4
+
5
+ A configurable password generator for Ruby with selectable character sets.
6
+
7
+ `PasswordForge` builds passwords from four character categories — uppercase,
8
+ lowercase, numeric and special — that you switch on or off independently. All
9
+ categories are enabled by default, and a clear error is raised if you disable
10
+ every one of them. Randomness is provided by Ruby's `SecureRandom`.
11
+
12
+ This gem is also a showcase for building and shipping a Ruby gem the **Kiro
13
+ way**. Kiro skills, project steering, hooks and an MCP server are being added
14
+ incrementally (see the [Roadmap](#roadmap)).
15
+
16
+ ## Installation
17
+
18
+ > **Not on RubyGems yet.** The first public release is planned for 0.1.0 (see
19
+ > the [Roadmap](#roadmap)). Until then, see [Trying it
20
+ > locally](#trying-it-locally) to run the gem from source or via a local path
21
+ > dependency.
22
+
23
+ Once published, install the gem and add it to the application's Gemfile by
24
+ executing:
25
+
26
+ ```bash
27
+ bundle add password_forge
28
+ ```
29
+
30
+ If Bundler is not being used to manage dependencies, install the gem by
31
+ executing:
32
+
33
+ ```bash
34
+ gem install password_forge
35
+ ```
36
+
37
+ ## Usage
38
+
39
+ ### Quick start
40
+
41
+ ```ruby
42
+ require "password_forge"
43
+
44
+ # All character sets enabled, default length of 16
45
+ PasswordForge.generate
46
+ # => "aB3$xY7!qR2@kL9%"
47
+ ```
48
+
49
+ ### Using the generator directly
50
+
51
+ The constructor takes four boolean flags (all `true` by default) plus a
52
+ `length`. This mirrors the design of the original C# / NuGet package:
53
+
54
+ ```ruby
55
+ generator = PasswordForge::Generator.new(
56
+ upper_case: true, # include A-Z
57
+ lower_case: true, # include a-z
58
+ numeric_case: true, # include 0-9
59
+ special_case: true, # include special characters
60
+ length: 16
61
+ )
62
+
63
+ generator.generate # => "aB3$xY7!qR2@kL9%"
64
+ ```
65
+
66
+ ### Examples
67
+
68
+ ```ruby
69
+ # A 20-character password using every character set
70
+ PasswordForge::Generator.new(length: 20).generate
71
+
72
+ # A 4-digit numeric PIN
73
+ PasswordForge::Generator.new(
74
+ upper_case: false, lower_case: false, numeric_case: true, special_case: false, length: 4
75
+ ).generate
76
+ # => "8391"
77
+
78
+ # Letters only (no digits, no symbols)
79
+ PasswordForge::Generator.new(
80
+ numeric_case: false, special_case: false, length: 24
81
+ ).generate
82
+ ```
83
+
84
+ ### Error handling
85
+
86
+ Disabling every character set raises `PasswordForge::NoCharsetSelectedError`:
87
+
88
+ ```ruby
89
+ PasswordForge::Generator.new(
90
+ upper_case: false, lower_case: false, numeric_case: false, special_case: false
91
+ )
92
+ # => raises PasswordForge::NoCharsetSelectedError
93
+ ```
94
+
95
+ A non-positive or non-integer `length` raises `ArgumentError`.
96
+
97
+ ## Character sets
98
+
99
+ | Flag | Characters |
100
+ | -------------- | ------------------------ |
101
+ | `upper_case` | `A`–`Z` |
102
+ | `lower_case` | `a`–`z` |
103
+ | `numeric_case` | `0`–`9` |
104
+ | `special_case` | `` !"#$%&'()*+,-./:;<=>?@[\]^_`{|}~ `` |
105
+
106
+ ## Trying it locally
107
+
108
+ You can run the gem without installing it from RubyGems:
109
+
110
+ - **From this repository:** `ruby examples/smoke_test.rb` prints a few sample
111
+ passwords straight from the source tree.
112
+ - **From another project:** add `gem "password_forge", path:
113
+ "/path/to/password-forge-ruby-gem"` to that project's `Gemfile`, run `bundle
114
+ install`, then `require "password_forge"`. See
115
+ [`examples/local-consumer/`](examples/local-consumer) for a working example.
116
+
117
+ Full instructions, including the interactive console and building a local
118
+ `.gem`, are in [docs/local-testing.md](docs/local-testing.md).
119
+
120
+ ## Roadmap
121
+
122
+ `PasswordForge` is developed in incremental, tagged releases:
123
+
124
+ - **0.0.1** — Core generator, character sets, validation, tests, docs.
125
+ - **0.0.2** — Local-testing examples (`examples/`) and documentation (`docs/`).
126
+ - **0.1.0** — First public release on RubyGems.org via Trusted Publishing.
127
+ - **0.2.0** — Kiro skills for gem authors (feature TDD, version bump, release).
128
+ - **0.3.0** — Project `.kiro/` folder with steering and conventions.
129
+ - **0.4.0** — Kiro hooks (run specs on save, changelog reminders, and more).
130
+ - **0.5.0** — A Ruby MCP server exposing password generation as a tool.
131
+ - **0.6.0** — A fluent/builder API on top of the keyword-argument API.
132
+
133
+ ## Development
134
+
135
+ After checking out the repo, run `bin/setup` to install dependencies. Then run
136
+ `bundle exec rake` to run the tests and the linter. You can also run
137
+ `bin/console` for an interactive prompt to experiment, or
138
+ `ruby examples/smoke_test.rb` for a quick check straight from source.
139
+
140
+ To install this gem onto your local machine, run `bundle exec rake install`. For
141
+ the full local workflow — including running it from a separate project and
142
+ uninstalling — see [docs/local-testing.md](docs/local-testing.md).
143
+
144
+ ## Contributing
145
+
146
+ Bug reports and pull requests are welcome on GitHub at
147
+ <https://github.com/devandreacarratta/password-forge-ruby-gem>.
148
+
149
+ ## License
150
+
151
+ The gem is available as open source under the terms of the
152
+ [MIT License](https://opensource.org/licenses/MIT).
data/Rakefile ADDED
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler/gem_tasks"
4
+ require "rspec/core/rake_task"
5
+
6
+ RSpec::Core::RakeTask.new(:spec)
7
+
8
+ require "rubocop/rake_task"
9
+
10
+ RuboCop::RakeTask.new
11
+
12
+ task default: %i[spec rubocop]
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PasswordForge
4
+ # Value object holding the available character sets and building the pool
5
+ # of characters from the selected sets.
6
+ #
7
+ # The four sets mirror the character categories of the original C# design:
8
+ # uppercase (A-Z), lowercase (a-z), numeric (0-9) and special (punctuation).
9
+ module Charset
10
+ # Uppercase letters, from 'A' to 'Z'.
11
+ UPPER = ("A".."Z").to_a.freeze
12
+
13
+ # Lowercase letters, from 'a' to 'z'.
14
+ LOWER = ("a".."z").to_a.freeze
15
+
16
+ # Numeric digits, from '0' to '9'.
17
+ NUMERIC = ("0".."9").to_a.freeze
18
+
19
+ # Special (punctuation) characters.
20
+ SPECIAL = %w[! " # $ % & ' ( ) * + , - . / : ; < = > ? @ [ \\ ] ^ _ ` { | } ~].freeze
21
+
22
+ # Builds the pool of characters from the selected sets.
23
+ #
24
+ # @param upper [Boolean] include uppercase letters (A-Z)
25
+ # @param lower [Boolean] include lowercase letters (a-z)
26
+ # @param numeric [Boolean] include numeric digits (0-9)
27
+ # @param special [Boolean] include special characters
28
+ # @return [Array<String>] the combined pool of unique characters
29
+ def self.build(upper:, lower:, numeric:, special:)
30
+ pool = []
31
+ pool.concat(UPPER) if upper
32
+ pool.concat(LOWER) if lower
33
+ pool.concat(NUMERIC) if numeric
34
+ pool.concat(SPECIAL) if special
35
+ pool
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PasswordForge
4
+ # Base error class for all PasswordForge-specific errors.
5
+ class Error < StandardError; end
6
+
7
+ # Raised when a generator is configured with no character set selected,
8
+ # mirroring the +InvalidCharSetException+ of the original C# design.
9
+ class NoCharsetSelectedError < Error
10
+ # Default message shown when no character set is selected.
11
+ DEFAULT_MESSAGE = "At least one character set must be selected " \
12
+ "(upper_case, lower_case, numeric_case or special_case)."
13
+
14
+ def initialize(message = DEFAULT_MESSAGE)
15
+ super
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,63 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "securerandom"
4
+
5
+ module PasswordForge
6
+ # Generates random passwords from selectable character sets.
7
+ #
8
+ # The constructor mirrors the original C# +PasswordGenerator+ design: four
9
+ # boolean flags select the character sets, all enabled by default, and a
10
+ # {NoCharsetSelectedError} is raised when none is selected.
11
+ #
12
+ # @example Generate a password with all character sets (default)
13
+ # PasswordForge::Generator.new.generate # => "aB3$xY7!qR2@kL9%"
14
+ #
15
+ # @example Generate a numeric-only PIN
16
+ # PasswordForge::Generator.new(
17
+ # upper_case: false, lower_case: false, numeric_case: true, special_case: false, length: 4
18
+ # ).generate # => "8391"
19
+ class Generator
20
+ # Default password length used when none is specified.
21
+ DEFAULT_LENGTH = 16
22
+
23
+ # @return [Integer] the configured password length
24
+ attr_reader :length
25
+
26
+ # @param upper_case [Boolean] include uppercase letters (A-Z)
27
+ # @param lower_case [Boolean] include lowercase letters (a-z)
28
+ # @param numeric_case [Boolean] include numeric digits (0-9)
29
+ # @param special_case [Boolean] include special characters
30
+ # @param length [Integer] the length of the generated password
31
+ # @raise [NoCharsetSelectedError] if no character set is selected
32
+ # @raise [ArgumentError] if length is not a positive integer
33
+ def initialize(upper_case: true, lower_case: true, numeric_case: true, special_case: true,
34
+ length: DEFAULT_LENGTH)
35
+ Validation.validate_charset_selection(
36
+ upper: upper_case, lower: lower_case, numeric: numeric_case, special: special_case
37
+ )
38
+ Validation.validate_length(length)
39
+
40
+ @length = length
41
+ @pool = Charset.build(
42
+ upper: upper_case, lower: lower_case, numeric: numeric_case, special: special_case
43
+ )
44
+ end
45
+
46
+ # Generates a random password of the configured length.
47
+ #
48
+ # Uses {SecureRandom} for cryptographically secure randomness.
49
+ #
50
+ # @return [String] the generated password
51
+ def generate
52
+ Array.new(@length) { @pool[SecureRandom.random_number(@pool.length)] }.join
53
+ end
54
+ end
55
+
56
+ # Convenience wrapper around {Generator#generate}.
57
+ #
58
+ # @see Generator#initialize
59
+ # @return [String] the generated password
60
+ def self.generate(**options)
61
+ Generator.new(**options).generate
62
+ end
63
+ end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PasswordForge
4
+ # Validates the configuration of a password generator.
5
+ #
6
+ # Mirrors the validation responsibilities of the original C# design:
7
+ # rejecting a configuration with no character set selected and rejecting
8
+ # an invalid password length.
9
+ module Validation
10
+ # Ensures that at least one character set is selected.
11
+ #
12
+ # @param upper [Boolean] uppercase flag
13
+ # @param lower [Boolean] lowercase flag
14
+ # @param numeric [Boolean] numeric flag
15
+ # @param special [Boolean] special flag
16
+ # @raise [NoCharsetSelectedError] if every flag is false
17
+ # @return [void]
18
+ def self.validate_charset_selection(upper:, lower:, numeric:, special:)
19
+ return if upper || lower || numeric || special
20
+
21
+ raise NoCharsetSelectedError
22
+ end
23
+
24
+ # Ensures that the requested password length is a positive integer.
25
+ #
26
+ # @param length [Integer] the requested password length
27
+ # @raise [ArgumentError] if length is not a positive integer
28
+ # @return [void]
29
+ def self.validate_length(length)
30
+ raise ArgumentError, "length must be an Integer, got #{length.class}" unless length.is_a?(Integer)
31
+ raise ArgumentError, "length must be a positive integer, got #{length}" unless length.positive?
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PasswordForge
4
+ VERSION = "0.0.2"
5
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "password_forge/version"
4
+ require_relative "password_forge/errors"
5
+ require_relative "password_forge/charset"
6
+ require_relative "password_forge/validation"
7
+ require_relative "password_forge/generator"
8
+
9
+ # PasswordForge generates random passwords from selectable character sets.
10
+ module PasswordForge
11
+ end
@@ -0,0 +1,4 @@
1
+ module PasswordForge
2
+ VERSION: String
3
+ # See the writing guide of rbs: https://github.com/ruby/rbs#guides
4
+ end
metadata ADDED
@@ -0,0 +1,58 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: password_forge
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.2
5
+ platform: ruby
6
+ authors:
7
+ - Andrea Carratta
8
+ bindir: exe
9
+ cert_chain: []
10
+ date: 1980-01-02 00:00:00.000000000 Z
11
+ dependencies: []
12
+ description: PasswordForge generates random passwords from selectable character sets
13
+ (uppercase, lowercase, numeric, special). Built as a learning-friendly, well-tested
14
+ Ruby gem with a clean, idiomatic API.
15
+ executables: []
16
+ extensions: []
17
+ extra_rdoc_files: []
18
+ files:
19
+ - ".kiro/steering/product.md"
20
+ - ".kiro/steering/structure.md"
21
+ - ".kiro/steering/tech.md"
22
+ - CHANGELOG.md
23
+ - LICENSE.txt
24
+ - README.md
25
+ - Rakefile
26
+ - lib/password_forge.rb
27
+ - lib/password_forge/charset.rb
28
+ - lib/password_forge/errors.rb
29
+ - lib/password_forge/generator.rb
30
+ - lib/password_forge/validation.rb
31
+ - lib/password_forge/version.rb
32
+ - sig/password_forge.rbs
33
+ homepage: https://github.com/devandreacarratta/password-forge-ruby-gem
34
+ licenses:
35
+ - MIT
36
+ metadata:
37
+ homepage_uri: https://github.com/devandreacarratta/password-forge-ruby-gem
38
+ source_code_uri: https://github.com/devandreacarratta/password-forge-ruby-gem/tree/main
39
+ changelog_uri: https://github.com/devandreacarratta/password-forge-ruby-gem/blob/main/CHANGELOG.md
40
+ rubygems_mfa_required: 'true'
41
+ rdoc_options: []
42
+ require_paths:
43
+ - lib
44
+ required_ruby_version: !ruby/object:Gem::Requirement
45
+ requirements:
46
+ - - ">="
47
+ - !ruby/object:Gem::Version
48
+ version: 3.0.0
49
+ required_rubygems_version: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - ">="
52
+ - !ruby/object:Gem::Version
53
+ version: '0'
54
+ requirements: []
55
+ rubygems_version: 4.0.16
56
+ specification_version: 4
57
+ summary: A configurable password generator with selectable character sets.
58
+ test_files: []