{"id":13879587,"url":"https://github.com/baweaver/qo","last_synced_at":"2025-07-16T15:32:37.766Z","repository":{"id":56889783,"uuid":"128871547","full_name":"baweaver/qo","owner":"baweaver","description":"Qo - Query Object - Pattern matching and fluent querying in Ruby","archived":false,"fork":false,"pushed_at":"2019-03-03T09:32:21.000Z","size":817,"stargazers_count":361,"open_issues_count":4,"forks_count":10,"subscribers_count":8,"default_branch":"master","last_synced_at":"2024-10-29T01:19:16.893Z","etag":null,"topics":["functional-programming","pattern-matching","rspec-examples","ruby"],"latest_commit_sha":null,"homepage":null,"language":"Ruby","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/baweaver.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE.txt","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2018-04-10T04:00:26.000Z","updated_at":"2024-03-25T09:52:46.000Z","dependencies_parsed_at":"2022-08-20T15:20:42.221Z","dependency_job_id":null,"html_url":"https://github.com/baweaver/qo","commit_stats":null,"previous_names":[],"tags_count":13,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/baweaver%2Fqo","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/baweaver%2Fqo/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/baweaver%2Fqo/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/baweaver%2Fqo/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/baweaver","download_url":"https://codeload.github.com/baweaver/qo/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":226143895,"owners_count":17580245,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["functional-programming","pattern-matching","rspec-examples","ruby"],"created_at":"2024-08-06T08:02:26.008Z","updated_at":"2024-11-24T08:31:29.688Z","avatar_url":"https://github.com/baweaver.png","language":"Ruby","readme":"# Qo\n\n[![Build Status](https://travis-ci.org/baweaver/qo.svg?branch=master)](https://travis-ci.org/baweaver/qo)\n[![Maintainability](https://api.codeclimate.com/v1/badges/186e9cbb7003842acaf0/maintainability)](https://codeclimate.com/github/baweaver/qo/maintainability)\n[![Gem Version](https://badge.fury.io/rb/qo.svg)](https://badge.fury.io/rb/qo)\n\nShort for Query Object, my play at Ruby pattern matching and fluent querying, [pronounced \"Q-whoah\"](img/whoa_lemur.png).\n\n![Qo Lemur logo](img/qo_logo.png)\n\n[Read the Docs for more detailed information](https://baweaver.github.io/qo/)\n\n## How does it work?\n\nMostly by using Ruby language features like `to_proc` and `===`.\n\nThere's an article explaining most of the base mechanics behind Qo:\n\n[For Want of Pattern Matching in Ruby - The Creation of Qo](https://medium.com/@baweaver/for-want-of-pattern-matching-in-ruby-the-creation-of-qo-c3b267109b25)\n\nMost of it, though, utilizes Triple Equals. If you're not familiar with what all you can do with it in Ruby, I would encourage you to read this article as well:\n\n[Triple Equals Black Magic](https://medium.com/rubyinside/triple-equals-black-magic-d934936a6379)\n\nThe original inspiration was from a chat I'd had with a few other Rubyists about pattern matching, which led to this experiment:\n\n[Having fun with M and Q](https://gist.github.com/baweaver/611389c41c9005d025fb8e55448bf5f5)\n\nFast forward a few months and I kind of wanted to make it real, so here it is. Introducing Qo!\n\n## Usage\n\nNote that Qo uses the [Any](https://www.github.com/baweaver/any) gem for wildcard matching. Any will respond true to any `==` or `===` query against it,\nand is included in the gem.\n\n### Quick Start\n\nQo is used for pattern matching in Ruby. All Qo matchers respond to `===` and `to_proc` meaning they can be used with `case` and Enumerable functions alike:\n\n\n```ruby\ncase ['Foo', 42]\nwhen Qo[Any, 42] then 'Truly the one answer'\nelse nil\nend\n\n# Run a select like an AR query, getting the age attribute against a range\npeople.select(\u0026Qo[age: 18..30])\n```\n\nHow about some pattern matching? There are two styles:\n\n#### Pattern Match\n\n##### Case Statements\n\nQo case statements work much like a Ruby case statement, except in that they leverage\nthe full power of Qo matchers behind the scenes.\n\n```ruby\n# How about some \"right-hand assignment\" pattern matching\nname_longer_than_three = -\u003e person { person.name.size \u003e 3 }\n\nperson_with_truncated_name = Qo.case(people.first) { |m|\n  m.when(name_longer_than_three) { |person|\n    Person.new(person.name[0..2], person.age)\n  }\n\n  m.else\n}\n```\n\nIt takes in a value directly, and returns the result, much like a case statement.\n\nNote that if `else` receives no block, it will default to an identity function\n(`{ |v| v }`). If no else is provided and there's no match, you'll get back a nil.\nYou can write this out if you wish.\n\n##### Match Statements\n\nMatch statements are like case statements, except in that they don't directly take\na value to match against. They're waiting for a value to come in later from\nsomething else.\n\n```ruby\nname_longer_than_three = -\u003e person { person.name.size \u003e 3 }\n\npeople_with_truncated_names = people.map(\u0026Qo.match { |m|\n  m.when(name_longer_than_three) { |person| Person.new(person.name[0..2], person.age) }\n  m.else\n})\n\n# And standalone like a case:\nQo.match { |m|\n  m.when(age: 10..19) { |person| \"#{person.name} is a teen that's #{person.age} years old\" }\n  m.else { |person| \"#{person.name} is #{person.age} years old\" }\n}.call(people.first)\n```\n\n### Qo'isms\n\nQo supports three main types of queries: `and`, `or`, and `not`.\n\nMost examples are written in terms of `and` and its alias `[]`. `[]` is mostly used for portable syntax:\n\n```ruby\nQo[/Rob/, 22]\n\n# ...is functionally the same as an and query, which uses `all?` to match\nQo.and(/Rob/, 22)\n\n# This is shorthand for\nQo::Matchers::BaseMatcher.new('and', /Rob/, 22)\n\n# An `or` matcher uses the same shorthand as `and` but uses `any?` behind the scenes instead:\nQo.or(/Rob/, 22)\n\n# Same with not, except it uses `none?`\nQo.not(/Rob/, 22)\n```\n\nQo has a few Qo'isms, mainly based around triple equals in Ruby. See the above articles for tutorials on that count.\n\nWe will assume the following data:\n\n```ruby\npeople_arrays = [\n  ['Robert', 22],\n  ['Roberta', 22],\n  ['Foo', 42],\n  ['Bar', 18]\n]\n\npeople_objects = [\n  Person.new('Robert', 22),\n  Person.new('Roberta', 22),\n  Person.new('Foo', 42),\n  Person.new('Bar', 17),\n]\n```\n\n### 1 - Wildcard Matching\n\nQo has a concept of a Wildcard, `Any`, which will match against any value\n\n```ruby\nQo[Any, Any] === ['Robert', 22] # true\n```\n\nA single wildcard will match anything, and can frequently be used as an always true:\n\n```ruby\nQo[Any] === :literally_anything_here\n```\n\n### 2 - Array Matching\n\nThe first way a Qo matcher can be defined is by using `*varargs`:\n\n```ruby\nQo::Matchers::BaseMatcher(type, *varargs, **kwargs)\n```\n\nThis gives us the `and` matcher shorthand for array matchers.\n\n#### 2.1 - Array matched against an Array\n\nWhen an Array matcher is run against an Array, it will compare elements by index in the following priority:\n\n1. Does it case match (`===`)?\n2. Does it have a predicate method by that name that matches?\n\nThis functionality is left biased and permissive, meaning that if the right side of the argument is longer it will ignore those items in the match. If it's shorter? Not so much.\n\n##### 2.1.1 - Case Match present\n\nWe've seen some case matching so far with `Range` and `Regex`:\n\n```ruby\n# Standalone\n\nQo[/Rob/, Any] === ['Robert', 22]\n# =\u003e true\n\n# Case statement\n\ncase ['Roberta', 22]\nwhen Qo[Any, 0..9] then 'child'\nwhen Qo[Any, 10..19] then 'teen'\nwhen Qo[Any, 20..99] then 'adult'\nelse 'not sure'\nend\n# =\u003e 'adult'\n\n# Select\n\npeople_arrays.select(\u0026Qo[Any, 10..19])\n# =\u003e [['Bar', 18]]\n```\n\n##### 2.1.2 - Predicate Method matched\n\nIf no case match is found, it will attempt to see if a predicate method by the same name exists, call it, and check the result:\n\n```ruby\ndirty_values = [nil, '', true]\n\n# Standalone\n\nQo[:nil?] === [nil]\n# =\u003e true, though you could also just use Qo[nil]\n\n# Case statement\n\ncase ['Roberta', nil]\nwhen Qo[Any, :nil?] then 'no age'\nelse 'not sure'\nend\n# =\u003e 'no age'\n\n# Select\n\npeople_arrays.select(\u0026Qo[Any, :even?])\n# =\u003e [[\"Robert\", 22], [\"Roberta\", 22], [\"Foo\", 42], [\"Bar\", 18]]\n```\n\n#### 2.2 - Array matched against an Object\n\nWhen an Array matcher is matched against anything other than an Array it will follow the priority:\n\n2. Does it case match (`===`)?\n3. Does it have a predicate method by that name that matches?\n\nEvery argument provided will be run against the target object.\n\n##### 2.2.1 - Case Match present\n\n```ruby\n# Standalone\n\nQo[Integer, 15..25] === 20\n# =\u003e true\n\n# Case statement - functionally indistinguishable from a regular case statement\n\n# Select\n\n[nil, '', 10, 'string'].select(\u0026Qo.or(/str/, 10..20))\n# =\u003e [10, \"string\"]\n```\n\n##### 2.2.2 - Predicate Method matched\n\nNow this is where some of the fun starts in\n\n```ruby\n# Standalone\n\nQo.or(:nil?, :empty?) === nil\n# =\u003e true\nQo.not(:nil?, :empty?) === nil\n# =\u003e false\n\n# Case statement\n\ncase 42\nwhen Qo[Integer, :even?, 40..50] then 'oddly specific number criteria'\nelse 'nope'\nend\n# =\u003e \"oddly specific number criteria\"\n\n# Reject\n\n[nil, '', 10, 'string'].reject(\u0026Qo.or(:nil?, :empty?))\n# =\u003e [10, \"string\"]\n```\n\n### 3 - Hash Matching\n\n#### 3.1 - Hash matched against a Hash\n\n1. Does the key exist on the other hash?\n2. Are the match value and match target hashes?\n3. Does the target object's value case match against the match value?\n4. Does the target object's value predicate match against the match value?\n5. What about the String version of the match key? Abort if it can't coerce.\n\n##### 3.1.1 - Key present\n\nChecks to see if the key is even present on the other object, false if not.\n\n##### 3.1.2 - Match value and target are hashes\n\nIf both the match value (`match_key: matcher`) and the match target are hashes, Qo will begin a recursive descent starting at the match key until it finds a matcher to try out:\n\n```ruby\nQo[a: {b: {c: 5..15}}] === {a: {b: {c: 10}}}\n# =\u003e true\n\n# Na, no fun. Deeper!\nQo.and(a: {\n  f: 5..15,\n  b: {\n    c: /foo/,\n    d: 10..30\n  }\n}).call(a: {\n  f: 10,\n  b: {\n    c: 'foobar',\n    d: 20\n  }\n})\n# =\u003e true\n\n# It can get chaotic with `or` though. Anything anywhere in there matches and\n# it'll pass.\nQo.or(a: {\n  f: false,\n  b: {\n    c: /nope/,\n    d: 10..30\n  }\n}).call(a: {\n  f: 10,\n  b: {\n    c: 'foobar',\n    d: 20\n  }\n})\n```\n\n##### 3.1.3 - Case match present\n\nIf a case match is present for the key, it'll try and compare:\n\n```ruby\n# Standalone\n\nQo[name: /Foo/] === {name: 'Foo'}\n# =\u003e true\n\n# Case statement\n\ncase {name: 'Foo', age: 42}\nwhen Qo[age: 40..50] then 'Gotcha!'\nelse 'nope'\nend\n# =\u003e \"Gotcha!\"\n\n# Select\n\npeople_hashes = people_arrays.map { |n, a| {name: n, age: a} }\npeople_hashes.select(\u0026Qo[age: 15..25])\n# =\u003e [{:name=\u003e\"Robert\", :age=\u003e22}, {:name=\u003e\"Roberta\", :age=\u003e22}, {:name=\u003e\"Bar\", :age=\u003e18}]\n```\n\n##### 3.1.4 - Predicate match present\n\nMuch like our array friend above, if a predicate style method is present see if it'll work\n\n```ruby\n# Standalone\n\nQo[name: :empty?] === {name: ''}\n# =\u003e true\n\n# Case statement\n\ncase {name: 'Foo', age: nil}\nwhen Qo[age: :nil?] then 'No age provided!'\nelse 'nope'\nend\n# =\u003e \"No age provided!\"\n\n# Reject\n\npeople_hashes = people_arrays.map { |(n,a)| {name: n, age: a} } \u003c\u003c {name: 'Ghost', age: nil}\npeople_hashes.reject(\u0026Qo[age: :nil?])\n# =\u003e [{:name=\u003e\"Robert\", :age=\u003e22}, {:name=\u003e\"Roberta\", :age=\u003e22}, {:name=\u003e\"Bar\", :age=\u003e18}]\n```\n\nCareful though, if the key doesn't exist that won't match. I'll have to consider this one later.\n\n##### 3.1.5 - String variant present\n\nCoerces the key into a string if possible, and sees if that can provide a valid case match\n\n#### 3.2 - Hash matched against an Object\n\n1. Does the object respond to the match key?\n2. Does the result of sending the match key as a method case match the provided value?\n3. Does a predicate method exist for it?\n\n##### 3.2.1 - Responds to match key\n\nIf it doesn't know how to deal with it, false out.\n\n##### 3.2.2 - Case match present\n\nThis is where we can get into some interesting code, much like the hash selections above\n\n```ruby\n# Standalone\n\nQo[name: /Rob/] === people_objects.first\n# =\u003e true\n\n# Case statement\n\ncase people_objects.first\nwhen Qo[name: /Rob/] then \"It's Rob!\"\nelse 'Na, not them'\nend\n# =\u003e \"It's Rob!\"\n\n# Select\n\npeople_objects.select(\u0026Qo[name: /Rob/])\n# =\u003e [Person(Robert, 22), Person(Roberta, 22)]\n```\n\n##### 3.2.3 - Predicate match present\n\n```ruby\n# Standalone\n\nQo[name: :empty?] === Person.new('', 22)\n# =\u003e true\n\n# Case statement\n\ncase Person.new('', nil)\nwhen Qo[age: :nil?] then 'No age provided!'\nelse 'nope'\nend\n# =\u003e \"No age provided!\"\n\n# Select\n\npeople_hashes.select(\u0026Qo[age: :nil?])\n# =\u003e []\n```\n\n### 4 - Right Hand Pattern Matching\n\nThis is where I start going a bit off into the weeds. We're going to try and get RHA style pattern matching in Ruby.\n\n```ruby\nQo.case(['Robert', 22]) { |m|\n  m.when(Any, 20..99) { |n, a| \"#{n} is an adult that is #{a} years old\" }\n  m.else\n}\n# =\u003e \"Robert is an adult that is 22 years old\"\n```\n\n```ruby\nQo.case(people_objects.first) { |m|\n  m.when(name: Any, age: 20..99) { |person| \"#{person.name} is an adult that is #{person.age} years old\" }\n  m.else\n}\n```\n\nIn this case it's trying to do a few things:\n\n1. Iterate over every matcher until it finds a match\n2. Execute its block function\n\nIf no block function is provided, it assumes an identity function (`-\u003e v { v }`) instead. If no match is found, `nil` will be returned.\n\n```ruby\nname_longer_than_three = -\u003e person { person.name.size \u003e 3 }\n\npeople_objects.map(\u0026Qo.match { |m|\n  m.when(name_longer_than_three) { |person| Person.new(person.name[0..2], person.age) }\n\n  m.else\n})\n\n# =\u003e [Person(age: 22, name: \"Rob\"), Person(age: 22, name: \"Rob\"), Person(age: 42, name: \"Foo\"), Person(age: 17, name: \"Bar\")]\n```\n\nSo we just truncated everyone's name that was longer than three characters.\n\n### 5 - Helper functions\n\nThere are a few functions added for convenience, and it should be noted that because all Qo matchers respond to `===` that they can be used as helpers as well.\n\n#### 5.1 - Dig\n\nDig is used to get in deep at a nested hash value. It takes a dot-path and a `===` respondent matcher:\n\n```ruby\nQo.dig('a.b.c', Qo.or(1..5, 15..25)) === {a: {b: {c: 1}}}\n# =\u003e true\n\nQo.dig('a.b.c', Qo.or(1..5, 15..25)) === {a: {b: {c: 20}}}\n# =\u003e true\n```\n\nTo be fair that means anything that can respond to `===`, including classes and other such things.\n\n#### 5.2 - Count By\n\nThis ends up coming up a lot, especially around querying, so let's get a way to count by!\n\n```ruby\nQo.count_by([1,2,3,2,2,2,1])\n\n# =\u003e {\n#   1 =\u003e 2,\n#   2 =\u003e 4,\n#   3 =\u003e 1\n# }\n\nQo.count_by([1,2,3,2,2,2,1], \u0026:even?)\n\n# =\u003e {\n#   false =\u003e 3,\n#   true  =\u003e 4\n# }\n```\n\nThis feature may be added to Ruby 2.6+: https://bugs.ruby-lang.org/issues/11076\n\n### 6 - Custom Pattern Matchers\n\nWith the release of Qo 1.0.0 we introduced the idea of custom branches and pattern matchers for more advanced\nusers of the library.\n\nConsider a Monadic type like `Some` and `None`:\n\n```ruby\n# Technically Some and None don't exist yet, so we have to \"cheat\" instead\n# of just saying `Some` for the precondition\n#\n# We start by defining two branches that match against a Some type and a None\n# type, extracting the value on match before yielding to their associated\n# functions\nSomeBranch = Qo.create_branch(\n  name:        'some',\n  precondition: -\u003e v { v.is_a?(Some) },\n  extractor:    :value\n)\n\nNoneBranch = Qo.create_branch(\n  name:        'none',\n  precondition: -\u003e v { v.is_a?(None) },\n  extractor:    :value\n)\n\n# Now we create a new pattern matching class with those branches. Note that\n# there's nothing stopping you from making as many branches as you want,\n# except that it may get confusing after a while.\nSomePatternMatch = Qo.create_pattern_match(branches: [\n  SomeBranch,\n  NoneBranch\n])\n\nclass Some\n  # There's also a provided mixin that gives an `match` method that\n  # works exactly like a pattern match without having to use it explicitly\n  include SomePatternMatch.mixin\n\n  attr_reader :value\n\n  def initialize(value) @value = value end\n  def self.[](value)    new(value)     end\n\n  def fmap(\u0026fn)\n    new_value = fn.call(value)\n    new_value ? Some[new_value] : None[value]\n  end\nend\n\nclass None\n  include SomePatternMatch.mixin\n\n  attr_reader :value\n\n  def initialize(value) @value = value end\n  def self.[](value)    new(value)     end\n\n  def fmap(\u0026fn) None[value] end\nend\n\n# So now we can pattern match with `some` and `none` branches using the `match`\n# method that was mixed into both types.\nSome[1]\n  .fmap { |v| v * 2 }\n  .match { |m|\n    m.some { |v| v + 100 }\n    m.none { \"OHNO!\" }\n  }\n=\u003e 102\n\nSome[1]\n  .fmap { |v| nil }\n  .match { |m|\n    m.some { |v| v + 100 }\n    m.none { \"OHNO!\" }\n  }\n=\u003e \"OHNO!\"\n```\n\n### 7 - Hacky Fun Time\n\nThese examples will grow over the next few weeks as I think of more fun things to do with Qo. PRs welcome if you find fun uses!\n\n#### 7.1 - JSON and HTTP\n\n\u003e Note that Qo does not support deep querying of hashes (yet)\n\n##### 7.1.1 - JSON Placeholder\n\nQo tries to be clever though, it assumes Symbol keys first and then String keys, so how about some JSON?:\n\n```ruby\nrequire 'json'\nrequire 'net/http'\n\nposts = JSON.parse(\n  Net::HTTP.get(URI(\"https://jsonplaceholder.typicode.com/posts\")), symbolize_names: true\n)\n\nusers = JSON.parse(\n  Net::HTTP.get(URI(\"https://jsonplaceholder.typicode.com/users\")), symbolize_names: true\n)\n\n# Get all posts where the userId is 1.\nposts.select(\u0026Qo[userId: 1])\n\n# Get users named Nicholas or have two names and an address somewhere with a zipcode\n# that starts with 9 or 4.\n#\n# Qo matchers return a `===` respondant object, remember, so we can totally nest them.\nusers.select(\u0026Qo.and(\n  name: Qo.or(/^Nicholas/, /^\\w+ \\w+$/),\n  address: {\n    zipcode: Qo.or(/^9/, /^4/)\n  }\n))\n\n# We could even use dig to get at some of the same information. This and the above will\n# return the same results even.\nusers.select(\u0026Qo.and(\n  Qo.dig('address.zipcode', Qo.or(/^9/, /^4/)),\n  name: Qo.or(/^Nicholas/, /^\\w+ \\w+$/)\n))\n```\n\nNifty!\n\n##### Yield Self HTTP status matching\n\nYou can even use `#yield_self` to pipe values into a pattern matching block. In\nthis particular case it'll let you check against the type signatures of the\nHTTP responses.\n\n```ruby\ndef get_url(url)\n  Net::HTTP.get_response(URI(url)).yield_self(\u0026Qo.match { |m|\n    m.when(Net::HTTPSuccess) { |response| response.body.size },\n    m.else                   { |response| raise response.message }\n  })\nend\n\nget_url('https://github.com/baweaver/qo')\n# =\u003e 142387\nget_url('https://github.com/baweaver/qo/does_not_exist')\n# =\u003e RuntimeError: Not Found\n```\n\nThe difference between this and case? Well, the first is you can pass this to\n`yield_self` for a more inline solution. The second is that any Qo matcher can\nbe used in there, including predicate and content checks on the body:\n\n```ruby\nm.when(Net::HTTPSuccess, body: /Qo/)\n```\n\nYou could put as many checks as you want in there, or use different Qo matchers\nnested to get even further in.\n\nNow if we wanted to add more power and create an HTTP matcher:\n\n```ruby\nHTTP_Matcher = Qo.create_pattern_match(branches: [\n  Qo.create_branch(name: 'success', precondition: Net::HTTPSuccess),\n  Qo.create_branch(name: 'error', precondition: Net::HTTPError),\n  Qo::Braches::ElseBranch\n])\n\ndef get_url(url)\n  Net::HTTP.get_response(URI(url)).then(\u0026HTTP_Matcher.match { |m|\n    m.success { |response| response.body.size },\n    m.else    { |response| raise response.message }\n  })\nend\n```\n\n#### 7.2 - Opsy Stuff\n\n##### 7.2.1 - NMap\n\nWhat about NMap for our Opsy friends? Well, simulated, but still fun.\n\n```ruby\nhosts = (`nmap -oG - -sP 192.168.1.* 10.0.0.* | grep Host`).lines.map { |v| v.split[1..2] }\n=\u003e [[\"192.168.1.1\", \"(Router)\"], [\"192.168.1.2\", \"(My Computer)\"], [\"10.0.0.1\", \"(Gateway)\"]]\n\nhosts.select(\u0026Qo[IPAddr.new('192.168.1.1/8')])\n=\u003e [[\"192.168.1.1\", \"(Router)\"], [\"192.168.1.2\", \"(My Computer)\"]]\n```\n\n##### 7.2.2 - `df`\n\nThe nice thing about Unix style commands is that they use headers, which means CSV can get a hold of them for some good formatting. It's also smart enough to deal with space separators that may vary in length:\n\n```ruby\nrows = CSV.new(`df -h`, col_sep: \" \", headers: true).read.map(\u0026:to_h)\n\nrows.map(\u0026Qo.match { |m|\n  m.when(Avail: /Gi$/) { |row|\n    \"#{row['Filesystem']} mounted on #{row['Mounted']} [#{row['Avail']} / #{row['Size']}]\"\n  }\n}).compact\n# =\u003e [\"/dev/***** mounted on / [186Gi / 466Gi]\"]\n```\n\n## Installation\n\nAdd this line to your application's Gemfile:\n\n```ruby\ngem 'qo'\n```\n\nAnd then execute:\n\n    $ bundle\n\nOr install it yourself as:\n\n    $ gem install qo\n\n## Development\n\nAfter checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.\n\nTo install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).\n\n## Contributing\n\nBug reports and pull requests are welcome on GitHub at https://github.com/baweaver/qo. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct.\n\n## License\n\nThe gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).\n\n## Code of Conduct\n\nEveryone interacting in the Qo project’s codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/baweaver/qo/blob/master/CODE_OF_CONDUCT.md).\n","funding_links":[],"categories":["Ruby"],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbaweaver%2Fqo","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fbaweaver%2Fqo","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbaweaver%2Fqo/lists"}