:pぴー.sendせんど(:pぴー, :pぴー)

mokoaki
mokoriso@gmail.com

2017/07/22

標準添付ライブラリ

Ruby技術者認定試験の書籍写経メモ

pp []
=> NoMethodError: undefined method 'pp'

require 'pp'
=> true

pp []
[]
=> []

StringIO

バッファとポインタ

あれ?(少なくとも)Ruby2.4ではrequireしなくても使えたわ

バッファ StringIO#string デフォは “”
ポインタ StringIO#pos デフォは 0
sio = StringIO.new

sio.string
=> ""

sio.pos
=> 0
sio = StringIO.new

sio.string
=> ""

sio.pos
=> 0

sio.string = '12345'

sio.pos
=> 0

sio.gets
=> "12345"

sio.pos
=> 5

StringIO#puts

sio = StringIO.new
=> #<StringIO:0x007fdf310478c0>

sio.puts('12345')
=> nil

sio.string
=> "12345\n"

sio.pos
=> 6

StringIO#gets

sio = StringIO.new
=> #<StringIO:0x007fdf310478c0>

sio.puts('12')
sio.puts('34')

sio.string
=> "12\n34\n"

sio.pos
=> 6

sio.gets
=> nil

sio.pos = 0

sio.gets
=> "12\n"

sio.pos
=> 3

インスタンス生成

new インスタンスを返す
open ブロックの評価結果を返す
open ブロックを取らない場合は new と同じ
StringIO.new(string = '', mode = 'r+')
StringIO.open(string = '', mode = 'r+')
StringIO.open(string = '', mode = 'r+') { |sio| ... }

mode r

StringIO.open('1234', 'r') do |sio|
  p "string:#{sio.string}, pos:#{sio.pos}"
  sio.puts('a')
  #=> IOError: not opened for writing
end
=> "string:1234, pos:0"

mode w

StringIO.open('1234', 'w') do |sio|
  p "string:#{sio.string}, pos:#{sio.pos}"
  p sio.gets
  #=> IOError: not opened for reading
end
=> "string:, pos:0"

mode a

StringIO.open('1234', 'a') do |sio|
  p "string:#{sio.string}, pos:#{sio.pos}"
  sio.puts('AB')
  p "string:#{sio.string}, pos:#{sio.pos}"
end
=> "string:1234, pos:0"
=> "string:1234AB\n, pos:7"

mode r+

StringIO.open('1234', 'r+') do |sio|
  p "string:#{sio.string}, pos:#{sio.pos}"
  sio.puts('AB')
  p "string:#{sio.string}, pos:#{sio.pos}"
end
=> "string:1234, pos:0"
=> "string:AB\n4, pos:3"

mode w+

StringIO.open('1234', 'w+') do |sio|
  p "string:#{sio.string}, pos:#{sio.pos}"
end
=> "string:, pos:0"

mode a+

StringIO.open('1234', 'a+') do |sio|
  p "string:#{sio.string}, pos:#{sio.pos}"
  sio.puts('AB')
  p "string:#{sio.string}, pos:#{sio.pos}"
end
=> "string:1234, pos:0"
=> "string:1234AB\n, pos:7"

StringIO#putc

StringIO.open('1234', 'r+') do |sio|
  sio.pos = 2
  p sio.putc('AB')
  p "string:#{sio.string}, pos:#{sio.pos}"
end
=> "string:12A4, pos:3"

String#print StringIO#puts

StringIO.open do |sio|
  p sio.puts('A', 'B')
  p sio.puts(['C', 'D'])
  p sio.print(['E', 'F'])
  p "string:#{sio.string}, pos:#{sio.pos}"
end
=> "string:A\nB\nC\nD\n[\"E\", \"F\"], pos:18"

StringIO#printf

まぁいつものアレ

StringIO.open do |sio|
  p sio.printf('%02d', 1)
  p "string:#{sio.string}, pos:#{sio.pos}"
end
"string:01, pos:2"

StringIO#read

ぶっちゃけややこしい

StringIO.open('1234') do |sio|
  p sio.read
end
=> "1234"
StringIO.open('1234') do |sio|
  sio.pos = 2
  p sio.read
end
=> "34"
StringIO.open('1234') do |sio|
  sio.pos = 2
  p sio.read(1)
end
=> "3"

StringIO#getc StringIO#readchar

現在のポインタから1文字取得する

StringIO.open('123') do |sio|
  sio.pos = 1
  p sio.getc
  p sio.getc
  p sio.getc
end
=> "2"
=> "3"
=> nil

readchar は似たような動作だが、終端でエラーが発生する

StringIO.open('123') do |sio|
  sio.pos = 1
  p sio.readchar
  p sio.readchar
  p sio.readchar
end
=> "2"
=> "3"
=> EOFError: end of file reached

StringIO#gets StringIO#readline

現在のポインタから改行までの文字列を取得する

StringIO.open("123\n456") do |sio|
  sio.pos = 1
  p sio.gets
  p sio.gets
  p sio.gets
end
=> "23\n"
=> "456"
=> nil

readline は似たような動作だが、終端でエラーが発生する

StringIO.open("123\n456") do |sio|
  sio.pos = 1
  p sio.readline
  p sio.readline
  p sio.readline
end
=> "23\n"
=> "456"
=> EOFError: end of file reached

StringIO#pos= StringIO#seek

ポインタを移動する

StringIO.open("123\n456") do |sio|
  sio.seek(-2, IO::SEEK_END)
  p sio.getc
end
=> "5"

YAML

require 'yaml'

yaml_data = <<-DATA
data_array:
  - a
  - b
  - c
data_key_value:
  key1: value1
  key2: value2
DATA

yaml = YAML.load(yaml_data)
=> {"data_array"=>["a", "b", "c"], "data_key_value"=>{"key1"=>"value1", "key2"=>"value2"}}

yaml.class
=> Hash

YAML.load_file

ファイルを直接読み込んでオブジェクトを生成する

require 'yaml'

YAML.load_file('path/to/yaml_file')

YAML.load_stream

require 'yaml'

yaml_data = <<-DATA
- a
---
- b
DATA

yaml = YAML.load(yaml_data)
=> ["a"]

yaml.class
=> Array

yaml = YAML.load_stream(yaml_data)
=> [["a"], ["b"]]

yaml.class
=> Array

IOインスタンスを渡す方が実際の使い方?

yaml = YAML.load_stream(open('path/to/yaml_file'))

YAML.load_document

こんなことも出来るらしいけど、ホントこの書籍はやる気ないわ 俺のやる気も失せるわ

YAML.load_document(open('path/to/yaml_file')) do |yaml|
  yaml.first
end

YAML.dump

オブジェクトをyaml形式の文字列にして返す

moko = {
  homo: true,
  homos: [
    :a,
    :b,
  ]
}

YAML.dump(moko)
=> "---\n:homo: true\n:homos:\n- :a\n- :b\n"

第2引数にIOオブジェクトを渡すとそちらに書き込む

moko = {
  homo: true,
  homos: [
    :a,
    :b,
  ]
}

sio = StringIO.new

YAML.dump(moko, sio)

sio.string
=> "---\n:homo: true\n:homos:\n- :a\n- :b\n"

YAML.dump_stream

moko = {
  homo: true,
  homos: [
    :a,
    :b,
  ]
}

YAML.dump_stream(moko, moko.dup)
=> "---\n:homo: true\n:homos: &1\n- :a\n- :b\n---\n:homo: true\n:homos: *1\n"

JSON

JSON.parse JSON.load

require 'json'

json_data = <<-DATA
{"homo": true, "homos": ["a", "b"]}
DATA

json = JSON.parse(json_data)
=> {"homo"=>true, "homos"=>["a", "b"]}

json.class
=> Hash

JSON.parse は第2引数に 要素を読み込む毎のオプションがあったりする

JSON.load は第2引数に 要素を読み込む毎に呼び出されるProcオブジェクトを渡せる 渡した所で何の役に立つのか謎 今回の例で言うと配列を順番に渡し、最後に配列を渡す。jsonの評価結果に影響を及ぼさないなら何の意味が・・?

require 'json'

json_data = <<-DATA
[1, 2]
DATA

json = JSON.load(json_data, lambda { |i| p i * 2 })
=> 2
=> 4
=> [1, 2, 1, 2]
=> [1, 2]

json
=> [1, 2]

JSON.parse(IO)

JSON.load_file 的なメソッドは用意されていないが、IOから読み込むことが出来る

require 'json'

File.open('path/to/json_file') do |f|
  JSON.load(f)
end
require 'json'

sio = StringIO.new('["a", "b"]')

JSON.load(sio)
=> ["a", "b"]

JSON.dump(object, io = nil, imit = nil)

require 'json'

sio = StringIO.new()

JSON.dump({hoge: ["a", "b"]}, sio)

sio.string
=> "{\"hoge\":[\"a\",\"b\"]}"

CSV

CSV.parse

ブロックを渡すと行毎に処理ができる

require 'csv'

csv_data = <<-DATA
a,b,c
d,e,f
DATA

csv = CSV.parse(csv_data)
=> [["a", "b", "c"], ["d", "e", "f"]]
require 'csv'

csv_data = <<-DATA
a,b,c
d,e,f
DATA

csv = CSV.parse(csv_data) do |row|
  p row
end
=> ["a", "b", "c"]
=> ["d", "e", "f"]

CSV.open

mode

r 読み込み
w 書き込み
rb バイナリ書き込み
wb バイナリ書き込み
require 'csv'

CSV.open('path/to/csv_file') do |row|
  row.each do |col|
    col
  end
end

CSVデータの書き込み

ブロックを渡すパターンと渡さないパターン

require 'csv'

CSV.open('temp1.csv', 'w') do |row|
  row << [1, 2, 3, 4]
  row << [5, 6, 7, 8]
end

csv = CSV.open('temp2.csv', 'w')
csv << [1, 2, 3]
csv << [4, 5, 6]
csv.close
$ cat temp1.csv
1,2,3,4
5,6,7,8

$ cat temp2.csv
1,2,3
4,5,6

CSV.foreach CSV.read CSV.readlines

正直、何のために複数のメソッドになっているのか謎

FileUtils

FileUtils.cd(dir, options = {})

カレントディレクトリを引数dirに変更する ブロックを渡すとブロック内部のみカレントディレクトリが変わる

FileUtils.pwd

カレントディレクトリを文字列で返す

require 'fileutils'

FileUtils.pwd
=> "/Users/mokoaki/xxxx/xxxx"

FileUtils.cd('/Users') do |a|
  p a
  p FileUtils.pwd
end
=> "/Users"
=> "/Users"

FileUtils.pwd
=> "/Users/mokoaki/xxxx/xxxx"

FileUtils.cp FileUtils.copy(src, dest, options = {})

FileUtils.cp_r(src, dest, options = {})

FileUtils.mv FileUtils.move(src, dest, options = {})

ぶっちゃけややこしい!

FileUtils#rm FileUtils#remove(list, options = {})

FileUtils#rm_dir(list, options = {})

FileUtils#rm_r FileUtils#rm_fr(list, options = {})

FileUtils#touch(list, options = {})

FileUtils#mkdir FileUtils#mkdir_p(list, options = {})

FileUtils#chown(user, group, list, options = {})

FileUtils#chmod(mode, list, options = {})

FileUtils#ln_s FileUtils#symlink(src, dest, options = {})

FileUtils#ln FileUtils#link(src, dest, options = {})

ネットワーク

この辺りは辛い!けどがんばれ!

クラスツリー

IO IOクラス
BaseSocket ソースを表す抽象クラス
IPSocket インターネットドメインソケットの抽象クラス
TCPSocket TCP通信を扱うのに適したインターフェイスを提供
TCPServer ↑そのサーバ側のソケットのクラス
SOCKSSocket TCPSocketをSOCKS対応したクラス(?)使用にはコンパイル時にオプションが必要
UDPSocket UDP通信を扱うのに適したインターフェイスを提供
UNIXSocket UNIXドメインソケットによるプロセス間通信を扱うのに適したインターフェイスを提供
UNIXServer ↑そのサーバ側
Socket ホスト間、プロセス間の通信を行う通信ソケットを扱うクラス。IOクラスを継承しており、IOクラスを同じインターフェイスでデータの送受信を行うことができる

TCPServer

require "socket"

server = TCPServer.new(10080)

loop do
  client = server.accept # この行でクライアントからの接続を待機する
  client.puts('Hello TCPServer.')
  client.puts(Time.now.strftime('%Y/%m/%d %T %N'))
  client.close
end

TCPSocket

require "socket"

socket = TCPSocket.new('localhost', 10080)

while line = socket.gets
  puts line
end

UDPを扱うクラス

UDPSocketクラスのインスタンスの生成を行う

UDPSocket.new([socktype])

socktype にはアドレスファミリーと呼ばれるネットワークアドレスの種類を指定する

Socket::AF_INET IPv4ネットワーク (デフォ)
Socket::AF_INET6 IPv6ネットワーク

定義したソケットをホストのポートに関連付ける

socket.bind(host, port)
socket.recv(max_packet[, flags])

UDPSocketを使い、10000ポートでUDP通信を待ち受ける

require 'socket'

max_packet = 1024

socket = UDPSocket.new
socket.bind('0.0.0.0', 10000)

print socket.recv(max_packet)

UDPSocketを使い、10000ポートに対してデータを送信する

require 'socket'

socket = UDPSocket.new

socket.send("Hello UDP world.\n", 0, 'localhost', 10000)

socket.close

プロセス間通信を制御するクラス

UNIXServerを使ってプロセス間通信の待ち受けを行う

実行するとカレントディレクトリにソケットファイルが作成される。プロセス間通信はをのソケットを経由して行われる

require 'socket'
require 'fileutils'

socket_path = 'test_socket'

# 同名ソケットは削除する
if File.exist?(socket_path)
  FileUtils.rm(socket_path)
end

server = UNIXServer.new(socket_path)

loop do
  client = server.accept # この行でクライアントからの接続を待機する
  client.puts('Hello UNIXServer.')
  client.puts(Time.now.strftime('%Y/%m/%d %T %N'))
  client.close
end

UNIXSocketを使ってプロセス間通信にてデータの取得を行う

require 'socket'

socket_path = 'test_socket'

socket = UNIXSocket.new(socket_path)

print socket.gets
print socket.gets

uri

スキームってなんぞ

プロトコル毎に、どんなふうに並べて書くかの決まり

スキーム ユーザ情報   ホスト情報   ポート番号 パス
https:// username:password @ example.com : 21 /public

URI.parse(uri_string) URI(uri_string)

require 'uri'

uri = URI.parse('http://username:pass@example.com:80/index.html')

uri.scheme   #=> "http"
uri.user     #=> "username"
uri.password #=> "pass"
uri.host     #=> "example.com"
uri.port     #=> 80
uri.path     #=> "/index.html"

URI.split(url_string)

[scheme, userinfo, host, port, registory, path, opaque, query, fragment] を返す

require 'uri'

p URI.split('http://username:pass@example.com:80/index.html')
=> ["http", "username:pass", "example.com", "80", nil, "/index.html", nil, nil, nil]

URI.escape(string) URI.encode(string)

require 'uri'

p URI.encode('http://example.com/モコ')
=> "http://example.com/%E3%83%A2%E3%82%B3"

p URI.encode('http://example.com/モコ'.encode('EUC-JP'))
=> "http://example.com/%A5%E2%A5%B3"

URI.unescape(str) URI.decode(str)

require 'uri'

p URI.decode('http://example.com/%E3%83%A2%E3%82%B3')
=> "http://example.com/モコ"

p URI.unescape('http://example.com/%A5%E2%A5%B3').force_encoding('EUC-JP').encode('UTF-8')
"http://example.com/モコ"

net/http

Net::HTTP HTTPクライアントの為のクラス
Net::HTTPRequest HTTPリクエストの為のクラス
Net::HTTPResponse HTTPレスポンスの為のクラス

接続されていないインスタンスの作成

Net:HTTP.new(address, port = 80, proxy_addr = nil, proxy_port = nil)

接続オープン、クローズ

ブロックを渡すと処理終了時に自動でfinishしてくれる

接続されたインスタンスを作成

ブロックを渡すと処理終了時に自動でfinish

Net:HTTP.start(address, port = 80, proxy_addr = nil, proxy_port = nil)
Net:HTTP.start(address, port = 80, proxy_addr = nil, proxy_port = nil) { |http| ... }

コンテンツの取得 get(path, header = nil)

コンテンツの取得 Net::HTTP.get(address, path, port = 80)

未接続のインスタンスを作成してからコンテンツを取得する

require 'net/http'

net = Net::HTTP.new('example.com')
net.start

response = net.get('/')
net.finish

p res.body
#=> htmlがダラダラと流れる

上記の例にブロックを渡してfinish自動版

require 'net/http'

net = Net::HTTP.new('example.com')

net.start do |http|
  response = http.get('/')
  p response.body
  #=> htmlがダラダラと流れる
end

接続済のインスタンスを作成してからコンテンツを取得する

require 'net/http'

net = Net::HTTP.start('example.com')

response = net.get('/')
net.finish

p response.body
#=> htmlがダラダラと流れる

上記の例にブロックを渡してfinish自動版

require 'net/http'

Net::HTTP.start('example.com') do |http|
  response = http.get('/')
  p response.body
  #=> htmlがダラダラと流れる
end

Net::HTTP.get で直接コンテンツを取得

require 'net/http'

responce = Net::HTTP.get('example.com', '/')

response.class
=> Net::HTTPOK

p response.body
#=> htmlがダラダラと流れる

コンテンツの取得 post(path, data, header = nil, dest = nil)

↓すんませんテストできてないです

require 'net/http'

Net::HTTP.start('localhost') do |http|
  response = http.post('/', '')
  p response.body
end

Net::HTTP.start('localhost') do |http|
  response = []
  http.post('/', '', nil, response)
  p response
end

HTTPRequest

require 'net/http'

http = Net::HTTP.new('www.mokoaki.net', 80)
request = Net::HTTP::Get.new('/')
request[:hage] = 'hage-!' # ヘッダを指定してみたり
request.basic_auth('user_name', 'password') #BASIC認証用のヘッダを付加してみたり
response = http.request(request)

p response.body
#=> htmlがダラダラと流れる

HTTPResponse

require 'net/http'

Net::HTTP.start('example.com') do |http|
  response = http.get('/')

  p response.code
  #=> 200
  p response.message
  #=> "OK"
  p response['content-length']
  #=> "606"
end

日付、時間

クラスツリー

dateライブラリには日付を扱うDateクラスと日付と時間、タイムゾーンを扱うDateTimeクラスが含まれている

Dateクラス DateTimeクラス の時間データの持ち方

Dateクラスの減算は Rational が返ってくる

require 'date'

DateTime.new(2011, 1, 1) - Date.new(2010, 1, 1)
=> (365/1) # Rationalが返ってくる

<<, >> メソッドは nヶ月前、nヶ月後の日付オブジェクトを返す

(DateTime.new(2011, 1, 1) << 1).class
=> DateTime

(Date.new(2011, 1, 1) << 1).class
=> Date

(DateTime.new(2011, 1, 1) << 1).strftime('%Y/%m/%d')
=> "2010/12/01"

(Date.new(2011, 1, 1) >> 1).strftime('%Y/%m/%d')
=> "2011/02/01"

Date.civil Date.new(year, mon, mday, start)

start はグレゴリオ暦を使い始めた荷を表すユリウス日です。との事だが、だから何って感じ

Date.new.strftime('%Y/%m/%d')
=> "-4712/01/01"

Date.new(2010).strftime('%Y/%m/%d')
=> "2010/01/01"

Date.new(2010, 10).strftime('%Y/%m/%d')
=> "2010/10/01"

Date.new(2010, 10, 10).strftime('%Y/%m/%d')
=> "2010/10/10"

Date.parse(str, complete = true, start)

require 'date'

Date.parse('10/4/1').class
=> Date

Date.parse('10/4/1').strftime('%Y/%m/%d')
=> "2010/04/01"

Date.parse('10/4/1', false).strftime('%Y/%m/%d')
=> "0010/04/01"

Date.strptime(str, frrmat, start)

require 'date'

Date.strptime('10/4/1', '%y/%m/%d').class
=> Date

Date.strptime('10/4/1', '%y/%m/%d').strftime('%Y/%m/%d')
=> "2010/04/01"

Date.strptime('10_HOMO_2010_HAGE_20', '%m_HOMO_%Y_HAGE_%d').strftime('%Y/%m/%d')
=> "2010/10/20"

Date.today

本日の日付でDateインスタンスを生成する

Date#year Date#mon(month) Date#day(mday)

require 'date'

Date.today.mon == Date.today.month
=> true

Date.today.day == Date.today.mday
=> true

Date#next Date#succ

次の日付のDateインスタンスを生成

require 'date'

Date.today.day + 1 == Date.today.next.day
#=> true

Date.step(limit, step) Date.downto(min) Date.upto(max)

require 'date'

day1 = Date.today
day2 = day1.next.next.next.next.next

p day1.strftime('%Y/%m/%d')
#=> "2017/09/14"

p day2.strftime('%Y/%m/%d')
#=> "2017/09/19"

day1.step(day2, 2) { |d| p d.strftime('%Y/%m/%d') }
#=> "2017/09/14"
#=> "2017/09/16"
#=> "2017/09/18"

day1.upto(day2) { |d| p d.strftime('%Y/%m/%d') }
#=> "2017/09/14"
#=> "2017/09/15"
#=> "2017/09/16"
#=> "2017/09/17"
#=> "2017/09/18"
#=> "2017/09/19"

day2.downto(day1) { |d| p d.strftime('%Y/%m/%d') }
#=> "2017/09/19"
#=> "2017/09/18"
#=> "2017/09/17"
#=> "2017/09/16"
#=> "2017/09/15"
#=> "2017/09/14"

Date#leap?

閏年ならtrue

Date#to_s

あ、’%Y-%m-%d’ なのか。見やすいじゃん

require 'date'

Date.today.to_s
=> "2010-04-03"

Date.strftime(format)

件のフォーマットのアレ。超べんり

require 'date'

Date.today.strftime('%Y_%y_%Y')
#=> "2017_17_2017"

DateTime#civil(year = -4712, mon = 1, mday = 1, hour = 0, min = 0, sec = 0, offset = 0, start = Date::ITALY)

DateTime#new(year = -4712, mon = 1, mday = 1, hour = 0, min = 0, sec = 0, offset = 0, start = Date::ITALY)

require 'date'

DateTime.new(2010, 4, 3, 10, 10, 10).to_s
#=> "2010-04-03T10:10:10+00:00"

DateTime.new(2010, 4, 3, 10, 10, 10, Rational('9/24')).to_s
#=> "2010-04-03T10:10:10+09:00"

DateTime.new(2010, 4, 3, 10, 10, 10, 'GMT+09').to_s
#=> "2010-04-03T10:10:10+09:00"

DateTime.new(2010, 4, 3, 10, 10, 10, '+0900').to_s
#=> "2010-04-03T10:10:10+09:00"

DateTime.new(2010, 4, 3, 10, 10, 10, '+09:00').to_s
#=> "2010-04-03T10:10:10+09:00"

DateTime.now

現在時刻でDateTimeインスタンスを生成 ちゃんとタイムゾーンも反映されている

DateTime#hour min sec zone offset

時分秒、タイムゾーンの文字列表現、タイムゾーンのRationalを返す

require 'date'

DateTime.now.hour
=> 18

DateTime.now.zone
=> "+09:00"

DateTime.now.offset
=> (3/8)

マルチスレッド・同期

Threadクラス に書きました

開発ツール

Rubyは

等のツールを添付ライブラリの形で提供している。ソースに組み込んで利用するものあれば、使用したいときだけコマンドラインオプションに組み込んで呼び出すものもある

debugライブラリの呼び出し

デバッグを行うdebugライブラリはrubyコマンドに -r オプションを付けて呼び出す

$ ruby -rdebug moko.rb

debug や profile 等のライブラリはバグの原因を特定したり性能問題を解決したりする際に便利(ぐぐってね)

test/unit

moko.rb

class Moko
  def hage
    true
  end

  def hello(name)
    "Hello #{name}."
  end
end

test_moko.rb

require 'test/unit'
require 'moko'

class TestMoko < Test::Unit::TestCase
  def setup
    @moko = Moko.new
  end

  def teardown
  end

  def test_hage
    assert_equal(true, @moko.hage)
  end

  def test_hello
    assert_equal('Hello World.', @moko.hello('World'))
  end
end

moko.rb を require できるようにカレントディレクトリをロードパスに追加して実行する

$ ruby test_moko.rb
Loaded suite test_moko
Started
..

Finished in 0.000781 seconds.
------------------------------------------------------------------------------------------------------------------------------------------------------------
2 tests, 2 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications
100% passed
------------------------------------------------------------------------------------------------------------------------------------------------------------
2560.82 tests/s, 2560.82 assertions/s

moko_test.rb の、

require 'moko'

の箇所を

require './moko'

require_relative 'moko'

と書けば起動時にロードパスの追加は必要ない気がするけど

$ ruby test_moko.rb
Loaded suite test_moko
Started
..

Finished in 0.001216 seconds.
------------------------------------------------------------------------------------------------------------------------------------------------------------
2 tests, 2 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications
100% passed
------------------------------------------------------------------------------------------------------------------------------------------------------------
1644.74 tests/s, 1644.74 assertions/s

好きに書けばいいと思う

失敗した時の例

moko.rb を変更してから

class Moko
  def hage
    false
  end

  def hello(name)
    "Hello Hage #{name}."
  end
end

再実行してみる

と表示してくれる訳です

$ ruby test_moko.rb
Loaded suite test_moko
Started
F
============================================================================================================================================================
Failure: test_hage(TestMoko)
test_moko.rb:13:in `test_hage'
     10:   end
     11:
     12:   def test_hage
  => 13:     assert_equal(true, @moko.hage)
     14:   end
     15:
     16:   def test_hello
<true> expected but was
<false>

diff:
? tru e
? fals
============================================================================================================================================================
F
============================================================================================================================================================
Failure: test_hello(TestMoko)
test_moko.rb:17:in `test_hello'
     14:   end
     15:
     16:   def test_hello
  => 17:     assert_equal('Hello World.', @moko.hello('World'))
     18:   end
     19: end
     20:
<"Hello World."> expected but was
<"Hello Hage World.">

diff:
? Hello Hage World.
============================================================================================================================================================


Finished in 0.016101 seconds.
------------------------------------------------------------------------------------------------------------------------------------------------------------
2 tests, 2 assertions, 2 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications
0% passed
------------------------------------------------------------------------------------------------------------------------------------------------------------
124.22 tests/s, 124.22 assertions/s

test/unit の使い方

検証によく使われるメソッド

を以下で説明していきます だいたいこんな感じのコードで実行し、必要そうなところだけコピペしていきます

require 'test/unit'

class TestMoko < Test::Unit::TestCase
  def test_hage
    assert(false, '検証')
  end
end

assert(boolean, message = nil)

booleanが trueであれば検証が成功 messageにてメッセージを指定できる

Failure:
  assert 検証.
  <false> is not true.
test_hage(TestMoko)
test_moko.rb:5:in `test_hage'
     2:
     3: class TestMoko < Test::Unit::TestCase
     4:   def test_hage
  => 5:     assert(false, 'assert 検証')
     6:   end
     7: end
     8:

assert(message = nil) { … }

Failure: assertブロック付き 検証.
test_hage(TestMoko)
test_moko.rb:5:in `test_hage'
     2:
     3: class TestMoko < Test::Unit::TestCase
     4:   def test_hage
  => 5:     assert('assertブロック付き 検証') do
     6:       false
     7:     end
     8:   end
Failure:
  assertブロック付き 検証.
        ['2', '4'] == (1..6).select(&:even?).map(&:to_s)
                   |         |               |
                   |         |               ["2", "4", "6"]
                   |         [2, 4, 6]
                   false
test_hage(TestMoko)
test_moko.rb:5:in `test_hage'
     2:
     3: class TestMoko < Test::Unit::TestCase
     4:   def test_hage
  => 5:     assert('assertブロック付き 検証') do
     6:       ['2', '4'] == (1..6).select(&:even?).map(&:to_s)
     7:     end
     8:   end

assert_equal(expected, actual, message = nil)

Failure: test_hage(TestMoko)
test_moko.rb:5:in `test_hage'
     2:
     3: class TestMoko < Test::Unit::TestCase
     4:   def test_hage
  => 5:     assert_equal(true, false, 'assert_equal 検証')
     6:   end
     7: end
assert_equal 検証
<true> expected but was
<false>

diff:
? tru e
? fals

assert_not_equal(expected, actual, message = nil)

Failure:
  assert_not_equal 検証.
  <true> was expected to be != to
  <true>.
test_hage(TestMoko)
test_moko.rb:5:in `test_hage'
     2:
     3: class TestMoko < Test::Unit::TestCase
     4:   def test_hage
  => 5:     assert_not_equal(true, true, 'assert_not_equal 検証')
     6:   end
     7: end

assert_instance_of(klass, object, message = nil)

object が klassクラスのインスタンスであれば検証が成功する

Failure:
  assert_instance_of 検証.
  <1> was expected to be instance_of?
  <String> but was
  <Integer>.
test_hage(TestMoko)
test_moko.rb:5:in `test_hage'
     2:
     3: class TestMoko < Test::Unit::TestCase
     4:   def test_hage
  => 5:     assert_instance_of(String, 1, 'assert_instance_of 検証')
     6:   end
     7: end

assert_nil(object, message = nil)

object が nil であれば検証が成功する

Failure:
  assert_nil 検証.
  <1> was expected to be nil.
test_hage(TestMoko)
test_moko.rb:5:in `test_hage'
     2:
     3: class TestMoko < Test::Unit::TestCase
     4:   def test_hage
  => 5:     assert_nil(1, 'assert_nil 検証')
     6:   end
     7: end

assert_not_nil(object, message = nil)

object が nil でなければ検証が成功する

Failure:
  assert_not_nil 検証.
  <nil> was expected to not be nil.
test_hage(TestMoko)
test_moko.rb:5:in `test_hage'
     2:
     3: class TestMoko < Test::Unit::TestCase
     4:   def test_hage
  => 5:     assert_not_nil(nil, 'assert_not_nil 検証')
     6:   end
     7: end

assert_kind_off(klass, object, message = nil)

Failure:
  assert_kind_of 検証.
  <"a"> was expected to be kind_of?
  <Integer> but was
  <String>.
test_hage(TestMoko)
test_moko.rb:5:in `test_hage'
     2:
     3: class TestMoko < Test::Unit::TestCase
     4:   def test_hage
  => 5:     assert_kind_of(Integer, 'a', 'assert_kind_of 検証')
     6:   end
     7: end

assert_match(pattern, string, message = nil)

patternに指定した正規表現がstringにマッチするなら検証が成功する

Failure:
  assert_match 検証.
  </\d/> was expected to be =~
  <"a">.
test_hage(TestMoko)
test_moko.rb:5:in `test_hage'
     2:
     3: class TestMoko < Test::Unit::TestCase
     4:   def test_hage
  => 5:     assert_match(/\d/, 'a', 'assert_match 検証')
     6:   end
     7: end

assert_not_match(pattern, string, message = nil)

patternに指定した正規表現がstringにマッチしないなら検証が成功する

Failure:
  assert_not_match 検証.
  </\d/> was expected to not match
  <"0">.
test_hage(TestMoko)
test_moko.rb:5:in `test_hage'
     2:
     3: class TestMoko < Test::Unit::TestCase
     4:   def test_hage
  => 5:     assert_not_match(/\d/, '0', 'assert_not_match 検証')
     6:   end
     7: end

assert_raise(expected_exception_klass, message = nil) { … }

ブロック処理中に expected_exception_klass にて指定した例外をraiseした場合に検証が成功する

Failure:
  assert_raise 検証.
  <ZeroDivisionError> exception was expected but none was thrown.
test_hage(TestMoko)
test_moko.rb:5:in `test_hage'
     2:
     3: class TestMoko < Test::Unit::TestCase
     4:   def test_hage
  => 5:     assert_raise(ZeroDivisionError , 'assert_raise 検証') { 1 / 1 }
     6:   end
     7: end

assert_nothing_raised(*expected_exception_klass, message = nil) { … }

ブロック処理中に expected_exception_klass にて指定した例外がraiseされなかった場合に検証が成功する

Failure:
  assert_nothing_raised 検証.
  Exception raised:
  ZeroDivisionError(<divided by 0>)
test_hage(TestMoko)
test_moko.rb:5:in `test_hage'
     2:
     3: class TestMoko < Test::Unit::TestCase
     4:   def test_hage
  => 5:     assert_nothing_raised(ZeroDivisionError, NameError, 'assert_nothing_raised 検証') { 1 / 0 }
     6:   end
     7: end

flunk(message = nil)

Failure: 例えばこの検証は後で実装する、等.
test_hage(TestMoko)
test_moko.rb:5:in `test_hage'
     2:
     3: class TestMoko < Test::Unit::TestCase
     4:   def test_hage
  => 5:     flunk('例えばこの検証は後で実装する、等')
     6:   end
     7: end
     8:

assert_throw(expected_symbol, message = nil) { … }

ブロック処理中に expected_symbol が throwされ、catchされない場合に検証が成功する

Failure:
  assert_throw 検証.
  <:exit> should have been thrown.
test_hage(TestMoko)
test_moko.rb:5:in `test_hage'
     2:
     3: class TestMoko < Test::Unit::TestCase
     4:   def test_hage
  => 5:     assert_throw(:exit, 'assert_throw 検証') { catch(:exit) { throw :exit } }
     6:   end
     7: end
     8:

assert_nothing_thrown(message = nil) { … }

ブロック処理中に何もthrowされない場合に検証が成功する

Failure:
  assert_nothing_thrown 検証.
  <:exit> was thrown when nothing was expected.
test_hage(TestMoko)
test_moko.rb:5:in `test_hage'
     2:
     3: class TestMoko < Test::Unit::TestCase
     4:   def test_hage
  => 5:     assert_nothing_thrown('assert_nothing_thrown 検証') { throw :exit }
     6:   end
     7: end
     8:

RDoc

適当に試してみる

moko.rb

class Moko < Integer
  class << self
    # コメント1
    def test1
      'a'
    end
  end

  =begin
  コメント2
  =end
  def test2
    'b'
  end
end
$ rdoc
Parsing sources...
100% [ 1/ 1]  moko.rb

Generating Darkfish format into /current/dir/doc...

  Files:      1

  Classes:    1 (1 undocumented)
  Modules:    0 (0 undocumented)
  Constants:  0 (0 undocumented)
  Attributes: 0 (0 undocumented)
  Methods:    2 (1 undocumented)

  Total:      3 (2 undocumented)
   33.33% documented

  Elapsed: 0.1s

doc/index.html あたりをブラウザで確認すると、ナウい感じのドキュメントが見れる

コメントはいろいろマークアップしてかっこよくできる でも・・なんかアスキー文字しか対応してない感じがする

いつもの太字で表示される

# = 見出し1
# == 見出し2
# === 見出し5
# ==== 見出し5
# ===== 見出し5
# ====== 見出し6

* アスタで囲むとボールド体で装飾される

# *リスト1*

_ アンスコで囲むとイタリック体で装飾される

# _リスト1_

+ プラスで囲むとタイプライター体で装飾される

# +リスト1+

* - で文字列を開始すると番号なしリストが作れる

# * リスト1
# * リスト2
# * リスト3

# - リスト1
# - リスト2
# - リスト3

番号.(ピリオド)で番号付きリスト

# 1. リスト1
# 2. リスト2
# 3. リスト3

[]で囲んだ文字列で開始するとラベル付きリストが作れる

# [大項目1] コメント1
# [大項目2] コメント2
# [大項目3] コメント3

コロンで区切ってもラベル付きリストが作れる

# 大項目1:: コメント1
# 大項目2:: コメント2
# 大項目3:: コメント3

コマンドライン

シェル上で動作するプログラムへオプションを渡したい時、コマンドライン解析用のライブラリとして optparse を利用できる

正直、感覚的な動作ではなく、ショボいと思う

require 'optparse'

options = {}

OptionParser.new do |opt|
  opt.on('-v', '--version') do |value|
    options[:version] = value
  end

  opt.on('-i value') do |value|
    options[:input_path] = value
  end

  opt.parse!
end

p options
p ARGV
$ ruby moko.rb -v -i file_path hage
{:version=>true, :input_path=>"file_path"}
["hage"]

--help を渡すと登録されたオプションの一覧を出力する

$ ruby moko.rb -v -i file_path hoge --help
Usage: moko [options]
    -v, --version
    -i value

インスタンス作成時に –helpオプションで表示される文字列を指定できる。正直ショボい

ヘッダに表示される文字, widthを超えるようなら改行して表示、インデント

OptionParser.new('ヘッダに表示される文字', 10, '> ')

こんな感じでインスタンスを作っておくと こんな感じで表示される

$ruby moko.rb --help
ヘッダに表示される文字
> -v
>     --version
> -i=

onメソッドでオプションの定義を行い、parseメソッドで引数の解釈を開始する

-v ショートオプションを定義 ブロック引数には true が渡される
-vnandemo ショートオプションを定義 ブロック引数には オプションの後に渡した文字列が渡される
–version ロングオプションを定義 ブロック引数には true が渡される
–no-version ロングオプションを定義 ブロック引数には false が渡される
–[no-]version ロングオプションを定義 呼び出し方により、ブロック引数には true / false が渡される
–version=nandemo ロングオプションを定義 ブロック引数には オプションの後に渡した文字列が渡される

parse(argv = ARGV) / parse!(argv = ARGV)