Slide 1

Slide 1 text

TTY Ruby alchemist's secret potion 1 — © Piotr Murach 2018

Slide 2

Slide 2 text

2 — © Piotr Murach 2018

Slide 3

Slide 3 text

3 — © Piotr Murach 2018

Slide 4

Slide 4 text

© Fuji Television Network Inc./East Entertainment 4 — © Piotr Murach 2018

Slide 5

Slide 5 text

5 — © Piotr Murach 2018

Slide 6

Slide 6 text

6 — © Piotr Murach 2018

Slide 7

Slide 7 text

7 — © Piotr Murach 2018

Slide 8

Slide 8 text

8 — © Piotr Murach 2018

Slide 9

Slide 9 text

Is it easy to build terminal applications in Ruby? 9 — © Piotr Murach 2018

Slide 10

Slide 10 text

You know like Rails easy? 10 — © Piotr Murach 2018

Slide 11

Slide 11 text

Hello tty gem! 11 — © Piotr Murach 2018

Slide 12

Slide 12 text

github.com piotrmurach/tty 12 — © Piotr Murach 2018

Slide 13

Slide 13 text

piotrmurach.github.io/tty 13 — © Piotr Murach 2018

Slide 14

Slide 14 text

Principles 14 — © Piotr Murach 2018

Slide 15

Slide 15 text

Principles 1. Convention over Configuration 15 — © Piotr Murach 2018

Slide 16

Slide 16 text

Principles 1. Convention over Configuration 2. Omakase 16 — © Piotr Murach 2018

Slide 17

Slide 17 text

Principles 1. Convention over Configuration 2. Omakase 3. Intuitive 17 — © Piotr Murach 2018

Slide 18

Slide 18 text

Sweet spot 18 — © Piotr Murach 2018

Slide 19

Slide 19 text

Multiple Concepts (Bundler) 19 — © Piotr Murach 2018

Slide 20

Slide 20 text

Complex Subcommands (Git) 20 — © Piotr Murach 2018

Slide 21

Slide 21 text

Many options & flags 21 — © Piotr Murach 2018

Slide 22

Slide 22 text

How do I start? 22 — © Piotr Murach 2018

Slide 23

Slide 23 text

$ gem install tty 23 — © Piotr Murach 2018

Slide 24

Slide 24 text

teletype 24 — © Piotr Murach 2018

Slide 25

Slide 25 text

❤ Bundler ❤ 25 — © Piotr Murach 2018

Slide 26

Slide 26 text

26 — © Piotr Murach 2018

Slide 27

Slide 27 text

Build command line applications the easy way! 27 — © Piotr Murach 2018

Slide 28

Slide 28 text

$ teletype new app 28 — © Piotr Murach 2018

Slide 29

Slide 29 text

29 — © Piotr Murach 2018

Slide 30

Slide 30 text

▾ app/ ├── ▾ exe/ │ └── app ├── ▾ lib/ │ ├── ▾ app/ │ │ ├── ▸ commands/ │ │ ├── ▸ templates/ │ │ ├── cli.rb │ │ ├── command.rb │ │ └── version.rb │ └── app.rb ... └── app.gemspec 30 — © Piotr Murach 2018

Slide 31

Slide 31 text

CLI vs Web cli ↔ router command ↔ controller stdin ↔ request stdout ↔ response template ↔ view 31 — © Piotr Murach 2018

Slide 32

Slide 32 text

Adding Commands 32 — © Piotr Murach 2018

Slide 33

Slide 33 text

$ teletype add config --args name # required argument 33 — © Piotr Murach 2018

Slide 34

Slide 34 text

$ teletype add config --args name # required argument $ teletype add config --args "name = nil" # optional argument 34 — © Piotr Murach 2018

Slide 35

Slide 35 text

$ teletype add config --args name # required argument $ teletype add config --args "name = nil" # optional argument $ teletype add config --args *names # variadic argument 35 — © Piotr Murach 2018

Slide 36

Slide 36 text

$ teletype add config --args name value --desc 'Add configuration option' 36 — © Piotr Murach 2018

Slide 37

Slide 37 text

app/lib ▾ app/ ├── ▾ commands/ │ └── config.rb ├── ▾ templates/ │ └── ▸ config/ ├── cli.rb ├── cmd.rb └── version.rb 37 — © Piotr Murach 2018

Slide 38

Slide 38 text

lib/app/cli.rb module App class CLI < Thor desc 'config NAME VALUE', 'Add configuration option' def config(name, value) if options[:help] invoke :help, ['config'] else require_relative 'commands/config' App::Commands::Config.new(name, value, options).execute end end end end 38 — © Piotr Murach 2018

Slide 39

Slide 39 text

lib/app/commands/config.rb module App module Commands class Config < App::Cmd def initialize(name, value, options) @name = name @value = value @options = options end def execute(input: $stdin, output: $stdout) output.puts "OK" end end end end 39 — © Piotr Murach 2018

Slide 40

Slide 40 text

lib/app/cmd.rb module App class Cmd ... def generator require 'tty-file' TTY::File end ... end end 40 — © Piotr Murach 2018

Slide 41

Slide 41 text

Testing Commands 41 — © Piotr Murach 2018

Slide 42

Slide 42 text

... ├── ▾ spec/ │ ├── ▾ integration/ │ │ └── config_spec.rb │ ├── ▸ support/ │ ├── ▾ unit/ │ │ └── config_spec.rb │ ├── app_spec.rb │ └── spec_helper.rb ... 42 — © Piotr Murach 2018

Slide 43

Slide 43 text

app/spec/integration/config_spec.rb RSpec.describe "`app config` command", type: :cli do end 43 — © Piotr Murach 2018

Slide 44

Slide 44 text

app/spec/integration/config_spec.rb RSpec.describe "`app config` command", type: :cli do it "executes `app help config` command successfully" do end end 44 — © Piotr Murach 2018

Slide 45

Slide 45 text

app/spec/integration/config_spec.rb RSpec.describe "`app config` command", type: :cli do it "executes `app help config` command successfully" do output = `app help config` end end 45 — © Piotr Murach 2018

Slide 46

Slide 46 text

app/spec/integration/config_spec.rb RSpec.describe "`app config` command", type: :cli do it "executes `app help config` command successfully" do output = `app help config` expected_output = <<-OUT Usage: app config NAME VALUE Options: -h, [--help], [--no-help] # Display usage information Add configuration option OUT end end 46 — © Piotr Murach 2018

Slide 47

Slide 47 text

app/spec/integration/config_spec.rb RSpec.describe "`app config` command", type: :cli do it "executes `app help config` command successfully" do output = `app help config` expected_output = <<-OUT Usage: app config NAME VALUE Options: -h, [--help], [--no-help] # Display usage information Add configuration option OUT expect(output).to eq(expected_output) end end 47 — © Piotr Murach 2018

Slide 48

Slide 48 text

app/spec/unit/config_spec.rb require 'app/commands/config' RSpec.describe App::Commands::Config do end 48 — © Piotr Murach 2018

Slide 49

Slide 49 text

app/spec/unit/config_spec.rb require 'app/commands/config' RSpec.describe App::Commands::Config do it "executes `app config` command successfully" do end end 49 — © Piotr Murach 2018

Slide 50

Slide 50 text

app/spec/unit/config_spec.rb require 'app/commands/config' RSpec.describe App::Commands::Config do it "executes `app config` command successfully" do output = StringIO.new end end 50 — © Piotr Murach 2018

Slide 51

Slide 51 text

app/spec/unit/config_spec.rb require 'app/commands/config' RSpec.describe App::Commands::Config do it "executes `app config` command successfully" do output = StringIO.new name = nil value = nil options = {} end end 51 — © Piotr Murach 2018

Slide 52

Slide 52 text

app/spec/unit/config_spec.rb require 'app/commands/config' RSpec.describe App::Commands::Config do it "executes `app config` command successfully" do output = StringIO.new name = nil value = nil options = {} command = App::Commands::Config.new(name, value, options) end end 52 — © Piotr Murach 2018

Slide 53

Slide 53 text

app/spec/unit/config_spec.rb require 'app/commands/config' RSpec.describe App::Commands::Config do it "executes `app config` command successfully" do output = StringIO.new name = nil value = nil options = {} command = App::Commands::Config.new(name, value, options) command.execute(output: output) end end 53 — © Piotr Murach 2018

Slide 54

Slide 54 text

app/spec/unit/config_spec.rb require 'app/commands/config' RSpec.describe App::Commands::Config do it "executes `config` command successfully" do output = StringIO.new name = nil value = nil options = {} command = App::Commands::Config.new(name, value, options) command.execute(output: output) expect(output.string).to eq("OK\n") end end 54 — © Piotr Murach 2018

Slide 55

Slide 55 text

$ app config color true # => OK 55 — © Piotr Murach 2018

Slide 56

Slide 56 text

56 — © Piotr Murach 2018

Slide 57

Slide 57 text

57 — © Piotr Murach 2018

Slide 58

Slide 58 text

19 gems! 58 — © Piotr Murach 2018

Slide 59

Slide 59 text

TTY gems → Single purpose 59 — © Piotr Murach 2018

Slide 60

Slide 60 text

TTY gems → Single purpose → Minimal API 60 — © Piotr Murach 2018

Slide 61

Slide 61 text

TTY gems → Single purpose → Minimal API → Composable 61 — © Piotr Murach 2018

Slide 62

Slide 62 text

tty-prompt ! Fukuoka Ruby Award GMO Pepabo ! 62 — © Piotr Murach 2018

Slide 63

Slide 63 text

Ask Prompt require 'tty-prompt' 63 — © Piotr Murach 2018

Slide 64

Slide 64 text

Ask Prompt require 'tty-prompt' prompt = TTY::Prompt.new 64 — © Piotr Murach 2018

Slide 65

Slide 65 text

Ask Prompt require 'tty-prompt' prompt = TTY::Prompt.new prompt.ask('What is your name?', default: ENV['USER']) 65 — © Piotr Murach 2018

Slide 66

Slide 66 text

Ask Prompt prompt.ask('At what price per coin?') do |q| ... end 66 — © Piotr Murach 2018

Slide 67

Slide 67 text

Ask Prompt prompt.ask('At what price per coin?') do |q| q.required(true, 'You need to provide a price') end 67 — © Piotr Murach 2018

Slide 68

Slide 68 text

Ask Prompt prompt.ask('At what price per coin?') do |q| q.required(true, 'You need to provide a price') q.validate(/[\d.]+/, 'Invalid price provided') end 68 — © Piotr Murach 2018

Slide 69

Slide 69 text

Ask Prompt prompt.ask('At what price per coin?') do |q| q.required(true, 'You need to provide a price') q.validate(/[\d.]+/, 'Invalid price provided') q.convert ->(p) { p.to_f } end 69 — © Piotr Murach 2018

Slide 70

Slide 70 text

Masked Prompt heart = prompt.decorate('❤', :magenta) 70 — © Piotr Murach 2018

Slide 71

Slide 71 text

Masked Prompt heart = prompt.decorate('❤', :magenta) res = prompt.mask('What is your secret?', mask: heart) do |q| ... end 71 — © Piotr Murach 2018

Slide 72

Slide 72 text

Masked Prompt heart = prompt.decorate('❤', :magenta) res = prompt.mask('What is your secret?', mask: heart) do |q| q.validate(/[a-z\ ]{5,15}/) end puts "Secret: \"#{res}\"" 72 — © Piotr Murach 2018

Slide 73

Slide 73 text

Masked Prompt 73 — © Piotr Murach 2018

Slide 74

Slide 74 text

Multiple Choice Prompt drinks = %w(vodka beer wine whisky bourbon) prompt.multi_select('Choose your favourite drink?', drinks, help: '(Use arrow keys and Enter to finish)') 74 — © Piotr Murach 2018

Slide 75

Slide 75 text

Multiple Choice Prompt 75 — © Piotr Murach 2018

Slide 76

Slide 76 text

Prompt events (Vim style ;-) prompt.on(:keypress) do |event| ... end 76 — © Piotr Murach 2018

Slide 77

Slide 77 text

Prompt events (Vim style ;-) prompt.on(:keypress) do |event| if event.value == 'j' ... end if event.value == 'k' ... end end 77 — © Piotr Murach 2018

Slide 78

Slide 78 text

Prompt events (Vim style ;-) prompt.on(:keypress) do |event| if event.value == 'j' prompt.trigger(:keydown) end if event.value == 'k' prompt.trigger(:keyup) end end 78 — © Piotr Murach 2018

Slide 79

Slide 79 text

Collect Prompt prompt.collect do ... end 79 — © Piotr Murach 2018

Slide 80

Slide 80 text

Collect Prompt prompt.collect do key(:name).ask('Name?') key(:age).ask('Age?', convert: :int) end 80 — © Piotr Murach 2018

Slide 81

Slide 81 text

Collect Prompt prompt.collect do key(:name).ask('Name?') key(:age).ask('Age?', convert: :int) key(:address) do key(:street).ask('Street?', required: true) key(:city).ask('City?') key(:zip).ask('Zip?', validate: /\A\d{3}\Z/) end end 81 — © Piotr Murach 2018

Slide 82

Slide 82 text

Collect Prompt { "name": "Piotr", "age": 33, "address": { "street": "West Street", "city": "Sheffield", "zip": "S1 EF" } } 82 — © Piotr Murach 2018

Slide 83

Slide 83 text

tty-config 83 — © Piotr Murach 2018

Slide 84

Slide 84 text

Config require 'tty-config' 84 — © Piotr Murach 2018

Slide 85

Slide 85 text

Config require 'tty-config' config = TTY::Config.new 85 — © Piotr Murach 2018

Slide 86

Slide 86 text

Config require 'tty-config' config = TTY::Config.new config.filename = 'investments' 86 — © Piotr Murach 2018

Slide 87

Slide 87 text

Config require 'tty-config' config = TTY::Config.new config.filename = 'investments' config.extname = '.toml' 87 — © Piotr Murach 2018

Slide 88

Slide 88 text

Config require 'tty-config' config = TTY::Config.new config.filename = 'investments' config.extname = '.toml' config.append_path Dir.pwd 88 — © Piotr Murach 2018

Slide 89

Slide 89 text

Config config.set(:settings, :base, value: 'USD') 89 — © Piotr Murach 2018

Slide 90

Slide 90 text

Config config.set(:settings, :base, value: 'USD') config.set("settings.exchange", value: 'Bitfinex') 90 — © Piotr Murach 2018

Slide 91

Slide 91 text

Config config.set(:settings, :base, value: 'USD') config.set("settings.exchange", value:'Bitfinex') config.set(:coins, value: ['BTC']) 91 — © Piotr Murach 2018

Slide 92

Slide 92 text

Config config.set(:settings, :base, value: 'USD') config.set("settings.exchange", value:'Bitfinex') config.set(:coins, value: ['BTC']) config.append('ETH', 'TRX', 'DASH', to: :coins) 92 — © Piotr Murach 2018

Slide 93

Slide 93 text

Config config.set(:settings, :base, value: 'USD') config.set("settings.exchange", value:'Bitfinex') config.set(:coins, value: ['BTC']) config.append('ETH', 'TRX', 'DASH', to: :coins) config.fetch(:settings, :base) # => 'USD' 93 — © Piotr Murach 2018

Slide 94

Slide 94 text

Config config.set(:settings, :base, value: 'USD') config.set("settings.exchange", value:'Bitfinex') config.set(:coins, value: ['BTC']) config.append('ETH', 'TRX', 'DASH', to: :coins) config.fetch(:settings, :base) # => 'USD' config.fetch(:coins) # => ['BTC', 'ETH', 'TRX', 'DASH'] 94 — © Piotr Murach 2018

Slide 95

Slide 95 text

Config config.validate(:settings, :base) do |key, value| ... end 95 — © Piotr Murach 2018

Slide 96

Slide 96 text

Config config.validate(:settings, :base) do |key, value| if value.length != 3 raise TTY::Config::ValidationError, "Currency code needs to be 3 chars long." end end 96 — © Piotr Murach 2018

Slide 97

Slide 97 text

Config config.validate(:settings, :base) do |key, value| if value.length != 3 raise TTY::Config::ValidationError, "Currency code needs to be 3 chars long." end end config.set(:settings, :base, value: 'PL') # raises TTY::Config::ValidationError ... 97 — © Piotr Murach 2018

Slide 98

Slide 98 text

Config config.read config.read('./investments.toml') 98 — © Piotr Murach 2018

Slide 99

Slide 99 text

Config config.read config.read('./investments.toml') config.write config.write('./investments.toml') 99 — © Piotr Murach 2018

Slide 100

Slide 100 text

tty-progressbar 100 — © Piotr Murach 2018

Slide 101

Slide 101 text

Progress Bar require 'pastel' 101 — © Piotr Murach 2018

Slide 102

Slide 102 text

Progress Bar require 'pastel' pastel = Pastel.new green = pastel.on_green(" ") red = pastel.on_red(" ") 102 — © Piotr Murach 2018

Slide 103

Slide 103 text

Progress Bar require 'tty-progressbar' 103 — © Piotr Murach 2018

Slide 104

Slide 104 text

Progress Bar require 'tty-progressbar' bar = TTY::ProgressBar.new( ":bar :percent :elapsed", ) 104 — © Piotr Murach 2018

Slide 105

Slide 105 text

Progress Bar require 'tty-progressbar' bar = TTY::ProgressBar.new( ":bar :percent :elapsed", total: 30, ) 105 — © Piotr Murach 2018

Slide 106

Slide 106 text

Progress Bar require 'tty-progressbar' bar = TTY::ProgressBar.new( ":bar :percent :elapsed", total: 30, complete: green, incomplete: red, ) 106 — © Piotr Murach 2018

Slide 107

Slide 107 text

Progress Bar require 'tty-progressbar' bar = TTY::ProgressBar.new( ":bar :percent :elapsed", total: 30, complete: green, incomplete: red, hide_cursor: true ) 107 — © Piotr Murach 2018

Slide 108

Slide 108 text

Progress Bar require 'tty-progressbar' bar = TTY::ProgressBar.new( ":bar :percent :elapsed", total: 30, complete: green, incomplete: red, hide_cursor: true ) 30.times do sleep(0.1) bar.advance end 108 — © Piotr Murach 2018

Slide 109

Slide 109 text

Progress Bar 109 — © Piotr Murach 2018

Slide 110

Slide 110 text

bars = TTY::ProgressBar::Multi.new("main [:bar] :percent") 110 — © Piotr Murach 2018

Slide 111

Slide 111 text

bars = TTY::ProgressBar::Multi.new("main [:bar] :percent") bar1 = bars.register "foo [:bar] :percent", total: 15 bar2 = bars.register "bar [:bar] :percent", total: 10 bar3 = bars.register "baz [:bar] :percent", total: 25 111 — © Piotr Murach 2018

Slide 112

Slide 112 text

require 'tty-progressbar' bars = TTY::ProgressBar::Multi.new("main [:bar] :percent") bar1 = bars.register "foo [:bar] :percent", total: 15 bar2 = bars.register "bar [:bar] :percent", total: 10 bar3 = bars.register "baz [:bar] :percent", total: 25 th1 = Thread.new { 15.times { sleep(0.1); bar1.advance } } th2 = Thread.new { 10.times { sleep(0.1); bar2.advance } } th3 = Thread.new { 25.times { sleep(0.1); bar3.advance } } 112 — © Piotr Murach 2018

Slide 113

Slide 113 text

require 'tty-progressbar' bars = TTY::ProgressBar::Multi.new("main [:bar] :percent") bar1 = bars.register "foo [:bar] :percent", total: 15 bar2 = bars.register "bar [:bar] :percent", total: 10 bar3 = bars.register "baz [:bar] :percent", total: 25 th1 = Thread.new { 15.times { sleep(0.1); bar1.advance } } th2 = Thread.new { 10.times { sleep(0.1); bar2.advance } } th3 = Thread.new { 25.times { sleep(0.1); bar3.advance } } [th1, th2, th3].each(&:join) 113 — © Piotr Murach 2018

Slide 114

Slide 114 text

Multi Progress Bar 114 — © Piotr Murach 2018

Slide 115

Slide 115 text

tty-spinner 115 — © Piotr Murach 2018

Slide 116

Slide 116 text

require 'tty-spinner' require 'pastel' 116 — © Piotr Murach 2018

Slide 117

Slide 117 text

require 'tty-spinner' require 'pastel' pastel = Pastel.new spinners = TTY::Spinner::Multi.new("[:spinner] Downloading files...") 117 — © Piotr Murach 2018

Slide 118

Slide 118 text

require 'tty-spinner' require 'pastel' pastel = Pastel.new spinners = TTY::Spinner::Multi.new("[:spinner] Downloading files...") ['file1', 'file2', 'file3'].each do |file| end 118 — © Piotr Murach 2018

Slide 119

Slide 119 text

require 'tty-spinner' require 'pastel' pastel = Pastel.new spinners = TTY::Spinner::Multi.new("[:spinner] Downloading files...") ['file1', 'file2', 'file3'].each do |file| spinners.register("[:spinner] #{file}") do |sp| end end 119 — © Piotr Murach 2018

Slide 120

Slide 120 text

require 'tty-spinner' require 'pastel' pastel = Pastel.new spinners = TTY::Spinner::Multi.new("[:spinner] Downloading files...") ['file1', 'file2', 'file3'].each do |file| spinners.register("[:spinner] #{file}") do |sp| sleep(rand * 5) end end 120 — © Piotr Murach 2018

Slide 121

Slide 121 text

require 'tty-spinner' require 'pastel' pastel = Pastel.new spinners = TTY::Spinner::Multi.new("[:spinner] Downloading files...") ['file1', 'file2', 'file3'].each do |file| spinners.register("[:spinner] #{file}") do |sp| sleep(rand * 5) sp.success(pastel.green("success")) end end 121 — © Piotr Murach 2018

Slide 122

Slide 122 text

require 'tty-spinner' require 'pastel' pastel = Pastel.new spinners = TTY::Spinner::Multi.new("[:spinner] Downloading files...") ['file1', 'file2', 'file3'].each do |file| spinners.register("[:spinner] #{file}") do |sp| sleep(rand * 5) sp.success(pastel.green("success")) end end spinners.auto_spin 122 — © Piotr Murach 2018

Slide 123

Slide 123 text

Multi Spinner 123 — © Piotr Murach 2018

Slide 124

Slide 124 text

tty-markdown 124 — © Piotr Murach 2018

Slide 125

Slide 125 text

Terminal Markdown require 'tty-markdown' output = TTY::Markdown.parse_file('example.md') puts output 125 — © Piotr Murach 2018

Slide 126

Slide 126 text

126 — © Piotr Murach 2018

Slide 127

Slide 127 text

An Example 127 — © Piotr Murach 2018

Slide 128

Slide 128 text

128 — © Piotr Murach 2018

Slide 129

Slide 129 text

129 — © Piotr Murach 2018

Slide 130

Slide 130 text

$ coinpare coins 130 — © Piotr Murach 2018

Slide 131

Slide 131 text

131 — © Piotr Murach 2018

Slide 132

Slide 132 text

$ coinpare markets 132 — © Piotr Murach 2018

Slide 133

Slide 133 text

133 — © Piotr Murach 2018

Slide 134

Slide 134 text

$ coinpare holdings 134 — © Piotr Murach 2018

Slide 135

Slide 135 text

TTY gems require 'pastel' require 'tty-config' require 'tty-cursor' require 'tty-editor' require 'tty-font' require 'tty-pager' require 'tty-prompt' require 'tty-spinner' require 'tty-table' 135 — © Piotr Murach 2018

Slide 136

Slide 136 text

prompt.collect do key('settings') do end end 136 — © Piotr Murach 2018

Slide 137

Slide 137 text

prompt.collect do key('settings') do key('base').ask('What base currency to convert holdings to?') do |q| q.default "USD" q.convert ->(b) { b.upcase } q.validate(/\w{3}/, 'Currency code needs to be 3 chars long') end end end 137 — © Piotr Murach 2018

Slide 138

Slide 138 text

prompt.collect do key('settings') do key('base').ask('What base currency to convert holdings to?') do |q| q.default "USD" q.convert ->(b) { b.upcase } q.validate(/\w{3}/, 'Currency code needs to be 3 chars long') end key('exchange').ask('What exchange would you like to use?') do |q| q.default "CCCAGG" q.required true end end end 138 — © Piotr Murach 2018

Slide 139

Slide 139 text

prompt.collect do key('settings') do key('base').ask('What base currency to convert holdings to?') do |q| ... end key('exchange').ask('What exchange would you like to use?') do |q| ... end end while prompt.yes?("Do you want to add coin to your altfolio?") end end 139 — © Piotr Murach 2018

Slide 140

Slide 140 text

prompt.collect do key('settings') do key('base').ask('What base currency to convert holdings to?') do |q| ... end key('exchange').ask('What exchange would you like to use?') do |q| ... end end while prompt.yes?("Do you want to add coin to your altfolio?") key('holdings') end end 140 — © Piotr Murach 2018

Slide 141

Slide 141 text

prompt.collect do key('settings') do key('base').ask('What base currency to convert holdings to?') do |q| ... end key('exchange').ask('What exchange would you like to use?') do |q| ... end end while prompt.yes?("Do you want to add coin to your altfolio?") key('holdings').values(&context.ask_coin) end end 141 — © Piotr Murach 2018

Slide 142

Slide 142 text

def ask_coin -> (prompt) do ... end end 142 — © Piotr Murach 2018

Slide 143

Slide 143 text

def ask_coin -> (prompt) do key('name').ask('What coin do you own?') do |q| ... end key('amount').ask('What amount?') do |q| ... end key('price').ask('At what price per coin?') do |q| ... end end end 143 — © Piotr Murach 2018

Slide 144

Slide 144 text

def ask_coin -> (prompt) do key('name').ask('What coin do you own?') do |q| q.default 'BTC' q.required(true, 'You need to provide a coin') q.validate(/\w{2,}/, 'Currency can only be chars.') q.convert ->(coin) { coin.upcase } end key('amount').ask('What amount?') do |q| q.required(true, 'You need to provide an amount') q.validate(/[\d.]+/, 'Invalid amount provided') q.convert ->(am) { am.to_f } end key('price').ask('At what price per coin?') do |q| q.required(true, 'You need to provide a price') q.validate(/[\d.]+/, 'Invalid prince provided') q.convert ->(p) { p.to_f } end end end 144 — © Piotr Murach 2018

Slide 145

Slide 145 text

! @piotr_murach ! piotrmurach ! piotr.murach 145 — © Piotr Murach 2018

Slide 146

Slide 146 text

͋Γ͕ͱ͏͍͟͝·͢! 146 — © Piotr Murach 2018