tumugi 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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