11import 'dart:io' ;
2+ import 'dart:math' as math;
3+ import 'package:equatable/equatable.dart' ;
4+ import 'package:meta/meta.dart' ;
25import 'package:path/path.dart' as p;
36import 'package:rps/rps_exception.dart' ;
47import 'package:yaml/yaml.dart' ;
@@ -9,6 +12,7 @@ class ScriptContext {
912 final List <String > path;
1013 String get stringPath => path.join (' ' );
1114
15+ /// List of remaining arguments
1216 List <String > get rest => arguments.sublist (position + 1 );
1317
1418 final int position;
@@ -43,66 +47,104 @@ class ScriptContext {
4347 }
4448}
4549
50+ @immutable
4651abstract class PubspecCommand {
47- final String path;
48- final String ? command;
52+ String get path;
53+ String ? get command;
4954
5055 /// Whether this command should be executed
5156 bool get executable;
57+ }
5258
53- PubspecCommand (this .path, this .command);
54-
55- @override
56- bool operator == (Object other) {
57- return other is PubspecCommand &&
58- other.command == command &&
59- other.path == path;
60- }
59+ class PositionalArgument {
60+ final int position;
61+ final int start;
62+ final int end;
6163
62- @override
63- int get hashCode => Object .hash (path, command, runtimeType);
64+ PositionalArgument (this .position, this .start, this .end);
6465}
6566
66- class ExecutableCommand extends PubspecCommand {
67+ class ExecutableCommand extends PubspecCommand with EquatableMixin {
6768 @override
6869 bool get executable => true ;
6970
70- ExecutableCommand (super .path, super .command);
71-
7271 @override
73- bool operator == (Object other) {
74- return other is ExecutableCommand &&
75- other.command == command &&
76- other.path == path;
77- }
78-
72+ final String command;
7973 @override
80- int get hashCode => Object .hash (path, command, runtimeType);
74+ final String path;
75+
76+ static final _positionalArgumentsRegexp = RegExp (r'\$\{\s{0,}[0-9]+\s{0,}\}' );
77+
78+ ExecutableCommand ._(this .command, this .path);
79+
80+ factory ExecutableCommand (String path, String command, List <String > rest) {
81+ final argumentsInCommand = _positionalArgumentsRegexp.allMatches (command);
82+
83+ if (argumentsInCommand.isNotEmpty) {
84+ final usedArguments = < int > {};
85+ final filledCommand = command.replaceAllMapped (
86+ _positionalArgumentsRegexp,
87+ (match) {
88+ final content = match.group (0 )! ;
89+ final value = content.substring (2 , content.length - 1 ).trim ();
90+ final argumentIndex = int .tryParse (value);
91+ if (argumentIndex == null ) {
92+ throw RpsException (
93+ "Bad argument script ($content ). "
94+ "Only positional arguments are supported." ,
95+ );
96+ } else if (argumentIndex >= rest.length) {
97+ throw RpsException (
98+ 'The script "$path " defines a positional argument $content , '
99+ 'but ${rest .length } positional argument(s) are given.' ,
100+ );
101+ } else {
102+ usedArguments.add (argumentIndex);
103+ return rest[argumentIndex];
104+ }
105+ },
106+ );
107+
108+ final lastUsed = usedArguments.reduce (math.max);
109+ if (lastUsed > usedArguments.length) {
110+ final unusedArguments = List <int >.generate (lastUsed, (index) => index)
111+ .where ((e) => ! usedArguments.contains (e));
112+
113+ throw RpsException (
114+ 'The script defines unused positional argument(s): '
115+ '${unusedArguments .map ((a ) => '\$ {$a }' ).join (', ' )}' ,
116+ );
117+ }
118+
119+ return ExecutableCommand ._(
120+ [filledCommand, ...rest.sublist (lastUsed + 1 )].join (' ' ),
121+ path,
122+ );
123+ } else {
124+ return ExecutableCommand ._([command, ...rest].join (' ' ), path);
125+ }
126+ }
81127
82128 @override
83- String toString () => 'ExecutableCommand("$ path ": "$ command ")' ;
129+ List < Object ?> get props => [ command, path] ;
84130}
85131
86132/// Indicates that reference was made to the command
87- class RefCommand extends PubspecCommand {
88- RefCommand (String path) : super (path, null );
89-
133+ class RefCommand extends PubspecCommand with EquatableMixin {
90134 /// This command is only a trace mark, nothing to call here.
91135 @override
92136 bool get executable => false ;
93137
94138 @override
95- bool operator == (Object other) {
96- return other is RefCommand &&
97- other.command == command &&
98- other.path == path;
99- }
139+ Null get command => null ;
100140
101141 @override
102- int get hashCode => Object .hash (path, runtimeType);
142+ final String path;
143+
144+ RefCommand (this .path);
103145
104146 @override
105- String toString () => 'HookCommand("$ path ")' ;
147+ List < Object ?> get props => [ path] ;
106148}
107149
108150class Pubspec {
@@ -177,8 +219,9 @@ class Pubspec {
177219 );
178220 } else if (value is String ) {
179221 yield * _examinateCommand (
180- command: [ value, ...context.rest]. join ( ' ' ) ,
222+ command: value,
181223 path: context.stringPath,
224+ rest: context.rest,
182225 );
183226 } else if (value is Map ) {
184227 yield * _getHookCommands (
@@ -190,7 +233,11 @@ class Pubspec {
190233 if (_hasScriptKey (value)) {
191234 final script = value[scriptKey];
192235 if (script is String ) {
193- yield * _examinateCommand (command: script, path: context.stringPath);
236+ yield * _examinateCommand (
237+ command: script,
238+ path: context.stringPath,
239+ rest: context.rest,
240+ );
194241 } else if (script is Map ) {
195242 final platformKey = '\$ ${Platform .operatingSystem }' ;
196243 final command = script[platformKey] ?? script[defaultScriptKey];
@@ -205,7 +252,8 @@ class Pubspec {
205252 } else {
206253 yield * _examinateCommand (
207254 command: command,
208- path: [context.stringPath, ...context.rest].join (' ' ),
255+ path: context.stringPath,
256+ rest: context.rest,
209257 );
210258 }
211259 }
@@ -234,12 +282,13 @@ class Pubspec {
234282 Iterable <PubspecCommand > _examinateCommand ({
235283 required String command,
236284 required String path,
285+ required List <String > rest,
237286 }) sync * {
238287 if (command.startsWith (r'$' )) {
239288 yield RefCommand (path);
240289 yield * getCommands (command.substring (1 ).split (RegExp (r'\s+' )));
241290 } else {
242- yield ExecutableCommand (path, command);
291+ yield ExecutableCommand (path, command, rest );
243292 }
244293 }
245294
@@ -261,6 +310,9 @@ class Pubspec {
261310 yield * _examinateCommand (
262311 path: [...context.path, key].join (' ' ),
263312 command: value,
313+
314+ /// TODO? ignore for hooks
315+ rest: [],
264316 );
265317 }
266318 }
0 commit comments