1- import type { Maybe } from "./maybe.ts" ;
2- import type { Param } from "./param.ts" ;
1+ import { Just , type Maybe } from "./maybe.ts" ;
2+ import type { AnyParam , Param } from "./param.ts" ;
33import type { CLIRead , Symbol } from "./read.ts" ;
44import type { Rest } from "./rest.ts" ;
55import type { Result } from "./result.ts" ;
66import type { Word } from "./tokenize.ts" ;
77import type { TokenInput , Tokenizer , TokenRange } from "./tokenizer.ts" ;
8- import type { AnyPhase , Issue , Path } from "./types.ts" ;
8+ import type { AnyPhase , Issue , OutputOf , Path } from "./types.ts" ;
99
1010export interface Binding < T > {
1111 readonly rest : Rest ;
@@ -24,20 +24,20 @@ export interface PhaseSegment {
2424 readonly path : Path ;
2525}
2626
27- export function fromCLI < const K extends string , T > ( options : {
28- readonly param : Param < K , T > ;
27+ export function fromCLI < P extends Param < string , unknown , "one" > > ( options : {
28+ readonly param : P ;
2929 readonly view : TokenInput < Symbol > ;
3030 readonly rest : Rest ;
31- } ) : Maybe < Binding < T > > {
31+ } ) : Maybe < Binding < OutputOf < P [ "schema" ] > > > {
3232 let { param, view, rest } = options ;
3333 return fromRead ( param , param . cli . read ( view ) , rest ) ;
3434}
3535
36- export function fromValues < const K extends string , T > ( options : {
37- readonly param : Param < K , T > ;
36+ export function fromValues < P extends AnyParam > ( options : {
37+ readonly param : P ;
3838 readonly route : Path ;
3939 readonly rest : Rest ;
40- } ) : Maybe < Binding < T > > {
40+ } ) : Maybe < Binding < OutputOf < P [ "schema" ] > > > {
4141 let { param, route, rest } = options ;
4242 let claim = rest . values . claim ( {
4343 route,
@@ -60,11 +60,11 @@ export function fromValues<const K extends string, T>(options: {
6060 } ;
6161}
6262
63- export function fromEnv < const K extends string , T > ( options : {
64- readonly param : Param < K , T > ;
63+ export function fromEnv < P extends AnyParam > ( options : {
64+ readonly param : P ;
6565 readonly route : Path ;
6666 readonly rest : Rest ;
67- } ) : Maybe < Binding < T > > {
67+ } ) : Maybe < Binding < OutputOf < P [ "schema" ] > > > {
6868 let { param, route, rest } = options ;
6969 let claim = rest . envs . claim ( {
7070 route,
@@ -78,14 +78,18 @@ export function fromEnv<const K extends string, T>(options: {
7878
7979 let value = claim . result . value . value ;
8080
81+ let candidates = param . cardinality === "one"
82+ ? param . decode ( value )
83+ : param . decode ( [ value ] ) ;
84+
8185 return {
8286 exists : true ,
8387 value : {
8488 rest : {
8589 ...rest ,
8690 envs : claim . rest ,
8791 } ,
88- result : decode ( param , value , param . decode ( value ) , [ param . name ] ) ,
92+ result : decode ( param , value , candidates , [ param . name ] ) ,
8993 } ,
9094 } ;
9195}
@@ -97,12 +101,12 @@ export function bindPhase(options: {
97101} ) : PhaseBinding {
98102 let { phase, segment } = options ;
99103 let rest = options . rest ;
100- let params = Object . values ( phase . model . params ) as Param < string , unknown > [ ] ;
104+ let params = Object . values ( phase . model . params ) as AnyParam [ ] ;
101105 let pending = new Map ( params . map ( ( param ) => [ param . name , param ] ) ) ;
102106 let results = new Map < string , Result < unknown > > ( ) ;
103107
104108 function settle (
105- param : Param < string , unknown > ,
109+ param : AnyParam ,
106110 binding : Binding < unknown > ,
107111 ) : void {
108112 rest = binding . rest ;
@@ -111,7 +115,7 @@ export function bindPhase(options: {
111115 }
112116
113117 function accept (
114- param : Param < string , unknown > ,
118+ param : AnyParam ,
115119 attempt : Maybe < Binding < unknown > > ,
116120 ) : void {
117121 if ( ! attempt . exists ) {
@@ -121,14 +125,16 @@ export function bindPhase(options: {
121125 settle ( param , attempt . value ) ;
122126 }
123127
128+ let captures = new Map < string , Capture > ( ) ;
129+
124130 // Every pending reader proposes a claim against the same immutable view.
125131 // Commit the proposal beginning earliest in argv, then recompute the view.
126132 // This lets `--port 9000` outrank a positional claim on `9000` without
127133 // attaching binding-policy metadata to either reader.
128134 while ( true ) {
129135 let horizon = first ( rest . tokens , segment . range ) ;
130136 let offer : {
131- param : Param < string , unknown > ;
137+ param : AnyParam ;
132138 read : CLIRead ;
133139 index : number ;
134140 } | undefined ;
@@ -154,9 +160,58 @@ export function bindPhase(options: {
154160 break ;
155161 }
156162
163+ if ( offer . param . cardinality === "many" ) {
164+ rest = {
165+ ...rest ,
166+ tokens : offer . read . claim . rest ,
167+ } ;
168+
169+ let current = captures . get ( offer . param . name ) ?? {
170+ param : offer . param ,
171+ values : [ ] ,
172+ issues : [ ] ,
173+ failed : false ,
174+ } ;
175+
176+ current . issues . push ( ...offer . read . result . issues ?? [ ] ) ;
177+
178+ if ( offer . read . result . ok && offer . read . result . value . exists ) {
179+ current . values . push ( offer . read . result . value . value ) ;
180+ } else {
181+ current . failed = true ;
182+ }
183+ captures . set ( offer . param . name , current ) ;
184+
185+ continue ;
186+ }
187+
157188 accept ( offer . param , fromRead ( offer . param , offer . read , rest ) ) ;
158189 }
159190
191+ // deal wil all multiple
192+ for ( let capture of captures . values ( ) ) {
193+ if ( capture . failed ) {
194+ settle ( capture . param , {
195+ rest,
196+ result : { ok : false , issues : capture . issues } ,
197+ } ) ;
198+ } else {
199+ let values = capture . values ;
200+
201+ let candidates : Maybe < unknown [ ] > [ ] = values . every (
202+ ( value ) : value is string => typeof value === "string" ,
203+ )
204+ ? capture . param . decode ( values )
205+ : [ Just ( values ) ] ;
206+
207+ let result = merge (
208+ decode ( capture . param , values , candidates , [ capture . param . name ] ) ,
209+ capture . issues ,
210+ ) ;
211+ settle ( capture . param , { result, rest } ) ;
212+ }
213+ }
214+
160215 // Address sources have stable visibility once the route is known. They are
161216 // tried only after CLI has reached its fixed point so a provisional CLI miss
162217 // cannot let a lower-priority source settle the parameter too early.
@@ -210,11 +265,11 @@ export function bindPhase(options: {
210265 return { rest, model, issues, valid } ;
211266}
212267
213- function fromRead < T > (
214- param : Param < string , T > ,
268+ function fromRead < P extends Param < string , unknown , "one" > > (
269+ param : P ,
215270 read : CLIRead ,
216271 rest : Rest ,
217- ) : Maybe < Binding < T > > {
272+ ) : Maybe < Binding < OutputOf < P [ "schema" ] > > > {
218273 let path = [ param . name ] ;
219274
220275 if ( ! read . result . ok ) {
@@ -235,7 +290,10 @@ function fromRead<T>(
235290 }
236291
237292 let value = read . result . value . value ;
238- let candidates = typeof value === "string" ? param . decode ( value ) : [ value ] ;
293+
294+ let candidates = typeof value === "string"
295+ ? param . decode ( value )
296+ : [ Just ( value ) ] ;
239297 let result = merge (
240298 decode ( param , value , candidates , path ) ,
241299 read . result . issues ,
@@ -253,12 +311,12 @@ function fromRead<T>(
253311 } ;
254312}
255313
256- function decode < T > (
257- param : Param < string , T > ,
314+ function decode < P extends AnyParam > (
315+ param : P ,
258316 value : unknown ,
259- candidates : unknown [ ] ,
317+ candidates : Maybe < unknown > [ ] ,
260318 path : string [ ] ,
261- ) : Result < T > {
319+ ) : Result < OutputOf < P [ "schema" ] > > {
262320 if ( candidates . length === 0 ) {
263321 return {
264322 ok : false ,
@@ -271,7 +329,11 @@ function decode<T>(
271329
272330 let issues : readonly Issue [ ] | undefined ;
273331
274- for ( let candidate of candidates ) {
332+ let values = candidates . flatMap ( ( candidate ) =>
333+ candidate . exists ? [ candidate . value ] : [ ]
334+ ) ;
335+
336+ for ( let candidate of values ) {
275337 let result = validate ( param , candidate , path ) ;
276338 if ( result . ok ) {
277339 return result ;
@@ -285,11 +347,11 @@ function decode<T>(
285347 } ;
286348}
287349
288- function validate < T > (
289- param : Param < string , T > ,
350+ function validate < P extends AnyParam > (
351+ param : P ,
290352 value : unknown ,
291353 path : string [ ] ,
292- ) : Result < T > {
354+ ) : Result < OutputOf < P [ "schema" ] > > {
293355 let validated = param . schema [ "~standard" ] . validate ( value ) ;
294356 if ( validated instanceof Promise ) {
295357 return {
@@ -350,3 +412,10 @@ function earliest(tokens: readonly Symbol[]): number {
350412
351413 return first ;
352414}
415+
416+ interface Capture {
417+ param : Param < string , unknown , "many" > ;
418+ values : ( string | boolean ) [ ] ;
419+ issues : Issue [ ] ;
420+ failed : boolean ;
421+ }
0 commit comments