fiber_pattern 0.2.0 → 0.4.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: d0ed0351785dcc4a977f481938aecf3b3d787b91752558bc307cf2b72ad962e8
4
+ data.tar.gz: c16dbb1de69fa26de99bc7dc12504db55c1707bf5a9e7388973fdf1954998e18
5
5
  SHA512:
6
- metadata.gz: 332c10d1d8a7cd0cf594ac22d597e0b6dc589155094157ecb7f7ca26552347ae1d8917ea63fcadce24158661ec668919a91b07cd2231e13f1f0a34532c2c2b42
7
- data.tar.gz: de8c48b9c34681594201c7443e478bda797d432af8d2b3a1d66767cb94f4dfd84b44e30554c87b0b9d62743dd9cf236e04e11e26a7f90d4d5c87e858c9b243fd
6
+ metadata.gz: d9fc5ede7f7838208da2e8382414bebf621af83d557df70d5222531b8087b389dda3d213608149d4f478c146ef803b676c93925d4c0749e2cd889bd71132ba3e
7
+ data.tar.gz: '0090202feec59f5190da9296132831e0ffaa12389cf0fed5a66c4222662c5700aec07a42ef2b0b1fa0c748261a801eccf6bc63baf8130be985f7b98a4d882699'
@@ -0,0 +1,61 @@
1
+ # frozen_string_literal: true
2
+
3
+ module FiberPattern
4
+ # Value object representing key body dimensions.
5
+ #
6
+ # Accepts any set of named measurements as keyword arguments.
7
+ # Each value should be a FiberUnits::Length.
8
+ #
9
+ # @example
10
+ # body = FiberPattern::BodyMeasurements.new(
11
+ # bust: 36.inches,
12
+ # waist: 30.inches,
13
+ # hip: 38.inches,
14
+ # arm_length: 24.inches
15
+ # )
16
+ # body.bust # => 36.inches
17
+ # body[:waist] # => 30.inches
18
+ # body.measurements # => [:bust, :waist, :hip, :arm_length]
19
+ class BodyMeasurements
20
+ # @return [Hash<Symbol, FiberUnits::Length>] all measurements
21
+ attr_reader :data
22
+
23
+ # @param measurements [Hash<Symbol, FiberUnits::Length>] named body measurements
24
+ def initialize(**measurements)
25
+ raise ArgumentError, "at least one measurement is required" if measurements.empty?
26
+
27
+ @data = measurements.freeze
28
+ define_accessors!
29
+ end
30
+
31
+ # Returns the measurement names.
32
+ #
33
+ # @return [Array<Symbol>]
34
+ def measurements
35
+ data.keys
36
+ end
37
+
38
+ # Hash-style access to a measurement.
39
+ #
40
+ # @param name [Symbol]
41
+ # @return [FiberUnits::Length, nil]
42
+ def [](name)
43
+ data[name]
44
+ end
45
+
46
+ # Returns measurements as a plain hash.
47
+ #
48
+ # @return [Hash<Symbol, FiberUnits::Length>]
49
+ def to_h
50
+ data.dup
51
+ end
52
+
53
+ private
54
+
55
+ def define_accessors!
56
+ data.each_key do |name|
57
+ define_singleton_method(name) { data[name] }
58
+ end
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,74 @@
1
+ # frozen_string_literal: true
2
+
3
+ module FiberPattern
4
+ # Value object representing ease preferences per measurement.
5
+ #
6
+ # Ease is the difference between body measurements and finished garment
7
+ # measurements. Positive ease creates a looser fit; negative ease creates
8
+ # a tighter fit.
9
+ #
10
+ # @example
11
+ # ease = FiberPattern::Ease.new(
12
+ # bust: 4.inches,
13
+ # waist: 2.inches,
14
+ # hip: 2.inches
15
+ # )
16
+ # ease.bust # => 4.inches
17
+ # ease[:waist] # => 2.inches
18
+ # ease.for(:bust) # => 4.inches
19
+ class Ease
20
+ # @return [Hash<Symbol, FiberUnits::Length>] all ease values
21
+ attr_reader :data
22
+
23
+ # @param ease_values [Hash<Symbol, FiberUnits::Length>] named ease values
24
+ def initialize(**ease_values)
25
+ raise ArgumentError, "at least one ease value is required" if ease_values.empty?
26
+
27
+ @data = ease_values.freeze
28
+ define_accessors!
29
+ end
30
+
31
+ # Returns the ease value for a given measurement.
32
+ #
33
+ # @param name [Symbol]
34
+ # @return [FiberUnits::Length]
35
+ # @raise [ArgumentError] if no ease is defined for the measurement
36
+ def for(name)
37
+ raise ArgumentError, "no ease defined for #{name.inspect}" unless data.key?(name)
38
+
39
+ data[name]
40
+ end
41
+
42
+ # Hash-style access to an ease value.
43
+ #
44
+ # @param name [Symbol]
45
+ # @return [FiberUnits::Length, nil]
46
+ def [](name)
47
+ data[name]
48
+ end
49
+
50
+ # Returns all measurement names that have ease defined.
51
+ #
52
+ # @return [Array<Symbol>]
53
+ def measurements
54
+ data.keys
55
+ end
56
+
57
+ # Returns ease values as a plain hash.
58
+ #
59
+ # @return [Hash<Symbol, FiberUnits::Length>]
60
+ def to_h
61
+ data.dup
62
+ end
63
+
64
+ private
65
+
66
+ def define_accessors!
67
+ data.each_key do |name|
68
+ next if name == :for # avoid overriding #for
69
+
70
+ define_singleton_method(name) { data[name] }
71
+ end
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,76 @@
1
+ # frozen_string_literal: true
2
+
3
+ module FiberPattern
4
+ # Combines body measurements with ease to produce finished garment dimensions.
5
+ #
6
+ # @example
7
+ # body = FiberPattern::BodyMeasurements.new(bust: 36.inches, waist: 30.inches)
8
+ # ease = FiberPattern::Ease.new(bust: 4.inches, waist: 2.inches)
9
+ # garment = FiberPattern::GarmentSizing.new(body: body, ease: ease)
10
+ # garment.bust # => 40.inches
11
+ # garment.waist # => 32.inches
12
+ class GarmentSizing
13
+ # @return [FiberPattern::BodyMeasurements]
14
+ attr_reader :body
15
+
16
+ # @return [FiberPattern::Ease]
17
+ attr_reader :ease
18
+
19
+ # @param body [FiberPattern::BodyMeasurements] body measurements
20
+ # @param ease [FiberPattern::Ease] ease preferences
21
+ def initialize(body:, ease:)
22
+ validate!(body, ease)
23
+ @body = body
24
+ @ease = ease
25
+ define_accessors!
26
+ end
27
+
28
+ # Returns the finished garment dimension for a given measurement.
29
+ #
30
+ # @param name [Symbol]
31
+ # @return [FiberUnits::Length] body measurement + ease
32
+ # @raise [ArgumentError] if the measurement is not defined
33
+ def dimension(name)
34
+ raise ArgumentError, "unknown measurement #{name.inspect}" unless body.data.key?(name)
35
+
36
+ body[name] + (ease[name] || zero_length(body[name]))
37
+ end
38
+
39
+ # Hash-style access to finished dimensions.
40
+ #
41
+ # @param name [Symbol]
42
+ # @return [FiberUnits::Length]
43
+ def [](name)
44
+ dimension(name)
45
+ end
46
+
47
+ # Returns all finished garment dimensions.
48
+ #
49
+ # @return [Hash<Symbol, FiberUnits::Length>]
50
+ def dimensions
51
+ body.measurements.each_with_object({}) do |name, result|
52
+ result[name] = dimension(name)
53
+ end
54
+ end
55
+
56
+ private
57
+
58
+ def validate!(body, ease)
59
+ ease.measurements.each do |name|
60
+ unless body.measurements.include?(name)
61
+ raise ArgumentError, "ease defines #{name.inspect} but body measurements does not"
62
+ end
63
+ end
64
+ end
65
+
66
+ def define_accessors!
67
+ body.measurements.each do |name|
68
+ define_singleton_method(name) { dimension(name) }
69
+ end
70
+ end
71
+
72
+ def zero_length(reference)
73
+ reference * 0
74
+ end
75
+ end
76
+ end
@@ -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
@@ -0,0 +1,86 @@
1
+ # frozen_string_literal: true
2
+
3
+ module FiberPattern
4
+ # Standard size chart based on Craft Yarn Council (CYC) guidelines.
5
+ #
6
+ # Provides lookup of standard body measurements by size, and a closest-size
7
+ # finder that matches body measurements to the nearest standard size.
8
+ #
9
+ # @example
10
+ # chart = FiberPattern::SizeChart.new
11
+ # chart.size(:m) # => { bust: 36.inches, waist: 28.inches, hip: 38.inches }
12
+ # chart.closest_size(bust: 37.inches, waist: 29.inches, hip: 39.inches) # => :m
13
+ class SizeChart
14
+ # CYC standard women's body measurements (inches).
15
+ # Source: Craft Yarn Council Standards & Guidelines
16
+ CYC_WOMEN = {
17
+ xs: {bust: 28, waist: 20, hip: 30},
18
+ s: {bust: 32, waist: 24, hip: 34},
19
+ m: {bust: 36, waist: 28, hip: 38},
20
+ l: {bust: 40, waist: 32, hip: 42},
21
+ xl: {bust: 44, waist: 36, hip: 46},
22
+ xxl: {bust: 48, waist: 40, hip: 50},
23
+ xxxl: {bust: 52, waist: 44, hip: 54},
24
+ xxxxl: {bust: 56, waist: 48, hip: 58},
25
+ xxxxxl: {bust: 60, waist: 52, hip: 62}
26
+ }.freeze
27
+
28
+ # @return [Hash<Symbol, Hash<Symbol, FiberUnits::Length>>] size chart data
29
+ attr_reader :chart
30
+
31
+ # @return [Array<Symbol>] ordered size names
32
+ attr_reader :sizes
33
+
34
+ # Creates a new size chart.
35
+ #
36
+ # @param chart [Hash<Symbol, Hash<Symbol, Numeric>>] raw chart data mapping size names
37
+ # to measurement hashes. Values are converted to inches. Defaults to CYC_WOMEN.
38
+ def initialize(chart: CYC_WOMEN)
39
+ @chart = chart.each_with_object({}) do |(size_name, measurements), result|
40
+ result[size_name] = measurements.transform_values { |v| v.is_a?(Numeric) ? v.inches : v }
41
+ end.freeze
42
+ @sizes = @chart.keys.freeze
43
+ end
44
+
45
+ # Returns body measurements for a standard size.
46
+ #
47
+ # @param name [Symbol] size name
48
+ # @return [Hash<Symbol, FiberUnits::Length>]
49
+ # @raise [ArgumentError] if the size is not in the chart
50
+ def size(name)
51
+ raise ArgumentError, "unknown size #{name.inspect}" unless chart.key?(name)
52
+
53
+ chart[name]
54
+ end
55
+
56
+ # Finds the closest standard size to the given body measurements.
57
+ #
58
+ # Compares using the sum of squared differences across all provided
59
+ # measurements. Only measurements present in both the query and the
60
+ # chart are compared.
61
+ #
62
+ # @param measurements [Hash<Symbol, FiberUnits::Length>] body measurements to match
63
+ # @return [Symbol] the closest size name
64
+ # @raise [ArgumentError] if no measurements overlap with the chart
65
+ def closest_size(**measurements)
66
+ comparable_keys = measurements.keys & chart.values.first.keys
67
+ raise ArgumentError, "no comparable measurements provided" if comparable_keys.empty?
68
+
69
+ sizes.min_by do |size_name|
70
+ size_data = chart[size_name]
71
+ comparable_keys.sum do |key|
72
+ diff = measurements[key].value - size_data[key].value
73
+ diff * diff
74
+ end
75
+ end
76
+ end
77
+
78
+ # Returns a BodyMeasurements object for a standard size.
79
+ #
80
+ # @param name [Symbol] size name
81
+ # @return [FiberPattern::BodyMeasurements]
82
+ def body_measurements_for(name)
83
+ BodyMeasurements.new(**size(name))
84
+ end
85
+ end
86
+ 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.4.0"
7
7
  end
8
8
  # :nocov:
data/lib/fiber_pattern.rb CHANGED
@@ -6,6 +6,13 @@ 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"
12
+ require_relative "fiber_pattern/body_measurements"
13
+ require_relative "fiber_pattern/ease"
14
+ require_relative "fiber_pattern/garment_sizing"
15
+ require_relative "fiber_pattern/size_chart"
9
16
 
10
17
  # Utilities for generating fiber pattern measurements from gauge data.
11
18
  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.4.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,15 @@ files:
48
76
  - LICENSE.txt
49
77
  - README.md
50
78
  - lib/fiber_pattern.rb
79
+ - lib/fiber_pattern/body_measurements.rb
80
+ - lib/fiber_pattern/ease.rb
81
+ - lib/fiber_pattern/garment_sizing.rb
82
+ - lib/fiber_pattern/grade_rules.rb
83
+ - lib/fiber_pattern/grader.rb
51
84
  - lib/fiber_pattern/repeat.rb
52
85
  - lib/fiber_pattern/scaling.rb
86
+ - lib/fiber_pattern/shaping.rb
87
+ - lib/fiber_pattern/size_chart.rb
53
88
  - lib/fiber_pattern/sizing.rb
54
89
  - lib/fiber_pattern/version.rb
55
90
  - sig/fiber_pattern.rbs