|
NAMERinci::function - Metadata for your functions/methodsSPECIFICATION VERSION1.1 VERSIONThis document describes version 1.1.100 of Rinci::function (from Perl distribution Rinci), released on 2022-01-23.INTRODUCTIONThis document describes metadata for functions/methods. Since the metadata properties describe features and the way a function works, this document also describes how a function should support those properties.This specification is part of Rinci. Please do a read up on it first, if you have not already done so. SPECIFICATIONEnveloped resultFunction should return an enveloped result to express error code/message as well as actual result. The envelope can be produced by the function itself, or added by a wrapper tool. Result envelope is modeled after HTTP or PSGI response; it is an array in the following format:[STATUS*, MESSAGE, RESULT, META] STATUS is a 3-digit integer and is the only required element, much like HTTP response status code and is explained further in "Envelope status codes". MESSAGE is a string containing error message. RESULT (or PAYLOAD) is the actual content to be returned and can be omitted or set to undef if the function does not need to return anything. META is called result metadata, a defhash containing extra data, analogous to HTTP response headers. Result metadata is specified further in Rinci::resmeta. Some example of an enveloped results: [200] [200, "OK", 42] [404, "Not found"] [200, "Account created", {id=>9323}, {}] [500, "Can't delete foo: permission denied", undef, {"cmdline.exit_code"=>300, perm_err=>1}] As mentioned, an enveloped result can contain error code/message as well as the actual result. It can also be easily converted to HTTP response message. And it can also contain extra data, useful for things like the transaction protocol (explained in Rinci::Transaction). Special argumentsSpecial arguments are some known arguments that start with dash ("-") and serve special purposes. You need not specify them in the "args" metadata property. Examples of special arguments include "-dry_run", "-tx_action", "-action", "-confirm/*", "-arg_part_start", "-arg_part_len", "-res_part_start", "-res_part_len". Many of them will be explained in other related sections/documents, but some will be described here:
Functions vs methodsSince in many programming languages (like Perl 5, Python, Ruby, PHP) static functions are not that differentiated from methods, functions and methods share the same Rinci spec. But there are certain properties that can be used to declare if a function is (also) a method or not. See "is_func", "is_meth", "is_class_meth" properties below for details.Multiple dispatchThis specification also does not (yet) have any recommendation on how to best handle functions in languages that support multiple dispatch, like Perl 6: whether we should create multiple metadata or just one. It is more up to the tool and what you want to do with the metadata.Envelope status codesIn general, status codes map directly to HTTP response status codes. Below are the suggestion on which codes to use (or avoid). An asterisk ("*") marks which codes are not defined in HTTP specification and introduced by this specification.
is_funcBool. Specify that the function can be called as a static function (i.e. procedural, not as a method). Default is true if unspecified, but becomes false if is_meth or is_class_meth is set to true.Example: # specify that function can be called a method *as well as* a static function is_meth => 1 is_func => 1 # if not specified, will default to false after is_meth set to 1 is_methBool. Specify that the function can be called as an instance (object) method. Default is false.Example: # specify that function is a method is_meth => 1 is_class_methBool. Specify that the function can be called as a class method. Examples of class methods include the constructor, but there are others. Default is false.Example: # specify that function is a class method is_class_meth => 1 args (function property)Hash. Specify arguments. Property value is defhash of argument names and argument specification. Argument name must only contain letters, numbers, and underscores (and do not start with a number).Argument specification is a hash containing these keys: schema (argument property) Data::Sah schema for argument value. default Any. Give default value for argument. This takes precedence over schema, which can also specify default value. This is useful if you want to share a common schema over several arguments but want to have different default for each argument. For example, you have a "ticket_status" schema. In "create_ticket" function you want the default "status" argument to be "new", while in "reply_ticket" you want the default "status" to be "answered". summary (argument property) Str. From DefHash. A one-line plaintext summary, much like the "summary" property in variable metadata. req Bool. Specify that argument is required (although its value can be undef/null). Default is false. description (argument property) Str. From DefHash. A longer description of marked up text, much like the "description" property. It is suggested to format the text to 74 columns. tags (argument property) Array of (str|hash). From DefHash. An array of tags, can be used by tools to categorize arguments. Not unlike the "tags" property. pos Non-negative integer. Argument position when specified in an ordered fashion, e.g. in an array. Starts from zero. slurpy Bool. Only relevant if pos is specified, specify whether argument should gobble up all remaining values in an ordered argument list into an array. Old alias: "greedy" greedy Old alias for "slurpy". Will be removed in Rinci 1.2. partial (argument property) Bool. Whether or not argument value can be sent partially. Only argument types that have a length notion can be set as partial (e.g. "str" where the unit is character, "buf" where the unit is byte, and "array" where the unit is element). There should at most be one argument with this property set to true. To send partial argument, you can use the special arguments "-arg_len" (total argument length), "-arg_part_start" (start position of the part that is being sent), "-arg_part_len" (length of part that is being sent). Example (in Perl): # function metadata { v => 1.1, summary => "Upload a file", args => { name => {schema=>"str*", req=>1}, data => {schema=>"buf*", req=>1, partial=>1}, }, } # function usage example: send the first 10MiB of data upload_file(name=>"myvideo.mp4", data=>substr($data, 0, 10_000_000), -arg_len => 24_500_000, -arg_part_start => 0, -arg_part_len => 10_000_000); # send the next 10MiB upload_file(name=>"myvideo.mp4", data=>substr($data, 10_000_000, 10_000_000), -arg_len => 24_500_000, -arg_part_start => 10_000_000, -arg_part_len => 10_000_000); # send the last 4.5 MiB upload_file(name=>"myvideo.mp4", data=>substr($data, 20_000_000), -arg_len => 24_500_000, -arg_part_start => 20_000_000, -arg_part_len => 4_500_000); stream (argument property) Bool. By setting this property to true, function can specify that it accepts streaming data for this argument. It is useful when argument value is large or of undetermined/infinite length. To send value as a stream, caller must send a subroutine reference (callback) instead which the function will call repeatedly until it gets undef to signify exhaustion of data. cmdline_aliases Hash. Specify aliases for use in command-line options (or other possibly suitable situation where arguments are parsed from command-line-like options). Keys are alias names, values are itself hashes (alias specification). Valid alias specification keys: "summary" (a string, optional), "schema" (optional, defaults to argument's schema), "is_flag" (bool, optional, if set to 1 then it is a shortcut for specifying "schema" to "["bool", {"is":1}]"), "code" (a code to set argument value, optional, will be given "(\%args, $val)"); if not set, the default behavior is simply to set the argument value). cmdline_on_getopt Code. A hook that will be called when argument is specified as a command-line option. In Perl, hook will be called with a hash argument containing this key: "arg" (str, argument name), "value" (str, option value), "args" (hash, the argument hash defined so far). This can be useful if you want to process a command-line option directly on a per-option basis instead of getting the final resulting argument value. For example (in Perl): args => { library => { schema => ['array*' => of => 'str*'], cmdline_aliases => { I => {} }, cmdline_on_getopt => sub { my %args = @_; require lib; lib->import($args{value}); }, }, module => { schema => ['array*' => of => 'str*'], cmdline_aliases => { M => {} }, cmdline_on_getopt => sub { my %args = @_; require Module::Load; Module::Load::load($args{value}); }, }, } With command-line argument like this: -I dir1 -M mod1 -I dir2 -M mod2 Without any "cmdline_on_getopt" hooks, the function will receive this argument hash: { library => ['dir1', 'dir2'], module => ['mod1', 'mod2'] } but there is no way to know the order of options being specified in the command-line. With the hooks, the function can load modules correctly (e.g. loading "mod1" won't search in "dir2" as that directory has not been added by -I). completion (argument property) Code. A code to supply argument value completion. Will be explained in the examples. index_completion Code. A code to supply argument element index completion. Applicable to the following argument types: hash: for completing hash pair keys. (See also "element_completion" for completing hash pair values). See examples for how to use this property. element_completion Code. A code to supply argument element value completion. Applicable to the following argument types: array: for completing array element values. hash: for completing hash pair values. (See also "index_completion" for completing hash keys). See examples for how to use this property. is_password Experimental. Bool. Describe that argument holds password. Programs can react to this in several ways, for example they can turn off echoing to terminal when asking value from standard input. Or they can redact values to "****" when logging. cmdline_src Str. Normally in a command-line program, command-line options and arguments will map to function arguments, e.g. "--arg 2" will set the "foo" argument to 2 and positional arguments (argument which specifies the "pos" property and optionally also a "slurpy" property with true value) will get or slurp command-line arguments. In some cases, this is not convenient. When supplying larger amount of data, a complex structures, or a stream, we might want to use other sources. The "cmdline_src" property can be set to one of the following value for this purpose:
Other sources might be defined in the future. TODO: Define "web_src" property? TODO: A way to define record separator? cmdline_prompt Str. String to display when asking for argument value from stdin (if "cmdline_src" property value is "stdin_line". TODO: cmdline_prompt_template? meta Experimental. Hash. This allows specifying argument submetadata, used e.g. when dealing with forms (a form field/widget can be a subform). Value is Rinci function metadata. element_meta Experimental. Hash. This allows specifying argument element submetadata, used e.g. when dealing with forms (a form field/widget can contain an array of records/subforms). Value is Rinci function metadata. deps (argument property) Hash. This property specifies argument's dependencies to other arguments (but possibly to other things too, in the future). This is similar to function's "deps (function property)" property. It is a hash or dep types and values. The most important dep type is "arg" (dependency to another argument). Some dep types are special: "all", "any", "none". Example: args => { delete => { schema=>'bool', }, force => { summary => 'Force deletion', schema => 'bool', deps => {arg=>'delete'}, }, The above example states that argument "force" "depends on" "delete". What it means (usually) is that specifying "force" only makes sense when "delete" is also specified. In a CLI context: % prog --delete --force Specifying "--force" without "--delete" doesn't make sense. filters Experimental. Array of string, or code. Filters to apply before argument is converted from text and validated. examples (argument property) Array. Each element is sample argument values. But if it is a hash, will be assumed as a DefHash with the actual value put in the "value" property (so if your sample argument is a hash like "{}" (in JSON), you have to specify it as "{"value":{}, "summary":"Optional summary..."}" (in JSON). Note that a Sah schema can also have an examples clause which you can use to put examples in. And Rinci function metadata also has examples property too. Example function metadata and its implementation in Perl: $SPEC{multiply2} = { v => 1.1, summary => 'Multiple two numbers', args => { a => { summary => 'The first operand', description => '... a longer description ...', schema=>['float*', { examples => [1, -10, 0, 3.333], }], pos => 0, tags => ['category:operand'], }, b => { summary => 'The second operand', description => '... a longer description ...', schema => 'float*', pos => 1, tags => ['category:operand'], examples => [ 1, -10, 0, 3.333, {value => 1e-10, summary => 'Blah blah'}, ], }, round => { summary => 'Whether to round result', description => '... a longer description ...', schema => [bool => {default=>0}], pos => 2, tags => ['category:options'], cmdline_aliases => { r=>{}, R=>{summary=>'Equivalent to --round=0', code=>sub { my ($args, $val) = @_; $args->{round}=0 }}, }, }, } }; sub multiply2 { my %args = @_; my $res = $args{a} * $args{b}; $res = int($res) if $round; [200, "OK", $res]; } By default, without any wrapper, the function is called with a named hash style: multiply2(a=>4, b=>3); # 12 But with the information from the metadata, a wrapper tool like Perinci::Sub::Wrapper is able to change the calling style to positional: multiply2(4, 3.1, 1); # 12 A command-line tool will also enable the function to be called named options as well as positional arguments: % multiply2 --a 2 --b 3 % multiply2 2 --b 3 % multiply2 2 3 As mentioned earlier, "cmdline_alises" is parsed by command-line option parser: % multiply2 2 3.5 -r ; # equivalent to multiply2 2 3 --round % multiply2 2 3.5 -R ; # equivalent to multiply2 2 3 --noround (--round=0) Aliases in "cmdline_aliases" are not recognized as real arguments: multiply2(a=>4, b=>3, r=>0); # unknown argument r Another example (demonstrates "cmdline_aliases"): $SPEC{smtpd} = { v => 1.1, summary => 'Control SMTP daemon', args => { action => { schema => ['str*' => {in=>[qw/status start stop restart/]}], pos => 0, req => 1, cmdline_aliases => { status => { schema => [bool=>{is=>1}], summary => 'Alias for setting action=status', code => sub { $_[0]{action} = 'status' }, }, start => { schema => [bool=>{is=>1}], summary => 'Alias for setting action=start', code => sub { $_[0]{action} = 'start' }, }, stop => { schema => [bool=>{is=>1}], summary => 'Alias for setting action=stop', code => sub { $_[0]{action} = 'stop' }, }, restart => { schema => [bool=>{is=>1}], summary => 'Alias for setting action=restart', code => sub { $_[0]{action} = 'restart' }, }, }, }, force => { schema => 'bool', }, }, }; Another example (demonstrates slurpy): $SPEC{multiply_many} = { v => 1.1, summary => 'Multiple numbers', args => { nums => { schema => ['array*' => {of=>'num*', min_len=>1}], pos => 0, slurpy => 1 }, }, }; sub multiply_many { my %args = @_; my $nums = $args{nums}; my $ans = 1; $ans *= $_ for @$nums; [200, "OK", $ans]; } After wrapping, in positional mode it can then be called: multiply_many(2, 3, 4); # 24 which is the same as (in normal named-argument style): multiply_many(nums => [2, 3, 4]); # 24 In command-line: % multiply-many 2 3 4 in addition to the normal: % multiply-many --nums '[2, 3, 4]' completion. This argument specification key specifies how to complete argument value (e.g. in shell or Riap::HTTP) and is supplied an anonymous function as value. The function will be called with arguments: word=>... (which is the formed word so far, ci=>0|1 (whether completion should be done case-insensitively). The function should return an array containing a list of possible candidates, or a hash containing these keys: "completion" (array, list of possible candidates) and extra keys for formatting hints e.g. "is_path" (bool, whether the list of completion is path-like, meaning it can be traversed/dug to multiple levels) "path_sep" (string, path separator character), "type" (string, either "filename", "env", or other types). For an example of implementation for this, see Perinci::Sub::Complete in Perl which provides tab completion for argument values. Example: $SPEC{delete_user} = { v => 1.1, args => { username => { schema => 'str*', pos => 0, completion => sub { my %args = @_; my $word = $args{word} // ""; # find users beginning with $word local $CWD = "/home"; return [grep {-d && $_ ~~ /^\Q$word/} <*>]; }, }, force => {schema=>[bool => {default=>0}]}, }, }; When "delete_user" is executed over the command line and the Tab key is pressed: $ delete-user --force --username fo<tab> $ delete-user fo<tab> then bash will try to complete with usernames starting with "fo". element_completion. This is like completion, but for array or hash elements. Argument type must be "array" or "hash". Example for array: $SPEC{delete_users} = { v => 1.1, args => { usernames => { schema => ['array*' => of => 'str*'], req => 1, pos => 0, slurpy => 1, element_completion => sub { my %args = @_; my $word = $args{word} // ""; # find users beginning with $word local $CWD = "/home"; my $res = [grep {-d && $_ ~~ /^\Q$word/} <*>]; # exclude users already mentioned by user my $ary = $args{args}{usernames}; $res = [grep {!($_ ~~ @$ary)}] @$res; return $res; }, }, }, }; When "delete_users" is executed over the command line: $ delete-users c<tab> ; # will complete with all users beginning with c $ delete-users charlie c<tab> ; # will complete with users but exclude charlie $ delete-users charlie chucky <tab> ; # and so on Example for hash (as well as index_completion property to complete hash keys): $SPEC{create_file} = { v => 1.1, args => { filename => { schema => 'str*', req => 1, pos => 0, }, content => { schema => 'buf*', req => 1, pos => 1, }, mode => { summary => 'Permission mode', schema => 'posint*', }, extra_attrs => { 'x.name.is_plural' => 1, 'x.name.singular' => 'extra_attr', schema => ['hash*' => of => 'str*'], index_completion => sub { # complete with list of known attributes my %args = @_; require Complete::Util; Complete::Util::complete_array_elem( word => $args{word}, array => [qw/mtime ctime owner group/], ); }, element_completion => sub { my %args = @_; my $word = $args{word} // ""; my $index = $args{index}; if ($index eq 'owner') { require Complete::Unix; return Complete::Unix::complete_user(word=>$word); } elsif ($index eq 'group') { require Complete::Unix; return Complete::Unix::complete_group(word=>$word); } else { return undef; } }, }, }, }; When "create_file" is executed over the command line: $ create-file file1 "hello filesystem" --extra-attr <tab>; # will complete with list of known attributes $ create-file file1 "hello filesystem" --extra-attr owner=<tab>; # will complete with list of Unix users args_relsHash. This property is used to expression relationships between arguments. The value is actually Sah schema hash clause set (see hash type in Sah::Type). The arguments are represented as a hash, and you can use the various Sah clauses to express relationships between the arguments (hash keys) because the Sah hash type supports such clauses, e.g. "choose_one", "choose_all", "req_one", "req_all", "dep_any", "dep_all", "req_dep_any", "req_dep_all".Examples: args_rels => { choose_one => ['delete', 'add', 'edit'], choose_all => ['red', 'green', 'blue'], } The above example says that only one of "delete", "add", "edit" can be specified. And if any of "red", "green", "blue" is specified then all must be specified. In CLI context this translates to: % prog --delete item % prog --delete --add item ; # error, both --delete and --add specified % prog --red 255 --green 255 --blue 0 % prog --red 255 --blue 0 ; # error, --green is missing Another example: XXX args_asStr. Specify in what form the function expects the arguments. The value is actually implementation-specific since it describes the function implementation. For example in Perinci for Perl, these values are recognized: "array", "hash", "arrayref", "hashref". This property is useful for wrapper to be able to convert one form to another.The default value is also left to the implementation. For interimplementation communication (e.g. via Riap::HTTP or Riap::TCP), named arguments are always used so this property is irrelevant. resultDefHash. Specify function return value. It is a defhash containing keys:
Note that since functions normally return enveloped result, instead of returning: RESULT your functions normally have to return an enveloped result: [STATUS, MESSAGE, RESULT, METADATA] Examples: # result is an integer result => {schema => 'int*'} # result is an integer starting from zero result => {schema => ['int*' => {ge=>0}]} # result is an array of records result => { summary => 'Matching addressbook entries', schema => ['array*' => { summary => 'blah blah blah ...', of => ['hash*' => {allowed_keys=>[qw/name age address/]} ] }] } result_nakedBool. If set to true, specify that function does not envelope its results. The default is false, to encourage functions to create envelopes. However, wrapper should be able to create or strip envelope if needed. For example, if you have "traditional" functions which does not do envelopes, you can set this property to true, and the wrapper can generate the envelope for the functions.examples (function property)Array. This property allows you to put examples in a detailed and structured way, as an alternative to putting everything in "description".Each example is a defhash, it specifies what arguments are used, what the results are, and some description. It can be used when generating API/usage documentation, as well as for testing data. It can also be used for testing (function will be run with specified arguments and the result will be matched against expected result). Known properties:
Example: # part of metadata for Math::is_prime function examples => [ { args => {num=>10}, result => 0, # summary no needed here, already clear. }, { args => {}, result => 400, summary => 'Num argument is required', }, { argv => [-5], result => 1, summary => 'Also works for negative integers', }, ], Another example demonstrating "src" for a function called "list_countries": examples => [ { src => 'for c in `list-countries`; do wget http://flags.org/country/$c; done', src_plang => 'bash', }, { src => <<'EOT', my $res = list_countries(detail => 1, sort=>['-popsize']); die "Can't list countries: $res->[0] - $res->[1]" unless $res->[0] == 200; my $i = 0; for my $c (@{ $res->[2] }) { $i++; say "$i. $_->{name}'s population: $_->{popsize}"; EOT src_plang => 'perl', }, ], featuresDefHash. Allows functions to express their features. Each hash key contains feature name, which must only contain letters/numbers/underscores.Below is the list of defined features. New feature names may be defined by extension.
deps (function property)Hash. This property specifies function's dependencies to various things. It is a hash of dep types and values. Some dep types are special: "all", "any", and "none".deps => { DEPTYPE => DEPVALUE, ..., all => [ {DEPTYPE=>DEPVALUE, ...}, ..., }, any => [ {DEPTYPE => DEPVALUE, ...}, ..., ], none => [ {DEPTYPE => DEPVALUE, ...}, ...., ], } A dependency can be of any type: another function, environment variables, programs, OS software packages, etc. It is up to the dependency checker library to make use of this information. For the dependencies to be declared as satisfied, all of the clauses must be satisfied. Below is the list of defined dependency types. New dependency type may be defined by an extension.
If you add a new language-specific dependency type, please prefix it with the language code, e.g. "perl_module", "perl_func", "ruby_gem", "python_egg". These dependency types have also been defined by some existing tools: "deb" (dependency to a Debian package), "rpm" (dependency to an RPM package), "js_url" (loading a remote JavaScript script URL), "file" (existence of a), "perl_run_func" (running a Perl subroutine and getting a successful enveloped result). Some of these might be declared as part of the core dependency types in the future. FAQWhat is the difference between "summary" or "description" in the Sah schema and arg specification?Example:{ args => { src => { summary => "Source path", description => "...", schema => ["str*", { summary => "...", description => "...", ... }], ... }, dest => { summary => "Target path", description => "...", schema => ["str*", { summary => "...", description => "...", ... }], ... }, ... }, } As you can see, each argument has a "summary" and "description", but the schema for each argument also has a "summary" and "description" schema clauses. What is the difference and which should be put into which? The argument specification's "summary" (and "description") describe the argument itself, in this example it says that "src" means "The source path" and "dest" means "The target path". The argument schema's "summary" (and "description") describe the data type and valid values. In this example it could say, e.g., "a Unix-path string with a maximum length of 255 characters". In fact, "src" and "dest" are probably of the same type ("Unix path") and can share schema. { ... args => { src => { ... schema => "unix_path", }, dest => { ... schema => "unix_path", }, ... }, } What is the difference between setting req=>1 in the argument specification and req=>1 in schema?Example:# Note: remember that in Sah, str* is equivalent to [str => {req=>1}] args => { a => { schema=>"str" }, b => { schema=>"str*" }, c => { req=>1, schema=>"str" }, d => { req=>1, schema=>"str*" }, } In particular look at "b" and "c". "b" is not a required argument (no req=>1 in the argument spec) but if it is specified, than it cannot be undef/null (since the schema says [str=>{req=>1}], a.k.a "str*"). On the other hand, "c" is a required argument (req=>1 in the argument spec) but you can specify undef/null as the value. The following are valid: func(c=>undef, d=>1); But the following are not: func(b=>1, d=>1); # c is not specified func(b=>undef, c=>1, d=>1); # b has undef value func(b=>1, c=>1, d=>undef); # d has undef value Should I add a new metadata property, or add a new feature name to the "features" property, or add a new dependency type to the "deps" property?If your property describes a dependency to something, it should definitely be a new dependency type. If your property only describes what the function can do and does not include any wrapper code, then it probably goes into "features". Otherwise, it should probably become a new metadata property.For example, if you want to declare that your function can only be run under a certain moon phase (e.g. full moon), it should definitely go as a new dependency type, so it becomes: deps => { moon_phase => 'full' }. Another example, "reverse" is a feature name, because it just states that if we pass "-reverse" => 1 special argument to a reversible function, it can do a reverse operation. It doesn't include any wrapper code, all functionality is realized by the function itself. On the other hand, "timeout" is a metadata property because it involves adding adding some wrapping code (a timeout mechanism, e.g. an eval() block and alarm() in Perl). HOMEPAGEPlease visit the project's homepage at <https://metacpan.org/release/Rinci>.SOURCESource repository is at <https://github.com/perlancar/perl-Rinci>.SEE ALSORelated specifications: Sah, HTTP/1.1 (RFC 2068)Rinci AUTHORperlancar <perlancar@cpan.org>CONTRIBUTINGTo contribute, you can send patches by email/via RT, or send pull requests on GitHub.Most of the time, you don't need to build the distribution yourself. You can simply modify the code, then test via: % prove -l If you want to build the distribution (e.g. to try to install it locally on your system), you can install Dist::Zilla, Dist::Zilla::PluginBundle::Author::PERLANCAR, and sometimes one or two other Dist::Zilla plugin and/or Pod::Weaver::Plugin. Any additional steps required beyond that are considered a bug and can be reported to me. COPYRIGHT AND LICENSEThis software is copyright (c) 2022, 2020, 2019, 2018, 2017, 2016, 2015, 2014, 2013, 2012 by perlancar <perlancar@cpan.org>.This is free software; you can redistribute it and/or modify it under the same terms as the Perl 5 programming language system itself. BUGSPlease report any bugs or feature requests on the bugtracker website <https://rt.cpan.org/Public/Dist/Display.html?Name=Rinci>When submitting a bug or request, please include a test-file or a patch to an existing test-file that illustrates the bug or desired feature.
Visit the GSP FreeBSD Man Page Interface. |