diff --git a/.DS_Store b/.DS_Store
new file mode 100644
index 0000000..5d77551
Binary files /dev/null and b/.DS_Store differ
diff --git a/.github/workflows/linters.yml b/.github/workflows/linters.yml
new file mode 100644
index 0000000..a193d16
--- /dev/null
+++ b/.github/workflows/linters.yml
@@ -0,0 +1,19 @@
+name: Linters
+
+on: pull_request
+
+jobs:
+ rubocop:
+ name: Rubocop
+ runs-on: ubuntu-18.04
+ steps:
+ - uses: actions/checkout@v2
+ - uses: actions/setup-ruby@v1
+ with:
+ ruby-version: 2.6.x
+ - name: Setup Rubocop
+ run: |
+ gem install --no-document rubocop:'~>0.81.0' # https://docs.rubocop.org/en/stable/installation/
+ [ -f .rubocop.yml ] || wget https://raw.githubusercontent.com/microverseinc/linters-config/master/ruby/.rubocop.yml
+ - name: Rubocop Report
+ run: rubocop --color
\ No newline at end of file
diff --git a/.gitignore b/.gitignore
index e3200e0..7324fda 100644
--- a/.gitignore
+++ b/.gitignore
@@ -54,3 +54,10 @@ build-iPhoneSimulator/
# Used by RuboCop. Remote config files pulled in from inherit_from directive.
# .rubocop-https?--*
+
+.history
+.vscode
+
+.github
+
+.idea
\ No newline at end of file
diff --git a/.idea/.gitignore b/.idea/.gitignore
new file mode 100644
index 0000000..73f69e0
--- /dev/null
+++ b/.idea/.gitignore
@@ -0,0 +1,8 @@
+# Default ignored files
+/shelf/
+/workspace.xml
+# Datasource local storage ignored files
+/dataSources/
+/dataSources.local.xml
+# Editor-based HTTP Client requests
+/httpRequests/
diff --git a/.idea/misc.xml b/.idea/misc.xml
new file mode 100644
index 0000000..da55c2a
--- /dev/null
+++ b/.idea/misc.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/modules.xml b/.idea/modules.xml
new file mode 100644
index 0000000..d310386
--- /dev/null
+++ b/.idea/modules.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/ruby-custom-enumerables.iml b/.idea/ruby-custom-enumerables.iml
new file mode 100644
index 0000000..6e7c09c
--- /dev/null
+++ b/.idea/ruby-custom-enumerables.iml
@@ -0,0 +1,15 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/vcs.xml b/.idea/vcs.xml
new file mode 100644
index 0000000..94a25f7
--- /dev/null
+++ b/.idea/vcs.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.replit b/.replit
new file mode 100644
index 0000000..4947139
--- /dev/null
+++ b/.replit
@@ -0,0 +1,2 @@
+language = "ruby"
+run = "rspec"
\ No newline at end of file
diff --git a/.rspec b/.rspec
new file mode 100644
index 0000000..775c62b
--- /dev/null
+++ b/.rspec
@@ -0,0 +1,2 @@
+--require spec_helper
+--format documentation
\ No newline at end of file
diff --git a/.rubocop.yml b/.rubocop.yml
new file mode 100644
index 0000000..f6641eb
--- /dev/null
+++ b/.rubocop.yml
@@ -0,0 +1,51 @@
+AllCops:
+ Exclude:
+ - "README.md"
+ - "Guardfile"
+ - "Rakefile"
+
+ DisplayCopNames: true
+
+Layout/LineLength:
+ Max: 120
+Metrics/MethodLength:
+ Max: 20
+Metrics/AbcSize:
+ Max: 50
+Metrics/ClassLength:
+ Max: 150
+Metrics/BlockLength:
+ ExcludedMethods: ['describe']
+ Max: 30
+
+
+Style/Documentation:
+ Enabled: false
+Style/ClassAndModuleChildren:
+ Enabled: false
+Style/EachForSimpleLoop:
+ Enabled: false
+Style/AndOr:
+ Enabled: false
+Style/DefWithParentheses:
+ Enabled: false
+Style/FrozenStringLiteralComment:
+ EnforcedStyle: never
+
+Layout/HashAlignment:
+ EnforcedColonStyle: key
+Layout/ExtraSpacing:
+ AllowForAlignment: false
+Layout/MultilineMethodCallIndentation:
+ Enabled: true
+ EnforcedStyle: indented
+Lint/RaiseException:
+ Enabled: false
+Lint/StructNewOverride:
+ Enabled: false
+Style/HashEachMethods:
+ Enabled: false
+Style/HashTransformKeys:
+ Enabled: false
+Style/HashTransformValues:
+ Enabled: false
\ No newline at end of file
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..35bbf87
--- /dev/null
+++ b/README.md
@@ -0,0 +1,77 @@
+# RUBY CUSTOM ENUMERABLES
+ [](https://repl.it/@aliabdulaziz/ruby-custom-enumerables)
+
+> The Enumerable module that gets mixed into the Array and Hash classes (among others) and provides you with lots of handy iterator methods. To prove that there's no magic to it, custom build of those methods.
+
+
+This iterators are built using ruby v2.7
+
+## Built With
+
+- Ruby
+- rubocop for linting purpose
+
+## Live Demo
+
+- [Live view on repl.it](https://repl.it/@aliabdulaziz/ruby-custom-enumerables)
+
+## Getting Started
+
+**Install on Your Own Machine.**
+**You can skip the linter setup if you don't want to validate the code against good coding standards.**
+
+Set up your machine.
+
+### Prerequisites
+
+- ruby
+- rubocop
+- rspec
+
+### Setup
+
+- Follow the link below to install git
+ > [download git](https://git-scm.com/downloads)
+- Follow the link below to install ruby
+ > [Install Ruby](https://www.theodinproject.com/courses/ruby-programming/lessons/installing-ruby-ruby-programming)
+- Follow the link below to set up rspec
+ > [Set up RSpec (testing tool) to run the tests](https://relishapp.com/rspec/docs/gettingstarted)
+- Follow the link below to set up a linter
+ > [Set up Linter (rubocop) to check code errors](https://github.com/rubocop-hq/rubocop)
+- Clone the repository
+ > Clone the repository by run the code below on your computer terminal.
+ `git clone https://github.com/abredi/ruby-custom-enumerables.git`
+
+
+
+### Usage
+
+TO run unit tests
+ > Open your terminal then go to the project directory and execute `rspec` command
+For running the program
+ > Open your terminal then go to the project directory and run the `ruby lib/main.rb` file on your terminal.
+
+## Authors
+
+👤 **Abdulaziz Ali**
+
+- Github: [@abredi](https://github.com/abredi)
+- Twitter: [@rediabdulaziz](https://twitter.com/rediabdulaziz)
+- Linkedin: [linkedin](https://www.linkedin.com/in/abdulaziz-ali-98948011a)
+
+
+
+## 🤝 Contributing
+
+Contributions, issues and feature requests are welcome!
+
+Feel free to check the [issues page](issues/).
+
+## Show your support
+
+Give a ⭐️ if you like this project!
+
+
+## 📝 License
+
+This project is [MIT](LICENSE) licensed.
diff --git a/lib/main.rb b/lib/main.rb
new file mode 100644
index 0000000..9fed2a0
--- /dev/null
+++ b/lib/main.rb
@@ -0,0 +1,191 @@
+def check_regex(flag, array, cond, neg)
+ if neg
+ array.my_each do |item|
+ return !flag unless cond.match(item.to_s)
+ end
+ else
+ array.my_each do |item|
+ return !flag if cond.match(item.to_s)
+ end
+ end
+ flag
+end
+
+def check_class(flag, array, cond, neg)
+ if neg
+ array.my_each do |item|
+ return !flag unless item.is_a?(cond)
+ end
+ else
+ array.my_each do |item|
+ return !flag if item.is_a?(cond)
+ end
+ end
+ flag
+end
+
+def check_condition(flag, array, cond, neg = false)
+ return check_regex(flag, array, cond, neg) if cond.is_a?(Regexp)
+
+ return check_class(flag, array, cond, neg) if cond.is_a?(Class)
+
+ if neg
+ array.my_each do |item|
+ return !flag unless item == cond
+ end
+ else
+ array.my_each do |item|
+ return !flag if item == cond
+ end
+ end
+
+ flag
+end
+
+def check_block(flag, array, neg = false)
+ if neg
+ array.my_each do |item|
+ return !flag unless item
+ end
+ else
+ array.my_each do |item|
+ return !flag if item
+ end
+ end
+ flag
+end
+
+def check_symbol(acc, init, symbol, array)
+ init = symbol || init
+ array.my_each do |item|
+ acc = acc.send(init, item)
+ end
+ acc
+end
+
+module Enumerable
+ def my_each
+ return enum_for unless block_given?
+
+ array = is_a?(Range) ? to_a : self
+
+ array.map { |item| yield(item) }
+ self
+ end
+
+ def my_each_with_index
+ return enum_for unless block_given?
+
+ array = is_a?(Range) ? to_a : self
+ index = -1
+ array.my_each { |item| yield(item, index += 1) }
+ self
+ end
+
+ def my_select
+ return enum_for unless block_given?
+
+ array = is_a?(Range) ? to_a : self
+ filtered = []
+ array.my_each { |item| filtered.push(item) if yield(item) }
+ filtered
+ end
+
+ def my_all?(cond = nil)
+ array = is_a?(Range) ? to_a : self
+ flag = true
+ return check_condition(flag, array, cond, true) if cond
+
+ return check_block(flag, array, true) unless block_given?
+
+ array.my_each do |item|
+ return false unless yield(item)
+ end
+ flag
+ end
+
+ def my_any?(cond = nil)
+ array = is_a?(Range) ? to_a : self
+ flag = false
+
+ return check_condition(flag, array, cond) if cond
+
+ return check_block(flag, array) unless block_given?
+
+ array.my_each do |item|
+ return true if yield(item)
+ end
+ flag
+ end
+
+ def my_none?(cond = nil)
+ array = is_a?(Range) ? to_a : self
+ flag = true
+
+ return check_condition(flag, array, cond) if cond
+
+ return check_block(flag, array) unless block_given?
+
+ array.my_each do |item|
+ return false if yield(item)
+ end
+ flag
+ end
+
+ def my_count(arg = nil)
+ array = is_a?(Range) ? to_a : self
+
+ if arg
+ return array.my_select { |val| val if val == arg }.length
+ end
+
+ count = array.length
+
+ return count unless block_given?
+
+ array.my_each do |item|
+ count -= 1 unless yield(item)
+ end
+ count
+ end
+
+ def my_map(prok = nil)
+ return enum_for unless block_given?
+
+ array = is_a?(Range) ? to_a : self
+
+ filtered = []
+ if prok
+ array.my_each { |item| filtered.push(prok.call(item)) }
+ else
+ array.my_each { |item| filtered.push(yield(item)) }
+ end
+ filtered
+ end
+
+ def my_inject(init = nil, symbol = nil)
+ ary = is_a?(Range) ? to_a : self
+
+ acc = init
+
+ if !init || init.is_a?(Symbol)
+ acc = ary[0]
+ array = ary.slice(1, ary.length - 1)
+ else
+ array = ary
+ end
+
+ return check_symbol(acc, init, symbol, array) if init.is_a?(Symbol) || symbol.is_a?(Symbol)
+
+ array.my_each do |item|
+ acc = yield(acc, item)
+ end
+ acc
+ end
+end
+
+def multiply_els(ary)
+ ary.my_inject do |acc, n|
+ acc * n
+ end
+end
diff --git a/spec/main_spec.rb b/spec/main_spec.rb
new file mode 100644
index 0000000..2c74d1f
--- /dev/null
+++ b/spec/main_spec.rb
@@ -0,0 +1,244 @@
+require 'rspec'
+require_relative '../lib/main'
+
+RSpec.describe Enumerable do
+ let(:fruits_ary) { %w[apple banana strawberry pineapple] }
+ let(:raw_range) { (1..5) }
+ let(:str_ary) { %w[Hayat Sky-Light Hayat] }
+ let(:number_ary) { [5, 6, 7, 8, 9, 10] }
+
+ context '#my_each' do
+ it 'should return array equal to the original' do
+ expect(fruits_ary.my_each(&:upcase)).to eql(fruits_ary)
+ end
+
+ it 'should return range equal to the original' do
+ expect(raw_range.my_each { |item| item }).to eql(raw_range)
+ end
+
+ it 'should be execute the given block code' do
+ cap = ''
+ str_ary.my_each do |item|
+ cap = item.upcase
+ end
+ expect(cap).to eql(str_ary.last.upcase)
+ end
+
+ it 'should work with range' do
+ last = 0
+ raw_range.my_each do |item|
+ last = item
+ end
+ expect(last).to eql(raw_range.last)
+ end
+ end
+
+ context '#my_each_with_index' do
+ it 'should return array equal to the original' do
+ expect(fruits_ary.my_each_with_index { |value| value }).to eql(fruits_ary)
+ end
+
+ it 'should return range equal to the original' do
+ expect(raw_range.my_each_with_index { |value| value }).to eql(raw_range)
+ end
+
+ it 'should be excute the given block code' do
+ cap = ''
+ fruits_ary.my_each_with_index do |item|
+ cap = item.upcase
+ end
+ expect(cap).to eql(fruits_ary.last.upcase)
+ end
+
+ it 'should work with range' do
+ last = 0
+ fruits_ary.my_each_with_index do |item, key|
+ last = key
+ item
+ end
+ expect(last).to eql(fruits_ary.length - 1)
+ end
+ end
+
+ context '#my_select' do
+ it 'should return filtered array based on the given block' do
+ expect(fruits_ary.my_select { |value| value == 'banana' }).to eql(['banana'])
+ end
+
+ it 'should work with range' do
+ expect(raw_range.my_select { |value| value if value == 1 }).to eql([1])
+ end
+
+ it 'should return Enumerable if the block is missing' do
+ expect(fruits_ary.my_select.is_a?(Enumerable)).to eql(true)
+ end
+ end
+
+ context '#my_all?' do
+ it 'should return false based on the given block' do
+ expect(fruits_ary.my_all? { |value| value == 'banana' }).to eql(false)
+ end
+
+ it 'should work with range' do
+ expect(raw_range.my_all? { |val| val.is_a?(Numeric) }).to eql(true)
+ end
+
+ it 'should return true if all of the collection match the given paramenter' do
+ expect([3, 3, 3, 3].my_all?(3)).to eql(true)
+ end
+
+ it 'should return true if all of the collection is a member of such class::Integer' do
+ expect(number_ary.my_all? { |val| val.is_a?(Integer) }).to eql(true)
+ end
+
+ it 'should return true if all of the collection is a member of such class::Integer #in shorter form' do
+ expect(number_ary.my_all?(Integer)).to eql(true)
+ end
+
+ it 'should work with a class as an paramenter; return true false all items in the array are not a member String' do
+ expect(fruits_ary.my_all? { |val| val.is_a?(String) }).to eql(true)
+ end
+
+ it 'should return false if the given array has a falsy data' do
+ expect([nil, true, 99].my_all?).to eql(false)
+ end
+
+ it 'should return false if the given block return false; even once' do
+ expect([99, 100, 99].my_all? { |value| value == 99 }).to eql(false)
+ end
+
+ it 'should return true no falsy data is provided' do
+ expect([1, true, 'hi', []].my_all?).to eql(true)
+ end
+ end
+
+ context '#my_any?' do
+ it 'should return true based on the given block if it returns true' do
+ expect(fruits_ary.my_any? { |value| value == 'banana' }).to eql(true)
+ end
+
+ it 'should work with range' do
+ expect(raw_range.my_any? { |val| val.is_a?(String) }).to eql(false)
+ end
+
+ it 'should work with regex and return false if the given paramenter not match the collection' do
+ expect(fruits_ary.my_any?(/z/)).to eql(false)
+ end
+
+ it 'should work with regex and return true if the given paramenter match the collection' do
+ expect(fruits_ary.my_any?(/apple/)).to eql(true)
+ end
+
+ it 'should return true if the given array has one truthy data' do
+ expect([nil, true, 99].my_any?).to eql(true)
+ end
+
+ it 'should return true if the given type parameter match one of the element' do
+ ary = fruits_ary + [2]
+ expect(ary.my_any?(Numeric)).to eql(true)
+ end
+
+ it 'should return true if the given parameter match one of the element' do
+ expect(fruits_ary.my_any?('banana')).to eql(true)
+ end
+
+ it 'should return fasle no truthy data is provided' do
+ expect([].my_any?).to eql(false)
+ end
+ end
+
+ context '#my_none?' do
+ it 'should return false based on the given block ' do
+ expect(fruits_ary.my_none? { |value| value == 'banana' }).to eql(false)
+ end
+
+ it 'should work with range' do
+ expect(raw_range.my_none? { |val| val.is_a?(String) }).to eql(true)
+ end
+
+ it 'should return true if the given array has no truthy data' do
+ expect([nil, false].my_none?).to eql(true)
+ end
+
+ it 'should return true no truthy data is provided' do
+ expect([].my_none?).to eql(true)
+ end
+ end
+
+ context '#my_count' do
+ it 'should return the length of the array if no block given' do
+ expect(fruits_ary.my_count).to eql(fruits_ary.length)
+ end
+
+ it 'should work with range' do
+ expect(raw_range.my_count).to eql(5)
+ end
+
+ it 'should return true if the given array has no truthy data' do
+ expect(fruits_ary.my_count(100)).to eql(fruits_ary.count(100))
+ end
+
+ it 'should return true no truthy data is provided' do
+ expect(fruits_ary.my_count { |item| item.eql?('banana') }).to eql(1)
+ end
+ end
+
+ context '#my_map' do
+ it 'should return the filtered of the array based on the given block' do
+ expect(fruits_ary.my_map { |item| item.length * 100 }).to eql(fruits_ary.map { |item| item.length * 100 })
+ end
+
+ it 'should work with range' do
+ expect(raw_range.my_map { |i| i * i }).to eql([1, 4, 9, 16, 25])
+ end
+
+ it 'should return Enumerable if the block is missing' do
+ expect(fruits_ary.my_map.is_a?(Enumerable)).to eql(true)
+ end
+ end
+
+ context '#my_inject' do
+ it 'should return the accumulated value' do
+ expect(number_ary.my_inject { |acc, n| acc + n }).to eql(45)
+ end
+
+ it 'should work with range' do
+ expect((1..4).inject { |product, n| product * n }).to eql(24)
+ end
+
+ it 'should return the accumulated value based the given parameter' do
+ expect((5..10).my_inject(100) { |acc, n| acc + n }).to eql(145)
+ end
+
+ it 'should return the accumulated value based the given wild card' do
+ expect((1..4).my_inject(:+) { |acc, n| acc + n }).to eql(10)
+ end
+
+ it 'should return the accumulated value based the given wild card and initial value' do
+ expect((1..4).my_inject(10, :*) { |acc, n| acc + n }).to eql(240)
+ end
+
+ it 'should return the longest word' do
+ expect(str_ary.inject { |memo, word| memo.length > word.length ? memo : word }).to eql('Sky-Light')
+ end
+
+ it 'should return the longest wor' do
+ result = str_ary.my_inject(Hash.new(0)) do |res, vote|
+ res[vote] = res[vote] + 1
+ res
+ end
+ expect(result['Hayat']).to eql(2)
+ end
+
+ it 'should not mutate the original value' do
+ number_ary.my_inject { |product, n| product * n }
+ expect(number_ary).to eql([5, 6, 7, 8, 9, 10])
+ end
+ end
+
+ context '#multiply_els' do
+ it 'should return the accumulated value' do
+ expect(multiply_els(number_ary)).to eql(151_200)
+ end
+ end
+end
diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb
new file mode 100644
index 0000000..0cb0392
--- /dev/null
+++ b/spec/spec_helper.rb
@@ -0,0 +1,46 @@
+# This file was generated by the `rspec --init` command. Conventionally, all
+# specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`.
+# The generated `.rspec` file contains `--require spec_helper` which will cause
+# this file to always be loaded, without a need to explicitly require it in any
+# files.
+#
+# Given that it is always loaded, you are encouraged to keep this file as
+# light-weight as possible. Requiring heavyweight dependencies from this file
+# will add to the boot time of your test suite on EVERY test run, even for an
+# individual file that may not need all of that loaded. Instead, consider making
+# a separate helper file that requires the additional dependencies and performs
+# the additional setup, and require it from the spec files that actually need
+# it.
+#
+# See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
+RSpec.configure do |config|
+ # rspec-expectations config goes here. You can use an alternate
+ # assertion/expectation library such as wrong or the stdlib/minitest
+ # assertions if you prefer.
+ config.expect_with :rspec do |expectations|
+ # This option will default to `true` in RSpec 4. It makes the `description`
+ # and `failure_message` of custom matchers include text for helper methods
+ # defined using `chain`, e.g.:
+ # be_bigger_than(2).and_smaller_than(4).description
+ # # => "be bigger than 2 and smaller than 4"
+ # ...rather than:
+ # # => "be bigger than 2"
+ expectations.include_chain_clauses_in_custom_matcher_descriptions = true
+ end
+
+ # rspec-mocks config goes here. You can use an alternate test double
+ # library (such as bogus or mocha) by changing the `mock_with` option here.
+ config.mock_with :rspec do |mocks|
+ # Prevents you from mocking or stubbing a method that does not exist on
+ # a real object. This is generally recommended, and will default to
+ # `true` in RSpec 4.
+ mocks.verify_partial_doubles = true
+ end
+
+ # This option will default to `:apply_to_host_groups` in RSpec 4 (and will
+ # have no way to turn it off -- the option exists only for backwards
+ # compatibility in RSpec 3). It causes shared context metadata to be
+ # inherited by the metadata hash of host groups and examples, rather than
+ # triggering implicit auto-inclusion in groups with matching metadata.
+ config.shared_context_metadata_behavior = :apply_to_host_groups
+end