Skip to content

bkuhlmann/refinements

Repository files navigation

Refinements

Features

  • Arrays:

    • #compress - Removes nil and empty values without modifying itself.

    • #compress! - Removes nil and empty values while modifying itself.

    • #ring - Answers a circular array which can enumerate before, current, after elements.

  • BigDecimals:

    • #inspect - Allows one to inspect a big decimal with numeric representation.

  • DateTimes:

    • .utc - Answers new DateTime object for current UTC date/time.

  • Files:

    • #rewrite - When given a file path and a block, it provides the contents of the recently read file for manipulation and immediate writing back to the same file.

  • Hashes:

    • .infinite - Answers new hash where missing keys, even deeply nested, answer an empty hash.

    • .with_default - Answers new hash where every top-level missing key has the same default value.

    • #except - Answers new hash with given keys removed without modifying itself.

    • #except! - Answers new hash with given keys removed while modifying itself.

    • #symbolize_keys - Converts keys to symbols without modifying itself.

    • #symbolize_keys! - Converts keys to symbols while modifying itself.

    • #deep_merge - Merges deeply nested hashes together without modifying itself.

    • #deep_merge! - Merges deeply nested hashes together while modifying itself.

    • #deep_symbolize_keys - Symbolizes keys of nested hash without modifying itself. Does not handle nested arrays, though.

    • #deep_symbolize_keys! - Symbolizes keys of nested hash while modifying itself. Does not handle nested arrays, though.

    • #recurse - Applies block to nested hash. Does not handle nested arrays, though.

    • #rekey - Transforms keys per mapping (size of mapping can vary) without modifying itself.

    • #rekey! - Transforms keys per mapping (size of mapping can vary) while modifying itself.

    • #reverse_merge - Merges calling hash into passed in hash without modifying itself.

    • #reverse_merge! - Merges calling hash into passed in hash while modifying itself.

    • #use - Passes each hash value as a block argument for further processing.

  • Pathnames:

    • Pathname - Conversion function (refined from Kernel) which can cast nil into a pathname.

    • #name - Answers file name without extension.

    • #copy - Copies file from current location to new location.

    • #directories - Answers all or filtered directories for current path.

    • #extensions - Answers file extensions as an array.

    • #files - Answers all or filtered files for current path.

    • #gsub - Same behavior as String#gsub but answers a path with patterns replaced with desired substitutes.

    • #relative_parent_from - Answers relative path from parent directory. This is a complement to #relative_path_from.

    • #make_ancestors - Ensures all ancestor directories are created for a path.

    • #rewrite - When given a block, it provides the contents of the recently read file for manipulation and immediate writing back to the same file.

    • #touch - Updates access and modification times for path. Defaults to current time.

  • Strings:

    • #first - Answers first character of a string or first set of characters if given a number.

    • #last - Answers last character of a string or last set of characters if given a number.

    • #blank? - Answers true/false based on whether string is blank or not (i.e. <space>, \n, \t, \r).

    • #up - Answers string with only first letter upcased.

    • #down - Answers string with only first letter downcased.

    • #camelcase - Answers a camelcased string.

    • #snakecase - Answers a snakecased string.

    • #titleize - Answers titleized string.

    • #to_bool - Answers string as a boolean.

  • String IOs:

    • #reread - Answers full string by rewinding to beginning of string and reading all content.

Requirements

  1. Ruby.

  2. A solid understanding of Ruby refinements and lexical scope.

Setup

Production

To install, run:

gem install refinements

Add the following to your Gemfile file:

gem "refinements"

Development

To contribute, run:

git clone https://github.com/bkuhlmann/refinements.git
cd refinements
bin/setup

You can also use the IRB console for direct access to all objects:

bin/console

Usage

Requires

If all refinements are not desired, add the following to your Gemfile instead:

gem "refinements", require: false

…then require the specific refinement, as needed. Example:

require "refinements/arrays"
require "refinements/big_decimals"
require "refinements/date_times"
require "refinements/files"
require "refinements/hashes"
require "refinements/pathnames"
require "refinements/strings"

Using

Much like including/extending a module, you’ll need modify your object(s) to use the refinement(s):

class Example
  using Refinements::Arrays
  using Refinements::BigDecimals
  using Refinements::DateTimes
  using Refinements::Files
  using Refinements::Hashes
  using Refinements::Pathnames
  using Refinements::Strings
end

Examples

The following sections demonstrate how each refinement enriches your objects with new capabilities.

Array

example = ["An", nil, "", "Example"]
example.compress # => ["An", "Example"]
example # => ["An", nil, "", "Example"]

example = ["An", nil, "", "Example"]
example.compress! # => ["An", "Example"]
example # => ["An", "Example"]

example = [1, 2, 3]
example.ring # => #<Enumerator: ...>
example.ring { |(before, current, after)| puts "#{before} #{current} #{after}" }
# [3 1 2]
# [1 2 3]
# [2 3 1]

Big Decimal

BigDecimal.new("5.0E-10").inspect # => "#<BigDecimal:3fd3d458fe84 0.0000000005>"

DateTime

DateTime.utc # => #<DateTime: 2019-12-31T18:17:00+00:00 ((2458849j,65820s,181867000n),+0s,2299161j)>

File

File.rewrite("/test.txt") { |content| content.gsub "[placeholder]", "example" }

Hash

example = Hash.infinite
example[:a] # => {}
example[:a][:b][:c] # => {}

example = Hash.with_default ""
example[:a] # => ""
example = Hash.with_default []
example[:b] # => []

example = {a: 1, b: 2, c: 3}
example.except :a, :b # => {c: 3}
example # => {a: 1, b: 2, c: 3}

example = {a: 1, b: 2, c: 3}
example.except! :a, :b # => {c: 3}
example # => {c: 3}

example = {"a" => 1, "b" => 2}
example.symbolize_keys # => {a: 1, b: 2}
example # => {"a" => 1, "b" => 2}

example = {"a" => 1, "b" => 2}
example.symbolize_keys! # => {a: 1, b: 2}
example # => {a: 1, b: 2}

example = {a: 1, b: 2, c: 3}
example.slice :a, :c # => {a: 1, c: 3}
example # => {a: 1, b: 2, c: 3}

example = {a: 1, b: 2, c: 3}
example.slice! :a, :c # => {a: 1, c: 3}
example # => {a: 1, c: 3}

example = {a: "A", b: {one: "One", two: "Two"}}
example.deep_merge b: {one: 1} # => {a: "A", b: {one: 1, two: "Two"}}
example # => {a: "A", b: {one: "One", two: "Two"}}

example = {a: "A", b: {one: "One", two: "Two"}}
example.deep_merge! b: {one: 1} # => {a: "A", b: {one: 1, two: "Two"}}
example # => {a: "A", b: {one: 1, two: "Two"}}

example = {"a" => {"b" => 2}}
example.deep_symbolize_keys # => {a: {b: 1}}
example # => {"a" => {"b" => 2}}

example = {"a" => {"b" => 2}}
example.deep_symbolize_keys! # => {a: {b: 1}}
example # => {a: {b: 1}}

example = {"a" => {"b" => 1}}
example.recurse(&:symbolize_keys) # => {a: {b: 1}}
example.recurse(&:invert) # => {{"b" => 1} => "a"}

example = {a: 1, b: 2, c: 3}
example.rekey a: :amber, b: :blue # => {amber: 1, blue: 2, c: 3}
example # => {a: 1, b: 2, c: 3}

example = {a: 1, b: 2, c: 3}
example.rekey! a: :amber, b: :blue # => {amber: 1, blue: 2, c: 3}
example # => {amber: 1, blue: 2, c: 3}

example = {a: 1, b: 2}
example.reverse_merge a: 0, c: 3 # => {a: 1, b: 2, c: 3}
example # => {a: 1, b: 2}

example = {a: 1, b: 2}
example.reverse_merge! a: 0, c: 3 # => {a: 1, b: 2, c: 3}
example # => {a: 1, b: 2, c: 3}

example = {unit: "221B", street: "Baker Street", city: "London", country: "UK"}
example.use { |unit, street| "#{unit} #{street}" } # => "221B Baker Street"

Pathname

Pathname(nil) # => Pathname("")

Pathname("example.txt").name # => Pathname("example")

Pathname("input.txt").copy Pathname("output.txt")

Pathname("/example").directories # => [Pathname("a"), Pathname("b")]
Pathname("/example").directories "a*" # => [Pathname("a")]
Pathname("/example").directories flag: File::FNM_DOTMATCH # => [Pathname(".."), Pathname(".")]

Pathname("example.txt.erb").extensions # => [".txt", ".erb"]

Pathname("/example").files # => [Pathname("a.txt"), Pathname("a.png")]
Pathname("/example").files "*.png" # => [Pathname("a.png")]
Pathname("/example").files flag: File::FNM_DOTMATCH # => [Pathname(".ruby-version")]

Pathname("/a/path/some/path").gsub("path", "test") # => Pathname("/a/test/some/test")
Pathname("/%placeholder%/some/%placeholder%").gsub("%placeholder%", "test") # => Pathname("/test/some/test")

Pathname("/one/two/three").relative_parent_from("/one") # => Pathname "two"

Pathname("/one/two").make_ancestors
Pathname("/one").exist? # => true
Pathname("/one/two").exist? # => false

Pathname("/test.txt").rewrite { |content| content.sub "[placeholder]", "example" }

Pathname("example.txt").touch
Pathname("example.txt").touch at: Time.now - 1

String

"example".first # => "e"
"example".first 4 # => "exam"

"instant".last # => "t"
"instant".last 3 # => "ant"

" \n\t\r".blank? # => true

"example".up # => "Example"

"EXAMPLE".down # => "eXAMPLE"

"this_is_an_example".camelcase # => "ThisIsAnExample"

"ThisIsAnExample".snakecase # => "this_is_an_example"

"ThisIsAnExample".titleize # => "This Is An Example"

"true".to_bool # => true
"yes".to_bool # => true
"1".to_bool # => true
"".to_bool # => false
"example".to_bool # => false

String IO

io = StringIO.new
io.write "This is a test."
io.reread # => "This is a test."

io.reread(4) => "This"

buffer = "".dup
io.reread(buffer: buffer)
buffer # => "This is a test."

Tests

To test, run:

bundle exec rake

Versioning

Read Semantic Versioning for details. Briefly, it means:

  • Major (X.y.z) - Incremented for any backwards incompatible public API changes.

  • Minor (x.Y.z) - Incremented for new, backwards compatible, public API enhancements/fixes.

  • Patch (x.y.Z) - Incremented for small, backwards compatible, bug fixes.

Code of Conduct

Please note that this project is released with a CODE OF CONDUCT. By participating in this project you agree to abide by its terms.

Contributions

Read CONTRIBUTING for details.

License

Read LICENSE for details.

History

Read CHANGES for details.

Credits

Engineered by Brooke Kuhlmann.

Sponsor this project

 

Packages

No packages published

Contributors 4

  •  
  •  
  •  
  •  

Languages