├── .gitignore
├── Gemfile
├── lib
├── multi_block
│ ├── array.rb
│ ├── core_ext.rb
│ ├── version.rb
│ └── implementation.rb
└── multi_block.rb
├── CHANGELOG.md
├── Rakefile
├── MIT-LICENSE.txt
├── .github
└── workflows
│ └── test.yml
├── multi_block.gemspec
├── spec
└── multi_block_spec.rb
├── README.md
└── CODE_OF_CONDUCT.md
/.gitignore:
--------------------------------------------------------------------------------
1 | Gemfile.lock
2 |
--------------------------------------------------------------------------------
/Gemfile:
--------------------------------------------------------------------------------
1 | source 'https://rubygems.org'
2 |
3 | gemspec
4 |
--------------------------------------------------------------------------------
/lib/multi_block/array.rb:
--------------------------------------------------------------------------------
1 | class Array
2 | include MultiBlock::Array
3 | end
4 |
--------------------------------------------------------------------------------
/lib/multi_block/core_ext.rb:
--------------------------------------------------------------------------------
1 | class Object
2 | include MultiBlock::Object
3 | end
4 |
--------------------------------------------------------------------------------
/lib/multi_block/version.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module MultiBlock
4 | VERSION = "1.2"
5 | end
6 |
--------------------------------------------------------------------------------
/lib/multi_block.rb:
--------------------------------------------------------------------------------
1 | require 'named_proc'
2 |
3 | require_relative 'multi_block/version'
4 | require_relative 'multi_block/implementation'
5 | require_relative 'multi_block/core_ext'
6 |
7 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Change Log
2 |
3 | ## 1.2
4 |
5 | * Update project setup to 2021
6 | * Re-release
7 |
8 | ## 1.1
9 |
10 | * Updates for 2015 Rubies, drop support for Ruby 1
11 | * Simplify activating Array extension via: require 'multi_block/array'
12 |
13 | ## 1.0
14 |
15 | * Initial Release
16 |
--------------------------------------------------------------------------------
/Rakefile:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | # # #
4 | # Get gemspec info
5 |
6 | gemspec_file = Dir['*.gemspec'].first
7 | gemspec = eval File.read(gemspec_file), binding, gemspec_file
8 | info = "#{gemspec.name} | #{gemspec.version} | " \
9 | "#{gemspec.runtime_dependencies.size} dependencies | " \
10 | "#{gemspec.files.size} files"
11 |
12 | # # #
13 | # Gem build and install task
14 |
15 | desc info
16 | task :gem do
17 | puts info + "\n\n"
18 | print " "; sh "gem build #{gemspec_file}"
19 | FileUtils.mkdir_p 'pkg'
20 | FileUtils.mv "#{gemspec.name}-#{gemspec.version}.gem", 'pkg'
21 | puts; sh %{gem install --no-document pkg/#{gemspec.name}-#{gemspec.version}.gem}
22 | end
23 |
24 | # # #
25 | # Start an IRB session with the gem loaded
26 |
27 | desc "#{gemspec.name} | IRB"
28 | task :irb do
29 | sh "irb -I ./lib -r #{gemspec.name.tr '-', '/'}"
30 | end
31 |
32 | # # #
33 | # Run Specs
34 |
35 | require 'rspec/core/rake_task'
36 | RSpec::Core::RakeTask.new(:spec)
37 |
38 | task :default => :spec
39 |
--------------------------------------------------------------------------------
/MIT-LICENSE.txt:
--------------------------------------------------------------------------------
1 | The MIT LICENSE
2 |
3 | Copyright (c) 2011, 2015 Jan Lelis
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining
6 | a copy of this software and associated documentation files (the
7 | "Software"), to deal in the Software without restriction, including
8 | without limitation the rights to use, copy, modify, merge, publish,
9 | distribute, sublicense, and/or sell copies of the Software, and to
10 | permit persons to whom the Software is furnished to do so, subject to
11 | the following conditions:
12 |
13 | The above copyright notice and this permission notice shall be
14 | included in all copies or substantial portions of the Software.
15 |
16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
23 |
--------------------------------------------------------------------------------
/.github/workflows/test.yml:
--------------------------------------------------------------------------------
1 | name: Test
2 |
3 | on: [push, pull_request]
4 |
5 | jobs:
6 | test:
7 | name: Ruby ${{ matrix.ruby }} (${{ matrix.os }})
8 | if: "!contains(github.event.head_commit.message, '[skip ci]')"
9 | strategy:
10 | matrix:
11 | ruby:
12 | - 3.1
13 | - 3.0
14 | - 2.7
15 | - 2.6
16 | os:
17 | - ubuntu-latest
18 | - macos-latest
19 | runs-on: ${{matrix.os}}
20 | steps:
21 | - uses: actions/checkout@v2
22 | - name: Set up Ruby
23 | uses: ruby/setup-ruby@v1
24 | with:
25 | ruby-version: ${{matrix.ruby}}
26 | bundler-cache: true
27 | - name: Run tests
28 | run: bundle exec rake
29 |
30 | test-windows:
31 | name: Ruby ${{ matrix.ruby }} (windows-latest)
32 | if: "!contains(github.event.head_commit.message, '[skip ci]')"
33 | strategy:
34 | matrix:
35 | ruby:
36 | - 3.0
37 | - 2.7
38 | - 2.6
39 | runs-on: windows-latest
40 | steps:
41 | - uses: actions/checkout@v2
42 | - name: Set up Ruby
43 | uses: ruby/setup-ruby@v1
44 | with:
45 | ruby-version: ${{matrix.ruby}}
46 | bundler-cache: true
47 |
--------------------------------------------------------------------------------
/multi_block.gemspec:
--------------------------------------------------------------------------------
1 | # -*- encoding: utf-8 -*-
2 |
3 | require File.expand_path('../lib/multi_block/version', __FILE__)
4 |
5 | Gem::Specification.new do |s|
6 | s.name = "multi_block"
7 | s.version = MultiBlock::VERSION
8 | s.authors = ["Jan Lelis"]
9 | s.email = "hi@ruby.consulting"
10 | s.homepage = "https://github.com/janlelis/multi_block"
11 | s.summary = 'MultiBlock is a mini framework for passing multiple blocks to methods.'
12 | s.description = 'MultiBlock is a mini framework for passing multiple blocks to methods. It uses "named procs" to accomplish this in a nice way. The receiving method can either yield all blocks, or just call specific ones, identified by order or name. '
13 | s.required_ruby_version = '>= 1.9.3'
14 | s.files = Dir.glob %w{multi_block.gemspec lib/multi_block.rb lib/multi_block/version.rb lib/multi_block/implementation.rb lib/multi_block/core_ext.rb lib/multi_block/array.rb spec/multi_block_spec.rb}
15 | s.extra_rdoc_files = ["README.md", "MIT-LICENSE.txt", "CHANGELOG.md", "CODE_OF_CONDUCT.md"]
16 | s.license = 'MIT'
17 | s.add_dependency 'named_proc', '~> 1.2'
18 | s.add_development_dependency 'rspec', '~> 3.2'
19 | s.add_development_dependency 'rake', '~> 13.0'
20 | end
21 |
--------------------------------------------------------------------------------
/lib/multi_block/implementation.rb:
--------------------------------------------------------------------------------
1 | module MultiBlock
2 | # Multiple block transformation method
3 | def self.[](*proc_array)
4 | # Create hash representation, proc_array will still get used sometimes
5 | proc_hash = {}
6 | proc_array.each{ |proc|
7 | proc_hash[proc.name] = proc if proc.respond_to?(:name)
8 | }
9 |
10 | # Build yielder proc
11 | Proc.new{ |*proc_names_and_args|
12 | if proc_names_and_args.empty? # call all procs
13 | ret = proc_array.map(&:call)
14 |
15 | proc_array.size == 1 ? ret.first : ret
16 | else
17 | proc_names, *proc_args = *proc_names_and_args
18 |
19 | if proc_names.is_a? Hash # keys: proc_names, values: args
20 | proc_names.map{ |proc_name, proc_args|
21 | proc = proc_name.is_a?(Integer) ? proc_array[proc_name] : proc_hash[proc_name.to_sym]
22 | proc or raise LocalJumpError, "wrong block name given (#{proc_name})"
23 |
24 | [proc, Array(proc_args)]
25 | }.map{ |proc, proc_args|
26 | proc.call(*proc_args)
27 | }
28 |
29 | else
30 | ret = Array(proc_names).map{ |proc_name|
31 | proc = proc_name.is_a?(Integer) ? proc_array[proc_name] : proc_hash[proc_name.to_sym]
32 | proc or raise LocalJumpError, "wrong block name given (#{proc_name})"
33 |
34 | [proc, Array(proc_args)]
35 | }.map{ |proc, proc_args|
36 | proc.call(*proc_args)
37 | }
38 |
39 | ret.size == 1 ? ret.first : ret
40 |
41 | end
42 | end
43 | }
44 | end
45 |
46 | # Low level mixins
47 | module Object
48 | private
49 |
50 | def blocks
51 | MultiBlock
52 | end
53 | end
54 |
55 | # Optional Array mixin
56 | module Array
57 | def to_proc
58 | ::MultiBlock[*self]
59 | end
60 | end
61 | end
62 |
--------------------------------------------------------------------------------
/spec/multi_block_spec.rb:
--------------------------------------------------------------------------------
1 | # encoding: utf-8
2 | require_relative '../lib/multi_block'
3 |
4 | describe "Object#blocks" do
5 | it "returns the MutliBlock constant (for calling [] on it)" do
6 | expect( blocks ).to be MultiBlock
7 | end
8 | end
9 |
10 | describe "MultiBlock#[]" do
11 | describe "yield without arguments" do
12 | it "calls every block and returns array of results" do
13 | def null
14 | yield
15 | end
16 |
17 | expect( null(&blocks[
18 | proc{5},
19 | proc{6},
20 | ]) ).to eq [5,6]
21 | end
22 | end
23 |
24 | describe "yield with symbol" do
25 | it "calls the specified proc, other args get passed" do
26 | def symbol
27 | yield :success, "Code Brawl!"
28 | end
29 |
30 | expect( symbol(&blocks[
31 | proc{5},
32 | proc.success{|e| e.swapcase},
33 | proc.error{6},
34 | ]) ).to eq "cODE bRAWL!"
35 | end
36 |
37 | it 'will raise LocalJumpError if proc name is wrong' do
38 | def wrong_name
39 | yield :wrong, "Code Brawl!"
40 | end
41 |
42 | expect{
43 | wrong_name(&blocks[
44 | proc{5},
45 | proc.success{|e| e.swapcase},
46 | proc.error{6},
47 | ])
48 | }.to raise_exception(LocalJumpError)
49 | end
50 | end
51 |
52 | describe "yield with integer" do
53 | it "calls the n-th proc, other args get passed" do
54 | def integer
55 | yield 2
56 | end
57 |
58 | expect( integer(&blocks[
59 | proc{5},
60 | proc.success{|e| e.swapcase},
61 | proc.error{6},
62 | ]) ).to eq 6
63 | end
64 | end
65 |
66 | describe "yield with array" do
67 | it "calls all procs, indentified by symbol or integer, other args get passed" do
68 | def array
69 | yield [:success, :error], "Code Brawl!"
70 | end
71 |
72 | expect( array(&blocks[
73 | proc{5},
74 | proc.success{|e| e.swapcase},
75 | proc.error{|e| e.downcase},
76 | ]) ).to eq ["cODE bRAWL!", "code brawl!"]
77 | end
78 | end
79 |
80 | describe "yield with hash" do
81 | it "takes keys as proc names and passes values as proc args" do
82 | def hash
83 | yield success: "Code Brawl!", error: [500, "Internal Brawl Error"]
84 | end
85 |
86 | expect( hash(&blocks[
87 | proc{5},
88 | proc.success{|e| e.swapcase},
89 | proc.error{|no, msg| "Error #{no}: #{msg}"},
90 | ]).sort ).to eq ["Error 500: Internal Brawl Error", "cODE bRAWL!"]
91 | end
92 | end
93 | end
94 |
95 | describe "Array#to_proc" do
96 | it "works without blocks helper" do
97 | def another_method
98 | yield
99 | end
100 |
101 | proc_array = [
102 | proc{5},
103 | proc{6},
104 | ]
105 | proc_array.send :extend, MultiBlock::Array
106 |
107 | expect( another_method(&proc_array) ).to eq [5,6]
108 | end
109 | end
110 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # MultiBlock [
](https://badge.fury.io/rb/multi_block) [
](https://github.com/janlelis/multi_block/actions?query=workflow%3ATest)
2 |
3 | MultiBlock is a mini framework for passing multiple blocks to methods. It uses [named procs](https://github.com/janlelis/named_proc) to accomplish this with a simple syntax. The receiving method can either yield all blocks, or just call specific ones, identified by order or name.
4 |
5 | Currently supports CRuby only.
6 |
7 | ## Setup
8 |
9 | Add to Gemfile:
10 |
11 | gem 'multi_block'
12 |
13 |
14 | ## Usage
15 | ### Defining methods that use multiple blocks
16 |
17 | The first argument given to yield always defines the desired block(s). The other arguments get directly passed to the block(s):
18 |
19 | yield # calls all given procs without args
20 | yield :success # calls :success proc without args
21 | yield :success, "Code Brawl!" # calls :success proc with message
22 | yield 1 # calls first proc (:success in this case)
23 | yield [:success, :bonus] # calls :success and :bonus without args
24 | yield [:success, :bonus], "Code Brawl!" # calls both procs with same arg
25 | yield success: "Code Brawl!", # calls each keyed proc,
26 | error: [500, "Internal Brawl Error"] # values are the args
27 |
28 |
29 | ### Calling methods with multiple blocks
30 |
31 | Consider these two example methods:
32 |
33 | # calls the :success block if everything worked well
34 | def ajax
35 | yield rand(6) != 0 ? :success : :error
36 | end
37 |
38 | # calls the n-th block
39 | def dice
40 | random_number = rand(6)
41 | yield random_number, random_number + 1
42 | end
43 |
44 |
45 | Multiple blocks can be passed using `blocks`:
46 |
47 | ajax &blocks[
48 | proc.success{ puts "Yeah!" },
49 | proc.error { puts "Error..." },
50 | ]
51 |
52 |
53 | The dice method could, for example, be called in this way:
54 |
55 | dice &blocks[
56 | proc{ ":(" },
57 | proc{ ":/" },
58 | proc{ ":O" },
59 | proc{ ":b" },
60 | proc{ ":P" },
61 | proc{ rand(42) != 0 ? ":)" : ":D"},
62 | ]
63 |
64 |
65 | ## Bonus sugar: Array extension
66 |
67 | If you like the slim `&to_proc` operator, you can further optimize the syntax by activating the core extension for array:
68 |
69 | require 'multi_block/array'
70 |
71 | Now you do not need the `blocks` helper anymore. Instead just do:
72 |
73 | do_something, some_argument, &[
74 | proc.easy_way{
75 | # do it the easy way
76 | },
77 | proc.complex_way{
78 | # use complex heuristics, etc.
79 | },
80 | ]
81 |
82 |
83 | ## MIT License
84 |
85 | See the original gist: https://gist.github.com/4b2f5fd0b45118e46d0f
86 |
--------------------------------------------------------------------------------
/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------
1 | # Contributor Covenant Code of Conduct
2 |
3 | ## Our Pledge
4 |
5 | In the interest of fostering an open and welcoming environment, we as
6 | contributors and maintainers pledge to making participation in our project and
7 | our community a harassment-free experience for everyone, regardless of age, body
8 | size, disability, ethnicity, gender identity and expression, level of experience,
9 | nationality, personal appearance, race, religion, or sexual identity and
10 | orientation.
11 |
12 | ## Our Standards
13 |
14 | Examples of behavior that contributes to creating a positive environment
15 | include:
16 |
17 | * Using welcoming and inclusive language
18 | * Being respectful of differing viewpoints and experiences
19 | * Gracefully accepting constructive criticism
20 | * Focusing on what is best for the community
21 | * Showing empathy towards other community members
22 |
23 | Examples of unacceptable behavior by participants include:
24 |
25 | * The use of sexualized language or imagery and unwelcome sexual attention or
26 | advances
27 | * Trolling, insulting/derogatory comments, and personal or political attacks
28 | * Public or private harassment
29 | * Publishing others' private information, such as a physical or electronic
30 | address, without explicit permission
31 | * Other conduct which could reasonably be considered inappropriate in a
32 | professional setting
33 |
34 | ## Our Responsibilities
35 |
36 | Project maintainers are responsible for clarifying the standards of acceptable
37 | behavior and are expected to take appropriate and fair corrective action in
38 | response to any instances of unacceptable behavior.
39 |
40 | Project maintainers have the right and responsibility to remove, edit, or
41 | reject comments, commits, code, wiki edits, issues, and other contributions
42 | that are not aligned to this Code of Conduct, or to ban temporarily or
43 | permanently any contributor for other behaviors that they deem inappropriate,
44 | threatening, offensive, or harmful.
45 |
46 | ## Scope
47 |
48 | This Code of Conduct applies both within project spaces and in public spaces
49 | when an individual is representing the project or its community. Examples of
50 | representing a project or community include using an official project e-mail
51 | address, posting via an official social media account, or acting as an appointed
52 | representative at an online or offline event. Representation of a project may be
53 | further defined and clarified by project maintainers.
54 |
55 | ## Enforcement
56 |
57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be
58 | reported by contacting the project team at conduct@janlelis.com. All
59 | complaints will be reviewed and investigated and will result in a response that
60 | is deemed necessary and appropriate to the circumstances. The project team is
61 | obligated to maintain confidentiality with regard to the reporter of an incident.
62 | Further details of specific enforcement policies may be posted separately.
63 |
64 | Project maintainers who do not follow or enforce the Code of Conduct in good
65 | faith may face temporary or permanent repercussions as determined by other
66 | members of the project's leadership.
67 |
68 | ## Attribution
69 |
70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
71 | available at [http://contributor-covenant.org/version/1/4][version]
72 |
73 | [homepage]: https://contributor-covenant.org
74 | [version]: https://contributor-covenant.org/version/1/4/
75 |
--------------------------------------------------------------------------------