@recipe-scope/quantity-parser 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.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024
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 all
13
+ 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 THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,95 @@
1
+ # @recipe-scope/quantity-parser
2
+
3
+ Zero-dependency Japanese quantity string parser for recipe ingredients.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install @recipe-scope/quantity-parser
9
+ ```
10
+
11
+ ## Usage
12
+
13
+ ### Basic Parsing
14
+
15
+ ```typescript
16
+ import { QuantityParser } from '@recipe-scope/quantity-parser';
17
+
18
+ // Simple quantities
19
+ QuantityParser.parse('100g'); // { value: 100, unit: 'g' }
20
+ QuantityParser.parse('大さじ1'); // { value: 1, unit: '大さじ' }
21
+ QuantityParser.parse('1/2カップ'); // { value: 0.5, unit: 'カップ' }
22
+
23
+ // Full-width numbers
24
+ QuantityParser.parse('3個'); // { value: 3, unit: '個' }
25
+
26
+ // Fractions
27
+ QuantityParser.parse('1と1/2カップ'); // { value: 1.5, unit: 'カップ' }
28
+ QuantityParser.parse('半個'); // { value: 0.5, unit: '個' }
29
+
30
+ // Ranges (uses minimum value)
31
+ QuantityParser.parse('1~2本'); // { value: 1, unit: '本' }
32
+
33
+ // Parentheses (extracts grams/ml)
34
+ QuantityParser.parse('なす(100g)'); // { value: 100, unit: 'g' }
35
+
36
+ // Tube measurements
37
+ QuantityParser.parse('チューブ3cm'); // { value: 3, unit: 'チューブcm' }
38
+ ```
39
+
40
+ ### Formatting
41
+
42
+ ```typescript
43
+ import { QuantityFormatter } from '@recipe-scope/quantity-parser';
44
+
45
+ // Basic formatting
46
+ QuantityFormatter.format(100, 'g'); // '100 g'
47
+ QuantityFormatter.format(2, '個'); // '2 個'
48
+
49
+ // Spoon fractions
50
+ QuantityFormatter.format(1.5, '大さじ'); // '大さじ 1と1/2'
51
+ QuantityFormatter.format(0.5, '小さじ'); // '小さじ 1/2'
52
+
53
+ // Tube measurements
54
+ QuantityFormatter.format(3, 'チューブcm'); // 'チューブ3cm'
55
+
56
+ // Size prefixes
57
+ QuantityFormatter.format(2, '小個'); // '小2個'
58
+
59
+ // Ambiguous units
60
+ QuantityFormatter.format(0, '少々'); // '少々'
61
+ QuantityFormatter.format(0, '適量'); // '適量'
62
+ ```
63
+
64
+ ### Configuration
65
+
66
+ Access unit configuration for custom calculations:
67
+
68
+ ```typescript
69
+ import { UNITS_CONFIG } from '@recipe-scope/quantity-parser';
70
+
71
+ // Standard volumes in ml
72
+ UNITS_CONFIG.STANDARD_VOLUME_ML['大さじ']; // 15
73
+ UNITS_CONFIG.STANDARD_VOLUME_ML['小さじ']; // 5
74
+ UNITS_CONFIG.STANDARD_VOLUME_ML['カップ']; // 200
75
+
76
+ // Countable unit aliases
77
+ UNITS_CONFIG.UNIT_SINGLE_ALIASES; // ['個', '本', '匹', '尾', '玉', '粒', '株']
78
+
79
+ // Ambiguous units
80
+ UNITS_CONFIG.AMBIGUOUS; // ['少々', '適量', 'ひとつまみ', '適宜', 'ひとつかみ']
81
+ ```
82
+
83
+ ## Features
84
+
85
+ - **Zero dependencies** - Pure TypeScript implementation
86
+ - **Full-width support** - Handles both half-width and full-width numbers
87
+ - **Fraction parsing** - Supports various fraction formats (1/2, 半, 1と1/2, 8分の1)
88
+ - **Range handling** - Extracts minimum value from ranges (1~2個 → 1)
89
+ - **Parentheses extraction** - Prioritizes gram/ml values in parentheses
90
+ - **Unit normalization** - Normalizes units (cc → ml, コ → 個)
91
+ - **Preprocessing** - Removes cutting instructions (みじん切り, 乱切り, etc.)
92
+
93
+ ## License
94
+
95
+ MIT
@@ -0,0 +1,181 @@
1
+ /**
2
+ * Result of parsing a single value and unit from a strategy.
3
+ */
4
+ interface ParsedResult {
5
+ value: number;
6
+ unit: string;
7
+ }
8
+ /**
9
+ * Options for parsing quantities.
10
+ */
11
+ interface ParseOptions {
12
+ /**
13
+ * How to handle range values (e.g. "1~2").
14
+ * - 'min': Use the minimum value (default behavior in v1)
15
+ * - 'max': Use the maximum value
16
+ * - 'mean': Use the average/mean value
17
+ * @default 'min'
18
+ */
19
+ rangeMode?: 'min' | 'max' | 'mean';
20
+ }
21
+ /**
22
+ * Interface for parsing strategies.
23
+ */
24
+ interface ParseStrategy {
25
+ /**
26
+ * Attempts to parse the quantity string.
27
+ * Returns null if the strategy does not apply.
28
+ */
29
+ parse(quantity: string, options?: ParseOptions): ParsedResult | null;
30
+ }
31
+ /**
32
+ * Result of parsing a quantity string.
33
+ */
34
+ interface ParsedQuantityResult {
35
+ /** Numeric value extracted from the quantity string */
36
+ value: number;
37
+ /** Normalized unit string */
38
+ unit: string;
39
+ /** The preprocessed/normalized quantity string */
40
+ normalizedQuantity?: string;
41
+ }
42
+
43
+ /**
44
+ * Parses Japanese quantity strings into numeric values and normalized units.
45
+ *
46
+ * @example
47
+ * ```typescript
48
+ * QuantityParser.parse('大さじ1'); // { value: 1, unit: '大さじ' }
49
+ * QuantityParser.parse('1/2カップ'); // { value: 0.5, unit: 'カップ' }
50
+ * QuantityParser.parse('100g'); // { value: 100, unit: 'g' }
51
+ * ```
52
+ */
53
+ declare class QuantityParser {
54
+ private static strategies;
55
+ /**
56
+ * Parses a quantity string into a numeric value and a normalized unit.
57
+ * @param quantityStr - The quantity string to parse (e.g., "大さじ1", "100g", "1/2カップ")
58
+ * @param options - Parsing options (e.g. rangeMode)
59
+ * @returns Parsed result with value, unit, and normalized quantity string
60
+ */
61
+ static parse(quantityStr: string, options?: ParseOptions): ParsedQuantityResult;
62
+ private static preprocess;
63
+ private static postprocessUnit;
64
+ }
65
+
66
+ /**
67
+ * Formats quantity values with units for display.
68
+ *
69
+ * @example
70
+ * ```typescript
71
+ * QuantityFormatter.format(1.5, '大さじ'); // '大さじ 1と1/2'
72
+ * QuantityFormatter.format(100, 'g'); // '100 g'
73
+ * QuantityFormatter.format(3, 'チューブcm'); // 'チューブ3cm'
74
+ * ```
75
+ */
76
+ declare class QuantityFormatter {
77
+ /**
78
+ * Formats a numeric value with its unit for display.
79
+ * @param value - The numeric value
80
+ * @param unit - The unit string
81
+ * @returns Formatted string for display
82
+ */
83
+ static format(value: number, unit: string): string;
84
+ private static formatSpoon;
85
+ }
86
+
87
+ /**
88
+ * Unit definitions and measurement mappings for quantity parsing.
89
+ */
90
+ declare const UNITS_CONFIG: {
91
+ /**
92
+ * Standard volume mapping in Milliliters (ml).
93
+ * Used for density calculations.
94
+ */
95
+ readonly STANDARD_VOLUME_ML: {
96
+ readonly カップ: 200;
97
+ readonly 大さじ: 15;
98
+ readonly 小さじ: 5;
99
+ readonly ml: 1;
100
+ readonly cc: 1;
101
+ readonly ミリリットル: 1;
102
+ readonly L: 1000;
103
+ readonly リットル: 1000;
104
+ readonly 少々: 0.8;
105
+ readonly ひとつまみ: 1;
106
+ readonly 合: 180;
107
+ readonly 小: 5;
108
+ readonly 大: 15;
109
+ readonly 適量: 0;
110
+ readonly 適宜: 0;
111
+ };
112
+ /**
113
+ * Units that represent a "single entity" of a countable food.
114
+ * If a food has any of these in unitWeights, they can be treated as interchangeable
115
+ * fallbacks (1 entity = unitWeight).
116
+ */
117
+ readonly UNIT_SINGLE_ALIASES: readonly ["個", "本", "匹", "尾", "玉", "粒", "株"];
118
+ /**
119
+ * Ambiguous measurement expressions.
120
+ */
121
+ readonly AMBIGUOUS: readonly ["少々", "適量", "ひとつまみ", "適宜", "ひとつかみ"];
122
+ /**
123
+ * Special units with fixed weights or behaviors.
124
+ */
125
+ readonly SPECIAL_FIXED: readonly ["少々", "ひとつまみ"];
126
+ };
127
+ type StandardVolumeUnit = keyof typeof UNITS_CONFIG.STANDARD_VOLUME_ML;
128
+ type UnitSingleAlias = (typeof UNITS_CONFIG.UNIT_SINGLE_ALIASES)[number];
129
+ type AmbiguousUnit = (typeof UNITS_CONFIG.AMBIGUOUS)[number];
130
+ type SpecialFixedUnit = (typeof UNITS_CONFIG.SPECIAL_FIXED)[number];
131
+
132
+ /**
133
+ * Parses tube measurements like "チューブ3cm" or "チューブで2センチ".
134
+ */
135
+ declare class TubeStrategy implements ParseStrategy {
136
+ parse(quantity: string, options?: ParseOptions): ParsedResult | null;
137
+ }
138
+
139
+ /**
140
+ * Parses grams or milliliters in parentheses like "(100g)" or "(200ml)".
141
+ */
142
+ declare class GramsInParensStrategy implements ParseStrategy {
143
+ parse(quantity: string, options?: ParseOptions): ParsedResult | null;
144
+ }
145
+
146
+ /**
147
+ * Parses range expressions like "1~2個".
148
+ * Behavior depends on options.rangeMode (default: min).
149
+ */
150
+ declare class RangeStrategy implements ParseStrategy {
151
+ parse(quantity: string, options?: ParseOptions): ParsedResult | null;
152
+ }
153
+
154
+ /**
155
+ * Parses various fraction formats:
156
+ * - Mixed fractions: "1と1/2", "1・1/2"
157
+ * - Half: "1/2", "半"
158
+ * - Simple fractions: "1/4"
159
+ * - Japanese fractions: "8分の1"
160
+ */
161
+ declare class FractionStrategy implements ParseStrategy {
162
+ parse(quantity: string, options?: ParseOptions): ParsedResult | null;
163
+ }
164
+
165
+ /**
166
+ * Parses unit prefix patterns like "大さじ1", "小1", "大1/2", "カップ1".
167
+ */
168
+ declare class UnitPrefixStrategy implements ParseStrategy {
169
+ parse(quantity: string, options?: ParseOptions): ParsedResult | null;
170
+ }
171
+
172
+ /**
173
+ * Fallback strategy for parsing:
174
+ * - Explicit grams/ml trailing patterns
175
+ * - General number + suffix patterns
176
+ */
177
+ declare class SuffixStrategy implements ParseStrategy {
178
+ parse(quantity: string, options?: ParseOptions): ParsedResult | null;
179
+ }
180
+
181
+ export { type AmbiguousUnit, FractionStrategy, GramsInParensStrategy, type ParseOptions, type ParseStrategy, type ParsedQuantityResult, type ParsedResult, QuantityFormatter, QuantityParser, RangeStrategy, type SpecialFixedUnit, type StandardVolumeUnit, SuffixStrategy, TubeStrategy, UNITS_CONFIG, UnitPrefixStrategy, type UnitSingleAlias };
@@ -0,0 +1,181 @@
1
+ /**
2
+ * Result of parsing a single value and unit from a strategy.
3
+ */
4
+ interface ParsedResult {
5
+ value: number;
6
+ unit: string;
7
+ }
8
+ /**
9
+ * Options for parsing quantities.
10
+ */
11
+ interface ParseOptions {
12
+ /**
13
+ * How to handle range values (e.g. "1~2").
14
+ * - 'min': Use the minimum value (default behavior in v1)
15
+ * - 'max': Use the maximum value
16
+ * - 'mean': Use the average/mean value
17
+ * @default 'min'
18
+ */
19
+ rangeMode?: 'min' | 'max' | 'mean';
20
+ }
21
+ /**
22
+ * Interface for parsing strategies.
23
+ */
24
+ interface ParseStrategy {
25
+ /**
26
+ * Attempts to parse the quantity string.
27
+ * Returns null if the strategy does not apply.
28
+ */
29
+ parse(quantity: string, options?: ParseOptions): ParsedResult | null;
30
+ }
31
+ /**
32
+ * Result of parsing a quantity string.
33
+ */
34
+ interface ParsedQuantityResult {
35
+ /** Numeric value extracted from the quantity string */
36
+ value: number;
37
+ /** Normalized unit string */
38
+ unit: string;
39
+ /** The preprocessed/normalized quantity string */
40
+ normalizedQuantity?: string;
41
+ }
42
+
43
+ /**
44
+ * Parses Japanese quantity strings into numeric values and normalized units.
45
+ *
46
+ * @example
47
+ * ```typescript
48
+ * QuantityParser.parse('大さじ1'); // { value: 1, unit: '大さじ' }
49
+ * QuantityParser.parse('1/2カップ'); // { value: 0.5, unit: 'カップ' }
50
+ * QuantityParser.parse('100g'); // { value: 100, unit: 'g' }
51
+ * ```
52
+ */
53
+ declare class QuantityParser {
54
+ private static strategies;
55
+ /**
56
+ * Parses a quantity string into a numeric value and a normalized unit.
57
+ * @param quantityStr - The quantity string to parse (e.g., "大さじ1", "100g", "1/2カップ")
58
+ * @param options - Parsing options (e.g. rangeMode)
59
+ * @returns Parsed result with value, unit, and normalized quantity string
60
+ */
61
+ static parse(quantityStr: string, options?: ParseOptions): ParsedQuantityResult;
62
+ private static preprocess;
63
+ private static postprocessUnit;
64
+ }
65
+
66
+ /**
67
+ * Formats quantity values with units for display.
68
+ *
69
+ * @example
70
+ * ```typescript
71
+ * QuantityFormatter.format(1.5, '大さじ'); // '大さじ 1と1/2'
72
+ * QuantityFormatter.format(100, 'g'); // '100 g'
73
+ * QuantityFormatter.format(3, 'チューブcm'); // 'チューブ3cm'
74
+ * ```
75
+ */
76
+ declare class QuantityFormatter {
77
+ /**
78
+ * Formats a numeric value with its unit for display.
79
+ * @param value - The numeric value
80
+ * @param unit - The unit string
81
+ * @returns Formatted string for display
82
+ */
83
+ static format(value: number, unit: string): string;
84
+ private static formatSpoon;
85
+ }
86
+
87
+ /**
88
+ * Unit definitions and measurement mappings for quantity parsing.
89
+ */
90
+ declare const UNITS_CONFIG: {
91
+ /**
92
+ * Standard volume mapping in Milliliters (ml).
93
+ * Used for density calculations.
94
+ */
95
+ readonly STANDARD_VOLUME_ML: {
96
+ readonly カップ: 200;
97
+ readonly 大さじ: 15;
98
+ readonly 小さじ: 5;
99
+ readonly ml: 1;
100
+ readonly cc: 1;
101
+ readonly ミリリットル: 1;
102
+ readonly L: 1000;
103
+ readonly リットル: 1000;
104
+ readonly 少々: 0.8;
105
+ readonly ひとつまみ: 1;
106
+ readonly 合: 180;
107
+ readonly 小: 5;
108
+ readonly 大: 15;
109
+ readonly 適量: 0;
110
+ readonly 適宜: 0;
111
+ };
112
+ /**
113
+ * Units that represent a "single entity" of a countable food.
114
+ * If a food has any of these in unitWeights, they can be treated as interchangeable
115
+ * fallbacks (1 entity = unitWeight).
116
+ */
117
+ readonly UNIT_SINGLE_ALIASES: readonly ["個", "本", "匹", "尾", "玉", "粒", "株"];
118
+ /**
119
+ * Ambiguous measurement expressions.
120
+ */
121
+ readonly AMBIGUOUS: readonly ["少々", "適量", "ひとつまみ", "適宜", "ひとつかみ"];
122
+ /**
123
+ * Special units with fixed weights or behaviors.
124
+ */
125
+ readonly SPECIAL_FIXED: readonly ["少々", "ひとつまみ"];
126
+ };
127
+ type StandardVolumeUnit = keyof typeof UNITS_CONFIG.STANDARD_VOLUME_ML;
128
+ type UnitSingleAlias = (typeof UNITS_CONFIG.UNIT_SINGLE_ALIASES)[number];
129
+ type AmbiguousUnit = (typeof UNITS_CONFIG.AMBIGUOUS)[number];
130
+ type SpecialFixedUnit = (typeof UNITS_CONFIG.SPECIAL_FIXED)[number];
131
+
132
+ /**
133
+ * Parses tube measurements like "チューブ3cm" or "チューブで2センチ".
134
+ */
135
+ declare class TubeStrategy implements ParseStrategy {
136
+ parse(quantity: string, options?: ParseOptions): ParsedResult | null;
137
+ }
138
+
139
+ /**
140
+ * Parses grams or milliliters in parentheses like "(100g)" or "(200ml)".
141
+ */
142
+ declare class GramsInParensStrategy implements ParseStrategy {
143
+ parse(quantity: string, options?: ParseOptions): ParsedResult | null;
144
+ }
145
+
146
+ /**
147
+ * Parses range expressions like "1~2個".
148
+ * Behavior depends on options.rangeMode (default: min).
149
+ */
150
+ declare class RangeStrategy implements ParseStrategy {
151
+ parse(quantity: string, options?: ParseOptions): ParsedResult | null;
152
+ }
153
+
154
+ /**
155
+ * Parses various fraction formats:
156
+ * - Mixed fractions: "1と1/2", "1・1/2"
157
+ * - Half: "1/2", "半"
158
+ * - Simple fractions: "1/4"
159
+ * - Japanese fractions: "8分の1"
160
+ */
161
+ declare class FractionStrategy implements ParseStrategy {
162
+ parse(quantity: string, options?: ParseOptions): ParsedResult | null;
163
+ }
164
+
165
+ /**
166
+ * Parses unit prefix patterns like "大さじ1", "小1", "大1/2", "カップ1".
167
+ */
168
+ declare class UnitPrefixStrategy implements ParseStrategy {
169
+ parse(quantity: string, options?: ParseOptions): ParsedResult | null;
170
+ }
171
+
172
+ /**
173
+ * Fallback strategy for parsing:
174
+ * - Explicit grams/ml trailing patterns
175
+ * - General number + suffix patterns
176
+ */
177
+ declare class SuffixStrategy implements ParseStrategy {
178
+ parse(quantity: string, options?: ParseOptions): ParsedResult | null;
179
+ }
180
+
181
+ export { type AmbiguousUnit, FractionStrategy, GramsInParensStrategy, type ParseOptions, type ParseStrategy, type ParsedQuantityResult, type ParsedResult, QuantityFormatter, QuantityParser, RangeStrategy, type SpecialFixedUnit, type StandardVolumeUnit, SuffixStrategy, TubeStrategy, UNITS_CONFIG, UnitPrefixStrategy, type UnitSingleAlias };