README.md in crimp-0.2.0 vs README.md in crimp-1.0.0

- old
+ new

@@ -1,48 +1,190 @@ # Crimp [![Build Status](https://travis-ci.org/BBC-News/crimp.png?branch=master)](https://travis-ci.org/BBC-News/crimp) [![Gem Version](https://badge.fury.io/rb/crimp.png)](http://badge.fury.io/rb/crimp) -Creating an md5 hash of a number, string, array, or hash in Ruby +Creates an MD5 hash from simple data structures made of numbers, strings, booleans, nil, arrays or hashes. -![mighty-boosh-four-way-crimp-o](https://f.cloud.github.com/assets/180050/2148112/b44fd6fa-93de-11e3-9f9a-ad941f069b5c.gif) - -Shamelessly copied from [this Stack Overflow -answer](http://stackoverflow.com/a/6462589/3243663). - ## Installation Add this line to your application's Gemfile: - gem 'crimp' +```ruby +gem 'crimp' +``` + And then execute: - $ bundle +```shell +$ bundle +``` + Or install it yourself as: - $ gem install crimp +```shell +$ gem install crimp +``` ## Usage -```rb +```ruby require 'crimp' -Crimp.stringify({:a => {:b => 'b', :c => 'c'}, :d => 'd'}) +Crimp.signature({ a: { b: 1 } }) +=> "ac13c15d07e5fa3992fc6b15113db900" +``` -# => [\"aSymbol=>[\\\"bSymbol=>b\\\", \\\"cSymbol=>c\\\"]Array\",\"dSymbol=>d\"]Array" +## Multiplatform design -Crimp.signature({:a => {:b => 'b', :c => 'c'}, :d => 'd'}) +At the BBC we use Crimp to build keys for database and cache entries. -# => "68d07febc4f47f56fa6ef5de063a77b1" +If you want to build a similar library with your language of choice you should be able to follow the simple specifications defined in `spec/crimp_spec.rb`. Using these simple rules you will produce a string ready to be MD5 signed. +Once you get your string, is very important to be sure that you can produce the same key in any language. MD5 is your friend: + +### Ruby + +```ruby +irb(main):001:0> require 'digest' +=> true +irb(main):002:0> Digest::MD5.hexdigest('abc') +=> "900150983cd24fb0d6963f7d28e17f72" ``` +### Lua + +```lua +Lua 5.3.5 Copyright (C) 1994-2018 Lua.org, PUC-Rio +> md5 = require 'md5' +> md5.sumhexa('abc') +900150983cd24fb0d6963f7d28e17f72 +``` + +### Elixir + +``` elixir +Erlang/OTP 21 [erts-10.0.4] [source] [64-bit] [smp:4:4] [ds:4:4:10] [async-threads:1] [hipe] [dtrace] +Interactive Elixir (1.7.2) - press Ctrl+C to exit (type h() ENTER for help) +iex(1)> :crypto.hash(:md5 , "abc") |> Base.encode16() |> String.downcase +"900150983cd24fb0d6963f7d28e17f72" +``` + +### Node.js + +``` javascript +> var crypto = require('crypto'); +undefined +> crypto.createHash('md5').update('abc').digest('hex'); +'900150983cd24fb0d6963f7d28e17f72' +``` + +## Fine prints + +### Symbols + +To make Crimp signatures reproducible in any platform we decided to ignore Ruby symbols and treat them as strings, so: + +``` ruby +Crimp.signature(:a) == Crimp.Signature('a') +``` + +### Sets + +Also Sets get transformed to Arrays: + +``` ruby +Crimp.signature(Set.new(['a', 'b'])) == Crimp.signature(['a', 'b']) +``` + +### Sorting of collections + +Crimp signatures are generated against sorted collections. + +```ruby +Crimp.signature([1, 2]) == Crimp.signature([2, 1]) +Crimp.signature({'b' => 2, 'a' => 1}) == Crimp.signature({'a' => 1, 'b' => 2}) +``` + +Crimp also sorts nested collections. + +```ruby +Crimp.signature([1, [3, 2], 4]) == Crimp.signature([4, [2, 3], 1]) +Crimp.signature({'b' => {'d' => 2,'c' => 1}, 'a' => [3, 1, 2]}) == Crimp.signature({'a' => [1, 2, 3], 'b' => { 'c' => 1, 'd' => 2 }}) +``` + +### Custom objects + +Crimp will complain if you try to get a signature from an instance of some custom object: + +``` ruby +Crimp.signature(Object.new) +=> TypeError: Expected a (String|Number|Boolean|Nil|Hash|Array), Got Object +``` +It is your responsibility to pass a compatible representation of your object to Crimp. + +## Implementation details + +Under the hood Crimp annotates the passed data structure to a nested array of primitives (strings, numbers, booleans, nils, etc.) and a single byte to indicate the type of the primitive: + +| Type | Byte | +| :-: | :-: | +| String | `S` | +| Number | `N` | +| Boolean | `B` | +| nil | `_` | +| Array | `A` | +| Hash | `H` | + +You can verify it using the `#annotate` method: + +``` ruby +Crimp.annotate({ a: 1 }) +=> [[[[[1, "N"], ["a", "S"]], "A"]], "H"] +``` +Notice how Crimp marks the collection as Hash (`H`) and then transforms the tuple of key/values to an Array (`A`). + +Here's an example with nested hashes: + +```ruby +Crimp.annotate({ a: { b: 'c' } }) +=> [[[[["a", "S"], [[[[["b", "S"], ["c", "S"]], "A"]], "H"]], "A"]], "H"] +``` + +Before signing Crimp transforms the collection of nested array to a string. + +```ruby +Crimp.notation({ a: { b: 'c' } }) +=> "aSbScSAHAH" +``` + +Please note the Arrays and Hash keys are sorted before signing. + +``` ruby +Crimp.notation([3, 1, 2]) +=> "1N2N3NA" +``` + +key/value tuples get sorted as well. + +``` ruby +Crimp.notation({ a: 1 }) +=> "1NaSAH" +``` + +## Changelog + +| Version | Changes | +|---------|----------------------------------------------------------------------------| +|`v0.x` | Original version of Crimp. | +|`v0.2.0` | Crimp compatibility with Ruby >= 2.4, use this for legacy projects. | +|`v1.0.0` | Includes **breaking changes** and returns different signatures from `v0.2` | + ## Contributing -1. Fork it ( http://github.com/<my-github-username>/crimp/fork ) +1. Fork it ( http://github.com/BBC-News/crimp/fork ) 2. Create your feature branch (`git checkout -b my-new-feature`) 3. Commit your changes (`git commit -am 'Add some feature'`) 4. Push to the branch (`git push origin my-new-feature`) 5. Create new Pull Request