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
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
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.