{"id":13747490,"url":"https://github.com/piotrmurach/necromancer","last_synced_at":"2025-04-04T10:07:11.316Z","repository":{"id":23969800,"uuid":"27352340","full_name":"piotrmurach/necromancer","owner":"piotrmurach","description":"Conversion from one object type to another with a bit of black magic.","archived":false,"fork":false,"pushed_at":"2024-03-20T23:02:22.000Z","size":166,"stargazers_count":135,"open_issues_count":0,"forks_count":6,"subscribers_count":5,"default_branch":"master","last_synced_at":"2024-05-22T10:42:35.001Z","etag":null,"topics":["coercion","conversions","ruby","ruby-gem","transformation","type-conversion"],"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/piotrmurach.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":".github/FUNDING.yml","license":"LICENSE.txt","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null},"funding":{"github":"piotrmurach"}},"created_at":"2014-11-30T22:27:21.000Z","updated_at":"2024-06-18T16:51:49.600Z","dependencies_parsed_at":"2024-01-13T03:01:12.411Z","dependency_job_id":"2bf23d34-2feb-4ff7-a740-7f91cdd2f819","html_url":"https://github.com/piotrmurach/necromancer","commit_stats":{"total_commits":193,"total_committers":4,"mean_commits":48.25,"dds":"0.020725388601036232","last_synced_commit":"d8804e9e849ee1cc6e30ac37ca1b96482de94d11"},"previous_names":[],"tags_count":7,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/piotrmurach%2Fnecromancer","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/piotrmurach%2Fnecromancer/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/piotrmurach%2Fnecromancer/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/piotrmurach%2Fnecromancer/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/piotrmurach","download_url":"https://codeload.github.com/piotrmurach/necromancer/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247157046,"owners_count":20893202,"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":["coercion","conversions","ruby","ruby-gem","transformation","type-conversion"],"created_at":"2024-08-03T06:01:30.973Z","updated_at":"2025-04-04T10:07:11.296Z","avatar_url":"https://github.com/piotrmurach.png","language":"Ruby","funding_links":["https://github.com/sponsors/piotrmurach"],"categories":["Ruby"],"sub_categories":[],"readme":"# Necromancer\n\n[![Gem Version](https://badge.fury.io/rb/necromancer.svg)][gem]\n[![Actions CI](https://github.com/piotrmurach/necromancer/workflows/CI/badge.svg?branch=master)][gh_actions_ci]\n[![Build status](https://ci.appveyor.com/api/projects/status/qj3xn5gbbfi4puet?svg=true)][appveyor]\n[![Code Climate](https://codeclimate.com/github/piotrmurach/necromancer/badges/gpa.svg)][codeclimate]\n[![Coverage Status](https://coveralls.io/repos/github/piotrmurach/necromancer/badge.svg?branch=master)][coverage]\n[![Inline docs](https://inch-ci.org/github/piotrmurach/necromancer.svg?branch=master)][inchpages]\n\n[gem]: https://badge.fury.io/rb/necromancer\n[gh_actions_ci]: https://github.com/piotrmurach/necromancer/actions?query=workflow%3ACI\n[appveyor]: https://ci.appveyor.com/project/piotrmurach/necromancer\n[codeclimate]: https://codeclimate.com/github/piotrmurach/necromancer\n[coverage]: https://coveralls.io/github/piotrmurach/necromancer\n[inchpages]: https://inch-ci.org/github/piotrmurach/necromancer\n\n\u003e Conversion from one object type to another with a bit of black magic.\n\n**Necromancer** provides independent type conversion component for [TTY](https://github.com/piotrmurach/tty) toolkit.\n\n## Motivation\n\nConversion between Ruby core types frequently comes up in projects but is solved by half-baked solutions. This library aims to provide an independent and extensible API to support a robust and generic way to convert between core Ruby types.\n\n## Features\n\n* Simple and expressive API\n* Ability to specify own converters\n* Ability to compose conversions out of simpler ones\n* Support conversion of custom defined types\n* Ability to specify strict conversion mode\n\n## Installation\n\nAdd this line to your application's Gemfile:\n\n```ruby\ngem \"necromancer\"\n```\n\nAnd then execute:\n\n    $ bundle\n\nOr install it yourself as:\n\n    $ gem install necromancer\n\n## Contents\n\n* [1. Usage](#1-usage)\n* [2. Interface](#2-interface)\n  * [2.1 convert](#21-convert)\n  * [2.2 from](#22-from)\n  * [2.3 to](#23-to)\n  * [2.4 can?](#24-can)\n  * [2.5 configure](#25-configure)\n* [3. Converters](#3-converters)\n  * [3.1 Array](#31-array)\n  * [3.2 Boolean](#32-boolean)\n  * [3.3 DateTime](#33-datetime)\n  * [3.4 Hash](#34-hash)\n  * [3.5 Numeric](#35-numeric)\n  * [3.6 Range](#36-range)\n  * [3.7 Custom](#37-custom)\n    * [3.7.1 Using an Object](#371-using-an-object)\n    * [3.7.2 Using a Proc](#372-using-a-proc)\n\n## 1. Usage\n\n**Necromancer** knows how to handle conversions between various types using the `convert` method. The `convert` method takes as an argument the value to convert from.  Then to perform actual coercion use the `to` or more functional style `\u003e\u003e` method that accepts the type for the returned value which can be `:symbol`, `object` or `ClassName`.\n\nFor example, to convert a string to a [range](#36-range) type:\n\n```ruby\nNecromancer.convert(\"1-10\").to(:range)  # =\u003e 1..10\nNecromancer.convert(\"1-10\") \u003e\u003e :range   # =\u003e 1..10\nNecromancer.convert(\"1-10\") \u003e\u003e Range    # =\u003e 1..10\n```\n\nIn order to handle [boolean](#32-boolean) conversions:\n\n```ruby\nNecromancer.convert(\"t\").to(:boolean)   # =\u003e true\nNecromancer.convert(\"t\") \u003e\u003e true        # =\u003e true\n```\n\nTo convert string to [numeric](#35-numeric) value:\n\n```ruby\nNecromancer.convert(\"10e1\").to(:numeric)  # =\u003e 100\n```\n\nYou can convert string to [array](#31-array) of values like `boolean`, `integer` or `float`:\n\n```ruby\nNecromancer.convert(\"t,f,t\"]).to(:booleans)      # =\u003e [true, false, true]\nNecromancer.convert(\"1,2.3,3.0\"]).to(:integers)  # =\u003e [1, 2, 3]\nNecromancer.convert(\"1,2.3,3.0\"]).to(:floats)    # =\u003e [1.0, 2.3, 3.0]\n```\n\nTo convert string to [hash](#34-hash) value:\n\n```ruby\nNecromancer.convert(\"a:1 b:2 c:3\").to(:hash) # =\u003e {a: \"1\", b: \"2\", c: \"3\"}\nNecromancer.convert(\"a=1 b=2 c=3\").to(:hash) # =\u003e {a: \"1\", b: \"2\", c: \"3\"}\n````\n\nTo provide extra information about the conversion value type use the `from`:\n\n```ruby\nNecromancer.convert([\"1\", \"2.3\", \"3.0\"]).from(:array).to(:numeric) # =\u003e [1, 2.3, 3.0]\n```\n\n**Necromancer** also allows you to add [custom](#37-custom) conversions.\n\nWhen conversion isn't possible, a `Necromancer::NoTypeConversionAvailableError` is thrown indicating that `convert` doesn't know how to perform the requested conversion:\n\n```ruby\nNecromancer.convert(:foo).to(:float)\n# =\u003e Necromancer::NoTypeConversionAvailableError: Conversion 'foo-\u003efloat' unavailable.\n```\n\n## 2. Interface\n\n**Necromancer** will perform conversions on the supplied object through use of `convert`, `from` and `to` methods.\n\n### 2.1 convert\n\nFor the purpose of divination, **Necromancer** uses `convert` method to turn source type into target type. For example, in order to convert a string into a range type do:\n\n```ruby\nNecromancer.convert(\"1,10\").to(:range)  #  =\u003e 1..10\n```\n\nAlternatively, you can use block:\n\n```ruby\nNecromancer.convert { \"1,10\" }.to(:range) # =\u003e 1..10\n```\n\nConversion isn't always possible, in which case a `Necromancer::NoTypeConversionAvailableError` is thrown indicating that `convert` doesn't know how to perform the requested conversion:\n\n```ruby\nNecromancer.convert(:foo).to(:float)\n# =\u003e Necromancer::NoTypeConversionAvailableError: Conversion 'foo-\u003efloat' unavailable.\n```\n\n### 2.2 from\n\nTo specify conversion source type use `from` method:\n\n```ruby\nNecromancer.convert(\"1.0\").from(:string).to(:numeric)\n```\n\nIn majority of cases you do not need to specify `from` as the type will be inferred from the `convert` method argument and then appropriate conversion will be applied to result in `target` type such as `:numeric`. However, if you do not control the input to `convert` and want to ensure consistent behaviour please use `from`.\n\nThe source parameters are:\n\n* `:array`\n* `:boolean`\n* `:date`\n* `:datetime`\n* `:float`\n* `:integer`\n* `:numeric`\n* `:range`\n* `:string`\n* `:time`\n\n### 2.3 to\n\nTo convert objects between types, **Necromancer** provides several target types. The `to` or functional style `\u003e\u003e` method allows you to pass target as an argument to perform actual conversion. The target can be one of `:symbol`, `object` or `ClassName`:\n\n```ruby\nNecromancer.convert(\"yes\").to(:boolean)   # =\u003e true\nNecromancer.convert(\"yes\") \u003e\u003e :boolean    # =\u003e true\nNecromancer.convert(\"yes\") \u003e\u003e true        # =\u003e true\nNecromancer.convert(\"yes\") \u003e\u003e TrueClass   # =\u003e true\n```\n\nBy default, when target conversion fails the original value is returned. However, you can pass `strict` as an additional argument to ensure failure when conversion cannot be performed:\n\n```ruby\nNecromancer.convert(\"1a\").to(:integer, strict: true)\n# =\u003e raises Necromancer::ConversionTypeError\n```\n\nThe target parameters are:\n\n* `:array`\n* `:boolean`, `:booleans`, `:bools`, `:boolean_hash`, `:bool_hash`\n* `:date`\n* `:datetime`,\n* `:float`, `:floats`, `:float_hash`\n* `:integer`, `:integers`, `:ints`, `:integer_hash`, `:int_hash`\n* `:numeric`, `:numerics`, `:nums`, `:numeric_hash`, `:num_hash`\n* `:range`\n* `:string`\n* `:time`\n\n### 2.4 can?\n\nTo verify that a given conversion can be handled by **Necromancer** call `can?` with the `source` and `target` of the desired conversion.\n\n```ruby\nconverter = Necromancer.new\nconverter.can?(:string, :integer)   # =\u003e true\nconverter.can?(:unknown, :integer)  # =\u003e false\n```\n\n### 2.5 configure\n\nYou may set global configuration options on **Necromancer** instance by passing a block like so:\n\n```ruby\nNecromancer.new do |config|\n  config.strict true\nend\n```\n\nOr calling `configure` method:\n\n```ruby\nconverter = Necromancer.new\nconverter.configure do |config|\n  config.copy false\nend\n```\n\nAvailable configuration options are:\n\n* `strict` - ensures correct types for conversion, by default `false`\n* `copy` - ensures only copy is modified, by default `true`\n\n## 3. Converters\n\n**Necromancer** flexibility means you can register your own converters or use the already defined converters for such types as `Array`, `Boolean`, `Date`, `DateTime`, `Hash`, `Numeric`, `Range` and `Time`.\n\n### 3.1 Array\n\nThe **Necromancer** allows you to transform arbitrary object into array:\n\n```ruby\nNecromancer.convert(nil).to(:array)     # =\u003e []\nNecromancer.convert({x: 1}).to(:array)  # =\u003e [[:x, 1]]\n```\n\nIn addition, **Necromancer** excels at converting `,` or `-` delimited string into an array object:\n\n```ruby\nNecromancer.convert(\"a, b, c\").to(:array)  # =\u003e [\"a\", \"b\", \"c\"]\n```\n\nIf the string is a list of `-` or `,` separated numbers, they will be converted to their respective numeric types:\n\n```ruby\nNecromancer.convert(\"1 - 2 - 3\").to(:array)  # =\u003e [1, 2, 3]\n```\n\nIt handles conversion of string into an array of boolean values as well:\n\n```ruby\nNecromancer.convert(\"yes,no,t\").to(:booleans)    # =\u003e [true, false, true]\nNecromancer.convert(\"1 - f - FALSE\").to(:bools)  # =\u003e [true, false, false]\n```\n\nYou can also convert array containing string objects to array containing numeric values:\n\n```ruby\nNecromancer.convert([\"1\", \"2.3\", \"3.0\"]).to(:numerics) # =\u003e [1, 2.3, 3.0]\nNecromancer.convert([\"1\", \"2.3\", \"3.0\"]).to(:nums)     # =\u003e [1, 2.3, 3.0]\n```\n\nOr you can be more specific by using `:integers` and `:floats` as the resulting type:\n\n```ruby\nNecromancer.convert([\"1\", \"2.3\", \"3.0\"]).to(:integers) # =\u003e [1, 2, 3]\n```\n\nWhen in `strict` mode the conversion will raise a `Necromancer::ConversionTypeError` error like so:\n\n```ruby\nNecromancer.convert([\"1\", \"2.3\", false]).to(:numerics, strict: true)\n# =\u003e Necromancer::ConversionTypeError: false cannot be converted from `array` to `numerics`\n```\n\nHowever, in `non-strict` mode the value will be simply returned unchanged:\n\n```ruby\nNecromancer.convert([\"1\", \"2.3\", false]).to(:numerics, strict: false)\n# =\u003e [1, 2.3, false]\n```\n\n### 3.2 Boolean\n\nThe **Necromancer** allows you to convert a string object to boolean object. The `1`, `\"1\"`, `\"t\"`, `\"T\"`, `\"true\"`, `\"TRUE\"`, `\"y\"`, `\"Y\"`, `\"yes\"`, `\"Yes\"`, `\"on\"`, `\"ON\"` values are converted to `TrueClass`.\n\n```ruby\nNecromancer.convert(\"yes\").to(:boolean)  # =\u003e true\n```\n\nSimilarly, the `0`, `\"0\"`, `\"f\"`, `\"F\"`, `\"false\"`, `\"FALSE\"`, `\"n\"`, `\"N\"`, `\"no\"`, `\"No\"`, `\"off\"`, `\"OFF\"` values are converted to `FalseClass`.\n\n```ruby\nNecromancer.convert(\"no\").to(:boolean) # =\u003e false\n```\n\nYou can also convert an integer object to boolean:\n\n```ruby\nNecromancer.convert(1).to(:boolean)  # =\u003e true\nNecromancer.convert(0).to(:boolean)  # =\u003e false\n```\n\n### 3.3 DateTime\n\n**Necromancer** knows how to convert string to `date` object:\n\n```ruby\nNecromancer.convert(\"1-1-2015\").to(:date)    # =\u003e \"2015-01-01\"\nNecromancer.convert(\"01/01/2015\").to(:date)  # =\u003e \"2015-01-01\"\n```\n\nYou can also convert string to `datetime`:\n\n```ruby\nNecromancer.convert(\"1-1-2015\").to(:datetime)          # =\u003e \"2015-01-01T00:00:00+00:00\"\nNecromancer.convert(\"1-1-2015 15:12:44\").to(:datetime) # =\u003e \"2015-01-01T15:12:44+00:00\"\n```\n\nTo convert a string to a time instance do:\n\n```ruby\nNecromancer.convert(\"01-01-2015\").to(:time)       # =\u003e 2015-01-01 00:00:00 +0100\nNecromancer.convert(\"01-01-2015 08:35\").to(:time) # =\u003e 2015-01-01 08:35:00 +0100\nNecromancer.convert(\"12:35\").to(:time)            # =\u003e 2015-01-04 12:35:00 +0100\n```\n\n### 3.4 Hash\n\nWith **Necromancer** you can convert a string with pairs delimited by `=` or `:` characters into a hash:\n\n```ruby\nNecromancer.convert(\"a:1 b:2 c:3\").to(:hash)\nNecromancer.convert(\"a=1 b=2 c=3\").to(:hash)\n# =\u003e {a: \"1\", b: \"2\", c: \"3\"}\n```\n\nThe pairs can be separated by `\u0026` symbols and mix `=` and `:` pair delimiters:\n\n```ruby\nNecromancer.convert(\"a:1 \u0026 b=2 \u0026 c:3\").to(:hash)\n# =\u003e {a: \"1\", b: \"2\", c: \"3\"}\n```\n\nYou can also convert string to hash with integer values using `:int_hash` type:\n\n```ruby\nNecromancer.convert(\"a:1 b:2 c:3\").to(:int_hash)     # =\u003e {a: 1, b: 2, c: 3}\nNecromancer.convert(\"a:1 b:2 c:3\").to(:integer_hash) # =\u003e {a: 1, b: 2, c: 3}\n```\n\nSimilarly you can convert string to hash with `float` or `numeric` values using `:float_hash` and `numeric_hash` types:\n\n```ruby\nNecromancer.convert(\"a:1 b:2 c:3\").to(:float_hash)    # =\u003e {a: 1.0, b: 2.0, c: 3.0}\nNecromancer.convert(\"a:1 b:2.0 c:3\").to(:num_hash)    # =\u003e {a: 1, b:2.0, c: 3}\n```\n\nString can also be converted to hash with boolean values using `:boolean_hash` or `:bool_hash`:\n\n```ruby\nNecromancer.convert(\"a:yes b:no c:t\").to(:bool_hash)  # =\u003e {a: true, b: false, c: true}\n```\n\n### 3.5 Numeric\n\n**Necromancer** comes ready to convert all the primitive numeric values.\n\nTo convert a string to a float do:\n\n```ruby\nNecromancer.convert(\"1.2a\").to(:float)  #  =\u003e 1.2\n```\n\nConversion to numeric in strict mode raises `Necromancer::ConversionTypeError`:\n\n```ruby\nNecromancer.convert(\"1.2a\").to(:float, strict: true) # =\u003e raises error\n```\n\nTo convert a string to an integer do:\n\n```ruby\nNecromancer.convert(\"1a\").to(:integer)  #  =\u003e 1\n```\n\nHowever, if you want to convert string to an appropriate matching numeric type do:\n\n```ruby\nNecromancer.convert(\"1e1\").to(:numeric)   # =\u003e 10\n```\n\n### 3.6 Range\n\n**Necromancer** is no stranger to figuring out ranges from strings. You can pass `,`, `-`, `..`, `...` characters to denote ranges:\n\n```ruby\nNecromancer.convert(\"1,10\").to(:range)  # =\u003e 1..10\n```\n\nOr to create a range of letters:\n\n```ruby\nNecromancer.convert(\"a-z\").to(:range)   # =\u003e \"a\"..\"z\"\n```\n\nIt will handle space characters:\n\n```ruby\nNecromancer.convert(\"1 . . 10\") \u003e\u003e :range   # =\u003e 1..10\nNecromancer.convert(\"a . . . z\") \u003e\u003e :range  # =\u003e  \"a\"...\"z\"\n````\n\n### 3.7 Custom\n\nIn case where provided conversions do not match your needs you can create your own and `register` with **Necromancer** by using an `Object` or a `Proc`.\n\n#### 3.7.1 Using an Object\n\nFirstly, you need to create a converter that at minimum requires to specify `call` method that will be invoked during conversion:\n\n```ruby\nUpcaseConverter = Struct.new(:source, :target) do\n  def call(value, options = {})\n    value.upcase\n  end\nend\n```\n\nInside the `UpcaseConverter` you have access to global configuration options by directly calling `config` method.\n\nThen you need to specify what type conversions this converter will support. For example, `UpcaseConverter` will allow a string object to be converted to a new string object with content upper cased. This can be done:\n\n```ruby\nupcase_converter = UpcaseConverter.new(:string, :upcase)\n```\n\n**Necromancer** provides the `register` method to add converter:\n\n```ruby\nconverter = Necromancer.new\nconverter.register(upcase_converter)   # =\u003e true if successfully registered\n```\n\nFinally, by invoking `convert` method and specifying `:upcase` as the target for the conversion we achieve the result:\n\n```ruby\nconverter.convert(\"magic\").to(:upcase)   # =\u003e \"MAGIC\"\n```\n\n#### 3.7.2 Using a Proc\n\nUsing a Proc object you can create and immediately register a converter. You need to pass `source` and `target` of the conversion that will be used later on to match the conversion. The `convert` allows you to specify the actual conversion in block form. For example:\n\n```ruby\nconverter = Necromancer.new\n\nconverter.register do |c|\n  c.source= :string\n  c.target= :upcase\n  c.convert = proc { |value, options| value.upcase }\nend\n```\n\nThen by invoking the `convert` method and passing the `:upcase` conversion type you can transform the string like so:\n\n```ruby\nconverter.convert(\"magic\").to(:upcase)   # =\u003e \"MAGIC\"\n```\n\n## Contributing\n\nBug reports and pull requests are welcome on GitHub at https://github.com/piotrmurach/necromancer. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [code of conduct](https://github.com/piotrmurach/necromancer/blob/master/CODE_OF_CONDUCT.md).\n\n1. Fork it ( https://github.com/piotrmurach/necromancer/fork )\n2. Create your feature branch (`git checkout -b my-new-feature`)\n3. Commit your changes (`git commit -am 'Add some feature'`)\n4. Push to the branch (`git push origin my-new-feature`)\n5. Create a new Pull Request\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## Copyright\n\nCopyright (c) 2014 Piotr Murach. See LICENSE for further details.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpiotrmurach%2Fnecromancer","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fpiotrmurach%2Fnecromancer","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpiotrmurach%2Fnecromancer/lists"}