├── .rspec ├── .travis.yml ├── Gemfile ├── Gemfile.lock ├── Guardfile ├── LICENSE ├── README.md ├── Rakefile ├── lib ├── powercore.rb └── powercore │ ├── array.rb │ ├── date.rb │ ├── hash.rb │ ├── integer.rb │ ├── kernel.rb │ ├── object.rb │ ├── range.rb │ └── string.rb └── spec ├── lib └── powercore │ ├── array_spec.rb │ ├── date_spec.rb │ ├── hash_spec.rb │ ├── integer_spec.rb │ ├── kernel_spec.rb │ ├── object_spec.rb │ ├── range_spec.rb │ └── string_spec.rb └── spec_helper.rb /.rspec: -------------------------------------------------------------------------------- 1 | --color 2 | --require spec_helper 3 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: ruby 2 | 3 | rvm: 4 | - 2.6.3 5 | 6 | before_install: 7 | - gem update --system 8 | - gem install bundler 9 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source "https://rubygems.org" 2 | 3 | group :development, :test do 4 | gem "guard-rspec" 5 | gem "pry" 6 | gem "rake" 7 | gem "rspec" 8 | end 9 | -------------------------------------------------------------------------------- /Gemfile.lock: -------------------------------------------------------------------------------- 1 | GEM 2 | remote: https://rubygems.org/ 3 | specs: 4 | coderay (1.1.2) 5 | diff-lcs (1.3) 6 | ffi (1.11.1) 7 | formatador (0.2.5) 8 | guard (2.15.0) 9 | formatador (>= 0.2.4) 10 | listen (>= 2.7, < 4.0) 11 | lumberjack (>= 1.0.12, < 2.0) 12 | nenv (~> 0.1) 13 | notiffany (~> 0.0) 14 | pry (>= 0.9.12) 15 | shellany (~> 0.0) 16 | thor (>= 0.18.1) 17 | guard-compat (1.2.1) 18 | guard-rspec (4.7.3) 19 | guard (~> 2.1) 20 | guard-compat (~> 1.1) 21 | rspec (>= 2.99.0, < 4.0) 22 | listen (3.1.5) 23 | rb-fsevent (~> 0.9, >= 0.9.4) 24 | rb-inotify (~> 0.9, >= 0.9.7) 25 | ruby_dep (~> 1.2) 26 | lumberjack (1.0.13) 27 | method_source (0.9.2) 28 | nenv (0.3.0) 29 | notiffany (0.1.1) 30 | nenv (~> 0.1) 31 | shellany (~> 0.0) 32 | pry (0.12.2) 33 | coderay (~> 1.1.0) 34 | method_source (~> 0.9.0) 35 | rake (12.3.2) 36 | rb-fsevent (0.10.3) 37 | rb-inotify (0.10.0) 38 | ffi (~> 1.0) 39 | rspec (3.8.0) 40 | rspec-core (~> 3.8.0) 41 | rspec-expectations (~> 3.8.0) 42 | rspec-mocks (~> 3.8.0) 43 | rspec-core (3.8.2) 44 | rspec-support (~> 3.8.0) 45 | rspec-expectations (3.8.4) 46 | diff-lcs (>= 1.2.0, < 2.0) 47 | rspec-support (~> 3.8.0) 48 | rspec-mocks (3.8.1) 49 | diff-lcs (>= 1.2.0, < 2.0) 50 | rspec-support (~> 3.8.0) 51 | rspec-support (3.8.2) 52 | ruby_dep (1.5.0) 53 | shellany (0.0.1) 54 | thor (0.20.3) 55 | 56 | PLATFORMS 57 | ruby 58 | 59 | DEPENDENCIES 60 | guard-rspec 61 | pry 62 | rake 63 | rspec 64 | 65 | BUNDLED WITH 66 | 2.0.2 67 | -------------------------------------------------------------------------------- /Guardfile: -------------------------------------------------------------------------------- 1 | guard :rspec, cmd: "bundle exec rspec" do 2 | require "guard/rspec/dsl" 3 | dsl = Guard::RSpec::Dsl.new(self) 4 | 5 | rspec = dsl.rspec 6 | watch(rspec.spec_helper) { rspec.spec_dir } 7 | watch(rspec.spec_support) { rspec.spec_dir } 8 | watch(rspec.spec_files) 9 | 10 | ruby = dsl.ruby 11 | dsl.watch_spec_files_for(ruby.lib_files) 12 | end 13 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) Arturo Herrero, http://arturoherrero.com/ 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # PowerCore 2 | 3 | [![Code Climate](https://codeclimate.com/github/arturoherrero/powercore/badges/gpa.svg)](https://codeclimate.com/github/arturoherrero/powercore) 4 | [![Build Status](https://travis-ci.org/arturoherrero/powercore.svg?branch=master)](https://travis-ci.org/arturoherrero/powercore) 5 | 6 | PowerCore extends the Ruby Core with useful extensions. 7 | 8 | There are Ruby gems that do something similar, such as 9 | [ActiveSuppor Core Extensions][1] or [Powerpack][2]. 10 | 11 | In this case, this is just a collection of extensions for reference, *not* a 12 | Ruby gem. Who wants a new dependency in the code base? Just borrow the code 13 | that you consider useful, but be careful; most of the time I have created new 14 | methods but sometimes I have overridden the default Ruby implementation, or even 15 | worse, I have removed Ruby methods to do some tricks. Come and see! 16 | 17 | - [Array](#array) 18 | - [`average`](#average) 19 | - [Negative index `drop`](#negative-index-drop) 20 | - [`except`](#except) 21 | - [`fetch_dig`](#fetch_dig) 22 | - [`head`](#head) 23 | - [`histogram`](#histogram) 24 | - [`init`](#init) 25 | - [`mean`](#mean) 26 | - [`median`](#median) 27 | - [`mode`](#mode) 28 | - [`percentile`](#percentile) 29 | - [Negative index `take`](#negative-index-take) 30 | - [`tail`](#tail) 31 | - [`transpose` array of ranges](#transpose-array-of-ranges) 32 | - [Date](#date) 33 | - [`now`](#now) 34 | - [Integer](#integer) 35 | - [`clamp`](#clamp) 36 | - [`degrees`](#degrees) 37 | - [`negative`](#negative) 38 | - [`ordinal`](#ordinal) 39 | - [Hash](#hash) 40 | - [`except`](#except-1) 41 | - [`fetch_dig`](#fetch_dig-1) 42 | - [`first`](#first) 43 | - [`head`](#head-1) 44 | - [`init`](#init-1) 45 | - [`last`](#last) 46 | - [`tail`](#tail-1) 47 | - [Kernel](#kernel) 48 | - [`λ`](#λ) 49 | - [Object](#object) 50 | - [`assert`](#assert) 51 | - [`in?`](#in) 52 | - [`metaclass`](#metaclass) 53 | - [`not_in?`](#not_in) 54 | - [`not_nil?`](#not_nil) 55 | - [Pipe operator](#pipe-operator) 56 | - [Range](#range) 57 | - [`head`](#head-2) 58 | - [`init`](#init-2) 59 | - [`tail`](#tail-2) 60 | - [String](#string) 61 | - [`first`](#first-1) 62 | - [`head`](#head-3) 63 | - [`init`](#init-3) 64 | - [`last`](#last-1) 65 | - [`tail`](#tail-3) 66 | - [`to_bool`](#to_bool) 67 | 68 | 69 | ## Array 70 | 71 | #### `average` 72 | 73 | Calculates the mean of the elements. 74 | 75 | ```ruby 76 | [1, 2, 3, 4].average # => 2.5 77 | ``` 78 | 79 | #### Negative index `drop` 80 | 81 | Drops n elements from end of the array. 82 | 83 | ```ruby 84 | [1, 2, 3, 4].drop(-2) # => [1, 2] 85 | ``` 86 | 87 | If you pass a positive number, it delegates to the original implementation. 88 | 89 | ```ruby 90 | [1, 2, 3, 4].drop(2) # => [3, 4] 91 | ``` 92 | 93 | #### `except` 94 | 95 | Returns the array without the indexes specified. 96 | 97 | ```ruby 98 | [1, 2, 3, 4].except(1, 2) # => [1, 4] 99 | ``` 100 | 101 | #### `fetch_dig` 102 | 103 | Extracts the nested value specified by the sequence of indexes. 104 | 105 | ```ruby 106 | [[1, [2, 3]]].fetch_dig(0, 1, 1) # => 3 107 | ``` 108 | 109 | If the key can’t be found with no other arguments, it will raise an `IndexError` exception. 110 | 111 | ```ruby 112 | [[1, [2, 3]]].fetch_dig(1, 2, 3) # => IndexError 113 | ``` 114 | 115 | If the key can’t be found and an optional code block is specified, then that will be run and its result returned. 116 | 117 | ```ruby 118 | [[1, [2, 3]]].fetch_dig(1, 2, 3) { 2 } # => 2 119 | ``` 120 | 121 | #### `head` 122 | 123 | Returns the head of the array. 124 | 125 | ```ruby 126 | [1, 2, 3].head # => 1 127 | ``` 128 | 129 | #### `histogram` 130 | 131 | Builds the histogram in a hash. 132 | 133 | ```ruby 134 | [2, 1, 2, 2, 3, 3].histogram # => {1=>1, 2=>3, 3=>2} 135 | ``` 136 | 137 | #### `init` 138 | 139 | The initial part of the array without its last element. 140 | 141 | ```ruby 142 | [1, 2, 3].init # => [1, 2] 143 | ``` 144 | 145 | #### `mean` 146 | 147 | Calculates the mean of the elements. 148 | 149 | ```ruby 150 | [1, 2, 3, 4].mean # => 2.5 151 | ``` 152 | 153 | #### `median` 154 | 155 | Calculates the median of the elements. 156 | 157 | ```ruby 158 | [1, 2, 3, 4, 5].median # => 3 159 | [1, 2, 3, 4].median # => 2.5 160 | ``` 161 | 162 | #### `mode` 163 | 164 | Finds the mode value/s. 165 | 166 | ```ruby 167 | [1, 2, 3, 4].mode # => [1, 2, 3, 4] 168 | [1, 2, 2, 4].mode # => [2] 169 | [1, 1, 2, 4, 4].mode # => [1, 4] 170 | ``` 171 | 172 | #### `percentile` 173 | 174 | Returns the percentile value for a given percentage. 175 | 176 | ```ruby 177 | [1, 2, 3, 4].percentile(49) # => 2 178 | [1, 2, 3, 4].percentile(50) # => 3 179 | [1, 2, 3, 4, 5].percentile(50) # => 3 180 | ``` 181 | 182 | #### Negative index `take` 183 | 184 | Returns n elements from end of the array. 185 | 186 | ```ruby 187 | [1, 2, 3, 4].take(-2) # => [3, 4] 188 | ``` 189 | 190 | If you pass a positive number, it delegates to the original implementation. 191 | 192 | ```ruby 193 | [1, 2, 3, 4].take(2) # => [1, 2] 194 | ``` 195 | 196 | #### `tail` 197 | 198 | Returns the tail of the array. 199 | 200 | ```ruby 201 | [1, 2, 3, 4].tail # => [2, 3, 4] 202 | ``` 203 | 204 | #### `transpose` array of ranges 205 | 206 | Assumes that self is an array of ranges and transposes the rows and columns. 207 | 208 | ```ruby 209 | [(1..2), (3..4)].transpose # => [[1, 3], [2, 4]] 210 | ``` 211 | 212 | It also works with the original implementation, assuming an array of arrays. 213 | 214 | ```ruby 215 | [[1, 2], [3, 4]].transpose # => [[1, 3], [2, 4]] 216 | ``` 217 | 218 | 219 | ## Date 220 | 221 | #### `now` 222 | 223 | Returns the current day. 224 | 225 | ```ruby 226 | Date.now # => # 227 | ``` 228 | 229 | 230 | ## Integer 231 | 232 | #### `clamp` 233 | 234 | Clamps a comparable between a lower and upper bound. 235 | 236 | ```ruby 237 | 1.clamp(3, 6) # => 3 238 | 5.clamp(3, 6) # => 5 239 | 8.clamp(3, 6) # => 6 240 | 241 | 1.clamp(3..6) # => 3 242 | 5.clamp(3..6) # => 5 243 | 8.clamp(3..6) # => 6 244 | ``` 245 | 246 | #### `degrees` 247 | 248 | Converts a number of degrees into radians. 249 | 250 | ```ruby 251 | 90.degrees # => 1.5707963267948966 252 | ``` 253 | 254 | #### `negative` 255 | 256 | Negates the number. 257 | 258 | ```ruby 259 | 1.negative # => -1 260 | ``` 261 | 262 | #### `ordinal` 263 | 264 | Returns the ordinal of the number. 265 | 266 | ```ruby 267 | 1.ordinal # => "1st" 268 | 2.ordinal # => "2nd" 269 | ``` 270 | 271 | 272 | ## Hash 273 | 274 | #### `except` 275 | 276 | Returns the hash without the keys specified. 277 | 278 | ```ruby 279 | { a: 1, b: nil, c: nil, d: 4 }.except(:b, :d) # => {a: 1, c: nil} 280 | ``` 281 | 282 | #### `fetch_dig` 283 | 284 | Extracts the nested value specified by the sequence of keys. 285 | 286 | ```ruby 287 | { foo: { bar: { baz: 1 } }}.fetch_dig(:foo, :bar, :baz) # => 1 288 | ``` 289 | 290 | If the key can’t be found with no other arguments, it will raise an `KeyError` exception. 291 | 292 | ```ruby 293 | { foo: { bar: { baz: 1 } } }.fetch_dig(:foo, :zot, :xyz) # => KeyError 294 | ``` 295 | 296 | If the key can’t be found and an optional code block is specified, then that will be run and its result returned. 297 | 298 | ```ruby 299 | { foo: { bar: { baz: 1 } } }.fetch_dig(:foo, :zot, :xyz) { 2 } # => 2 300 | ``` 301 | 302 | #### `first` 303 | 304 | Returns the first elements of the hash. 305 | 306 | ```ruby 307 | { a: 1, b: 2, c: 3 }.first # => { a: 1 } 308 | { a: 1, b: 2, c: 3 }.first(2) # => { a: 1, b: 2 } 309 | ``` 310 | 311 | #### `head` 312 | 313 | Returns the first element of the hash. 314 | 315 | ```ruby 316 | { a: 1, b: 2, c: 3 }.head # => { a: 1 } 317 | ``` 318 | 319 | #### `init` 320 | 321 | The initial part of the hash without its last element. 322 | 323 | ```ruby 324 | { a: 1, b: 2, c: 3 }.init # => { a: 1, b: 2 } 325 | ``` 326 | 327 | #### `last` 328 | 329 | The initial part of the hash without its last element. 330 | 331 | ```ruby 332 | { a: 1, b: 2, c: 3 }.last # => { c: 3 } 333 | ``` 334 | 335 | #### `tail` 336 | 337 | The rest of the hash without its first element 338 | 339 | ```ruby 340 | { a: 1, b: 2, c: 3 }.tail # => { b: 2, c: 3 } 341 | ``` 342 | 343 | ## Kernel 344 | 345 | #### `λ` 346 | 347 | λ is lambda. 348 | 349 | ```ruby 350 | my_proc = λ { } 351 | ``` 352 | 353 | 354 | ## Object 355 | 356 | #### `assert` 357 | 358 | Asserts an expression. 359 | 360 | ```ruby 361 | assert(1 == 2) # => AssertError: AssertError 362 | assert(1 == 1) # => nil 363 | ``` 364 | 365 | #### `in?` 366 | 367 | Returns true if self is present in the given object. 368 | 369 | ```ruby 370 | 1.in?([1, 2, 3]) # => true 371 | "lo".in?("hello") # => true 372 | :b.in?({ a: 100, b: 200 }) # => true 373 | ``` 374 | 375 | #### `metaclass` 376 | 377 | Returns the eigenclass. 378 | 379 | ```ruby 380 | Object.new.metaclass # => #> 381 | ``` 382 | 383 | #### `not_in?` 384 | 385 | Returns true if self is not present in the given object. 386 | 387 | ```ruby 388 | 4.not_in?([1, 2, 3]) # => true 389 | "mo".not_in?("hello") # => true 390 | :c.not_in?({ a: 100, b: 200 }) # => true 391 | ``` 392 | 393 | #### `not_nil?` 394 | 395 | Returns true when an object is not nil. 396 | 397 | ```ruby 398 | nil.not_nil? # => false 399 | 1.not_nil? # => true 400 | ``` 401 | 402 | #### Pipe operator 403 | 404 | Pipe operator à la Bash/Elixir. 405 | 406 | ```ruby 407 | [1, 2, 3] | 408 | ->(array) { array.first } | 409 | ->(int) { int.to_s } | 410 | ->(string) { string + "2" } 411 | # => "12" 412 | 413 | [1, 2, 3] | :first | :to_s | ->(s) { s + "2" } # => "12" 414 | ``` 415 | 416 | 417 | ## Range 418 | 419 | #### `head` 420 | 421 | Returns the first object in the range. 422 | 423 | ```ruby 424 | (0..3).head # => 0 425 | ("a".."z").head # => "a" 426 | ``` 427 | 428 | #### `init` 429 | 430 | The initial part of the range without its last element. 431 | 432 | ```ruby 433 | (0..3).init # => 0..2 434 | ("a".."z").init # => "a".."y" 435 | ``` 436 | 437 | #### `tail` 438 | 439 | The rest of the range without its first element. 440 | 441 | ```ruby 442 | (0..3).tail # => 1..3 443 | ("a".."z").tail # => "b".."z" 444 | ``` 445 | 446 | 447 | ## String 448 | 449 | #### `first` 450 | 451 | Returns the first characters of the string. 452 | 453 | ```ruby 454 | "abc".first # => "a" 455 | "abc".first(2) # => "ab" 456 | ``` 457 | 458 | #### `head` 459 | 460 | Returns the first characters of the string. 461 | 462 | ```ruby 463 | "abc".head # => "a" 464 | ``` 465 | 466 | #### `init` 467 | 468 | The initial part of the string without its last element. 469 | 470 | ```ruby 471 | "abc".init # => "ab" 472 | ``` 473 | 474 | #### `last` 475 | 476 | Returns the last characters of the string. 477 | 478 | ```ruby 479 | "abc".last # => "c" 480 | "abc".last(2) # => "bc" 481 | ``` 482 | 483 | #### `tail` 484 | 485 | The rest of the string without its first element. 486 | 487 | ```ruby 488 | "abc".tail # => "bc" 489 | ``` 490 | 491 | #### `to_bool` 492 | 493 | Converts a string to boolean. 494 | 495 | ```ruby 496 | "true".to_bool # => true 497 | "false".to_bool # => false 498 | ``` 499 | 500 | 501 | ## Who made this? 502 | 503 | This was made by Arturo Herrero under the MIT License. Find me on Twitter 504 | [@ArturoHerrero][3]. 505 | 506 | 507 | [1]: http://edgeguides.rubyonrails.org/active_support_core_extensions.html 508 | [2]: https://github.com/bbatsov/powerpack 509 | [3]: https://twitter.com/ArturoHerrero 510 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require "rspec/core/rake_task" 2 | 3 | RSpec::Core::RakeTask.new(:spec) 4 | 5 | task default: :spec 6 | -------------------------------------------------------------------------------- /lib/powercore.rb: -------------------------------------------------------------------------------- 1 | Dir[File.expand_path("powercore/*.rb", __dir__)].each { |file| require file } 2 | -------------------------------------------------------------------------------- /lib/powercore/array.rb: -------------------------------------------------------------------------------- 1 | class Array 2 | # Drops n elements from the array. 3 | old_drop = instance_method(:drop) 4 | define_method(:drop) do |n| 5 | n >= 0 ? old_drop.bind(self).call(n) : self[0..n-1] 6 | end 7 | 8 | # Returns the array without the indexes specified. 9 | def except(*indexes) 10 | self.reject.with_index { |_, index| indexes.include?(index) } 11 | end 12 | 13 | # Extracts the nested value specified by the sequence of indexes. 14 | # If the key can’t be found, there are two options: 15 | # with no other argument, it will raise an IndexError exception; 16 | # if the optional code block is specified, 17 | # it will be executed and its result will be returned. 18 | def fetch_dig(*indexes, &block) 19 | indexes.inject(self) { |array, index| array.fetch(index) } 20 | rescue IndexError 21 | block&.call || raise 22 | end 23 | 24 | # Returns the head of the array. 25 | def head 26 | self.first 27 | end 28 | 29 | # Builds the histogram in a hash. 30 | def histogram 31 | self.each_with_object(Hash.new(0)) { |n, h| h[n] += 1 } 32 | end 33 | 34 | # The initial part of the array without its last element. 35 | def init 36 | self[0..-2] 37 | end 38 | 39 | # Calculates the mean of the elements. 40 | def mean 41 | self.empty? ? nil : self.sum.to_f / self.size 42 | end 43 | alias_method :average, :mean 44 | 45 | # Calculates the median of the elements. 46 | def median 47 | return nil if self.empty? 48 | sorted = self.sort 49 | size = self.size 50 | 51 | if size % 2 == 1 52 | sorted[size / 2] 53 | else 54 | (sorted[(size - 1) / 2] + sorted[size / 2]) / 2.0 55 | end 56 | end 57 | 58 | # Finds the mode value/s. 59 | def mode 60 | max = histogram.values.max 61 | histogram.map { |key, value| key if value == max }.compact 62 | end 63 | 64 | # Returns the percentile value for a given percentage. 65 | def percentile(percentage) 66 | size = self.size 67 | 68 | if size > 1 69 | index = size * percentage / 100.0 70 | self.sort[index] 71 | else 72 | self.first 73 | end 74 | end 75 | 76 | # Returns n elements from the array. 77 | old_take = instance_method(:take) 78 | define_method(:take) do |n| 79 | n >= 0 ? old_take.bind(self).call(n) : self.last(n.abs) 80 | end 81 | 82 | # The rest of the array without its first element. 83 | def tail 84 | self[1..-1] 85 | end 86 | 87 | # Assumes that self is an array of arrays or ranges 88 | # and transposes the rows and columns. 89 | old_transpose = instance_method(:transpose) 90 | define_method(:transpose) do 91 | old_transpose.bind(self.map(&:to_a)).call 92 | end 93 | end 94 | -------------------------------------------------------------------------------- /lib/powercore/date.rb: -------------------------------------------------------------------------------- 1 | require "time" 2 | 3 | class Date 4 | # Returns the current day. 5 | def self.now 6 | Time.now.to_date 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /lib/powercore/hash.rb: -------------------------------------------------------------------------------- 1 | class Hash 2 | # Returns the hash without the keys specified. 3 | def except(*keys) 4 | self.reject { |k, _| keys.include?(k) } 5 | end 6 | 7 | # Returns the first elements. 8 | def first(n = nil) 9 | if n.nil? 10 | self.empty? ? nil : Hash[*self.to_a.first] 11 | else 12 | n.zero? ? {} : Hash[self.to_a[0..n - 1]] 13 | end 14 | end 15 | 16 | # Extracts the nested value specified by the sequence of keys. 17 | # If the key can’t be found, there are two options: 18 | # with no other argument, it will raise an KeyError exception; 19 | # if the optional code block is specified, 20 | # it will be executed and its result will be returned. 21 | def fetch_dig(*keys, &block) 22 | keys.inject(self) { |hash, key| hash.fetch(key) } 23 | rescue KeyError 24 | block&.call || raise 25 | end 26 | 27 | # Returns the first element. 28 | def head 29 | Hash[*self.to_a.first] 30 | end 31 | 32 | # The initial part of the hash without its last element. 33 | def init 34 | Hash[self.to_a[0..-2]] 35 | end 36 | 37 | # Returns the last element. 38 | def last 39 | Hash[*self.to_a.last] 40 | end 41 | 42 | # The rest of the hash without its first element. 43 | def tail 44 | Hash[self.to_a[1..-1]] 45 | end 46 | end 47 | -------------------------------------------------------------------------------- /lib/powercore/integer.rb: -------------------------------------------------------------------------------- 1 | class Integer 2 | # Clamps a comparable between a lower and upper bound. 3 | def clamp(min, max = nil) 4 | if max.nil? && min.is_a?(Range) 5 | self < min.min ? min.min : self > min.max ? min.max : self 6 | else 7 | self < min ? min : self > max ? max : self 8 | end 9 | end 10 | 11 | # Converts a number of degrees into radians. 12 | def degrees 13 | self * Math::PI / 180 14 | end 15 | 16 | # Negates the number. 17 | def negative 18 | -self 19 | end 20 | 21 | # Returns the ordinal of the number. 22 | def ordinal 23 | self.to_s + 24 | if (11..13).include?(self % 100) 25 | "th" 26 | else 27 | case self % 10 28 | when 1 then "st" 29 | when 2 then "nd" 30 | when 3 then "rd" 31 | else "th" 32 | end 33 | end 34 | end 35 | end 36 | -------------------------------------------------------------------------------- /lib/powercore/kernel.rb: -------------------------------------------------------------------------------- 1 | module Kernel 2 | # λ is lambda. 3 | alias_method :λ, :lambda 4 | end 5 | -------------------------------------------------------------------------------- /lib/powercore/object.rb: -------------------------------------------------------------------------------- 1 | class Object 2 | # Asserts an expression. 3 | AssertError = Class.new(StandardError) 4 | def assert(expression) 5 | raise(AssertError, "assertion failed") unless expression 6 | end 7 | 8 | # Returns true if self is present in the given object. 9 | def in?(object) 10 | object.include?(self) 11 | end 12 | 13 | # Returns the eigenclass. 14 | def metaclass 15 | class << self 16 | self 17 | end 18 | end 19 | 20 | # Returns true if self is not present in the given object. 21 | def not_in?(object) 22 | !in?(object) 23 | end 24 | 25 | # Returns true when an object is not nil. 26 | def not_nil? 27 | !nil? 28 | end 29 | 30 | # Pipe operator à la Bash/Elixir. 31 | [Integer, Array, Set, TrueClass, FalseClass, NilClass].each do |klass| 32 | klass.class_eval { remove_method(:|) } 33 | end 34 | def |(pipe) 35 | if self.is_a?(Proc) 36 | ->(input) { pipe.call(self.call(input)) } 37 | elsif pipe.is_a?(Symbol) 38 | self.send(pipe) 39 | else 40 | pipe.call(self) 41 | end 42 | end 43 | end 44 | -------------------------------------------------------------------------------- /lib/powercore/range.rb: -------------------------------------------------------------------------------- 1 | class Range 2 | # Returns the first object in the range 3 | def head 4 | self.first 5 | end 6 | 7 | # The initial part of the range without its last element. 8 | def init 9 | self.first..self.to_a[-2] 10 | end 11 | 12 | # The rest of the range without its first element. 13 | def tail 14 | self.to_a[1]..self.last 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /lib/powercore/string.rb: -------------------------------------------------------------------------------- 1 | class String 2 | # Returns the first characters of the string. 3 | def first(n = nil) 4 | n.nil? ? self[0] : n.zero? ? "" : self[0..n - 1] 5 | end 6 | 7 | # Returns the first character of the string. 8 | def head 9 | self[0] 10 | end 11 | 12 | # The initial part of the string without its last element. 13 | def init 14 | self[0..-2] 15 | end 16 | 17 | # Returns the last chars of the string. 18 | def last(n = nil) 19 | n.nil? ? self[-1] : self[[0, self.size - n].max..-1] 20 | end 21 | 22 | # The rest of the string without its first element. 23 | def tail 24 | self[1..-1] 25 | end 26 | 27 | # Converts string to boolean. 28 | def to_bool 29 | return true if self =~ /^true$/ 30 | return false if self =~ /^false$/ 31 | raise(ArgumentError, "wrong element #{self}") 32 | end 33 | end 34 | -------------------------------------------------------------------------------- /spec/lib/powercore/array_spec.rb: -------------------------------------------------------------------------------- 1 | RSpec.describe Array do 2 | describe "#average" do 3 | it "calculates the mean of the elements" do 4 | expect([1, 2, 3, 4].average).to eq(2.5) 5 | end 6 | end 7 | 8 | describe "#drop" do 9 | context "positive number" do 10 | it "drops first n elements -keeping Ruby behavior-" do 11 | expect([1, 2, 3, 4].drop(0)).to eq([1, 2, 3, 4]) 12 | expect([1, 2, 3, 4].drop(2)).to eq([3, 4]) 13 | expect([1, 2, 3, 4].drop(7)).to eq([]) 14 | end 15 | end 16 | 17 | context "negative number" do 18 | it "drops last n elements" do 19 | expect([1, 2, 3, 4].drop(-2)).to eq([1, 2]) 20 | expect([1, 2, 3, 4].drop(-1)).to eq([1, 2, 3]) 21 | expect([1, 2, 3, 4].drop(-7)).to eq([]) 22 | end 23 | end 24 | end 25 | 26 | describe "#except" do 27 | it "returns the array without the indexes specified" do 28 | expect([1, 2, 3, 4].except(1, 2)).to eq([1, 4]) 29 | end 30 | end 31 | 32 | describe "#fetch_dig" do 33 | let(:array) { [[1, [2, 3]]] } 34 | 35 | it "extracts the nested value specified by the sequence of indexes" do 36 | expect(array.fetch_dig(0, 1, 1)).to eq(3) 37 | end 38 | 39 | context "key can't be found" do 40 | it "raises an IndexError exception" do 41 | expect { array.fetch_dig(1, 2, 3) }.to raise_error(IndexError) 42 | end 43 | 44 | context "the optional code block is specified" do 45 | it "runs the code block and returns its result" do 46 | expect(array.fetch_dig(1, 2, 3) { 2 }).to eq(2) 47 | expect(array.fetch_dig(1, 2, 3) { 2 - 1 + 1 }).to eq(2) 48 | end 49 | end 50 | end 51 | end 52 | 53 | describe "#head" do 54 | it "returns the head of the array" do 55 | expect([1, 2, 3].head).to eq(1) 56 | expect([].head).to eq(nil) 57 | end 58 | end 59 | 60 | describe "#histogram" do 61 | it "builds the histogram in a hash" do 62 | expect([2, 1, 2, 2, 3, 3].histogram).to eq({ 1 => 1, 2 => 3, 3 => 2 }) 63 | end 64 | end 65 | 66 | describe "#init" do 67 | it "returns the initial part of the array without its last element" do 68 | expect([1, 2, 3].init).to eq([1, 2]) 69 | end 70 | end 71 | 72 | describe "#mean" do 73 | it "calculates the mean of the elements" do 74 | expect([1, 2, 3, 4].mean).to eq(2.5) 75 | expect([].mean).to eq(nil) 76 | end 77 | end 78 | 79 | describe "#median" do 80 | it "calculates the median of the elements" do 81 | expect([1, 2, 3, 4, 5].median).to eq(3) 82 | expect([1, 2, 3, 4].median).to eq(2.5) 83 | expect([].median).to eq(nil) 84 | end 85 | end 86 | 87 | describe "#mode" do 88 | it "finds the mode value/s" do 89 | expect([].mode).to eq([]) 90 | expect([1, 2, 3, 4].mode).to eq([1, 2, 3, 4]) 91 | expect([1, 2, 2, 4].mode).to eq([2]) 92 | expect([1, 1, 2, 4, 4].mode).to eq([1, 4]) 93 | end 94 | end 95 | 96 | describe "#percentile" do 97 | it "returns the percentile value for a given percentage" do 98 | expect([1, 2, 3, 4].percentile(49)).to eq(2) 99 | expect([1, 2, 3, 4].percentile(50)).to eq(3) 100 | expect([1, 2, 3, 4, 5].percentile(50)).to eq(3) 101 | expect([].percentile(50)).to eq(nil) 102 | end 103 | end 104 | 105 | describe "#take" do 106 | context "positive number" do 107 | it "returns first n elements -keeping Ruby behavior-" do 108 | expect([1, 2, 3, 4].take(0)).to eq([]) 109 | expect([1, 2, 3, 4].take(2)).to eq([1, 2]) 110 | expect([1, 2, 3, 4].take(7)).to eq([1, 2, 3, 4]) 111 | end 112 | end 113 | 114 | context "negative number" do 115 | it "returns last n elements" do 116 | expect([1, 2, 3, 4].take(-2)).to eq([3, 4]) 117 | expect([1, 2, 3, 4].take(-1)).to eq([4]) 118 | expect([1, 2, 3, 4].take(-7)).to eq([1, 2, 3, 4]) 119 | end 120 | end 121 | end 122 | 123 | describe "#tail" do 124 | it "returns the rest of the array without its first element" do 125 | expect([1, 2, 3, 4].tail).to eq([2, 3, 4]) 126 | end 127 | end 128 | 129 | describe "#transpose" do 130 | context "array of arrays" do 131 | it "transposes the rows and columns -keeping Ruby behavior-" do 132 | expect([[1, 2], [3, 4]].transpose).to eq([[1, 3], [2, 4]]) 133 | end 134 | end 135 | 136 | context "array of ranges" do 137 | it "transposes the rows and columns" do 138 | expect([(1..2), (3..4)].transpose).to eq([[1, 3], [2, 4]]) 139 | end 140 | end 141 | end 142 | end 143 | -------------------------------------------------------------------------------- /spec/lib/powercore/date_spec.rb: -------------------------------------------------------------------------------- 1 | RSpec.describe Date do 2 | describe ".date" do 3 | it "returns the current day" do 4 | expect(Date.now).to eq(Date.parse(Time.now.to_s)) 5 | end 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /spec/lib/powercore/hash_spec.rb: -------------------------------------------------------------------------------- 1 | RSpec.describe Hash do 2 | describe "#except" do 3 | it "returns the hash without the keys specified" do 4 | expect({ a: 1, b: nil, c: nil, d: 4 }.except(:b, :d)).to eq({ a: 1, c: nil }) 5 | end 6 | end 7 | 8 | describe "#fetch_dig" do 9 | let(:hash) { { foo: { bar: { baz: 1 } } } } 10 | 11 | it "extracts the nested value specified by the sequence of keys" do 12 | expect(hash.fetch_dig(:foo, :bar, :baz)).to eq(1) 13 | end 14 | 15 | context "key can't be found" do 16 | it "raises a KeyError exception" do 17 | expect { hash.fetch_dig(:foo, :zot, :xyz) }.to raise_error(KeyError) 18 | end 19 | 20 | context "the optional code block is specified" do 21 | it "runs the code block and returns its result" do 22 | expect(hash.fetch_dig(:foo, :zot, :xyz) { 2 }).to eq(2) 23 | end 24 | end 25 | end 26 | end 27 | 28 | describe "#first" do 29 | it "returns the first element" do 30 | expect({ a: 1, b: 2, c: 3 }.first).to eq({ a: 1 }) 31 | expect({}.first).to eq(nil) 32 | end 33 | 34 | context "with paremeter" do 35 | it "returns the first n elements" do 36 | expect({}.first(0)).to eq({}) 37 | expect({ a: 1, b: 2, c: 3 }.first(0)).to eq({}) 38 | expect({ a: 1, b: 2, c: 3 }.first(2)).to eq({ a: 1, b: 2 }) 39 | expect({ a: 1, b: 2, c: 3 }.first(4)).to eq({ a: 1, b: 2, c: 3 }) 40 | end 41 | end 42 | end 43 | 44 | describe "#head" do 45 | it "returns the first element" do 46 | expect({ a: 1, b: nil, c: nil, d: 4 }.head).to eq({ a: 1 }) 47 | end 48 | end 49 | 50 | describe "#init" do 51 | it "returns the rest of the hash without its last element" do 52 | expect({ a: 1, b: 2, c: 3 }.init).to eq({ a: 1, b: 2 }) 53 | end 54 | end 55 | 56 | describe "#last" do 57 | it "returns the last element" do 58 | expect({ a: 1, b: 2, c: 3 }.last).to eq({ c: 3 }) 59 | end 60 | end 61 | 62 | describe "#tail" do 63 | it "returns the rest of the hash without its first element" do 64 | expect({ a: 1, b: 2, c: 3 }.tail).to eq({ b: 2, c: 3 }) 65 | end 66 | end 67 | end 68 | -------------------------------------------------------------------------------- /spec/lib/powercore/integer_spec.rb: -------------------------------------------------------------------------------- 1 | RSpec.describe Integer do 2 | describe "#clamp" do 3 | it "clamps a comparable between a lower and upper bound" do 4 | expect(1.clamp(3, 6)).to eq(3) 5 | expect(5.clamp(3, 6)).to eq(5) 6 | expect(8.clamp(3, 6)).to eq(6) 7 | end 8 | 9 | it "clamps a comparable between a lower and upper bound of a Range" do 10 | expect(1.clamp(3..6)).to eq(3) 11 | expect(5.clamp(3..6)).to eq(5) 12 | expect(8.clamp(3..6)).to eq(6) 13 | end 14 | end 15 | 16 | describe "#degrees" do 17 | it "converts a number of degrees into radians" do 18 | expect(90.degrees).to eq(1.5707963267948966) 19 | end 20 | end 21 | 22 | describe "#negative" do 23 | it "negates the number" do 24 | expect(1.negative).to eq(-1) 25 | end 26 | end 27 | 28 | describe "#ordinal" do 29 | it "returns the ordinal" do 30 | expect(1.ordinal).to eq("1st") 31 | expect(2.ordinal).to eq("2nd") 32 | expect(3.ordinal).to eq("3rd") 33 | expect(4.ordinal).to eq("4th") 34 | expect(12.ordinal).to eq("12th") 35 | expect(13.ordinal).to eq("13th") 36 | end 37 | end 38 | end 39 | -------------------------------------------------------------------------------- /spec/lib/powercore/kernel_spec.rb: -------------------------------------------------------------------------------- 1 | RSpec.describe Kernel do 2 | describe "λ" do 3 | let(:my_proc) { λ { } } 4 | 5 | it "is equivalent to lambda" do 6 | expect(my_proc).to receive(:call) 7 | my_proc.call 8 | end 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /spec/lib/powercore/object_spec.rb: -------------------------------------------------------------------------------- 1 | RSpec.describe Object do 2 | describe "#assert" do 3 | it "asserts the statement containing a boolean expression" do 4 | expect { assert(1 == 2) }.to raise_error(AssertError) 5 | expect { assert(1 == 1) }.to_not raise_error 6 | expect(assert(1 == 1)).to eq(nil) 7 | end 8 | end 9 | 10 | describe "#in?" do 11 | it "checks if self is present in the given object" do 12 | expect(1.in?([1, 2, 3])).to eq(true) 13 | expect("lo".in?("hello")).to eq(true) 14 | expect(:b.in?({ a: 100, b: 200 })).to eq(true) 15 | end 16 | end 17 | 18 | describe "#metaclass" do 19 | let(:object) { Object.new } 20 | 21 | it "returns the eigenclass of the object" do 22 | expect(object.metaclass).to eq(object.singleton_class) 23 | end 24 | end 25 | 26 | describe "#not_in?" do 27 | it "checks if self is not present in the given object" do 28 | expect(4.not_in?([1, 2, 3])).to eq(true) 29 | expect("mo".not_in?("hello")).to eq(true) 30 | expect(:c.not_in?({ a: 100, b: 200 })).to eq(true) 31 | end 32 | end 33 | 34 | describe "#not_nil?" do 35 | it "checks if an object is not nil" do 36 | expect(nil.not_nil?).to eq(false) 37 | expect(1.not_nil?).to eq(true) 38 | end 39 | end 40 | 41 | describe "pipe | it" do 42 | it "pipes à la Bash/Elixir" do 43 | expect( 44 | [1, 2, 3] | 45 | ->(array) { array.first } | 46 | ->(int) { int.to_s } | 47 | ->(string) { string + "2" } 48 | ).to eq("12") 49 | 50 | expect([1, 2, 3] | :first | :to_s | ->(s) { s + "2" }).to eq("12") 51 | end 52 | end 53 | end 54 | -------------------------------------------------------------------------------- /spec/lib/powercore/range_spec.rb: -------------------------------------------------------------------------------- 1 | RSpec.describe Range do 2 | describe "#head" do 3 | it "returns the first object in the range" do 4 | expect((0..3).head).to eq(0) 5 | expect(("a".."z").head).to eq("a") 6 | end 7 | end 8 | 9 | describe "#init" do 10 | it "returns the initial part of the range without its last element" do 11 | expect((0..3).init).to eq(0..2) 12 | expect(("a".."z").init).to eq("a".."y") 13 | end 14 | end 15 | 16 | describe "#tail" do 17 | it "returns the rest of the range without its first element" do 18 | expect((0..3).tail).to eq(1..3) 19 | expect(("a".."z").tail).to eq("b".."z") 20 | end 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /spec/lib/powercore/string_spec.rb: -------------------------------------------------------------------------------- 1 | RSpec.describe String do 2 | describe "#first" do 3 | it "returns the first character" do 4 | expect("abc".first).to eq("a") 5 | expect("".first).to eq(nil) 6 | end 7 | 8 | context "with paremeter" do 9 | it "returns the first n characters" do 10 | expect("".first(0)).to eq("") 11 | expect("abc".first(0)).to eq("") 12 | expect("abc".first(2)).to eq("ab") 13 | expect("abc".first(4)).to eq("abc") 14 | end 15 | end 16 | end 17 | 18 | describe "#head" do 19 | it "returns the first character" do 20 | expect("abc".head).to eq("a") 21 | expect("".head).to eq(nil) 22 | end 23 | end 24 | 25 | describe "#init" do 26 | it "returns the initial part of the string without its last character" do 27 | expect("abc".init).to eq("ab") 28 | end 29 | end 30 | 31 | describe "#last" do 32 | it "returns the last character" do 33 | expect("abc".last).to eq("c") 34 | expect("".last).to eq(nil) 35 | end 36 | 37 | context "with paremeter" do 38 | it "returns the last n characters" do 39 | expect("abc".last(0)).to eq("") 40 | expect("abc".last(2)).to eq("bc") 41 | expect("abc".last(4)).to eq("abc") 42 | end 43 | end 44 | end 45 | 46 | describe "#tail" do 47 | it "returns the rest of the string without its first character" do 48 | expect("abc".tail).to eq("bc") 49 | expect("".tail).to eq(nil) 50 | end 51 | end 52 | 53 | describe "#to_bool" do 54 | it "converts the given string into a boolean object" do 55 | expect("true".to_bool).to eq(true) 56 | expect("false".to_bool).to eq(false) 57 | end 58 | 59 | context "not valid string" do 60 | it "raises an error" do 61 | expect { "t".to_bool }.to raise_error(ArgumentError, "wrong element t") 62 | end 63 | end 64 | end 65 | end 66 | -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | require "pry" 2 | require "powercore" 3 | 4 | RSpec.configure do |config| 5 | config.expect_with :rspec do |expectations| 6 | expectations.include_chain_clauses_in_custom_matcher_descriptions = true 7 | end 8 | 9 | config.mock_with :rspec do |mocks| 10 | mocks.verify_partial_doubles = true 11 | end 12 | 13 | config.disable_monkey_patching! 14 | 15 | if config.files_to_run.one? 16 | config.default_formatter = 'doc' 17 | end 18 | 19 | config.order = :random 20 | Kernel.srand config.seed 21 | end 22 | --------------------------------------------------------------------------------