galaxy_converter 2.1.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: d95182718dfea56d5aa20b5367f980f4551e8023
4
+ data.tar.gz: 5c7eacee4bd821f49230dbfdab976a48cb21c040
5
+ SHA512:
6
+ metadata.gz: 19132e8261add543bf2080b29c4509bcae22e9c269d533b38ff5adc6c068abeedec758042974ea69f755c7f64477086d63971beb56415496165006625dda48d9
7
+ data.tar.gz: d634fe6ec2b59141020f40e87d02c0b641d6e375fb7f85c0888dfcc6589e3cbca837fe10076d88dff010549205e663bce9aabd987222c2aecd3588eb3955eb00
data/.gitignore ADDED
@@ -0,0 +1,10 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
10
+ /*.gem
data/Gemfile ADDED
@@ -0,0 +1,6 @@
1
+ source "https://rubygems.org"
2
+
3
+ git_source(:github) {|repo_name| "https://github.com/#{repo_name}" }
4
+
5
+ # Specify your gem's dependencies in galaxy_converter.gemspec
6
+ gemspec
data/README.md ADDED
@@ -0,0 +1,85 @@
1
+ ## Table of Contents
2
+ * [Scope](#scope)
3
+ * [Design](#design)
4
+ * [SRP](#srp)
5
+ * [Roman numerals](#roman-numerals)
6
+ * [Installation](#installation)
7
+ * [Usage](#usage)
8
+ * [Library](#library)
9
+ * [CLI](#cli)
10
+
11
+ ## Scope
12
+ This gem is the Ruby implementation of the `Merchant's Guide to the Galaxy` code-kata.
13
+
14
+ ## Design
15
+
16
+ ### SRP
17
+ The code design follows the single responsibility principle by using a dedicated class/module for any specific task.
18
+
19
+ ### Roman numerals
20
+ The design behind the `Roman numerals` logic was inspired by Sandi Metz's [solution](https://www.sandimetz.com/blog/2016/6/9/make-everything-the-same): in the beginning roman numerals can be expressed both with `subtractive` (eg `IV`) and `additive` (eg `IIII`) form.
21
+ Switching to the additive form makes easier to convert to Arabic numerals.
22
+
23
+ ## Installation
24
+ Install the gem from your shell:
25
+ ```shell
26
+ gem install galaxy_converter
27
+ ```
28
+
29
+ ## Usage
30
+
31
+ ### Library
32
+ Just require the library into your program and pass an array of notes to the converter:
33
+ ```ruby
34
+ require "galaxy_converter"
35
+
36
+ notes = ["glob is I", "prok is V", "pish is X", "tegj is L", "glob glob Silver is 34 Credits", "glob prok Gold is 57800 Credits", "pish pish Iron is 3910 Credits", "how much is pish tegj glob glob ?", "how many Credits is glob prok Silver ?", "how many Credits is glob prok Gold ?", "how many Credits is glob prok Iron ?", "how much wood could a woodchuck chuck if a woodchuck could chuck wood ? "]
37
+
38
+ puts GalaxyConverter.call(notes)
39
+ # pish tegj glob glob is 42
40
+ # glob prok Silver is 68 Credits
41
+ # glob prok Gold is 57800 Credits
42
+ # glob prok Iron is 782 Credits
43
+ # I have no idea what you are talking about
44
+ ```
45
+
46
+ ### CLI
47
+ The gem provides a CLI interface.
48
+ Once installed you will be able to use the `galaxy_converter` command from the terminal.
49
+
50
+ #### Help
51
+ You can print CLI help by:
52
+ ```shell
53
+ galaxy_converter -h
54
+ Usage: galaxy_converter ~/notes.txt
55
+ -h --help Print this help
56
+ <path-to-file> Load conversion notes
57
+ ```
58
+
59
+ #### Input
60
+ The program accepts as input a file containing several conversion notes:
61
+ ```txt
62
+ # ~/notes.txt
63
+ glob is I
64
+ prok is V
65
+ pish is X
66
+ tegj is L
67
+ glob glob Silver is 34 Credits
68
+ glob prok Gold is 57800 Credits
69
+ pish pish Iron is 3910 Credits
70
+ how much is pish tegj glob glob ?
71
+ how many Credits is glob prok Silver ?
72
+ how many Credits is glob prok Gold ?
73
+ how many Credits is glob prok Iron ?
74
+ how much wood could a woodchuck chuck if a woodchuck could chuck wood ?
75
+ ```
76
+
77
+ Just pass the file path to the program:
78
+ ```shell
79
+ galaxy_converter ~/notes.txt
80
+ > pish tegj glob glob is 42
81
+ > glob prok Silver is 68 Credits
82
+ > glob prok Gold is 57800 Credits
83
+ > glob prok Iron is 782 Credits
84
+ > I have no idea what you are talking about
85
+ ```
data/Rakefile ADDED
@@ -0,0 +1,16 @@
1
+ require "bundler/gem_tasks"
2
+ require "rake/testtask"
3
+
4
+ Rake::TestTask.new(:spec) do |t|
5
+ t.libs << "spec"
6
+ t.libs << "lib"
7
+ t.test_files = FileList["spec/**/*_spec.rb"]
8
+ end
9
+
10
+ Rake::TestTask.new(:bench) do |t|
11
+ t.libs << "spec"
12
+ t.libs << "lib"
13
+ t.test_files = FileList["spec/**/*_bench.rb"]
14
+ end
15
+
16
+ task :default => :spec
data/bin/console ADDED
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "galaxy_converter"
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ # (If you use this, don't forget to add pry to your Gemfile!)
10
+ # require "pry"
11
+ # Pry.start
12
+
13
+ require "irb"
14
+ IRB.start(__FILE__)
@@ -0,0 +1,9 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ lib = File.expand_path("../../lib", __FILE__)
4
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
5
+
6
+ require "galaxy_converter"
7
+
8
+ input = ARGV.fetch(0, "")
9
+ GalaxyConverter::CLI.new(input).call
data/bin/setup ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,25 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path("../lib", __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+
5
+ require "galaxy_converter/version"
6
+
7
+ Gem::Specification.new do |s|
8
+ s.name = "galaxy_converter"
9
+ s.version = GalaxyConverter::VERSION
10
+ s.authors = ["costajob"]
11
+ s.email = ["costajob@gmail.com"]
12
+ s.homepage = "https://bitbucket.org/costajob/galaxy_converter"
13
+ s.license = "MIT"
14
+ s.summary = "Implementation of the Merchant's Guide to the Galaxy kata"
15
+ s.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
16
+ s.bindir = "bin"
17
+ s.executables << "galaxy_converter"
18
+ s.require_paths = ["lib"]
19
+ s.required_ruby_version = ">= 2.2.2"
20
+
21
+ s.add_development_dependency "bundler", "~> 1.15"
22
+ s.add_development_dependency "rake", "~> 10.0"
23
+ s.add_development_dependency "minitest", "~> 5.0"
24
+ s.add_development_dependency "benchmark-ips", "~> 2"
25
+ end
@@ -0,0 +1,24 @@
1
+ require "galaxy_converter/roman_numeral"
2
+
3
+ module GalaxyConverter
4
+ class Abacus
5
+ def initialize(mapping = {}, unit = Roman::Numeral)
6
+ @mapping = mapping
7
+ @unit = unit
8
+ end
9
+
10
+ def call(units)
11
+ converted = convert(units)
12
+ result = @unit.new(converted)
13
+ return 0 unless result.valid?
14
+ result.to_i
15
+ end
16
+
17
+ private def convert(units)
18
+ units.split(" ").reduce("") do |to_convert, unit|
19
+ return unless @mapping[unit]
20
+ to_convert << @mapping[unit]
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,47 @@
1
+ require "galaxy_converter/note"
2
+ require "galaxy_converter/responder"
3
+
4
+ module GalaxyConverter
5
+ class CLI
6
+ HELP_FLAGS = %w[-h --help]
7
+ COL_WIDTH = 23
8
+
9
+ def initialize(input,
10
+ pipe = STDOUT,
11
+ responder = Responder,
12
+ note = Note)
13
+ @input = input
14
+ @pipe = pipe
15
+ @responder = responder
16
+ @note = note
17
+ end
18
+
19
+ def call
20
+ @pipe.puts output
21
+ end
22
+
23
+ private def output
24
+ return help if help?
25
+ return unless file?
26
+ data = File.readlines(@input).map(&:strip)
27
+ notes = @note.bulk(data)
28
+ @responder.new(notes).call
29
+ end
30
+
31
+ private def file?
32
+ File.file?(File.expand_path(@input))
33
+ end
34
+
35
+ private def help?
36
+ HELP_FLAGS.include?(@input)
37
+ end
38
+
39
+ private def help
40
+ [].tap do |h|
41
+ h << %q{Usage: galaxy_converter ~/notes.txt}
42
+ h << " %-#{COL_WIDTH}s Print this help" % "-h --help"
43
+ h << " %-#{COL_WIDTH}s Load conversion notes" % "<path-to-file>"
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,28 @@
1
+ module GalaxyConverter
2
+ class Note
3
+ PREFIXES = ["how much is", "how many credits is"]
4
+ QUESTION_MARK = "?"
5
+
6
+ def self.bulk(notes)
7
+ Array(notes).map { |body| new(body) }
8
+ end
9
+
10
+ attr_reader :body
11
+
12
+ def initialize(body)
13
+ @body = body.to_s.strip.downcase
14
+ end
15
+
16
+ def question?
17
+ @body.end_with?(QUESTION_MARK)
18
+ end
19
+
20
+ def commercial?
21
+ !!@body.index("credits")
22
+ end
23
+
24
+ def stripped
25
+ @body.sub(/#{PREFIXES.join("|")}/, "").sub(QUESTION_MARK, "").strip
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,44 @@
1
+ require "galaxy_converter/abacus"
2
+ require "galaxy_converter/note"
3
+
4
+ module GalaxyConverter
5
+ class Recognizer
6
+ attr_reader :abacus
7
+
8
+ def initialize(notes, abacus = Abacus)
9
+ notes = notes.reject(&:question?)
10
+ @commercials, @assertions = notes.partition(&:commercial?)
11
+ @abacus = abacus.new(mapping)
12
+ end
13
+
14
+ def goods
15
+ @goods ||= @commercials.reduce({}) do |acc, note|
16
+ matching = note.body.match(goods_rule)
17
+ next acc unless matching
18
+ units, name, credits = matching.captures
19
+ value = @abacus.call(units)
20
+ next acc if value.zero?
21
+ acc[name] = credits.to_f / value
22
+ acc
23
+ end
24
+ end
25
+
26
+ private def goods_rule
27
+ /([#{mapping.keys.join("|")}\s]+) (\w+) is (\d+)/
28
+ end
29
+
30
+ private def mapping
31
+ @mapping ||= @assertions.reduce({}) do |acc, note|
32
+ matching = note.body.match(mapping_rule)
33
+ next acc unless matching
34
+ unit, roman = matching.captures
35
+ acc[unit.strip] = roman.upcase
36
+ acc
37
+ end
38
+ end
39
+
40
+ private def mapping_rule
41
+ /(\w+) is (\w)/
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,49 @@
1
+ require "forwardable"
2
+ require "galaxy_converter/recognizer"
3
+
4
+ module GalaxyConverter
5
+ class Responder
6
+ extend Forwardable
7
+
8
+ def_delegators :@recognizer, :goods, :abacus
9
+
10
+ UNKNOWN_ANSWER = "I have no idea what you are talking about"
11
+
12
+ def initialize(notes, recognizer = Recognizer, abacus = Abacus)
13
+ @questions = notes.select(&:question?)
14
+ @recognizer = recognizer.new(notes, abacus)
15
+ end
16
+
17
+ def call
18
+ @questions.reduce([]) do |acc, note|
19
+ units, good = detect(note)
20
+ total = total(units, good)
21
+ acc << to_s(units, good, total, note.commercial?)
22
+ acc
23
+ end
24
+ end
25
+
26
+ private def to_s(units, good, total, commercial)
27
+ return UNKNOWN_ANSWER if total.zero?
28
+ [].tap do |s|
29
+ s << units
30
+ s << good.to_s.capitalize
31
+ s << "is"
32
+ s << "%g" % total
33
+ s << "Credits" if commercial
34
+ end.reject(&:empty?).join(" ")
35
+ end
36
+
37
+ private def total(units, good)
38
+ return abacus.call(units) unless good
39
+ abacus.call(units) * goods.fetch(good, 0)
40
+ end
41
+
42
+ private def detect(note)
43
+ tokens = note.stripped.split
44
+ return [tokens.join(" "), nil] unless note.commercial?
45
+ good = tokens.pop
46
+ [tokens.join(" "), good]
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,31 @@
1
+ module GalaxyConverter
2
+ module Roman
3
+ module Constraint
4
+ extend self
5
+
6
+ def violated?(value)
7
+ violations.any? { |violation| value.index(violation) }
8
+ end
9
+
10
+ private def violations
11
+ @violations ||= repetitions + subtractions
12
+ end
13
+
14
+ private def repetitions
15
+ %w[I X C M].map { |c| c * 4 } +
16
+ %w[D L V].map { |c| c * 2 }
17
+ end
18
+
19
+ private def subtractions
20
+ %w[V X].map { |c| "II#{c}"} +
21
+ %w[L C].map { |c| "XX#{c}"} +
22
+ %w[D M].map { |c| "CC#{c}"} +
23
+ %w[L C D M].map { |c| "I#{c}" } +
24
+ %w[D M].map { |c| "X#{c}" } +
25
+ %w[X L C D M].map { |c| "V#{c}" } +
26
+ %w[C D M].map { |c| "L#{c}"} +
27
+ ["DM"]
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,39 @@
1
+ require "galaxy_converter/roman_constraint"
2
+ require "galaxy_converter/roman_rule"
3
+
4
+ module GalaxyConverter
5
+ module Roman
6
+ class Numeral
7
+ SYMBOLS = {
8
+ "M" => 1000,
9
+ "D" => 500,
10
+ "C" => 100,
11
+ "L" => 50,
12
+ "X" => 10,
13
+ "V" => 5,
14
+ "I" => 1
15
+ }
16
+
17
+ def initialize(value, constraint = Constraint, rule = Rule)
18
+ @value = value.to_s.upcase
19
+ @constraint = constraint
20
+ @rule = rule
21
+ end
22
+
23
+ def to_s
24
+ @value
25
+ end
26
+
27
+ def to_i
28
+ @rule.call(@value).chars.reduce(0) do |total, symbol|
29
+ total += SYMBOLS.fetch(symbol, 0)
30
+ end
31
+ end
32
+
33
+ def valid?
34
+ return false if @value.empty?
35
+ !@constraint.violated?(@value)
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,22 @@
1
+ module GalaxyConverter
2
+ module Roman
3
+ module Rule
4
+ extend self
5
+
6
+ LONG_TO_SHORT = {
7
+ "DCCCC" => "CM", # 900
8
+ "CCCC" => "CD", # 400
9
+ "LXXXX" => "XC", # 90
10
+ "XXXX" => "XL", # 40
11
+ "VIIII" => "IX", # 9
12
+ "IIII" => "IV" # 4
13
+ }
14
+
15
+ def call(value)
16
+ LONG_TO_SHORT.reduce(value) do |to_convert, (long, short)|
17
+ to_convert.gsub(short, long)
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,3 @@
1
+ module GalaxyConverter
2
+ VERSION = "2.1.1"
3
+ end
@@ -0,0 +1,11 @@
1
+ require "galaxy_converter/version"
2
+ require "galaxy_converter/cli"
3
+
4
+ module GalaxyConverter
5
+ extend self
6
+
7
+ def call(notes)
8
+ notes = Note.bulk(notes)
9
+ Responder.new(notes).call
10
+ end
11
+ end
metadata ADDED
@@ -0,0 +1,119 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: galaxy_converter
3
+ version: !ruby/object:Gem::Version
4
+ version: 2.1.1
5
+ platform: ruby
6
+ authors:
7
+ - costajob
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2017-10-26 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.15'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.15'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '10.0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '10.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: minitest
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '5.0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '5.0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: benchmark-ips
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '2'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '2'
69
+ description:
70
+ email:
71
+ - costajob@gmail.com
72
+ executables:
73
+ - galaxy_converter
74
+ extensions: []
75
+ extra_rdoc_files: []
76
+ files:
77
+ - ".gitignore"
78
+ - Gemfile
79
+ - README.md
80
+ - Rakefile
81
+ - bin/console
82
+ - bin/galaxy_converter
83
+ - bin/setup
84
+ - galaxy_converter.gemspec
85
+ - lib/galaxy_converter.rb
86
+ - lib/galaxy_converter/abacus.rb
87
+ - lib/galaxy_converter/cli.rb
88
+ - lib/galaxy_converter/note.rb
89
+ - lib/galaxy_converter/recognizer.rb
90
+ - lib/galaxy_converter/responder.rb
91
+ - lib/galaxy_converter/roman_constraint.rb
92
+ - lib/galaxy_converter/roman_numeral.rb
93
+ - lib/galaxy_converter/roman_rule.rb
94
+ - lib/galaxy_converter/version.rb
95
+ homepage: https://bitbucket.org/costajob/galaxy_converter
96
+ licenses:
97
+ - MIT
98
+ metadata: {}
99
+ post_install_message:
100
+ rdoc_options: []
101
+ require_paths:
102
+ - lib
103
+ required_ruby_version: !ruby/object:Gem::Requirement
104
+ requirements:
105
+ - - ">="
106
+ - !ruby/object:Gem::Version
107
+ version: 2.2.2
108
+ required_rubygems_version: !ruby/object:Gem::Requirement
109
+ requirements:
110
+ - - ">="
111
+ - !ruby/object:Gem::Version
112
+ version: '0'
113
+ requirements: []
114
+ rubyforge_project:
115
+ rubygems_version: 2.6.11
116
+ signing_key:
117
+ specification_version: 4
118
+ summary: Implementation of the Merchant's Guide to the Galaxy kata
119
+ test_files: []