ディレクトリのバックアップ-削除あり(Ruby)

Ruby でバックアップ・プログラムを書きました。バックアップ先のフォルダがバックアップ元のフォルダの中身と再帰的に同じ内容になるようにします。なので、バックアップ先にあってバックアップ元の中身にないファイルやフォルダは(バックアップ先で)削除されるので注意して下さい。「削除がされない」バージョンは下のリンク先にあります。
再帰的なファイルのバックアップ(Ruby) - Camera Obscura

なお、機械的なコピーではありません。変更があった部分のみ処理します。基本的に Linux 向けで、Windows ではファイルの日付の考え方が Linux とはちがうので、期待した動作をしません。
 
使い方はこんな感じです。backup('バックアップ元', 'バックアップ先')

backup('/home/***/Documents', '/media/***/MyHDD/Documents')

 
 
backup.rb

require 'fileutils'

def backup(snd, rsv)
  #String{snd, rsv} -> String{r_fn[], s_fn[], s, r}, Time{mts, mtr}
  Dir.chdir(rsv); r_fn = Dir.glob("*")
  Dir.chdir(snd); s_fn = Dir.glob("*")

  r_fn.each do |fname|
    unless s_fn.include?(fname)
      FileUtils.rm_r(File.join(rsv, fname))
      puts "delete: #{fname}"
    end
  end

  s_fn.each do |fname|
    s = File.join(snd, fname)
    r = File.join(rsv, fname)
    mts = File.stat(s).mtime
    mtr = nil
    mtr = File.stat(r).mtime if r_fn.include?(fname)

    if FileTest.directory?(s)
      if r_fn.include?(fname)
	backup(s, r) if mts > mtr and FileTest.directory?(r)
      else
        puts "folder copy: #{fname}"
        FileUtils.cp_r(s, r)
      end
    elsif !r_fn.include?(fname) or mts > mtr
      puts "file copy: #{fname}"
      FileUtils.cp(s, r)
    end
  end
end

任意の階層だけ繰り返しをネストする多重 map 的メソッド(Ruby)

Array#nest_loop で、Integer#times の多重ネスト版です。配列にループ回数を入れて呼び出します。わかり切った多重ループを書くのが面倒なときに役立ちます。ブロックが与えられなければ Enumertor を返します。
 

[4, 2, 3].nest_loop do |i, j, k|
  print "#{[i, j, k]} "
end
#=>
[0, 0, 0] [0, 0, 1] [0, 0, 2]
[0, 1, 0] [0, 1, 1] [0, 1, 2]
[1, 0, 0] [1, 0, 1] [1, 0, 2]
[1, 1, 0] [1, 1, 1] [1, 1, 2]
[2, 0, 0] [2, 0, 1] [2, 0, 2]
[2, 1, 0] [2, 1, 1] [2, 1, 2]
[3, 0, 0] [3, 0, 1] [3, 0, 2]
[3, 1, 0] [3, 1, 1] [3, 1, 2]

 
for in 文を使って実装しているので、self の配列の中には範囲演算子や配列、ハッシュなどを使うことができます。また、ブロックのそれぞれの返り値が配列に入って返ります。なので、多重ループ版の map のように使えます。

p [2..6, 2, ["a", "b"]].nest_loop(&:itself)
#=>[[2, 0, "a"], [2, 0, "b"], [2, 1, "a"], [2, 1, "b"], [3, 0, "a"], [3, 0, "b"],
#   [3, 1, "a"], [3, 1, "b"], [4, 0, "a"], [4, 0, "b"], [4, 1, "a"], [4, 1, "b"],
#   [5, 0, "a"], [5, 0, "b"], [5, 1, "a"], [5, 1, "b"], [6, 0, "a"], [6, 0, "b"],
#   [6, 1, "a"], [6, 1, "b"]]

a = [:a, :b, :c]
p [a, a].nest_loop {|i, j| (j == :b) ? [i, "b"] : [i, j]}
#=>[[:a, :a], [:a, "b"], [:a, :c], [:b, :a], [:b, "b"],
#   [:b, :c], [:c, :a], [:c, "b"], [:c, :c]]



本体のコードです。(※注:2018/2/7 に修正しました。)
nest_loop.rb

class Array
  def nest_loop
    check = lambda do |obj|
      return 0...obj if obj.class.ancestors.include?(Integer)
      obj
    end
    
    e = Enumerator.new do |y|
      ns = lambda do |ax, args|
        a, ax = ax.first, ax.drop(1)
        for i in check.call(a)
          nxt = args + [i]
          if ax.empty?
            y << nxt
          else
            ns.call(ax, nxt)
          end
        end
      end
      ns.call(self, [])
    end
    block_given? ? e.map {|i| yield(i)} : e
  end
end

 

Gem 化

自家製の utility Gem 'kaki-utils' に同梱しました。
kaki-utils | RubyGems.org | your community gem host
インストールは $ gem install kaki-utils で。使い方は

require 'kaki/utils/nest_loop'

a = [1, 2, 3]
p [a, a].nest_loop {|i, j| i * j}    #=>[1, 2, 3, 2, 4, 6, 3, 6, 9]

という感じ。(2018/2/6)

GTK+で落書き 4(Ruby)

引き続き Gem 'oekaki' で落書きです。
oekaki | RubyGems.org | your community gem host
GTK+でお絵かきしてみた(Ruby) - Camera Obscura



Ruby コード。

require 'oekaki'
include Math

L = 300; O = L / 2
R = 140
step = 20

i = 0

Oekaki.app width: L, height: L do
  draw do
    color(0, 0, 0)
    rectangle(true, 0, 0, L, L)
    color(0x87 * 256, 0xce * 256, 0xeb * 256)    #skyblue
    arc(false, O - R, O - R, R * 2, R * 2, 0, 64 * 360)
  end
  
  id = timer(500) do
    ar = []
    θ = i * step * PI / 180
    x = R * cos(θ)
    y = R * sin(θ)
    ar << [O + x, O - y]
    3.times do
      x, y = -y, x
      ar << [O + x, O - y]
    end
    color(0x22 * 256, 0x8b * 256, 0x22 * 256)    #forestgreen
    polygon(false, ar)
    i += 1
    Gtk.timeout_remove(id) if i * step > 180
    true
  end
end

GTK+ でぽちぽち遊び(Ruby)

キャンバス上でマウスクリックして下さい。ぽちぽち円が描かれます。


右クリックで終了ボタンが出ます。


Ruby コード。

require 'oekaki'

L = 500; R = 25

Oekaki.app width: L, height: L do
  draw do
    color(0, 0, 0)
    rectangle(true, 0, 0, L, L)
  end
  
  quit_window = proc do
    make_window do |w|
      w.title = ""
      b = button do
        set_size_request(120, 40)
        add(Gtk::Label.new.set_markup('<span size="x-large">Quit!</span>'))
        signal_connect("clicked") {Gtk.main_quit}
      end
      add(b)
    end
  end

  mouse_button do |w, e|
    quit_window.call if e.button != 1
    color(rand(65536), rand(65536), rand(65536))
    arc(true, e.x - R, e.y - R, R * 2, R * 2, 0, 64 * 360) 
  end
end

 
Gem 'oekaki'(かつての 'mygtk')については
GTK+でお絵かきしてみた(Ruby) - Camera Obscura
メソッド Event#mouse_button, Event#make_window, Gtk::Window#button を加えました。
oekaki | RubyGems.org | your community gem host

複素フィボナッチ数列と GTK+ お絵かき

GTK+でお絵かきしてみた(Ruby) - Camera Obscura
これまで mygtk.rb と言っていた GTK+ お絵かきモジュールを、Gem 'oekaki' として RubyGems.org に登録いたしました。よろしかったら使ってやって下さい。
oekaki | RubyGems.org | your community gem host
その簡単な記録が
最小限度の Ruby Gem 作成と公開 - Marginalia
であります。


それとちょっと関係してといいますか、いつも拝読しているブログ「完全無欠で荒唐無稽な夢」の記事に、複素フィボナッチ数列というおもしろそうな題材がありましたので、お絵かきしてみました。数列を複素平面にプロットしただけであります。ただし、ものすごい勢いで原点から遠ざかったりもするので、原点からの距離は対数をとってあります。
複素フィボナッチ数列の件 - 完全無欠で荒唐無稽な夢

複素フィボナッチ数列とは,
  
というものですね。

まず、
  
の場合。

わかりにくいけれど、螺旋ですね。

上ブログ記事にあった、
  
の場合。

あんまり変わらないですかね。


Ruby のコード。

require 'oekaki'

L = 400
a = Complex(1, -1)
b = Complex(1,  1)

Oekaki.app width: L, height: L do
  draw do
    color(0, 0, 0)
    rectangle(true, 0, 0, L, L)
  end
  
  plot = lambda do |a|
    x1 = a.real
    y1 = a.imaginary
    r = Math.sqrt(x1 ** 2 + y1 ** 2)
    l = Math.log(r) * 2.5
    x = L / 2 + 1 + x1 * l / r
    y = L / 2 + 1 - y1 * l / r
    color(0, 65535, 0)
    arc(false, x, y, 3, 3, 0, 64 * 360) rescue return
  end  
  
  timer(20) do
    plot.call(a)
    a, b = b, b + Complex::I * a
  end
  
  key_in do |w, e|
    Gtk.main_quit if e.keyval == Gdk::Keyval::GDK_Return
  end
end

リターンキーで画面が閉じます。

Ruby Gem できた

野良 Gem の作り方(Ruby) - Camera Obscura
以前「野良 Gem」(「野良」なのは、僕が RubyGems.org に登録していないからです)を作ったのですが、いいかげんなやり方だったので、書き直してみます。

gem 'mygtk' を作ってみます。git が必要なので、なければインストールして下さい。
GitHub のアカウントに mygtk というリポジトリを作っておきます。
また、以下 Bundler を使うので、インストールしておいて下さい。


好きなディレクトリで

$ bundle gem mygtk

を実行します。すると mygtk フォルダが出来、色いろ他のファイルも作成されます。

$ cd mygtk

でフォルダの中に入ります。この中にある、mygtk.gemspec というファイルの中身を変更します。具体的には上のリンク先を見て下さい。spec.summary と spec.description は必ず中身を記述しなくてはなりません。また、依存する Gem があれば spec.add_dependency も記述します(標準添付ライブラリについては必要ありません)。

lib というディレクトリに入って、中の mygtk.rb を書きます(先頭に require "mygtk/version" が必要です)。これが require される Gem の本体になります。

git のインデックス(ステージ領域)に追加し、コミットします。

$ git add -A
$ git commit -m 'first commit'

Gem を作ります。

$ rake install

これで Gem が出来ているので、irb で require 'mygtk' などしてみて確認します。

リモートリポジトリ(この場合は GitHub)にファイルを上げます。

$ git remote add origin git@github.com:obelisk68/mygtk.git
$ git push -u origin master

リリースします。RubyGems.org に登録していれば、これで Gem が公開されます。登録していない場合はここでフリーズするので、しばらくして [Ctrl]+[C] で中断します。

$ rake release

これで GitHub に(野良)Gem が置かれました。終了です。


これで RubyGem に上げていれば、あとはふつうに Gem として使ってください。
「野良」の場合は、Bundler でインストールできます。Gemfile に

gem 'mygtk', github: 'obelisk68/mygtk'

と記述して、

$ bundle install

でインストールできます。使い方は以下。
GTK+でお絵かきしてみた(Ruby) - Camera Obscura


※参考
bundle gemは何をしてくれるのか? - ザリガニが見ていた...。
サルでもわかるGit入門 〜バージョン管理を使いこなそう〜 | どこでもプロジェクト管理バックログ

Ruby 実行のエラー時に n 回リトライするメソッド

n.times_retry でブロック内をエラーがあれば最大 n 回再実行し、それを超えれば停止します。エラーがないかリトライに成功すれば、そのまま次へ処理が渡されます。

引数の massage は true ならば情報を出力します(デフォルト true)。wait: n は retry の前に n秒スリーブします(デフォルトは 0)。ブロック引数の i は i 回目の実行ということです。

5.times_retry(message: true, wait: 1) do |i|
  puts i
  if i <= 3
    raise "sample error"
  end
end
puts "success"

__END__
#出力
1
Error: retry 1
times_retry.rb:23:in `block in <main>'
times_retry.rb:5:in `times_retry'
times_retry.rb:21:in `<main>'
2
Error: retry 2
times_retry.rb:23:in `block in <main>'
times_retry.rb:5:in `times_retry'
times_retry.rb:21:in `<main>'
3
Error: retry 3
times_retry.rb:23:in `block in <main>'
times_retry.rb:5:in `times_retry'
times_retry.rb:21:in `<main>'
4
success

 
メソッドのコード。

class Integer
  def times_retry(message: true, wait: 0)
    n = 1
    begin
      yield(n)
    rescue => e
      if n <= self
        puts "Error: retry #{n}" if message
        puts e.backtrace if message
        n += 1
        sleep(wait)
        retry
      end
      puts "Error: stop" if message
      raise e
    end
  end
end