著者:しょっさん
最近、自宅で二酸化炭素濃度をはかるようになりました。 予想していた以上に、家の中の空気が汚染されていることがわかって、衝撃を受けています。部屋が暖かいから眠くなるんだなー…なんて考えていましたが、暖かいんじゃなくて二酸化濃度が高いからという可能性が高いです。
二酸化炭素濃度を下げるには、換気をすることが基本ですが、この濃度を下げてからというもの、家での活動がとてもスムーズになり眠くなることが減りました。
なんでこんなことしてるのかって? もちろん、クリアーな頭の状態でプログラミングするため。さぁ今月も頑張っていきましょう!
記事本文掲載のシェルスクリプトマガジンvol.47は以下リンク先でご購入できます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
$ bundle gem reins Creating gem 'reins'... Do you want to generate tests with your gem? Type 'rspec' or 'minitest' to generate those test files now and in the future. rspec/minitest/(none): rspec Do you want to license your code permissively under the MIT license? This means that any other developer or company will be legally allowed to use your code for free as long as they admit you created it. You can read more about the MIT license at http://choosealicense.com/licenses/mit. y/(n): Do you want to include a code of conduct in gems you generate? Codes of conduct can increase contributions to your project by contributors who prefer collaborative, safe spaces. You can read more about the code of conduct at contributor-covenant.org. Having a codeof conduct means agreeing to the responsibility of enforcing it, so be sure that you are prepared todo that. Be sure that your email address is specified as a contact in the generated code of conduct so that people know who to contact in case of a violation. For suggestions about how to enforce codesof conduct, see http://bit.ly/coc-enforcement. y/(n): create reins/Gemfile create reins/.gitignore create reins/lib/reins.rb create reins/lib/reins/version.rb create reins/reins.gemspec create reins/Rakefile create reins/README.md create reins/bin/console create reins/bin/setup create reins/.travis.yml create reins/.rspec create reins/spec/spec_helper.rb create reins/spec/reins_spec.rb Initializing git repo in /xxxxxx/reins |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
├── Gemfile ├── Gemfile.lock ├── LICENSE ├── README.md ├── Rakefile ├── lib │ ├── reins │ │ ├── auth_service.rb │ │ ├── config.rb │ │ ├── host_registry.rb │ │ ├── receiver.rb │ │ ├── task_control.rb │ │ └── version.rb │ └── reins.rb ├── reins.gemspec └── spec ├── reins_spec.rb └── spec_helper.rb |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 |
# coding: utf-8 require "spec_helper" require "socket" RSpec.describe Reins do describe "定数/変数の設定" do it "Reins::VERSION の指定" do expect(Reins::VERSION).not_to be nil end it "logger の指定" do expect(Reins::logger).not_to be nil end it "data managerのインスタンス化" do expect(Reins::auth_service).not_to be nil end it "authenticatorのインスタンス化" do expect(Reins::regist_host).not_to be nil end end # AuthService classのテスト describe "AuthService" do describe "#authenticate_key" do let(:sha512) { "106a6484291b9778c224731501d2deeb71f2b83558a0e9784fe33646f56182f69de448e92fe83fd4e57d629987f9d0dd79bf1cbca4e83b996e272ba44faa6adb" } let(:normal) { Reins::AuthService.new } let(:other) { Reins::AuthService.new(sha512) } context "正常に認証された場合" do it { expect(normal.authenticate_key('DEMO', '192.168.0.10')).not_to eq(false) } it { expect(other.authenticate_key('40ruby', '192.168.0.10')).not_to eq(false) } end context "異なる認証キーで呼び出された場合" do it { expect(normal.authenticate_key('TEST', '192.168.0.10')).to eq(false) } it { expect(other.authenticate_key('DEMO', '192.168.0.10')).to eq(false) } end end describe "#is_varid" do let(:auth) { Reins::AuthService.new } let(:key) { auth.authenticate_key('DEMO', '192.168.0.10') } context "登録済みのコードの場合" do it "接続認証キーで、当のIPアドレスが返る" do allow(auth).to receive(:is_varid).and_return('192.168.0.10') expect(auth.is_varid(key)).to eq('192.168.0.10') end end context "未登録コードの場合" do it "nil" do expect(auth.is_varid(key)).to eq(nil) end end end end # HostRegistry Classのテスト describe 'HostRegistry' do let(:regist_test) { Reins::HostRegistry.new("test_db.csv") } let(:test_key) { "TestKey" } let(:localhost) { '127.0.0.1' } let(:correct_hosts) { ['192.168.0.10','1.0.0.1','239.255.255.254'] } let(:incorrect_hosts) { ['100','[100,100]','1:0:0:1','1/0/0/1','0.0.0.1','1.0.0.0','0.0.0.0','240.0.0.1','240.255.255.254','0.256.0.1','0.0.256.1','239.255.255.255','-1.0.0.0','0.-1.0.0','0.0.-1.0','0.0.0.-1'] } before do allow(regist_test).to receive(:store).and_return(true) end describe '#create' do subject {regist_test.create(localhost, test_key) } it '同じIPを追加すると false' do regist_test.create(localhost, test_key) is_expected.to eq(false) end end describe '#read_hosts' do subject {regist_test.read_hosts } context '正常に登録されている場合' do it '未登録時に呼び出すと、空の配列' do is_expected.to eq([]) end it 'localhost を1つ登録すると、[127.0.0.1]' do regist_test.create(localhost, test_key) is_expected.to eq([localhost]) end it '複数のアドレスを登録した場合は、複数のアドレス' do correct_hosts.each { |host|regist_test.create(host, test_key) } is_expected.to match_array(correct_hosts) end end context '不正なIPアドレスの場合' do it '有効範囲外のIPアドレスを登録しても登録されず、空の配列' do incorrect_hosts.each { |host|regist_test.create(host, test_key) } is_expected.to match_array([]) end end end describe '#read_hostkeys' do it '登録しなければ、空' do expect(regist_test.hosts).to eq([]) expect(regist_test.read_hostkeys).to eq({}) expect(regist_test.read_hostkeys.size).to eq(0) end it 'ハッシュ化された接続キーの一覧' do correct_hosts.each { |host|regist_test.create(host, test_key) } expect(regist_test.read_hostkeys.size).to eq(3) end end describe '#update' do before {regist_test.create(localhost, test_key) } context '正常に更新できる場合' do it '有効なIPアドレスへ変更すると host が変更' do before_host = localhost correct_hosts.each do |host| expect {regist_test.update(before_host, host) }.to change {regist_test.read_hosts }.from([before_host]).to([host]) before_host = host end end end context '更新できない場合' do it '有効範囲外のIPアドレスへ変更すると false' do incorrect_hosts.each do |host| expect(regist_test.update(localhost, host)).to eq(false) end end it '未登録のIPアドレスを指定すると false' do expect(regist_test.update('192.168.0.1','172.16.0.1')).to eq(false) end it 'すでに存在するIPアドレスへ変更すると false' do expect(regist_test.update('192.168.0.1',localhost)).to eq(false) end end end describe '#delete' do before {regist_test.create("192.168.0.1", test_key)} subject {regist_test.delete(localhost) } context '正常に削除できる場合' do it '登録済みのアドレスを削除すると、そのアドレス' do regist_test.create(localhost, test_key) is_expected.to eq(localhost) end end context '削除できない場合' do it '削除対象がなければ nil' do is_expected.to eq(nil) end end end end # TaskControl class のテスト describe 'TaskControl' do before { @server = TCPServer.new(24368) } after { @server.close } let(:tasks) { Reins::TaskControl.new } describe '#connect' do context 'クライアントへ接続できる場合' do subject { tasks.connect } it 'モックを使って正常接続のコール' do allow(tasks).to receive(:connect).and_return(true) is_expected.to eq(true) end end context 'クライアントへ接続できない場合' do it '接続先が存在しないと Standard Error' do # TODO: 実際は Raise ではなく、成否を True/False でもらうこととする expect{ Reins::TaskControl.new('localhost', 65000) }.to raise_error 'Not Connect' end it 'クライアントが停止していたら false' do allow(tasks).to receive(:connect).and_return(false) expect(tasks.connect).to eq(false) end end end describe '#disconnect' do context '正常に切断できた場合' do subject { tasks.disconnect } it 'こちらから、クライアントとの接続を切断して、成功すると nil' do is_expected.to eq(nil) end end end end end |
1 2 3 4 5 6 7 8 9 10 11 12 13 |
# filename: reins.rb require "reins/version" require "reins/auth_service" require "reins/host_registry" require "reins/task_control" require "reins/config" module Reins def start end module_function :start end |
1 2 3 |
module Reins VERSION = "0.1.7" end |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
# coding: utf-8 require 'logger' module Reins class << self attr_accessor :logger, :auth_service, :regist_host def configure yield self end end end Reins.configure do |config| config.logger = Logger.new(ENV['REINS_LOGGER'] || "/tmp/reins.log") config.auth_service = Reins::AuthService.new(ENV['REINS_KEY'] || "106a6484291b9778c224731501d2deeb71f2b83558a0e9784fe33646f56182f69de448e92fe83fd4e57d629987f9d0dd79bf1cbca4e83b996e272ba44faa6adb") config.regist_host = Reins::HostRegistry.new(ENV['REINS_DATABASE'] || "./40ruby.csv") end Reins::logger.level = ENV['REINS_LOGLEVEL'] ? eval("Logger::#{ENV['REINS_LOGLEVEL']}") : Logger::WARN |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 |
# coding: utf-8 # filename: auth_service.rb # require "digest/md5" require "digest/sha2" module Reins class AuthService # サーバの認証キーを定義する # == パラメータ # secret_key:: SHA512 でハッシュ化されたキーを指定する # == 返り値 # nil def initialize(secret_key = 'ac58c2bedf7c1d4e35136f2ca4f81acdece03fa9e90aeefa0d363488649c7f52d7064285923f814592d53c419f5c4db59ee1a867b4852d18e0fac6efd5874072') @secret_key = secret_key nil end # クライアント認証を行う # 接続元が要求してきたキーが、サーバ側で設定されているハッシュ値と比較する # == パラメータ # key:: ハッシュ化される前のキー # ip:: 接続元のIPアドレス # == 返り値 # 認証:: ハッシュ化されたクライアント固有識別の接続専用キー # 否認:: false def authenticate_key(key, ip) unless @secret_key == Digest::SHA512.hexdigest(key) Reins::logger.fatal("#{ip} : 認証が失敗しました") return false end Digest::SHA512.hexdigest("#{ip}:#{Random.new_seed}") end # クライアントの識別を行う # 要求されたクライアント固有の識別キーが登録されているものか判断する # == パラメータ # key:: クライアント固有の識別キー # == 返り値 # 識別された場合:: 登録されているIPアドレス # 否認された場合:: nil def is_varid(keycode) Reins::regist_host.read_hostkeys.key(keycode) end end end |
リスト6-a、6-bは本来ひとつのコードですが、本Webサイトの文法に抵触するため分割して掲載します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 |
# coding: utf-8 # filename: host_registry.rb require 'csv' module Reins class HostRegistry attr_reader :hosts # データベースの読込・初期化 # ファイルが存在していれば内容を読込、なければファイルを新規に作成 # == パラメータ # filename:: 読込・または保管先のファイル名 # == 返り値 # 特になし。但し、@hosts インスタンス変数へ、データベースの内容を保持 def initialize(filename) @filename = filename @hosts = File.exists?(@filename) ? CSV.read(@filename) : [] end # IPアドレスのチェック # IPアドレスとおぼしき文字列を検査し、IPアドレスっぽいかどうかを調査 # == パラメータ # ip:: チェックしたいIPアドレス文字列 # == 返り値 # true:: IPアドレスと思われる場合 # false:: IPアドレスではないと思われる場合 def check_ip(ip) # 引数は文字列のみを受け付けることとする return false unless ip.is_a?(String) # 正規表現で、各数値を入手 /^([0-9]*)\.([0-9]*)\.([0-9]*)\.([0-9]*)$/ =~ ip return false unless (1..239).cover?($1.to_i) return false unless (0..255).cover?($2.to_i) return false unless (0..255).cover?($3.to_i) return false unless (1..254).cover?($4.to_i) return true end # メモリ上のアドレスリストを、ファイルへ保管する # == パラメータ # filename:: 保管先ファイル名。指定がない場合は、初期化時に採用したファイル名 def store(filename = @filename) CSV.open(filename, "w") do |csv| @hosts.each do |addr| csv << addr end end end # アドレスを新規に登録する。既に登録済みのものであれば登録しない。 # == パラメータ # keyaddr:: 登録する IPアドレスをキーにした、接続用Keyをもつハッシュデータ # == 返り値 # true:: 登録できた # false:: 既に同じアドレスまたはIPアドレスではないため、登録せず def create(addr, key) if check_ip(addr) then unless read_hosts.include?(addr) then @hosts << [addr, key, Time.now] Reins::logger.info("#{addr} を追加しました") store return true else Reins::logger.error("#{addr} は既に登録されています.") end else Reins::logger.error("#{addr} は登録可能なIPアドレスではありません.") end false end |
74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 |
# 登録済みホスト一覧を、IPアドレスをKey、接続KeyをValueとするハッシュで返す # == 返り値 # hash: hash[ipアドレス] = key を一つの要素とする def read_hostkeys @hosts.each.map { |addr, key| [addr, key] }.to_h end # 登録済みホスト一覧を配列で返す # == 返り値 # array: 登録済みのホスト一覧 def read_hosts read_hostkeys.keys end # 登録済みのアドレスを、他のアドレスへ変更する # ただし、変更後のアドレスが既に登録されている場合や、登録済みアドレスが見つからない場合はエラーを返す # == パラメータ # before:: 既に登録済みのアドレス # after:: 変更後のアドレス def update(before, after) if read_hosts.include?(after) || !check_ip(after) Reins::logger.error("#{after} は既に登録済みか、有効なIPアドレスではありません.") return false elsif i = read_hosts.index(before) @hosts[i][0] = after Reins::logger.info("#{before} から #{after} へ変更しました.") return true else Reins::logger.error("変更元の #{before} が見つかりません.") return false end end # 登録済みのアドレスを削除する # == パラメータ # addr: 削除対象のアドレス # == 返り値 # string:: 削除された要素 # nil:: 削除すべき要素が見つからなかったとき def delete(addr) if i = read_hosts.index(addr) @hosts.delete(i) Reins::logger.info("#{addr} を削除しました.") store return addr else Reins::logger.warn("#{addr} が見つかりません.") return nil end end # まだアドレスが登録されていないかどうか # == 返り値 # true:: アドレス未登録 # false:: 1つ以上のアドレスが登録済み def empty? @hosts.empty? end end end |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 |
# encoding: utf-8 # filename: task_control.rb module Reins class TaskControl # クライアントとの接続 # == パラメータ # hostname:: 接続先クライアントのホスト名、またはIPアドレス # port:: 接続先クライアントのTCPポート番号。通常は 24368 # == 返り値 # 接続された通信用のソケットオブジェクト def initialize(hostname = '127.0.0.1', port = 24368) begin @s = TCPSocket.open(hostname, port) rescue => e notify(e) end end # クライアントとの死活確認 # == パラメータ # 特になし # == 返り値 # true:: 生存確認 # false:: クライアントが停止、またはネットワーク上に問題あり def connect begin true rescue => e notify(e) end end # エラーが発生した場合に一時的に飛ばされてくるメソッド def notify(error) raise(StandardError, 'Not Connect') end # クライアントとの接続を切断 # == パラメータ # 特になし # == 返り値 # nil:: 正常に切断 def disconnect @s.close end end end |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 |
$ bundle exec rake spec /Users/sho/.rbenv/versions/2.4.0/bin/ruby -I/Users/sho/Documents/working/ruby/40ruby/reins/vendor/bundle/ruby/2.4.0/gems/rspec-core-3.5.4/lib:/Users/sho/Documents/working/ruby/40ruby/reins/vendor/bundle/ruby/2.4.0/gems/rspec-support-3.5.0/lib /Users/sho/Documents/working/ruby/40ruby/reins/vendor/bundle/ruby/2.4.0/gems/rspec-core-3.5.4/exe/rspec --pattern spec/\*\*\{,/\*/\*\*\}/\*_spec.rb Reins 定数/変数の設定 Reins::VERSION の指定 logger の指定 data manager のインスタンス化 authenticatorのインスタンス化 AuthService #authenticate_key 正常に認証された場合 should not eq false should not eq false 異なる認証キーで呼び出された場合 should eq false should eq false #is_varid 登録済みのコードの場合 接続認証キーで、該当のIPアドレスが返る 未登録コードの場合 nil HostRegistry #create 同じIPを追加すると false #read_hosts 正常に登録されている場合 未登録時に呼び出すと、空の配列 localhost を1つ登録すると、[127.0.0.1] 複数のアドレスを登録した場合は、複数のアドレス 不正なIPアドレスの場合 有効範囲外のIPアドレスを登録しても登録されず、空の配列 #read_hostkeys 登録しなければ、空 ハッシュ化された接続キーの一覧 #update 正常に更新できる場合 有効なIPアドレスへ変更すると host が変更 更新できない場合 有効範囲外のIPアドレスへ変更すると false 未登録のIPアドレスを指定すると false すでに存在するIPアドレスへ変更すると false #delete 正常に削除できる場合 登録済みのアドレスを削除すると、そのアドレス 削除できない場合 削除対象がなければ nil TaskControl #connect クライアントへ接続できる場合 モックを使って正常接続のコール クライアントへ接続できない場合 接続先が存在しないと Standard Error クライアントが停止していたら false #disconnect 正常に切断できた場合 こちらから、クライアントとの接続を切断して、成功すると nil Finished in 0.04302 seconds (files took 0.37678 seconds to load) 27 examples, 0 failures |