tumugi 0.1.0 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: a8780828db349af4d7d5a31db4d85704f5c07d27
4
- data.tar.gz: c3e7ea47a53da76b5d9dbae3e1ae723bd43a89df
3
+ metadata.gz: fe91c5dd14b1c75b8562de885608d52d9f8ff927
4
+ data.tar.gz: 7ce1c778ca4614bb5a9a4be2e04ac1d75dc59a5b
5
5
  SHA512:
6
- metadata.gz: 2ea11f3233b611657f645e4b4f3e1e6ee390f27b7cad70b9f0fa383800d574ec6b48acc774adb759bcde80bb60cbde4bcb17833af57fa24886d44c6c76d69aa1
7
- data.tar.gz: fffbd00ab9c2a5efac1c3af410b25f80751b1a4fe37af17e667a74d72aa69a8c0e8fa5ba28d2157d841562ec5a38e76a0262780b863dfe5b08a2b1193f825a6a
6
+ metadata.gz: 5005eed43b4ec6261c0870c89afe93609c516719541f67dd9ef683f1b0742b6ed8e8dedb574eb578485612856d3096aab64198ef55c8e8cefead786d3f538663
7
+ data.tar.gz: 3208bf2657f87d7f76dd39a568e9ec356c549ddfca95986e8f879d3ac0b88b2c017ab150159b3cec7b653c37e9fdd86cda56414f1df817f228879bf061c18cee
data/.travis.yml CHANGED
@@ -1,11 +1,12 @@
1
1
  language: ruby
2
2
  rvm:
3
3
  - 2.0.0-p648
4
- - 2.1.8
5
- - 2.2.4
6
- - 2.3.0
4
+ - 2.1.10
5
+ - 2.2.5
6
+ - 2.3.1
7
7
  - jruby-9.0.5.0
8
- sudo: required
9
- before_install:
10
- - sudo apt-get install graphviz
11
- - gem install bundler -v 1.11.2
8
+ cache: bundler
9
+ addons:
10
+ apt:
11
+ packages:
12
+ - graphviz
data/CHANGELOG.md ADDED
@@ -0,0 +1,43 @@
1
+ # Change Log
2
+
3
+ ## [v0.2.0](https://github.com/tumugi/tumugi/tree/v0.2.0) (2016-05-02)
4
+ [Full Changelog](https://github.com/tumugi/tumugi/compare/v0.1.0...v0.2.0)
5
+
6
+ **Implemented enhancements:**
7
+
8
+ - Implement plugin architecture [\#18](https://github.com/tumugi/tumugi/pull/18) ([hakobera](https://github.com/hakobera))
9
+ - \[Breaking change\] Change eval scope of output, run method [\#14](https://github.com/tumugi/tumugi/pull/14) ([hakobera](https://github.com/hakobera))
10
+ - \[Breaking Change\] Add Task\#logger and Task\#log method [\#12](https://github.com/tumugi/tumugi/pull/12) ([hakobera](https://github.com/hakobera))
11
+ - Update command description / Set file options mandatory [\#11](https://github.com/tumugi/tumugi/pull/11) ([hakobera](https://github.com/hakobera))
12
+
13
+ **Fixed bugs:**
14
+
15
+ - Fix show command cannot handle task which id include underscore [\#13](https://github.com/tumugi/tumugi/pull/13) ([hakobera](https://github.com/hakobera))
16
+
17
+ **Merged pull requests:**
18
+
19
+ - Use bundler cache on travis [\#17](https://github.com/tumugi/tumugi/pull/17) ([hakobera](https://github.com/hakobera))
20
+ - Add gem version badge to README [\#16](https://github.com/tumugi/tumugi/pull/16) ([hakobera](https://github.com/hakobera))
21
+ - Add changelog of v0.1.0 [\#10](https://github.com/tumugi/tumugi/pull/10) ([hakobera](https://github.com/hakobera))
22
+
23
+ ## [v0.1.0](https://github.com/tumugi/tumugi/tree/v0.1.0) (2016-04-28)
24
+ **Implemented enhancements:**
25
+
26
+ - Retry when task failed [\#8](https://github.com/tumugi/tumugi/pull/8) ([hakobera](https://github.com/hakobera))
27
+ - Support run tasks concurrently [\#7](https://github.com/tumugi/tumugi/pull/7) ([hakobera](https://github.com/hakobera))
28
+ - Visualize [\#6](https://github.com/tumugi/tumugi/pull/6) ([hakobera](https://github.com/hakobera))
29
+ - Support task inheritance [\#5](https://github.com/tumugi/tumugi/pull/5) ([hakobera](https://github.com/hakobera))
30
+ - First DSL implementation [\#3](https://github.com/tumugi/tumugi/pull/3) ([hakobera](https://github.com/hakobera))
31
+
32
+ **Closed issues:**
33
+
34
+ - Features and Tasks for v0.1 [\#4](https://github.com/tumugi/tumugi/issues/4)
35
+
36
+ **Merged pull requests:**
37
+
38
+ - Create gem [\#2](https://github.com/tumugi/tumugi/pull/2) ([hakobera](https://github.com/hakobera))
39
+ - Add initial spec [\#1](https://github.com/tumugi/tumugi/pull/1) ([hakobera](https://github.com/hakobera))
40
+
41
+
42
+
43
+ \* *This Change Log was automatically generated by [github_changelog_generator](https://github.com/skywinder/Github-Changelog-Generator)*
data/README.md CHANGED
@@ -1,4 +1,4 @@
1
- [![Build Status](https://travis-ci.org/tumugi/tumugi.svg)](https://travis-ci.org/tumugi/tumugi) [![Code Climate](https://codeclimate.com/github/tumugi/tumugi/badges/gpa.svg)](https://codeclimate.com/github/tumugi/tumugi) [![Coverage Status](https://coveralls.io/repos/tumugi/tumugi/badge.svg?branch=master&service=github)](https://coveralls.io/github/tumugi/tumugi?branch=master)
1
+ [![Build Status](https://travis-ci.org/tumugi/tumugi.svg)](https://travis-ci.org/tumugi/tumugi) [![Code Climate](https://codeclimate.com/github/tumugi/tumugi/badges/gpa.svg)](https://codeclimate.com/github/tumugi/tumugi) [![Coverage Status](https://coveralls.io/repos/tumugi/tumugi/badge.svg?branch=master&service=github)](https://coveralls.io/github/tumugi/tumugi?branch=master) [![Gem Version](https://badge.fury.io/rb/tumugi.svg)](https://badge.fury.io/rb/tumugi)
2
2
 
3
3
  # Tumugi
4
4
 
data/Rakefile CHANGED
@@ -1,5 +1,6 @@
1
1
  require "bundler/gem_tasks"
2
2
  require "rake/testtask"
3
+ require "github_changelog_generator/task"
3
4
 
4
5
  Rake::TestTask.new(:test) do |t|
5
6
  t.libs << "test"
@@ -7,4 +8,7 @@ Rake::TestTask.new(:test) do |t|
7
8
  t.test_files = FileList['test/**/*_test.rb']
8
9
  end
9
10
 
11
+ GitHubChangelogGenerator::RakeTask.new :changelog do |config|
12
+ end
13
+
10
14
  task :default => :test
@@ -1,27 +1,26 @@
1
- require 'tumugi/target/file_target'
2
-
3
1
  class FileTask < Tumugi::Task
2
+ Tumugi::Plugin.register_task(:local_file, self)
3
+
4
4
  def output
5
- Tumugi::Target::FileTarget.new("/tmp/tumugi_#{self.id}.txt")
5
+ target(:local_file, "/tmp/tumugi_#{self.id}.txt")
6
6
  end
7
7
 
8
8
  def run
9
- @logger.info "#{self.id}#run"
10
- sleep 5
11
- File.write(output.path, 'done')
12
- @logger.info "#{self.id}#done"
9
+ log 'sleep 2 seconds'
10
+ sleep 2
11
+ output.open('w') {|f| f.puts('done') }
13
12
  end
14
13
  end
15
14
 
16
- task :task1, type: FileTask do
15
+ task :task1, type: :local_file do
17
16
  requires (1..10).map {|i| :"task2_#{i}"}
18
17
  end
19
18
 
20
19
  (1..10).each do |i|
21
- task :"task2_#{i}", type: FileTask do
20
+ task :"task2_#{i}", type: :local_file do
22
21
  requires [:task3]
23
22
  end
24
23
  end
25
24
 
26
- task :task3, type: FileTask do
25
+ task :task3, type: :local_file do
27
26
  end
@@ -1,12 +1,10 @@
1
- require 'tumugi/target/file_target'
2
-
3
1
  task :generate_data do
4
2
  output do
5
- Tumugi::Target::FileTarget.new("/tmp/data_#{Time.now.strftime('%Y-%m-%d')}.txt")
3
+ target(:local_file, "/tmp/tumugi_data_#{Time.now.strftime('%Y-%m-%d')}.txt")
6
4
  end
7
5
 
8
- run do |task|
9
- File.open(task.output.path, "w") do |f|
6
+ run do
7
+ output.open('w') do |f|
10
8
  (1..10).each do |i|
11
9
  f.puts i
12
10
  end
@@ -18,14 +16,14 @@ task :sum do
18
16
  requires :generate_data
19
17
 
20
18
  output do
21
- Tumugi::Target::FileTarget.new("/tmp/output_#{Time.now.strftime('%Y-%m-%d')}.txt")
19
+ target(:local_file, "/tmp/tumugi_output_#{Time.now.strftime('%Y-%m-%d')}.txt")
22
20
  end
23
21
 
24
- run do |task|
22
+ run do
25
23
  sum = 0
26
- File.foreach(task.input.path) do |line|
24
+ input.open do |line|
27
25
  sum += line.to_i
28
26
  end
29
- File.write(task.output.path, sum)
27
+ output { |f| f.puts(sum) }
30
28
  end
31
29
  end
data/examples/simple.rb CHANGED
@@ -1,21 +1,21 @@
1
1
  task :task1 do
2
2
  requires [:task2, :task3]
3
- run { puts 'task1#run' }
3
+ run { log 'task1#run' }
4
4
  end
5
5
 
6
6
  task :task2 do
7
7
  requires [:task4]
8
- run { puts 'task2#run' }
8
+ run { log 'task2#run' }
9
9
  end
10
10
 
11
11
  task :task3 do
12
12
  requires [:task4]
13
- run { puts 'task3#run' }
13
+ run { log 'task3#run' }
14
14
  end
15
15
 
16
16
  task :task4 do
17
17
  run do
18
- puts 'task4#run'
18
+ log 'task4#run'
19
19
  sleep 1
20
20
  end
21
21
  end
data/examples/target.rb CHANGED
@@ -1,51 +1,36 @@
1
- require 'tumugi/target/file_target'
1
+ require 'tumugi/plugin/target/local_file'
2
2
 
3
3
  task :task1 do
4
4
  requires [:task2, :task3]
5
-
6
- output do |task|
7
- Tumugi::Target::FileTarget.new("/tmp/#{task.id}.txt")
8
- end
9
-
10
- run do |task|
11
- puts 'task1#run'
12
- File.write(task.output.path, 'done')
5
+ output target(:local_file, "/tmp/tumugi_#{id}.txt")
6
+ run do
7
+ log 'task1#run'
8
+ output.open('w') {|f| f.puts('done') }
13
9
  end
14
10
  end
15
11
 
16
12
  task :task2 do
17
13
  requires [:task4]
18
-
19
- output do |task|
20
- Tumugi::Target::FileTarget.new("/tmp/#{task.id}.txt")
21
- end
22
-
23
- run do |task|
24
- puts 'task2#run'
25
- File.write(task.output.path, 'done')
14
+ output [target(:local_file, "/tmp/tumugi_#{id}.txt")]
15
+ run do
16
+ log 'task2#run'
17
+ output[0].open('w') {|f| f.puts('done') }
26
18
  end
27
19
  end
28
20
 
29
21
  task :task3 do
30
22
  requires [:task4]
31
-
32
- output do |task|
33
- Tumugi::Target::FileTarget.new("/tmp/#{task.id}.txt")
34
- end
35
-
36
- run do |task|
37
- puts 'task3#run'
38
- File.write(task.output.path, 'done')
23
+ output { target(:local_file, "/tmp/tumugi_#{id}.txt") }
24
+ run do
25
+ log 'task3#run'
26
+ output.open('w') {|f| f.puts('done') }
39
27
  end
40
28
  end
41
29
 
42
30
  task :task4 do
43
- output do
44
- Tumugi::Target::FileTarget.new('/tmp/task4.txt')
45
- end
46
-
47
- run do |task|
48
- puts 'task4#run'
49
- File.write(task.output.path, 'done')
31
+ output Tumugi::Plugin::LocalFileTarget.new("/tmp/tumugi_#{id}.txt")
32
+ run do
33
+ log 'task4#run'
34
+ output.open('w') {|f| f.puts('done') }
50
35
  end
51
36
  end
@@ -1,24 +1,28 @@
1
- require 'tumugi/target/file_target'
2
-
3
1
  class FileTask < Tumugi::Task
2
+ Tumugi::Plugin.register_task(:file, self)
3
+
4
4
  def output
5
- Tumugi::Target::FileTarget.new("/tmp/#{self.id}.txt")
5
+ target(:local_file, "/tmp/#{id}.txt")
6
6
  end
7
7
 
8
8
  def run
9
- puts "#{self.id}#run"
10
- File.write(output.path, 'done')
9
+ log "#{id}#run"
10
+ output.open('w') do |f|
11
+ f.puts('done')
12
+ end
11
13
  end
12
14
  end
13
15
 
14
- task :task1, type: FileTask do
16
+ # Task type can specified by task plugin ID
17
+ task :task1, type: :file do
15
18
  requires [:task2, :task3]
16
19
  end
17
20
 
18
- task :task2, type: FileTask do
21
+ task :task2, type: :file do
19
22
  requires [:task4]
20
23
  end
21
24
 
25
+ # You can also specify type by Class object
22
26
  task :task3, type: FileTask do
23
27
  requires [:task4]
24
28
  end
data/lib/tumugi.rb CHANGED
@@ -1,4 +1,24 @@
1
+ require 'tumugi/application'
2
+ require 'tumugi/config'
3
+ require 'tumugi/logger'
1
4
  require 'tumugi/version'
2
5
 
3
6
  module Tumugi
7
+ class << self
8
+ def application
9
+ @application ||= Tumugi::Application.new
10
+ end
11
+
12
+ def logger
13
+ @logger ||= Tumugi::Logger.new
14
+ end
15
+
16
+ def config
17
+ @config ||= Tumugi::Config.new
18
+ if block_given?
19
+ yield @config
20
+ end
21
+ @config
22
+ end
23
+ end
4
24
  end
@@ -1,7 +1,7 @@
1
- require "active_support/all"
2
-
3
1
  require 'tumugi/dag'
4
2
  require 'tumugi/dsl'
3
+ require 'tumugi/plugin'
4
+ require 'tumugi/target'
5
5
  require 'tumugi/command/run'
6
6
  require 'tumugi/command/show'
7
7
 
@@ -14,7 +14,8 @@ module Tumugi
14
14
  def execute(command, root_task_id, options)
15
15
  load(options[:file], true)
16
16
  dag = create_dag(root_task_id)
17
- cmd = "Tumugi::Command::#{command.to_s.classify}".constantize.new
17
+ command_module = Kernel.const_get("Tumugi").const_get("Command")
18
+ cmd = command_module.const_get("#{command.to_s.capitalize}").new
18
19
  cmd.execute(dag, options)
19
20
  end
20
21
 
@@ -0,0 +1,49 @@
1
+ require 'tempfile'
2
+ require 'forwardable'
3
+
4
+ module Tumugi
5
+ class AtomicFile
6
+ extend Forwardable
7
+ def_delegators :@temp_file,
8
+ :bin_mode?, :print, :printf, :putc, :puts, :write,
9
+ :seek, :set_encoding, :sync, :sync=, :sysseek,
10
+ :syswrite, :write, :write_nonblock
11
+
12
+ def initialize(path)
13
+ @path = path
14
+ end
15
+
16
+ attr_reader :path
17
+
18
+ def open(&block)
19
+ if block_given?
20
+ Tempfile.open(basename) do |fp|
21
+ @temp_file = fp
22
+ block.call(self)
23
+ close
24
+ end
25
+ else
26
+ @temp_file = Tempfile.open(basename)
27
+ end
28
+ self
29
+ end
30
+
31
+ def close
32
+ if @temp_file
33
+ move_to_final_destination(@temp_file)
34
+ @temp_file.close
35
+ @temp_file = nil
36
+ end
37
+ end
38
+
39
+ def move_to_final_destination(temp_file)
40
+ raise NotImplementedError, "You must implement #{self.class}##{__method__}"
41
+ end
42
+
43
+ private
44
+
45
+ def basename
46
+ @basename ||= File.basename(@path)
47
+ end
48
+ end
49
+ end
data/lib/tumugi/cli.rb CHANGED
@@ -1,6 +1,5 @@
1
1
  require 'thor'
2
2
  require 'tumugi'
3
- require 'tumugi/tumugi_module'
4
3
 
5
4
  module Tumugi
6
5
  class CLI < Thor
@@ -8,7 +7,7 @@ module Tumugi
8
7
 
9
8
  class << self
10
9
  def common_options
11
- option :file, aliases: '-f', desc: 'Task definition file name'
10
+ option :file, aliases: '-f', desc: 'Task definition file name', required: true
12
11
  option :config, aliases: '-c', desc: 'Configuration file name', default: 'tumugi.rb'
13
12
  end
14
13
  end
@@ -18,7 +17,7 @@ module Tumugi
18
17
  puts "tumugi v#{Tumugi::VERSION}"
19
18
  end
20
19
 
21
- desc "run", "Run workflow"
20
+ desc "run TASK", "Run TASK in a workflow"
22
21
  map "run" => "run_" # run is thor's reserved word, so this trick is needed
23
22
  option :workers, aliases: '-w', type: :numeric, desc: 'Number of workers to run task concurrently'
24
23
  option :quiet, type: :boolean, desc: 'Suppress log', default: false
@@ -29,7 +28,7 @@ module Tumugi
29
28
  Tumugi.application.execute(:run, task, options)
30
29
  end
31
30
 
32
- desc "show", "Show DAG of workflow"
31
+ desc "show TASK", "Show DAG of TASK in a workflow"
33
32
  common_options
34
33
  option :out, aliases: '-o', desc: 'Output file name. If not specified, output result to STDOUT'
35
34
  option :format, aliases: '-t', desc: 'Output file format. Only affected --out option is specified.', enum: ['dot', 'png', 'svg']
@@ -1,10 +1,13 @@
1
- require 'gviz'
1
+ require 'graphviz'
2
2
  require 'tmpdir'
3
3
  require 'fileutils'
4
+ require 'tumugi/mixin/listable'
4
5
 
5
6
  module Tumugi
6
7
  module Command
7
8
  class Show
9
+ include Tumugi::Mixin::Listable
10
+
8
11
  @@supported_formats = ['dot', 'png', 'jpg', 'svg', 'pdf']
9
12
 
10
13
  def execute(dag, options={})
@@ -17,22 +20,26 @@ module Tumugi
17
20
  format = options[:format]
18
21
  end
19
22
 
20
- graph = Graph do
21
- dag.tsort.each do |task|
22
- node task.id
23
- route task.id => task._requires.map {|t| t.id}
23
+ g = GraphViz.new(:G, type: :digraph, rankdir: "RL")
24
+ tasks = dag.tsort
25
+ tasks.each do |task|
26
+ g.add_nodes(task.id.to_s)
27
+ end
28
+ tasks.each do |task|
29
+ list(task._requires).each do |req|
30
+ g.add_edge(g.get_node(req.id.to_s), g.get_node(task.id.to_s))
24
31
  end
25
32
  end
26
33
 
27
- if out.present?
28
- Dir.mktmpdir do |dir|
29
- file_base_path = "#{File.dirname(dir)}/#{File.basename(out, '.*')}"
30
- graph.save(file_base_path, format == 'dot' ? nil : format)
31
- FileUtils.mkdir_p(File.dirname(out))
32
- FileUtils.copy("#{file_base_path}.#{format}", out)
34
+ if out
35
+ FileUtils.mkdir_p(File.dirname(out))
36
+ if format == 'dot'
37
+ File.write(out, g.to_s)
38
+ else
39
+ g.output(format => out)
33
40
  end
34
41
  else
35
- print graph
42
+ print g
36
43
  end
37
44
  end
38
45
  end
data/lib/tumugi/dag.rb CHANGED
@@ -1,10 +1,10 @@
1
1
  require 'tsort'
2
- require 'tumugi/helper'
2
+ require 'tumugi/mixin/listable'
3
3
 
4
4
  module Tumugi
5
5
  class DAG
6
6
  include TSort
7
- include Tumugi::Helper
7
+ include Tumugi::Mixin::Listable
8
8
 
9
9
  attr_reader :tasks
10
10
 
@@ -0,0 +1,38 @@
1
+ require 'tumugi'
2
+ require 'tumugi/file_system_error'
3
+
4
+ module Tumugi
5
+ # This class defines interfaces of file system
6
+ # such as local file, Amazon S3, Google Cloud Storage
7
+ class FileSystem
8
+ def exist?(path)
9
+ raise NotImplementedError, "You must implement #{self.class}##{__method__}"
10
+ end
11
+
12
+ def remove(path, recursive: true)
13
+ raise NotImplementedError, "You must implement #{self.class}##{__method__}"
14
+ end
15
+
16
+ def mkdir(path, parents: true, raise_if_exist: false)
17
+ raise NotImplementedError, "You must implement #{self.class}##{__method__}"
18
+ end
19
+
20
+ def directory?(path)
21
+ raise NotImplementedError, "You must implement #{self.class}##{__method__}"
22
+ end
23
+
24
+ def entries(path)
25
+ raise NotImplementedError, "You must implement #{self.class}##{__method__}"
26
+ end
27
+
28
+ def move(src, dest, raise_if_exist: false)
29
+ raise NotImplementedError, "You must implement #{self.class}##{__method__}"
30
+ end
31
+
32
+ def rename(path, dest)
33
+ Tumugi.logger.warn "File system #{self.class.name} client doesn't support atomic move."
34
+ raise FileAlreadyExistError if exist?(dest)
35
+ move(path, dest)
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,13 @@
1
+ module Tumugi
2
+ class FileSystemError < StandardError
3
+ end
4
+
5
+ class FileAlreadyExistError < FileSystemError
6
+ end
7
+
8
+ class MissingParentDirectoryError < FileSystemError
9
+ end
10
+
11
+ class NotADirectoryError < FileSystemError
12
+ end
13
+ end
data/lib/tumugi/logger.rb CHANGED
@@ -1,3 +1,4 @@
1
+ require 'logger'
1
2
  require 'forwardable'
2
3
 
3
4
  module Tumugi
@@ -0,0 +1,17 @@
1
+ module Tumugi
2
+ module Mixin
3
+ module Listable
4
+ def list(obj)
5
+ if obj.nil?
6
+ []
7
+ elsif obj.is_a?(Array)
8
+ obj
9
+ elsif obj.is_a?(Hash)
10
+ obj.map { |k,v| v }
11
+ else
12
+ [obj]
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,12 @@
1
+ require 'tumugi/plugin'
2
+
3
+ module Tumugi
4
+ module Mixin
5
+ module TaskHelper
6
+ def target(type, *args)
7
+ klass = Tumugi::Plugin.lookup_target(type)
8
+ klass.new(*args)
9
+ end
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,43 @@
1
+ require 'tumugi'
2
+ require 'tumugi/registry'
3
+
4
+ module Tumugi
5
+ module Plugin
6
+ TARGET_REGISTRY = Registry.new(:target, 'tumugi/plugin/target/')
7
+ TASK_REGISTRY = Registry.new(:task, 'tumugi/plugin/task/')
8
+
9
+ def self.register_target(type, klass)
10
+ register_impl('target', TARGET_REGISTRY, type, klass)
11
+ end
12
+
13
+ def self.register_task(type, klass)
14
+ register_impl('task', TASK_REGISTRY, type, klass)
15
+ end
16
+
17
+ def self.register_impl(kind, registry, type, value)
18
+ if !value.is_a?(Class)
19
+ raise "Invalid implementation as #{kind} plugin: '#{type}'. It must be a Class."
20
+ end
21
+ registry.register(type, value)
22
+ Tumugi.logger.debug "registered #{kind} plugin '#{type}'"
23
+ nil
24
+ end
25
+
26
+ def self.lookup_target(type)
27
+ lookup_impl('target', TARGET_REGISTRY, type)
28
+ end
29
+
30
+ def self.lookup_task(type)
31
+ lookup_impl('task', TASK_REGISTRY, type)
32
+ end
33
+
34
+ def self.lookup_impl(kind, registry, type)
35
+ obj = registry.lookup(type)
36
+ if obj.is_a?(Class)
37
+ obj
38
+ else
39
+ raise "#{kind} plugin '#{type}' is not a Class"
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,12 @@
1
+ require 'fileutils'
2
+ require 'tumugi/atomic_file'
3
+
4
+ module Tumugi
5
+ module Plugin
6
+ class AtomicLocalFile < AtomicFile
7
+ def move_to_final_destination(temp_file)
8
+ FileUtils.move(temp_file, path)
9
+ end
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,30 @@
1
+ require 'tumugi/target'
2
+ require 'tumugi/file_system'
3
+ require 'tumugi/file_system_error'
4
+
5
+ module Tumugi
6
+ module Plugin
7
+ class FileSystemTarget < Target
8
+ attr_reader :path
9
+
10
+ def initialize(path)
11
+ @path = path
12
+ end
13
+
14
+ def fs
15
+ raise NotImplementedError, "You must implement #{self.class}##{__method__}"
16
+ end
17
+
18
+ def open(mode="r")
19
+ end
20
+
21
+ def exist?
22
+ fs.exist?(@path)
23
+ end
24
+
25
+ def remove
26
+ fs.remove(@path)
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,62 @@
1
+ require 'fileutils'
2
+ require 'tumugi/file_system'
3
+ require 'tumugi/file_system_error'
4
+
5
+ module Tumugi
6
+ module Plugin
7
+ class LocalFileSystem
8
+ def exist?(path)
9
+ File.exist?(path)
10
+ end
11
+
12
+ def remove(path, recursive: true)
13
+ if recursive && directory?(path)
14
+ FileUtils.rm_r(path)
15
+ else
16
+ FileUtils.remove_file(path)
17
+ end
18
+ end
19
+
20
+ def mkdir(path, parents: true, raise_if_exist: false)
21
+ if File.exist?(path)
22
+ if raise_if_exist
23
+ raise FileAlreadyExistError
24
+ elsif !directory?(path)
25
+ raise NotADirectoryError
26
+ else
27
+ return
28
+ end
29
+ end
30
+
31
+ if parents
32
+ FileUtils.mkdir_p(path)
33
+ else
34
+ if File.exist?(File.expand_path("..", path))
35
+ FileUtils.mkdir(path)
36
+ else
37
+ raise MissingParentDirectoryError
38
+ end
39
+ end
40
+ end
41
+
42
+ def directory?(path)
43
+ File.directory?(path)
44
+ end
45
+
46
+ def entries(path)
47
+ if directory?(path)
48
+ Dir.glob(File.join(path, '*'))
49
+ else
50
+ raise NotADirectoryError
51
+ end
52
+ end
53
+
54
+ def move(src, dest, raise_if_exist: false)
55
+ if File.exist?(dest) && raise_if_exist
56
+ raise FileAlreadyExistError
57
+ end
58
+ FileUtils.mv(src, dest, force: true)
59
+ end
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,25 @@
1
+ require 'tumugi/plugin/atomic_local_file'
2
+ require 'tumugi/plugin/file_system_target'
3
+ require 'tumugi/plugin/local_file_system'
4
+
5
+ module Tumugi
6
+ module Plugin
7
+ class LocalFileTarget < FileSystemTarget
8
+ Plugin.register_target(:local_file, self)
9
+
10
+ def fs
11
+ @fs ||= LocalFileSystem.new
12
+ end
13
+
14
+ def open(mode="r", &block)
15
+ if mode.include? 'r'
16
+ File.open(path, mode, &block)
17
+ elsif mode.include? 'w'
18
+ AtomicLocalFile.new(path).open(&block)
19
+ else
20
+ raise 'Invalid mode: #{mode}'
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,56 @@
1
+ module Tumugi
2
+ class Registry
3
+ DEFAULT_PLUGIN_PATH = File.expand_path('plugin', __FILE__)
4
+
5
+ def initialize(kind, search_prefix)
6
+ @kind = kind
7
+ @search_prefix = search_prefix
8
+ @map = {}
9
+ @paths = [DEFAULT_PLUGIN_PATH]
10
+ end
11
+
12
+ attr_reader :kind, :paths
13
+
14
+ def register(type, value)
15
+ type = type.to_sym
16
+ @map[type] = value
17
+ end
18
+
19
+ def lookup(type)
20
+ t = type.to_sym
21
+ return @map[t] if @map.has_key?(t)
22
+ search(type)
23
+ return @map[t] if @map.has_key?(t)
24
+ raise "Unknown #{@kind} plugin '#{type}'"
25
+ end
26
+
27
+ def search(type)
28
+ path = "#{@search_prefix}#{type}"
29
+
30
+ # prefer LOAD_PATH than gems
31
+ [@paths, $LOAD_PATH].each do |paths|
32
+ files = paths.map { |lp|
33
+ lpath = File.expand_path(File.join(lp, "#{path}.rb"))
34
+ File.exist?(lpath) ? lpath : nil
35
+ }.compact
36
+ unless files.empty?
37
+ require files.sort.last
38
+ return
39
+ end
40
+ end
41
+
42
+ specs = Gem::Specification.find_all { |spec|
43
+ spec.contains_requirable_file? path
44
+ }
45
+
46
+ # prefer newer version
47
+ specs = specs.sort_by { |spec| spec.version }
48
+ if spec = specs.last
49
+ spec.require_paths.each do |lib|
50
+ file = "#{spec.full_gem_path}/#{lib}/#{path}"
51
+ return file
52
+ end
53
+ end
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,17 @@
1
+ require 'tumugi'
2
+
3
+ module Tumugi
4
+ class Target
5
+ def exist?
6
+ raise NotImplementedError, "You must implement #{self.class}##{__method__}"
7
+ end
8
+
9
+ def logger
10
+ @logger ||= Tumugi.logger
11
+ end
12
+
13
+ def log(msg)
14
+ logger.info(msg)
15
+ end
16
+ end
17
+ end
data/lib/tumugi/task.rb CHANGED
@@ -1,13 +1,14 @@
1
- require 'tumugi/helper'
1
+ require 'tumugi/mixin/listable'
2
+ require 'tumugi/mixin/task_helper'
2
3
 
3
4
  module Tumugi
4
5
  class Task
5
- include Tumugi::Helper
6
+ include Tumugi::Mixin::Listable
7
+ include Tumugi::Mixin::TaskHelper
6
8
 
7
9
  attr_accessor :state # :pending, :running, :completed, :skipped
8
10
 
9
11
  def initialize
10
- @logger = Tumugi.logger
11
12
  @state = :pending
12
13
  end
13
14
 
@@ -31,6 +32,14 @@ module Tumugi
31
32
  self
32
33
  end
33
34
 
35
+ def logger
36
+ @logger ||= Tumugi.logger
37
+ end
38
+
39
+ def log(msg)
40
+ logger.info(msg)
41
+ end
42
+
34
43
  # If you need to define task dependencies, override in subclass
35
44
  def requires
36
45
  []
@@ -56,7 +65,11 @@ module Tumugi
56
65
 
57
66
  def completed?
58
67
  outputs = list(output)
59
- !outputs.empty? && outputs.all?(&:exist?)
68
+ if outputs.empty?
69
+ @state == :completed || @state == :skipped
70
+ else
71
+ outputs.all?(&:exist?)
72
+ end
60
73
  end
61
74
 
62
75
  # Following methods are internal use only
@@ -1,12 +1,16 @@
1
1
  require 'tumugi/task'
2
+ require 'tumugi/plugin'
3
+ require 'tumugi/mixin/listable'
4
+ require 'tumugi/mixin/task_helper'
2
5
 
3
6
  module Tumugi
4
7
  class TaskDefinition
5
- include Tumugi::Helper
8
+ include Tumugi::Mixin::Listable
9
+ include Tumugi::Mixin::TaskHelper
6
10
 
7
11
  def self.define(id, opts={}, &block)
8
12
  td = Tumugi::TaskDefinition.new(id, opts)
9
- td.instance_eval(&block)
13
+ td.instance_eval(&block) if block_given?
10
14
  Tumugi.application.add_task(id, td)
11
15
  td
12
16
  end
@@ -16,6 +20,10 @@ module Tumugi
16
20
  def initialize(id, opts={})
17
21
  @id = id
18
22
  @opts = { type: Tumugi::Task }.merge(opts)
23
+
24
+ unless @opts[:type].is_a?(Class)
25
+ @opts[:type] = Tumugi::Plugin.lookup_task(@opts[:type])
26
+ end
19
27
  end
20
28
 
21
29
  def instance
@@ -35,7 +43,7 @@ module Tumugi
35
43
  end
36
44
 
37
45
  def output_eval(task)
38
- @out ||= @outputs.is_a?(Proc) ? @outputs.call(task) : @outputs
46
+ @out ||= @outputs.is_a?(Proc) ? task.instance_eval(&@outputs) : @outputs
39
47
  end
40
48
 
41
49
  def required_tasks
@@ -43,13 +51,14 @@ module Tumugi
43
51
  end
44
52
 
45
53
  def run_block(task)
46
- @run.call(task)
54
+ task.instance_eval(&@run)
47
55
  end
48
56
 
49
57
  private
50
58
 
51
59
  def create_task
52
60
  task = define_task.new
61
+ raise "Invalid type: '#{@opts[:type]}'" unless task.is_a?(Tumugi::Task)
53
62
  task.id = @id
54
63
  task
55
64
  end
@@ -1,3 +1,3 @@
1
1
  module Tumugi
2
- VERSION = "0.1.0"
2
+ VERSION = "0.2.0"
3
3
  end
data/tumugi.gemspec CHANGED
@@ -18,15 +18,17 @@ Gem::Specification.new do |spec|
18
18
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
19
19
  spec.require_paths = ['lib']
20
20
 
21
- spec.add_runtime_dependency 'activesupport', '~> 4.2'
22
- spec.add_runtime_dependency 'gviz', '~> 0.3.5'
23
- spec.add_runtime_dependency 'thor', '~> 0.19.1'
21
+ spec.required_ruby_version = '>= 2.0'
22
+
24
23
  spec.add_runtime_dependency 'parallel', '~> 1.8.0'
25
24
  spec.add_runtime_dependency 'retriable', '~> 2.1'
25
+ spec.add_runtime_dependency 'ruby-graphviz', '~> 1.2.2'
26
+ spec.add_runtime_dependency 'thor', '~> 0.19.1'
26
27
 
27
28
  spec.add_development_dependency 'bundler', '~> 1.11'
28
29
  spec.add_development_dependency 'rake', '~> 10.0'
29
30
  spec.add_development_dependency 'test-unit', '~> 3.1'
30
31
  spec.add_development_dependency 'test-unit-rr'
31
32
  spec.add_development_dependency 'coveralls'
33
+ spec.add_development_dependency 'github_changelog_generator'
32
34
  end
metadata CHANGED
@@ -1,85 +1,71 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: tumugi
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Kazuyuki Honda
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2016-04-28 00:00:00.000000000 Z
11
+ date: 2016-05-02 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
- name: activesupport
14
+ name: parallel
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
17
  - - "~>"
18
18
  - !ruby/object:Gem::Version
19
- version: '4.2'
19
+ version: 1.8.0
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - "~>"
25
25
  - !ruby/object:Gem::Version
26
- version: '4.2'
26
+ version: 1.8.0
27
27
  - !ruby/object:Gem::Dependency
28
- name: gviz
28
+ name: retriable
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
31
  - - "~>"
32
32
  - !ruby/object:Gem::Version
33
- version: 0.3.5
33
+ version: '2.1'
34
34
  type: :runtime
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
38
  - - "~>"
39
39
  - !ruby/object:Gem::Version
40
- version: 0.3.5
40
+ version: '2.1'
41
41
  - !ruby/object:Gem::Dependency
42
- name: thor
42
+ name: ruby-graphviz
43
43
  requirement: !ruby/object:Gem::Requirement
44
44
  requirements:
45
45
  - - "~>"
46
46
  - !ruby/object:Gem::Version
47
- version: 0.19.1
47
+ version: 1.2.2
48
48
  type: :runtime
49
49
  prerelease: false
50
50
  version_requirements: !ruby/object:Gem::Requirement
51
51
  requirements:
52
52
  - - "~>"
53
53
  - !ruby/object:Gem::Version
54
- version: 0.19.1
54
+ version: 1.2.2
55
55
  - !ruby/object:Gem::Dependency
56
- name: parallel
57
- requirement: !ruby/object:Gem::Requirement
58
- requirements:
59
- - - "~>"
60
- - !ruby/object:Gem::Version
61
- version: 1.8.0
62
- type: :runtime
63
- prerelease: false
64
- version_requirements: !ruby/object:Gem::Requirement
65
- requirements:
66
- - - "~>"
67
- - !ruby/object:Gem::Version
68
- version: 1.8.0
69
- - !ruby/object:Gem::Dependency
70
- name: retriable
56
+ name: thor
71
57
  requirement: !ruby/object:Gem::Requirement
72
58
  requirements:
73
59
  - - "~>"
74
60
  - !ruby/object:Gem::Version
75
- version: '2.1'
61
+ version: 0.19.1
76
62
  type: :runtime
77
63
  prerelease: false
78
64
  version_requirements: !ruby/object:Gem::Requirement
79
65
  requirements:
80
66
  - - "~>"
81
67
  - !ruby/object:Gem::Version
82
- version: '2.1'
68
+ version: 0.19.1
83
69
  - !ruby/object:Gem::Dependency
84
70
  name: bundler
85
71
  requirement: !ruby/object:Gem::Requirement
@@ -150,6 +136,20 @@ dependencies:
150
136
  - - ">="
151
137
  - !ruby/object:Gem::Version
152
138
  version: '0'
139
+ - !ruby/object:Gem::Dependency
140
+ name: github_changelog_generator
141
+ requirement: !ruby/object:Gem::Requirement
142
+ requirements:
143
+ - - ">="
144
+ - !ruby/object:Gem::Version
145
+ version: '0'
146
+ type: :development
147
+ prerelease: false
148
+ version_requirements: !ruby/object:Gem::Requirement
149
+ requirements:
150
+ - - ">="
151
+ - !ruby/object:Gem::Version
152
+ version: '0'
153
153
  description:
154
154
  email:
155
155
  - hakobera@gmail.com
@@ -160,6 +160,7 @@ extra_rdoc_files: []
160
160
  files:
161
161
  - ".gitignore"
162
162
  - ".travis.yml"
163
+ - CHANGELOG.md
163
164
  - Gemfile
164
165
  - LICENSE
165
166
  - README.md
@@ -174,19 +175,27 @@ files:
174
175
  - exe/tumugi
175
176
  - lib/tumugi.rb
176
177
  - lib/tumugi/application.rb
178
+ - lib/tumugi/atomic_file.rb
177
179
  - lib/tumugi/cli.rb
178
180
  - lib/tumugi/command/run.rb
179
181
  - lib/tumugi/command/show.rb
180
182
  - lib/tumugi/config.rb
181
183
  - lib/tumugi/dag.rb
182
184
  - lib/tumugi/dsl.rb
183
- - lib/tumugi/helper.rb
185
+ - lib/tumugi/file_system.rb
186
+ - lib/tumugi/file_system_error.rb
184
187
  - lib/tumugi/logger.rb
185
- - lib/tumugi/target/base.rb
186
- - lib/tumugi/target/file_target.rb
188
+ - lib/tumugi/mixin/listable.rb
189
+ - lib/tumugi/mixin/task_helper.rb
190
+ - lib/tumugi/plugin.rb
191
+ - lib/tumugi/plugin/atomic_local_file.rb
192
+ - lib/tumugi/plugin/file_system_target.rb
193
+ - lib/tumugi/plugin/local_file_system.rb
194
+ - lib/tumugi/plugin/target/local_file.rb
195
+ - lib/tumugi/registry.rb
196
+ - lib/tumugi/target.rb
187
197
  - lib/tumugi/task.rb
188
198
  - lib/tumugi/task_definition.rb
189
- - lib/tumugi/tumugi_module.rb
190
199
  - lib/tumugi/version.rb
191
200
  - tumugi.gemspec
192
201
  homepage: https://github.com/tumugi/tumugi
@@ -201,7 +210,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
201
210
  requirements:
202
211
  - - ">="
203
212
  - !ruby/object:Gem::Version
204
- version: '0'
213
+ version: '2.0'
205
214
  required_rubygems_version: !ruby/object:Gem::Requirement
206
215
  requirements:
207
216
  - - ">="
data/lib/tumugi/helper.rb DELETED
@@ -1,15 +0,0 @@
1
- module Tumugi
2
- module Helper
3
- def list(obj)
4
- if obj.nil?
5
- []
6
- elsif obj.is_a?(Array)
7
- obj
8
- elsif obj.is_a?(Hash)
9
- obj.map { |k,v| v }
10
- else
11
- [obj]
12
- end
13
- end
14
- end
15
- end
@@ -1,9 +0,0 @@
1
- module Tumugi
2
- module Target
3
- class Base
4
- def exist?
5
- raise NotImplementedError, "You must implement #{self.class}##{__method__}"
6
- end
7
- end
8
- end
9
- end
@@ -1,17 +0,0 @@
1
- require 'tumugi/target/base'
2
-
3
- module Tumugi
4
- module Target
5
- class FileTarget < Base
6
- attr_reader :path
7
-
8
- def initialize(path)
9
- @path = path
10
- end
11
-
12
- def exist?
13
- ::File.exist?(path)
14
- end
15
- end
16
- end
17
- end
@@ -1,23 +0,0 @@
1
- require 'tumugi/application'
2
- require 'tumugi/config'
3
- require 'tumugi/logger'
4
-
5
- module Tumugi
6
- class << self
7
- def application
8
- @application ||= Tumugi::Application.new
9
- end
10
-
11
- def logger
12
- @logger ||= Tumugi::Logger.new
13
- end
14
-
15
- def config
16
- @config ||= Tumugi::Config.new
17
- if block_given?
18
- yield @config
19
- end
20
- @config
21
- end
22
- end
23
- end