Full Code of jbodah/suggest_rb for AI

master f3330926931b cached
12 files
11.5 KB
3.8k tokens
13 symbols
1 requests
Download .txt
Repository: jbodah/suggest_rb
Branch: master
Commit: f3330926931b
Files: 12
Total size: 11.5 KB

Directory structure:
gitextract_7dbms7ij/

├── .gitignore
├── .travis.yml
├── Gemfile
├── README.md
├── Rakefile
├── bin/
│   ├── console
│   └── setup
├── lib/
│   ├── suggest/
│   │   └── version.rb
│   └── suggest.rb
├── suggest.gemspec
└── test/
    ├── suggest_test.rb
    └── test_helper.rb

================================================
FILE CONTENTS
================================================

================================================
FILE: .gitignore
================================================
/.bundle/
/.yardoc
/_yardoc/
/coverage/
/doc/
/pkg/
/spec/reports/
/tmp/
*.lock


================================================
FILE: .travis.yml
================================================
---
sudo: false
language: ruby
cache: bundler
rvm:
  - 2.3.8
before_install: gem install bundler -v 1.17.3


================================================
FILE: Gemfile
================================================
source "https://rubygems.org"

git_source(:github) {|repo_name| "https://github.com/#{repo_name}" }

# Specify your gem's dependencies in suggest.gemspec
gemspec


================================================
FILE: README.md
================================================
# Suggest

tells you which method does the thing you want to do

## Disclaimer

I don't recommend you ship this in your Gemfile. Keep it in your system's gems (e.g. `gem install`) and load it as needed (e.g. `irb -rsuggest`, `RUBY_OPT=-rsuggest irb`, etc)

## Installation

```
gem install suggest_rb
```

## Usage

```rb
require 'suggest'

# Object#what_returns? tells you which method returns the value
[1,2,3].what_returns? 1
=> [:first, :min]

# You can also specify the args you want that method to take
[1,2,3].what_returns? [1], args: [1]
=> [:first, :take, :grep, :min]

# By default, it only returns methods that don't mutate the object
[1,2,3].what_returns? [1], args: [1], allow_mutation: true
=> [:first, :take, :shift, :grep, :min]

# It works on several core modules including String
"HELLO".what_returns? "hello"
=> [:downcase, :swapcase]

# You can also specify a block that you want the method to accept
[1,2,3,4].what_returns?({true => [2,4], false => [1,3]}) { |n| n % 2 == 0 }
=> [:group_by]

# Object#what_mutates? tells you which method changes the object to the desired state
[1,2,3].what_mutates? [2, 3]
=> [:shift]

# You can also match on the return value
[1,2,3].what_mutates? [2, 3], returns: 1
=> [:shift]

[1,2,3].what_mutates? [2, 3], returns: 2
=> []

# You can specify which args to pass to the method
[1,2,3].what_mutates? [3], args: [2]
=> [:shift]

# It also works on a bunch of core modules
"HELLO".what_mutates? "hello"
=> [:swapcase!, :downcase!]

# And you can give it a block as well
[1,2,3,4].what_mutates? [2,4] { |n| n % 2 == 0 }
=> [:select!, :keep_if]

# You can use a lambda as an expected
[1,2,3,4].what_returns? -> (something_that) { something_that.to_i == 4 }
=> [:count, :length, :size, :last, :max]

# It respects the ruby version
# ruby 2.4.3
{a: 1, b: 2}.what_returns?({})
=> []
# ruby 2.5.0
{a: 1, b: 2}.what_returns?({})
=> [:slice]
```

## Note to Self

Snippet to use in `bin/console` for finding methods for blacklisting:

```
Suggest::SUGGEST_MODS.flat_map { |k| [k].product(k.instance_methods) }.select { |k, v| v == :rand }.map { |k, v| k.instance_method(v).owner }.uniq
```


================================================
FILE: Rakefile
================================================
require "bundler/gem_tasks"
require "rake/testtask"

Rake::TestTask.new(:test) do |t|
  t.libs << "test"
  t.libs << "lib"
  t.test_files = FileList["test/**/*_test.rb"]
end

task :default => :test


================================================
FILE: bin/console
================================================
#!/usr/bin/env ruby

require "bundler/setup"
require "suggest"

# You can add fixtures and/or initialization code here to make experimenting
# with your gem easier. You can also use a different console, if you like.

# (If you use this, don't forget to add pry to your Gemfile!)
# require "pry"
# Pry.start

require "irb"
IRB.start(__FILE__)


================================================
FILE: bin/setup
================================================
#!/usr/bin/env bash
set -euo pipefail
IFS=$'\n\t'
set -vx

bundle install

# Do any other automated setup that you need to do here


================================================
FILE: lib/suggest/version.rb
================================================
module Suggest
  VERSION = "0.5.2"
end


================================================
FILE: lib/suggest.rb
================================================
require "suggest/version"
require "set"

module Suggest
  SUGGEST_MODS = Set.new([
    Array,
    BasicObject,
    Comparable,
    Complex,
    Enumerable,
    FalseClass,
    Float,
    Hash,
    Integer,
    Math,
    NilClass,
    Numeric,
    Range,
    Regexp,
    Regexp,
    Set,
    String,
    Struct,
    Symbol,
    Time,
    TrueClass,
  ])

  UNSAFE_WITH_BLOCK = Set.new([
    [Array, :cycle],
    [Enumerable, :cycle]
  ])

  INCONSISTENT = Set.new([
    [Array, :sample],
    [Array, :shuffle],
    [Array, :shuffle!]
  ])

  TOO_COMPLICATED = Set.new([
    [String, :freeze],
    [Set, :freeze],
    [Set, :taint],
    [Set, :untaint],
    [Numeric, :singleton_method_added],
    [Numeric, :clone],
    [Numeric, :dup],
    [BasicObject, :instance_eval],
    [BasicObject, :instance_exec],
    [BasicObject, :__send__],
    [BasicObject, :singleton_method_added],
    [BasicObject, :singleton_method_removed],
    [BasicObject, :singleton_method_undefined]
  ])

  SELECTOR = ->(m) do
    SUGGEST_MODS.include?(m.owner) &&
      !INCONSISTENT.include?([m.owner, m.name]) &&
      !TOO_COMPLICATED.include?([m.owner, m.name])
  end

  module Mixin
    def what_returns?(expected, args: [], allow_mutation: false, allow_not_public: false, &block)
      methods.map(&method(:method)).select(&SELECTOR).select do |m|
        arity = m.arity
        next unless arity < 0 || arity == args.count

        post = clone

        next if block && UNSAFE_WITH_BLOCK.include?([m.owner, m.name])
        result = post.__send__(allow_not_public ? :send : :public_send, m.name, *args, &block) rescue next

        next unless allow_mutation || self == post

        if expected.is_a?(Proc) && expected.lambda?
          expected.call(result) rescue false
        else
          Suggest.eq?(result, expected)
        end
      end.map(&:name)
    end

    def what_mutates?(expected, args: [], allow_not_public: false, **opts, &block)
      methods.map(&method(:method)).select(&SELECTOR).select do |m|
        arity = m.arity
        next unless arity < 0 || arity == args.count

        post = clone

        next if block && UNSAFE_WITH_BLOCK.include?([m.owner, m.name])
        result = post.__send__(allow_not_public ? :send : :public_send, m.name, *args, &block) rescue next

        next if opts.key?(:returns) && !Suggest.eq?(result, opts[:returns])

        Suggest.eq?(post, expected)
      end.map(&:name)
    end
  end

  def self.eq?(result, expected)
    result.is_a?(expected.class) && result == expected
  end

  def self.suggestable!(mod, **corrections) # unsafe_with_block: [], inconsistent: [], too_complicated: []
    raise ArgumentError.new("Must support smart comparison (implement «#{mod}#==»)") if mod.instance_method(:==).owner == BasicObject

    SUGGEST_MODS << mod
    %w[unsafe_with_block inconsistent too_complicated].each do |correction|
      c = Suggest.const_get(correction.upcase)
      [mod].product(corrections.fetch(correction, [])).each(&c.method(:<<))
    end
    mod.include(Suggest::Mixin) unless mod.ancestors.include?(Suggest::Mixin)
  end

  def self.suggestable_methods
    SUGGEST_MODS.each_with_object([]) do |mod, candidates|
      owned_methods = mod.instance_methods.select { |m| mod.instance_method(m).owner == mod }
      next if owned_methods.none?
      candidates += [mod].product(owned_methods)
    end.reject do |m|
      INCONSISTENT.include?(m) || TOO_COMPLICATED.include?(m)
    end
  end
end

Suggest::SUGGEST_MODS.each do |mod|
  mod.include(Suggest::Mixin) unless mod.ancestors.include?(Suggest::Mixin)
end


================================================
FILE: suggest.gemspec
================================================

lib = File.expand_path("../lib", __FILE__)
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
require "suggest/version"

Gem::Specification.new do |spec|
  spec.name          = "suggest_rb"
  spec.version       = Suggest::VERSION
  spec.authors       = ["Josh Bodah"]
  spec.email         = ["jbodah@cargurus.com"]

  spec.summary       = %q{tells you which method does the thing you want to do}
  spec.homepage      = "https://github.com/jbodah/suggest_rb"

  # Specify which files should be added to the gem when it is released.
  # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
  spec.files         = Dir.chdir(File.expand_path('..', __FILE__)) do
    `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
  end
  spec.bindir        = "exe"
  spec.executables   = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
  spec.require_paths = ["lib"]

  spec.add_development_dependency "bundler"
  spec.add_development_dependency "rake", "~> 10.0"
  spec.add_development_dependency "minitest", "~> 5.0"
  spec.add_development_dependency "minitest-tagz"
  spec.add_development_dependency "pry-byebug"
end


================================================
FILE: test/suggest_test.rb
================================================
require "test_helper"

class SuggestTest < Minitest::Spec
  describe "#what_returns?" do
    it "works for Arrays" do
      assert_includes [1,2,3].what_returns?(1), :first
      assert_includes [1,2,3].what_returns?(1), :min
    end

    it "doesn't return methods that mutate" do
      refute_includes [1,2,3].what_returns?([1], args: [1]), :shift
      assert_includes [1,2,3].what_returns?([1], args: [1]), :take
    end

    it "can be told to allow mutation" do
      assert_includes [1,2,3].what_returns?([1], args: [1], allow_mutation: true), :shift
      assert_includes [1,2,3].what_returns?([1], args: [1], allow_mutation: true), :take
    end

    it "works on Strings" do
      assert_includes "HELLO".what_returns?("hello"), :downcase
      refute_includes "HELLO".what_returns?("hello"), :downcase!

      assert_includes "HELLO".what_returns?("hello", allow_mutation: true), :downcase
      assert_includes "HELLO".what_returns?("hello", allow_mutation: true), :downcase!
    end

    it "works on block expressions" do
      rv = [1,2,3,4].what_returns?({true => [2,4], false => [1,3]}) { |n| n % 2 == 0 }
      assert_includes rv, :group_by
    end

    it "doesn't return inconsistent methods" do
      rv = [1].what_returns?(1)
      refute_includes rv, :sample

      rv = [1].what_returns?([1])
      refute_includes rv, :shuffle
    end

    it "returns a private method of arity -2" do
      rv = Set.new([1]).what_returns? Set.new([1]), args: [[1]]
      refute_includes rv, :flatten_merge

      rv = Set.new([1]).what_returns? Set.new([1]), args: [[1]], allow_not_public: true
      assert_includes rv, :flatten_merge
    end

    it "allows dynamic convertion of anything to suggestable" do
      rv = NotYetSuggestable.new.what_returns?(42)
      refute_includes rv, :foo

      Suggest.suggestable!(NotYetSuggestable)
      rv = NotYetSuggestable.new.what_returns?(42)
      assert_includes rv, :foo

      assert_raises ArgumentError do
        Suggest.suggestable!(NotSuggestable)
      end
    end

    it "given a lambda, yields to the lambda to see if result is equal" do
      rv = [1,2,3].what_returns? -> (thing) { thing.to_s == "1" }
      assert_includes rv, :first
    end

    it "given a lambda, doesn't blow up" do
      [1,2,3].what_returns? -> (thing) { thing.first == 1 }
    end
  end

  describe "#what_mutates?" do
    it "returns methods that mutate" do
      assert_includes [1,2,3].what_mutates?([2, 3]), :shift
    end

    it "can check return values" do
      assert_includes [1,2,3].what_mutates?([2, 3], returns: 1), :shift
    end

    it "can be passed args" do
      assert_includes [1,2,3].what_mutates?([3], args: [2]), :shift
    end

    it "works on Strings" do
      assert_includes "HELLO".what_mutates?("hello"), :downcase!
    end

    it "works on block expressions" do
      rv = [1,2,3,4].what_mutates?([2,4]) { |n| n % 2 == 0 }
      assert_includes rv, :select!
    end

    it "doesn't return inconsistent methods" do
      rv = [1].what_mutates?([1])
      refute_includes rv, :shuffle!
    end
  end

  describe "suggestable_methods" do
    it "skips scary methods" do
      scary = [
        :taint,
        :untaint,
        :freeze,
        :trust,
        :untrust,
        /method_added/,
        /variable/,
        /method/,
        :clone,
        :dup,
      ]

      scary.each do |s|
        found = Suggest.suggestable_methods.find { |_klass, name| s === name }
        assert_nil found, "didn't expect #{found.inspect}"
      end
    end
  end
end


================================================
FILE: test/test_helper.rb
================================================
require "bundler/setup"

$LOAD_PATH.unshift File.expand_path("../../lib", __FILE__)
require "suggest"

require "minitest/autorun"
require "minitest/spec"
require "minitest/pride"

class NotYetSuggestable
  def foo
    42
  end

  def ==(other)
    other.is_a?(NotYetSuggestable) && other.foo == foo
  end
end

class NotSuggestable; end
Download .txt
gitextract_7dbms7ij/

├── .gitignore
├── .travis.yml
├── Gemfile
├── README.md
├── Rakefile
├── bin/
│   ├── console
│   └── setup
├── lib/
│   ├── suggest/
│   │   └── version.rb
│   └── suggest.rb
├── suggest.gemspec
└── test/
    ├── suggest_test.rb
    └── test_helper.rb
Download .txt
SYMBOL INDEX (13 symbols across 4 files)

FILE: lib/suggest.rb
  type Suggest (line 4) | module Suggest
    type Mixin (line 62) | module Mixin
      function what_returns? (line 63) | def what_returns?(expected, args: [], allow_mutation: false, allow_n...
      function what_mutates? (line 83) | def what_mutates?(expected, args: [], allow_not_public: false, **opt...
    function eq? (line 100) | def self.eq?(result, expected)
    function suggestable! (line 104) | def self.suggestable!(mod, **corrections) # unsafe_with_block: [], inc...
    function suggestable_methods (line 115) | def self.suggestable_methods

FILE: lib/suggest/version.rb
  type Suggest (line 1) | module Suggest

FILE: test/suggest_test.rb
  class SuggestTest (line 3) | class SuggestTest < Minitest::Spec

FILE: test/test_helper.rb
  class NotYetSuggestable (line 10) | class NotYetSuggestable
    method foo (line 11) | def foo
    method == (line 15) | def ==(other)
  class NotSuggestable (line 20) | class NotSuggestable; end
Condensed preview — 12 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (13K chars).
[
  {
    "path": ".gitignore",
    "chars": 80,
    "preview": "/.bundle/\n/.yardoc\n/_yardoc/\n/coverage/\n/doc/\n/pkg/\n/spec/reports/\n/tmp/\n*.lock\n"
  },
  {
    "path": ".travis.yml",
    "chars": 107,
    "preview": "---\nsudo: false\nlanguage: ruby\ncache: bundler\nrvm:\n  - 2.3.8\nbefore_install: gem install bundler -v 1.17.3\n"
  },
  {
    "path": "Gemfile",
    "chars": 162,
    "preview": "source \"https://rubygems.org\"\n\ngit_source(:github) {|repo_name| \"https://github.com/#{repo_name}\" }\n\n# Specify your gem'"
  },
  {
    "path": "README.md",
    "chars": 2137,
    "preview": "# Suggest\n\ntells you which method does the thing you want to do\n\n## Disclaimer\n\nI don't recommend you ship this in your "
  },
  {
    "path": "Rakefile",
    "chars": 198,
    "preview": "require \"bundler/gem_tasks\"\nrequire \"rake/testtask\"\n\nRake::TestTask.new(:test) do |t|\n  t.libs << \"test\"\n  t.libs << \"li"
  },
  {
    "path": "bin/console",
    "chars": 342,
    "preview": "#!/usr/bin/env ruby\n\nrequire \"bundler/setup\"\nrequire \"suggest\"\n\n# You can add fixtures and/or initialization code here t"
  },
  {
    "path": "bin/setup",
    "chars": 131,
    "preview": "#!/usr/bin/env bash\nset -euo pipefail\nIFS=$'\\n\\t'\nset -vx\n\nbundle install\n\n# Do any other automated setup that you need "
  },
  {
    "path": "lib/suggest/version.rb",
    "chars": 39,
    "preview": "module Suggest\n  VERSION = \"0.5.2\"\nend\n"
  },
  {
    "path": "lib/suggest.rb",
    "chars": 3572,
    "preview": "require \"suggest/version\"\nrequire \"set\"\n\nmodule Suggest\n  SUGGEST_MODS = Set.new([\n    Array,\n    BasicObject,\n    Compa"
  },
  {
    "path": "suggest.gemspec",
    "chars": 1175,
    "preview": "\nlib = File.expand_path(\"../lib\", __FILE__)\n$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)\nrequire \"suggest/ver"
  },
  {
    "path": "test/suggest_test.rb",
    "chars": 3539,
    "preview": "require \"test_helper\"\n\nclass SuggestTest < Minitest::Spec\n  describe \"#what_returns?\" do\n    it \"works for Arrays\" do\n  "
  },
  {
    "path": "test/test_helper.rb",
    "chars": 336,
    "preview": "require \"bundler/setup\"\n\n$LOAD_PATH.unshift File.expand_path(\"../../lib\", __FILE__)\nrequire \"suggest\"\n\nrequire \"minitest"
  }
]

About this extraction

This page contains the full source code of the jbodah/suggest_rb GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 12 files (11.5 KB), approximately 3.8k tokens, and a symbol index with 13 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!