piotrmurach/tty-option

`NoMethodError` with `convert :pathname_list`

Closed this issue · 6 comments

Describe the problem

Using convert :pathname_list results in undefined method 'strip' for ["vendor/spdx.json", "spec/frontend/editor/schema/ci/json_tests/positive_tests/retry.json"]:Array (NoMethodError)

Steps to reproduce the problem

module Jsonlinter
  class Cli
    include TTY::Option

    usage do
      program "jsonlinter"

      command "run"
    end

    argument :path do
      name "path"
      arity one_or_more
      convert :pathname_list
      desc "Paths (or files) to scan"
    end

    flag :help do
      short "-h"
      long "--help"
      desc "Print usage"
    end

    flag :silent do
      long "--silent"
      desc "Do not display any output"
    end

    def run
      if params[:help]
        print help
      elsif params.errors.any?
        puts params.errors.summary
      else
        pp params.to_h
      end
    end
  end
end

Actual behaviour

$ ruby -I/home/quintasan/source/jsonlinter/lib /home/quintasan/source/jsonlinter/exe/jsonlinter vendor/spdx.json spec/frontend/editor/schema/ci/json_tests/positive_tests/retry.json

/home/quintasan/.local/share/rtx/installs/ruby/3.1.4/lib/ruby/gems/3.1.0/gems/tty-option-0.3.0/lib/tty/option/conversions.rb:95:in `block (2 levels) in <module:Conversions>': undefined method `strip' for ["vendor/spdx.json", "spec/frontend/editor/schema/ci/json_tests/positive_tests/retry.json"]:Array (NoMethodError)

           .map { |v| v.strip.gsub(/\\\\,/, ",") }
                       ^^^^^^
        from /home/quintasan/.local/share/rtx/installs/ruby/3.1.4/lib/ruby/gems/3.1.0/gems/tty-option-0.3.0/lib/tty/option/conversions.rb:95:in `map'
        from /home/quintasan/.local/share/rtx/installs/ruby/3.1.4/lib/ruby/gems/3.1.0/gems/tty-option-0.3.0/lib/tty/option/conversions.rb:95:in `block in <module:Conversions>'
        from /home/quintasan/.local/share/rtx/installs/ruby/3.1.4/lib/ruby/gems/3.1.0/gems/tty-option-0.3.0/lib/tty/option/conversions.rb:118:in `block (3 levels) in <module:Conversions>'
        from /home/quintasan/.local/share/rtx/installs/ruby/3.1.4/lib/ruby/gems/3.1.0/gems/tty-option-0.3.0/lib/tty/option/param_conversion.rb:22:in `call'
        from /home/quintasan/.local/share/rtx/installs/ruby/3.1.4/lib/ruby/gems/3.1.0/gems/tty-option-0.3.0/lib/tty/option/pipeline.rb:29:in `block in call'
        from /home/quintasan/.local/share/rtx/installs/ruby/3.1.4/lib/ruby/gems/3.1.0/gems/tty-option-0.3.0/lib/tty/option/pipeline.rb:28:in `each'
        from /home/quintasan/.local/share/rtx/installs/ruby/3.1.4/lib/ruby/gems/3.1.0/gems/tty-option-0.3.0/lib/tty/option/pipeline.rb:28:in `call'
        from /home/quintasan/.local/share/rtx/installs/ruby/3.1.4/lib/ruby/gems/3.1.0/gems/tty-option-0.3.0/lib/tty/option/parser/arguments.rb:161:in `assign_argument'
        from /home/quintasan/.local/share/rtx/installs/ruby/3.1.4/lib/ruby/gems/3.1.0/gems/tty-option-0.3.0/lib/tty/option/parser/arguments.rb:62:in `block in parse'
        from /home/quintasan/.local/share/rtx/installs/ruby/3.1.4/lib/ruby/gems/3.1.0/gems/tty-option-0.3.0/lib/tty/option/parser/arguments.rb:58:in `each'
        from /home/quintasan/.local/share/rtx/installs/ruby/3.1.4/lib/ruby/gems/3.1.0/gems/tty-option-0.3.0/lib/tty/option/parser/arguments.rb:58:in `parse'
        from /home/quintasan/.local/share/rtx/installs/ruby/3.1.4/lib/ruby/gems/3.1.0/gems/tty-option-0.3.0/lib/tty/option/parser.rb:50:in `block in parse'
        from /home/quintasan/.local/share/rtx/installs/ruby/3.1.4/lib/ruby/gems/3.1.0/gems/tty-option-0.3.0/lib/tty/option/parser.rb:45:in `each'
        from /home/quintasan/.local/share/rtx/installs/ruby/3.1.4/lib/ruby/gems/3.1.0/gems/tty-option-0.3.0/lib/tty/option/parser.rb:45:in `parse'
        from /home/quintasan/.local/share/rtx/installs/ruby/3.1.4/lib/ruby/gems/3.1.0/gems/tty-option-0.3.0/lib/tty/option.rb:54:in `parse'
        from /home/quintasan/source/jsonlinter/exe/jsonlinter:38:in `<main>'

Expected behaviour

I expected it to work with multiple paths

Describe your environment

  • OS version: Ubuntu 22.04
  • Ruby version: 3.1.4
  • TTY::Option version: 0.3.0

It does work properly if I pass one path to it:

ruby -I/home/quintasan/source/jsonlinter/lib /home/quintasan/source/jsonlinter/exe/jsonlinter vendor/spdx.json                                                                    
{:help=>false, :silent=>false, :path=>[#<Pathname:vendor/spdx.json>]}

Hi Michał 👋

Thanks for using the tty-option gem.

I don't believe this to be a bug but a misunderstanding on how to use the library.

For your example to work you need to call the 2.6 parse method first.

For instance, passing the following paths to parse converts the paths as expected:

linter = Jsonlinter::Cli.new
linter.parse(%w[vendor/spdx.json spec/frontend/editor/schema/ci/json_tests/positive_tests/retry.json])
linter.run
# =>
# {:help=>false, :silent=>false, :path=>[#<Pathname:vendor/spdx.json>, #<Pathname:spec/frontend/editor/schema/ci/json_tests/positive_tests/retry.json>]}

Alternatively, add the parse method call to your run method:

def run
  parse
  if params[:help]
  ...
end

Then running:

ruby jsonlinter vendor/spdx.json spec/frontend/editor/schema/ci/json_tests/positive_tests/retry.json

Gives the same result.

@piotrmurach Fascinating but this is almost exactly the same way I run it:

cmd = Jsonlinter::Cli.new
cmd.parse
cmd.run

See https://gitlab.com/Quintasan/jsonlinter/-/blob/master/exe/jsonlinter?ref_type=heads

I had a quick play with your code.

It seems that when you comment out the require_realtive "runner" and the Jsonlinter::Runner.call(params) from the lib/jsonlinter/cli.rb and simply output puts params.to_h it works fine:

bundle exec exe/jsonlinter vendor/spdx.json spec/frontend/editor/schema/ci/json_tests/positive_tests/retry.json
# =>
# {:autocorrect=>false,
# :help=>false,
# :silent=>false,
# :paths=>[#<Pathname:vendor/spdx.json>, #<Pathname:spec/frontend/editor/schema/ci/json_tests/positive_tests/retry.json>]}

I wonder what is happening inside the runner file to 'override' this behaviour?

@piotrmurach fair enough, that seems to squarely put the problem on my side. I don't have access to a computer ATM so I'll play around with it to tomorrow.

As I was already playing with your gem I decided to dig into why this is happening.

The culprit is the activesupport dependency.

In particular, the inclusion of require "active_support/core_ext/array/grouping" in the lib/jsonlinter/discriminator.rb is the real issue. It overrides the Array#split core method. The split is used internally by tty-option to convert any list arguments whether they are pathname or other objects.

I consider activesupport use in gems an antipattern and red flag. Once you include it and override core ruby classes then all bets are off. I'd strongly encourage you to stop using this dependency as you may have a hard time finding bugs in the future. I don't believe there is a safe way to use it and any benefits gained may not outweigh the obvious downsides.

I'd recommend also before submitting issues to any project, isolate the problem and run a minimum case like I did in this issue thread without any other dependencies. This way you will quickly know whether the problem is with your code or external library. Open-source developer's time is limited and could be spent on resolving actual issues and implementing new features. Please don't take it as a reprimand, as I don't believe you meant to waste my time, but as a perspective to carry with you into the future.