Coder Social home page Coder Social logo

Comments (15)

denisdefreyne avatar denisdefreyne commented on July 26, 2024

The block passed to #option (and variants) is currently executed when the corresponding option is found, but I am not sure that it can be repurposed (in a backwards-compatible way) to verify and transform the value. It can break existing usage that relies on the option value not to change.

I believe that there are three common actions that Cri could/should support:

  • run a block (useful for e.g. --help, --version; currently supported)

  • validate (useful for e.g. verifying that a port number is an integer in the 0ā€“65535 range; not currently explicitly supported)

  • transform (useful for e.g. transforming input into an integer; not currently explicitly supported)

Some use cases:

  • Specify any number
  • Specify a number with requirements (e.g. --port >0 and <65535)
  • Specify a string with requirements (e.g. --seed length ā‰„ 20)

I am unsure in which order validation and transformation should happen. Validate-then-transform, or transform-then-validate?

Perhaps a transformer that can raise an exception, followed by additional validation, could work:

option :p, :port, 'Specify port to listen on',
  transform: ->(n) { Integer(n) },
  validate: ->(n) { (1..65535).include?(n) }
option :c, :'config-file', 'Specify configuration file',
  transform: ->(fn) { ConfigFile.read(fn) }
  validate: &:valid?

Any callable would be supported (not just procs), so this would also work:

ā€¦
  transform: method(:Integer)

or perhaps even

class IntegerTransformer
  def initialize(min: nil, max: nil)
    @min = min
    @max = max
  end

  def validate(n)
    Integer(n).tap do |parsed_n|
      (min ? parsed_n >= min : true) &&
      (min ? parsed_n >= min : true)
    end
  end
end
ā€¦
  transform: IntegerTransformer.new

ā€¦ though at this point it might be worth looking into already-existing validation solutions out there (dry-validation comes to mind, but it might be overkill.)


As for argument validation, here are some ideas:

  • Fixed number:

    num_arguments 1
  • Range:

    num_arguments 1..3
  • Callable:

    num_arguments -> (n) { n > 3 || n % 5 == 1 }

i.e. essentially anything that implements #===. WDYT?

from cri.

denisdefreyne avatar denisdefreyne commented on July 26, 2024

FYI, I have deprecated #optional and #required as their names are far too confusing.

from cri.

denisdefreyne avatar denisdefreyne commented on July 26, 2024

@eguzki Iā€™ve implemented transform: support in #68. Can you take a look and check whether it makes sense, and fits your use case?

(Option validation is not explicitly implemented yet, although transform: could be reused for that purpose for now. Argument validation is pending too.)

from cri.

denisdefreyne avatar denisdefreyne commented on July 26, 2024

The option value transformation is implemented and released in Cri 2.11. Can you take a look and confirm that it fits your needs?

from cri.

eguzki avatar eguzki commented on July 26, 2024

After, quick review, it seems this change allow us to "inject" configuration objects into options object. So every command can get configuration object instead of getting config file and create themselves configuration object.

I have not checked yet actually using this new feature. Will do it ASAP.

from cri.

eguzki avatar eguzki commented on July 26, 2024

I think this a good progress šŸ‘

Still missing the issue about validation and positional argument validation ;)

from cri.

denisdefreyne avatar denisdefreyne commented on July 26, 2024

After, quick review, it seems this change allow us to "inject" configuration objects into options object. So every command can get configuration object instead of getting config file and create themselves configuration object.

I believe the transformers should remain stateless; the configuration object is something you could construct afterwards, e.g.

option :p, :port, 'set port', argument: :required, transform: method(:Integer)
# options is a hash, e.g. { port: 12345 }
config = Config.new(options)

As for argument validation: itā€™d be nice to be able to name them, e.g. arguments[:filename] rather than arguments[0].

Some ideas for the API:

  • Example:

    arguments [:host, :port]

    Two positional arguments.

  • Example:

    argument :host
    argument :port

    Two positional arguments. This API might not make it clear enough that the list of arguments is ordered, however.

  • Example:

    argument 0 => :host
    argument 1 => :port

    Two positional arguments. Perhaps a bit verbose.

  • Example:

    argument host: 0
    argument port: 1

    Two positional arguments. Perhaps a bit verbose and/or confusing.

  • Example:

    argument host: 0
    argument port: 1
    argument filenames: 2..-1

    Several positional arguments, with :filenames being a list of arguments rather than a single one.

    Note that the range 2..-1 does not actually make sense in Ruby (itā€™s an empty range), but thereā€™s no easy way to express an open-ended range in Ruby 2.5. Itā€™s possible to use 2..Float::INFINITY, but thatā€™s icky. This will change in Ruby 2.6, which will allow ranges like 2.. which represents an open-ended range from 2 on.

  • Example:

    argument :host, pos: 0
    argument :port, pos: 1
    argument :filenames, from: 2

    Alternative to the above, with a cleaner syntax for open-ended ranges.

More thoughts:

  • Are there other requirements for argument validation? Other nice-to-haves?
  • parameter or argument?

from cri.

denisdefreyne avatar denisdefreyne commented on July 26, 2024

The same transformation would be used for arguments, e.g.

argument :ports, from: 0, transform: method(:Integer)

ā€¦ to specify a list of ports and convert them to integers.

from cri.

eguzki avatar eguzki commented on July 26, 2024

I believe the transformers should remain stateless; the configuration object is something you could construct afterwards, e.g.

Makes sense. This is something very specific for our needs and will think about that. thnx.

WRT positional arguments, I would expect simple validation of existence. Any further support for transformation or keyword based access API is nice to have.

The most flexible way seems your last example

argument :ports, from: 0, transform: method(:Integer)

Allows extension for validation or running a block. I would call it argument for consistency's sake.

from cri.

denisdefreyne avatar denisdefreyne commented on July 26, 2024

As the MVP, Iā€™d like to implement something a bit simpler:

param :port, transform: method(:Integer)
  • I renamed argument to param. An argument is a value, e.g. 8080, while a parameter is a name, e.g. port. I like to make that distinction.

  • transform: (same as before)

  • No more from/pos ā€” at least not for the MVP. For now, donā€™t support a variable number of arguments, and assume that the arguments are given in exactly the order of param calls.

Additionally, if no params are specified, Cri will assume that there is no argument validation needed. The no_params directive will tell Cri to do validation and check that there are indeed no arguments passed.


Later on:

  • Implement from: and pos: (though at: might be a better name than pos:)
  • Implement default:

from cri.

denisdefreyne avatar denisdefreyne commented on July 26, 2024

Very basic parameter checking is now implemented in master. It includes (for now) only parameters without support for transform, from/at, or default ā€” just the name.

For example:

param :source
param :target
% ./sync . hydra:/srv/data
Syncingā€¦
%
% ./sync hydra:/srv/data
sync: incorrect number of arguments given: expected 2, but got 1
%

from cri.

denisdefreyne avatar denisdefreyne commented on July 26, 2024

The released version 2.12.0 has the basic param support. Further improvements are in the pipeline.

from cri.

denisdefreyne avatar denisdefreyne commented on July 26, 2024

Cri v2.13.0 has support for explicitly specifying zero parameters using no_params, e.g.

command = Cri::Command.define do
  name        'reset'
  usage       'reset'
  summary     'resets the site'
  description 'ā€¦'
  no_params

  run do |opts, args, cmd|
    puts "Resettingā€¦"
  end
end
% my-tool reset x
reset: incorrect number of arguments given: expected 0, but got 1
% my-tool reset
Resettingā€¦
%

from cri.

denisdefreyne avatar denisdefreyne commented on July 26, 2024

The just-released Cri v2.15.0 has parameter transformation support, e.g.

param :id, transform: ->(a) { a.upcase }

When given abc123 as argument, arguments[:id] will be ABC123.

This can also be used for validation (raise an exception in the transform block).

from cri.

denisdefreyne avatar denisdefreyne commented on July 26, 2024

Iā€™ve created #74 (varargs) and #75 (params with default values) to track non-implemented features. Both feature requests have low priority at the moment, but if you have use cases where they could be useful, do let me know.

I believe that, apart from these, thereā€™s nothing left that needs to be implemented, so Iā€™ll close this ticket.

Thanks for your valuable feedback! I think it has made Cri a considerably better product, and Iā€™ve also benefited from the new features that Iā€™ve added to it.

from cri.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    šŸ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. šŸ“ŠšŸ“ˆšŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ā¤ļø Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.