fiber_pattern 0.2.0 → 0.3.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: 2975f472d7c4d0d21ffe8a6a6eb918d145b8cc37a7191e141c4aa167bd09e136
4
- data.tar.gz: 723d930af45c9e963001a0c678e50db9da2fe75690c9297bbf524f898cb5bb32
3
+ metadata.gz: 97a576e2a84dd27f7a1690c4bddbe30adfa4eea6bd323aa19dd3389a1e10e5cb
4
+ data.tar.gz: fa3d3634bab8fa1cdd9f64b2c2f9027d4afc1361a67a26284ed71df6ec8b353e
5
5
  SHA512:
6
- metadata.gz: 332c10d1d8a7cd0cf594ac22d597e0b6dc589155094157ecb7f7ca26552347ae1d8917ea63fcadce24158661ec668919a91b07cd2231e13f1f0a34532c2c2b42
7
- data.tar.gz: de8c48b9c34681594201c7443e478bda797d432af8d2b3a1d66767cb94f4dfd84b44e30554c87b0b9d62743dd9cf236e04e11e26a7f90d4d5c87e858c9b243fd
6
+ metadata.gz: bfc2843e21944d714cfee9270802e629ab1d08e340f1bfb7a7b59f2e5f8bd30469369b6108ad61f3429cfc306150f7af463d53dd43665d572ed6025a33df7c3d
7
+ data.tar.gz: 6f065951604cbcf7612ea115b2aceeff0978f71b0620d61ddafe4e0013dfa2f400f9baa084e45d0da432c5ed999913f240e9c53f1c3a79c013537667f743f83a
@@ -0,0 +1,54 @@
1
+ # frozen_string_literal: true
2
+
3
+ module FiberPattern
4
+ # Defines per-measurement step values used to grade a pattern across sizes.
5
+ #
6
+ # Each rule maps a measurement name to a step value (a FiberUnits::Length)
7
+ # that represents how much that measurement changes between adjacent sizes.
8
+ #
9
+ # @example
10
+ # rules = FiberPattern::GradeRules.new(
11
+ # bust: { step: 2.inches },
12
+ # waist: { step: 2.inches },
13
+ # sleeve_length: { step: 0.5.inches }
14
+ # )
15
+ # rules.step_for(:bust) # => 2.inches
16
+ class GradeRules
17
+ # @return [Hash<Symbol, Hash>] raw rules keyed by measurement name
18
+ attr_reader :rules
19
+
20
+ # @param rules [Hash<Symbol, Hash>] measurement rules, each with a :step key
21
+ def initialize(**rules)
22
+ validate!(rules)
23
+ @rules = rules.freeze
24
+ end
25
+
26
+ # Returns the step value for a given measurement.
27
+ #
28
+ # @param measurement [Symbol] measurement name
29
+ # @return [FiberUnits::Length] step between adjacent sizes
30
+ # @raise [ArgumentError] if the measurement has no rule defined
31
+ def step_for(measurement)
32
+ raise ArgumentError, "no rule defined for #{measurement.inspect}" unless rules.key?(measurement)
33
+
34
+ rules[measurement][:step]
35
+ end
36
+
37
+ # Returns all measurement names that have rules defined.
38
+ #
39
+ # @return [Array<Symbol>]
40
+ def measurements
41
+ rules.keys
42
+ end
43
+
44
+ private
45
+
46
+ def validate!(rules)
47
+ rules.each do |name, config|
48
+ unless config.is_a?(Hash) && config.key?(:step)
49
+ raise ArgumentError, "rule for #{name.inspect} must be a Hash with a :step key"
50
+ end
51
+ end
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,74 @@
1
+ # frozen_string_literal: true
2
+
3
+ module FiberPattern
4
+ # Applies grade rules to a base size's measurements to produce a full size range.
5
+ #
6
+ # @example
7
+ # grader = FiberPattern::Grader.new(
8
+ # base_size: :m,
9
+ # measurements: { bust: 36.inches, waist: 30.inches },
10
+ # rules: grade_rules
11
+ # )
12
+ # grader.size(:l) # => { bust: 38.inches, waist: 32.inches }
13
+ # grader.all_sizes # => { xs: {...}, s: {...}, ... }
14
+ class Grader
15
+ # Standard size progression from smallest to largest.
16
+ SIZES = %i[xs s m l xl xxl xxxl xxxxl xxxxxl].freeze
17
+
18
+ # @return [Symbol] the base size used as the grading anchor
19
+ # @return [Hash<Symbol, FiberUnits::Length>] base measurements
20
+ # @return [FiberPattern::GradeRules] grading rules
21
+ attr_reader :base_size, :measurements, :rules
22
+
23
+ # @param base_size [Symbol] one of the standard SIZES
24
+ # @param measurements [Hash<Symbol, FiberUnits::Length>] base size measurements
25
+ # @param rules [FiberPattern::GradeRules] grade rules to apply
26
+ # @param sizes [Array<Symbol>] optional custom size list (defaults to SIZES)
27
+ def initialize(base_size:, measurements:, rules:, sizes: SIZES)
28
+ @sizes = sizes
29
+ validate!(base_size, measurements, rules)
30
+ @base_size = base_size
31
+ @measurements = measurements.freeze
32
+ @rules = rules
33
+ end
34
+
35
+ # Returns graded measurements for a single size.
36
+ #
37
+ # @param target [Symbol] the size to compute
38
+ # @return [Hash<Symbol, FiberUnits::Length>]
39
+ # @raise [ArgumentError] if the target size is not in the size list
40
+ def size(target)
41
+ raise ArgumentError, "unknown size #{target.inspect}" unless @sizes.include?(target)
42
+
43
+ offset = @sizes.index(target) - @sizes.index(base_size)
44
+
45
+ measurements.each_with_object({}) do |(name, base_value), result|
46
+ step = rules.step_for(name)
47
+ result[name] = base_value + (step * offset)
48
+ end
49
+ end
50
+
51
+ # Returns graded measurements for all sizes.
52
+ #
53
+ # @return [Hash<Symbol, Hash<Symbol, FiberUnits::Length>>]
54
+ def all_sizes
55
+ @sizes.each_with_object({}) do |size_name, result|
56
+ result[size_name] = size(size_name)
57
+ end
58
+ end
59
+
60
+ private
61
+
62
+ def validate!(base_size, measurements, rules)
63
+ unless @sizes.include?(base_size)
64
+ raise ArgumentError, "base_size #{base_size.inspect} is not in the size list"
65
+ end
66
+
67
+ measurements.each_key do |name|
68
+ unless rules.measurements.include?(name)
69
+ raise ArgumentError, "no grade rule for measurement #{name.inspect}"
70
+ end
71
+ end
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,105 @@
1
+ # frozen_string_literal: true
2
+
3
+ module FiberPattern
4
+ # Calculates evenly distributed shaping (increases or decreases) across a span of rows.
5
+ #
6
+ # @example Decreasing from 60 to 40 stitches over 30 rows
7
+ # shaping = FiberPattern::Shaping.new(
8
+ # from: 60.stitches,
9
+ # to: 40.stitches,
10
+ # over: 30.rows,
11
+ # method: :decrease
12
+ # )
13
+ # shaping.total_changes # => 10
14
+ # shaping.every_n_rows # => 3
15
+ # shaping.schedule # => [{row: 1, action: :dec}, {row: 4, action: :dec}, ...]
16
+ class Shaping
17
+ # @return [FiberUnits::Stitches] starting stitch count
18
+ # @return [FiberUnits::Stitches] ending stitch count
19
+ # @return [FiberUnits::Rows] total rows available for shaping
20
+ # @return [Symbol] shaping method (:increase or :decrease)
21
+ # @return [Integer] stitches changed per shaping row (default 2 for paired shaping)
22
+ attr_reader :from, :to, :over, :method, :stitches_per_event
23
+
24
+ # @param from [FiberUnits::Stitches] starting stitch count
25
+ # @param to [FiberUnits::Stitches] target stitch count
26
+ # @param over [FiberUnits::Rows] number of rows available for shaping
27
+ # @param method [Symbol] :increase or :decrease
28
+ # @param stitches_per_event [Integer] stitches changed per shaping row (default 2 for paired shaping)
29
+ def initialize(from:, to:, over:, method:, stitches_per_event: 2)
30
+ validate!(from, to, over, method)
31
+ @from = from
32
+ @to = to
33
+ @over = over
34
+ @method = method
35
+ @stitches_per_event = stitches_per_event
36
+ end
37
+
38
+ # Total number of shaping events needed.
39
+ #
40
+ # @return [Integer]
41
+ def total_changes
42
+ stitch_difference / stitches_per_event
43
+ end
44
+
45
+ # Base interval between shaping rows.
46
+ #
47
+ # @return [Integer]
48
+ def every_n_rows
49
+ return 0 if total_changes.zero?
50
+
51
+ over.value / total_changes
52
+ end
53
+
54
+ # Row-by-row schedule of shaping events, distributing any remainder rows
55
+ # evenly across the span.
56
+ #
57
+ # @return [Array<Hash>] each entry has :row and :action keys
58
+ def schedule
59
+ return [] if total_changes.zero?
60
+
61
+ changes = total_changes
62
+ rows_available = over.value
63
+ action = (method == :decrease) ? :dec : :inc
64
+
65
+ base_interval = rows_available / changes
66
+ remainder = rows_available % changes
67
+
68
+ schedule = []
69
+ current_row = 0
70
+
71
+ changes.times do |i|
72
+ # Spread remainder evenly: the first `remainder` intervals get +1 row
73
+ interval = base_interval + ((i < remainder) ? 1 : 0)
74
+ current_row += interval
75
+ schedule << {row: current_row, action: action}
76
+ end
77
+
78
+ schedule
79
+ end
80
+
81
+ private
82
+
83
+ def stitch_difference
84
+ (from.value - to.value).abs
85
+ end
86
+
87
+ def validate!(from, to, over, method)
88
+ unless %i[increase decrease].include?(method)
89
+ raise ArgumentError, "method must be :increase or :decrease, got #{method.inspect}"
90
+ end
91
+
92
+ if method == :decrease && from.value < to.value
93
+ raise ArgumentError, "from must be greater than to for :decrease shaping"
94
+ end
95
+
96
+ if method == :increase && from.value > to.value
97
+ raise ArgumentError, "from must be less than to for :increase shaping"
98
+ end
99
+
100
+ if over.value <= 0
101
+ raise ArgumentError, "over must be a positive row count"
102
+ end
103
+ end
104
+ end
105
+ end
@@ -3,6 +3,6 @@
3
3
  # :nocov:
4
4
  module FiberPattern
5
5
  # Current gem version.
6
- VERSION = "0.2.0"
6
+ VERSION = "0.3.0"
7
7
  end
8
8
  # :nocov:
data/lib/fiber_pattern.rb CHANGED
@@ -6,6 +6,9 @@ require_relative "fiber_pattern/version"
6
6
  require_relative "fiber_pattern/sizing"
7
7
  require_relative "fiber_pattern/repeat"
8
8
  require_relative "fiber_pattern/scaling"
9
+ require_relative "fiber_pattern/shaping"
10
+ require_relative "fiber_pattern/grade_rules"
11
+ require_relative "fiber_pattern/grader"
9
12
 
10
13
  # Utilities for generating fiber pattern measurements from gauge data.
11
14
  module FiberPattern
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: fiber_pattern
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Meagan Waller
@@ -37,6 +37,34 @@ dependencies:
37
37
  - - ">="
38
38
  - !ruby/object:Gem::Version
39
39
  version: '0'
40
+ - !ruby/object:Gem::Dependency
41
+ name: minitest
42
+ requirement: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - ">="
45
+ - !ruby/object:Gem::Version
46
+ version: '0'
47
+ type: :development
48
+ prerelease: false
49
+ version_requirements: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - ">="
52
+ - !ruby/object:Gem::Version
53
+ version: '0'
54
+ - !ruby/object:Gem::Dependency
55
+ name: rake
56
+ requirement: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - ">="
59
+ - !ruby/object:Gem::Version
60
+ version: '0'
61
+ type: :development
62
+ prerelease: false
63
+ version_requirements: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - ">="
66
+ - !ruby/object:Gem::Version
67
+ version: '0'
40
68
  description: A Ruby gem for calculating pattern sizes and stitch counts for knitting
41
69
  and crochet projects.
42
70
  email:
@@ -48,8 +76,11 @@ files:
48
76
  - LICENSE.txt
49
77
  - README.md
50
78
  - lib/fiber_pattern.rb
79
+ - lib/fiber_pattern/grade_rules.rb
80
+ - lib/fiber_pattern/grader.rb
51
81
  - lib/fiber_pattern/repeat.rb
52
82
  - lib/fiber_pattern/scaling.rb
83
+ - lib/fiber_pattern/shaping.rb
53
84
  - lib/fiber_pattern/sizing.rb
54
85
  - lib/fiber_pattern/version.rb
55
86
  - sig/fiber_pattern.rbs