restartable 0.2.2 → 1.0.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,15 +1,15 @@
1
1
  ---
2
2
  !binary "U0hBMQ==":
3
3
  metadata.gz: !binary |-
4
- ZGMyYTJiYTBhNTgxOGIzOWM5NzRlZWQ2YWFhNzNkNGIzOTQ1NTgwYQ==
4
+ OTU0NzIyODRhMDJlMDgzYmViODZmYTA3OGJkMDY4Y2I3YTA4YTgwZQ==
5
5
  data.tar.gz: !binary |-
6
- ZDMwM2MwMDZmMWI5NGU4NmRhYjA5ZjQzZWIwYmI5MDI1ZjY3YjY0Yg==
7
- !binary "U0hBNTEy":
6
+ MWQyMDc2MTM2NWE1OTIwNDE4YjJmZTU3ZGNkYTljOGNmMjYwNWY2NQ==
7
+ SHA512:
8
8
  metadata.gz: !binary |-
9
- ZTMyMTEyODg1YmIzOGY0NWI3NWM3YjBlMThjY2Q5NzU4YjBmZjlmYWNkYzI1
10
- ZDczN2JhZDFmNTRmMTk3OTY5NjE1YTBmMDM3NTljZWJhMDRjNjE4NjY5M2Ux
11
- MjMzYzA3Y2NjYjI3MjgzMTY3MDBhMzMyYzg0YWVhM2E2M2JmMmE=
9
+ OGM0NTE3NWRkY2ViNWNmMzU1MjJmNTg2ZGFlOWE0YTBmMzQ1MDAwZTVkYjlj
10
+ ZDFjMjQxMDdjNTk4ZDllMDM5N2M2MGY1MDkyMGFjNTEyZTdjMzMzMDIyMDQ1
11
+ NGE5NDlkZDg2ZDFkZTdlNzgzNmZhYWVhZmUwYTg5OGNlZTIxZjI=
12
12
  data.tar.gz: !binary |-
13
- NmFiZGFkNGZkYjNiN2Y0OWRlYmVjMThlOGY5YWVjODYxOGRiMDM5ZmRlZGRk
14
- ZDFkMDNkZGI2NTY5MjkyODIxN2E4NGMwYzA0ODg3ODg4NDZlZGUxNzk5MDBl
15
- OGM4ZWJiZGYwOWY3YWQyYmM1ZmQwYjkxN2M5NzBlMDc3Mzc1MmQ=
13
+ NjhjNGM3MjlhNDk0ZWJkMDBiZjJiMWNjNWYyY2I5N2NjYjY4YmZlOTBmNDdm
14
+ NjdhZTEwY2U1YWVjNTk2ZWQyMjg0ZGRjZDY1YzQzMWQyNGRlY2Q0YTc5ZjI2
15
+ ZWQyNjMyYTk0MTVhZmJjNDQ0YWJmN2Y4MDdhMmQ3M2Y5YzI1MDM=
data/.rubocop.yml ADDED
@@ -0,0 +1,59 @@
1
+ AllCops:
2
+ Exclude:
3
+ - '*.gemspec'
4
+
5
+ Lint/EndAlignment:
6
+ AlignWith: variable
7
+
8
+ Lint/Eval:
9
+ Enabled: false
10
+
11
+ Metrics/AbcSize:
12
+ Max: 20
13
+
14
+ Metrics/MethodLength:
15
+ Max: 20
16
+
17
+ Style/AccessModifierIndentation:
18
+ EnforcedStyle: outdent
19
+
20
+ Style/BracesAroundHashParameters:
21
+ Enabled: false
22
+
23
+ Style/CaseIndentation:
24
+ IndentWhenRelativeTo: end
25
+
26
+ Style/DotPosition:
27
+ EnforcedStyle: trailing
28
+
29
+ Style/DoubleNegation:
30
+ Enabled: false
31
+
32
+ Style/Encoding:
33
+ EnforcedStyle: when_needed
34
+
35
+ Style/HashSyntax:
36
+ EnforcedStyle: hash_rockets
37
+
38
+ Style/IfUnlessModifier:
39
+ MaxLineLength: 40
40
+
41
+ Style/IndentHash:
42
+ EnforcedStyle: consistent
43
+
44
+ Style/PercentLiteralDelimiters:
45
+ PreferredDelimiters:
46
+ '%w': '[]'
47
+ '%W': '[]'
48
+
49
+ Style/Semicolon:
50
+ AllowAsExpressionSeparator: true
51
+
52
+ Style/SpaceBeforeBlockBraces:
53
+ EnforcedStyle: no_space
54
+
55
+ Style/SpaceInsideHashLiteralBraces:
56
+ EnforcedStyle: no_space
57
+
58
+ Style/TrailingComma:
59
+ EnforcedStyleForMultiline: comma
data/.travis.yml CHANGED
@@ -1,8 +1,17 @@
1
1
  language: ruby
2
2
  rvm:
3
3
  - 1.8.7
4
- - 1.9.2
5
4
  - 1.9.3
6
- - 2.0.0
7
- - ree
8
- script: bundle exec cucumber
5
+ - '2.0'
6
+ - '2.1'
7
+ script:
8
+ if [ -n "$RUBOCOP" ]; then
9
+ bundle exec rubocop
10
+ ; else
11
+ bundle exec cucumber
12
+ ; fi
13
+ matrix:
14
+ fast_finish: true
15
+ include:
16
+ - env: RUBOCOP=true
17
+ rvm: default
data/Gemfile CHANGED
@@ -1,3 +1,3 @@
1
- source "https://rubygems.org"
1
+ source 'https://rubygems.org'
2
2
 
3
3
  gemspec
data/README.markdown CHANGED
@@ -1,9 +1,12 @@
1
+ [![Gem Version](https://img.shields.io/gem/v/restartable.svg?style=flat)](https://rubygems.org/gems/restartable)
2
+ [![Build Status](https://img.shields.io/travis/toy/restartable/master.svg?style=flat)](https://travis-ci.org/toy/restartable)
3
+ [![Code Climate](https://img.shields.io/codeclimate/github/toy/restartable.svg?style=flat)](https://codeclimate.com/github/toy/restartable)
4
+ [![Dependency Status](https://img.shields.io/gemnasium/toy/restartable.svg?style=flat)](https://gemnasium.com/toy/restartable)
5
+
1
6
  # restartable
2
7
 
3
8
  Run code, Ctrl-C to restart, two Ctrl-C to stop.
4
9
 
5
- [![Build Status](https://travis-ci.org/toy/restartable.png?branch=master)](https://travis-ci.org/toy/restartable)
6
-
7
10
  ## Copyright
8
11
 
9
12
  Copyright (c) 2012-2014 Ivan Kuchin. See LICENSE.txt for details.
data/bin/restartable CHANGED
@@ -14,6 +14,12 @@ Usege:
14
14
 
15
15
  TEXT
16
16
 
17
+ op.on('-r', '--on-restart CMD', 'Run CMD on restart') do |cmd|
18
+ (options[:on_restart] ||= []) << proc{ system cmd }
19
+ end
20
+
21
+ op.separator nil
22
+
17
23
  op.on_tail('-h', '--help', 'Show full help') do
18
24
  puts option_parser.help
19
25
  exit
@@ -27,11 +33,11 @@ end
27
33
 
28
34
  begin
29
35
  option_parser.order!
30
- raise OptionParser::ParseError, 'No command to run' if ARGV.empty?
36
+ fail OptionParser::ParseError, 'No command to run' if ARGV.empty?
31
37
  rescue OptionParser::ParseError => e
32
- abort "#{e.to_s}\n\n#{option_parser.help}"
38
+ abort "#{e}\n\n#{option_parser.help}"
33
39
  end
34
40
 
35
41
  Restartable.new(options) do
36
- exec *ARGV
42
+ exec(*ARGV)
37
43
  end
@@ -1,32 +1,37 @@
1
1
  Feature: Restarting
2
2
 
3
3
  Scenario Outline: Restarting and terminating
4
+ Given I have set on restart to `$stdout.puts 'Restart!'`
4
5
  Given I have invoked restartable with `<code>`
5
6
 
6
7
  When I have waited for 1 second
7
8
  Then I should see "^C to restart, double ^C to stop" in stderr
8
9
  And I should see "Hello world!" in stdout
9
- And there should be an inner process
10
+ And there should be a child process
10
11
  When I interrupt restartable
11
- Then I should see "Killing children…" and "Waiting ^C 0.5 second than restart…" in stderr
12
- And inner process should terminate
12
+ Then I should see "Killing children…" in stderr
13
+ And I should see "Waiting ^C 0.5 second than restart…" in stderr within <timeout> seconds
14
+ And child process should terminate
13
15
 
14
16
  When I have waited for 1 second
15
- Then I should see "^C to restart, double ^C to stop" in stderr
17
+ Then I should see "Restart!" in stdout
18
+ And I should see "^C to restart, double ^C to stop" in stderr
16
19
  And I should see "Hello world!" in stdout
17
- And there should be an inner process
20
+ And there should be a child process
18
21
  When I interrupt restartable twice
19
22
  Then I should see "Killing children…" and "Don't restart!" in stderr
20
- And inner process should terminate
23
+ And child process should terminate within <timeout> seconds
21
24
  And restartable should finish
25
+ And I should not see "Waiting ^C 0.5 second than restart…" in stderr
26
+ And I should not see "Restart!" in stdout
22
27
 
23
28
  Examples:
24
- | code |
25
- | $stdout.puts "Hello world!" |
26
- | $stdout.puts "Hello world!"; 100.times{ sleep 1 } |
27
- | exec 'echo "Hello world!"; sleep 100' |
28
- | system 'echo "Hello world!"; sleep 100' |
29
- | fork{ $stdout.puts "Hello world!"; 100.times{ sleep 1 } } |
30
- | fork{ fork{ fork{ $stdout.puts "Hello world!"; 100.times{ sleep 1 } } } } |
31
- | Signal.trap("INT"){}; $stdout.puts "Hello world!"; 100.times{ sleep 1 } |
32
- | Signal.trap("INT"){}; Signal.trap("TERM"){}; $stdout.puts "Hello world!"; 100.times{ sleep 1 } |
29
+ | code | timeout |
30
+ | $stdout.puts "Hello world!" | 5 |
31
+ | $stdout.puts "Hello world!"; 100.times{ sleep 1 } | 5 |
32
+ | exec 'echo "Hello world!"; sleep 100' | 5 |
33
+ | system 'echo "Hello world!"; sleep 100' | 5 |
34
+ | fork{ $stdout.puts "Hello world!"; 100.times{ sleep 1 } } | 5 |
35
+ | fork{ fork{ fork{ $stdout.puts "Hello world!"; 100.times{ sleep 1 } } } } | 5 |
36
+ | Signal.trap("INT"){}; $stdout.puts "Hello world!"; 100.times{ sleep 1 } | 15 |
37
+ | Signal.trap("INT"){}; Signal.trap("TERM"){}; $stdout.puts "Hello world!"; 100.times{ sleep 1 } | 25 |
@@ -1,5 +1,9 @@
1
1
  require 'restartable'
2
2
 
3
+ Given(/^I have set on restart to `(.*?)`$/) do |command|
4
+ (@on_restart ||= []) << proc{ eval(command) }
5
+ end
6
+
3
7
  Given(/^I have invoked restartable with `(.*?)`$/) do |command|
4
8
  @stdout = IO.pipe
5
9
  @stderr = IO.pipe
@@ -13,7 +17,7 @@ Given(/^I have invoked restartable with `(.*?)`$/) do |command|
13
17
  @stderr[0].close
14
18
  STDERR.reopen(@stderr[1])
15
19
 
16
- Restartable.new do
20
+ Restartable.new(:on_restart => @on_restart) do
17
21
  Signal.trap('INT', 'EXIT')
18
22
  eval(command)
19
23
  end
@@ -27,17 +31,16 @@ When(/^I have waited for (\d+) second$/) do |seconds|
27
31
  sleep seconds.to_i
28
32
  end
29
33
 
30
- Then(/^I should see "(.*?)" in stdout$/) do |string|
31
- Timeout::timeout(5) do
32
- @stdout[0].gets.should include(string)
33
- end
34
- end
35
-
36
- Then(/^I should see "(.*?)" in stderr$/) do |arg|
37
- Timeout::timeout(60) do
34
+ Then(/^
35
+ I\ should\ see\ "(.*?)"
36
+ \ in\ std(out|err)
37
+ (?:\ within\ (\d+)\ seconds)?
38
+ $/x) do |arg, io_name, timeout|
39
+ io = (io_name == 'out' ? @stdout : @stderr)[0]
40
+ Timeout.timeout(timeout ? timeout.to_i : 5) do
38
41
  strings = arg.split(/".*?"/)
39
42
  until strings.empty?
40
- line = @stderr[0].gets
43
+ line = io.gets
41
44
  strings.reject! do |string|
42
45
  line.include?(string)
43
46
  end
@@ -45,34 +48,40 @@ Then(/^I should see "(.*?)" in stderr$/) do |arg|
45
48
  end
46
49
  end
47
50
 
51
+ Then(/^I should not see "(.*?)" in std(out|err)$/) do |arg, io_name|
52
+ io = (io_name == 'out' ? @stdout : @stderr)[0]
53
+ strings = arg.split(/".*?"/)
54
+ while (line = io.gets)
55
+ if strings.any?{ |string| line.include?(string) }
56
+ fail "Got #{line}"
57
+ end
58
+ end
59
+ end
60
+
48
61
  When(/^I interrupt restartable$/) do
49
62
  Process.kill('INT', -@pid)
50
63
  end
51
64
 
52
65
  When(/^I interrupt restartable twice$/) do
53
- Process.kill('INT', -@pid)
66
+ step 'I interrupt restartable'
54
67
  sleep 0.1
55
- Process.kill('INT', -@pid)
68
+ step 'I interrupt restartable'
56
69
  end
57
70
 
58
- Then(/^there should be an inner process$/) do
59
- Timeout::timeout(5) do
60
- until Sys::ProcTable.ps.any?{ |pe| pe.ppid == @pid }
61
- sleep 1
62
- end
71
+ Then(/^there should be a child process$/) do
72
+ Timeout.timeout(5) do
73
+ sleep 1 until Sys::ProcTable.ps.any?{ |pe| pe.ppid == @pid }
63
74
  end
64
75
  end
65
76
 
66
- Then(/^inner process should terminate$/) do
67
- Timeout::timeout(100) do
68
- until Sys::ProcTable.ps.none?{ |pe| pe.ppid == @pid }
69
- sleep 1
70
- end
77
+ Then(/^child process should terminate(?: within (\d+) seconds)?$/) do |timeout|
78
+ Timeout.timeout(timeout ? timeout.to_i : 5) do
79
+ sleep 1 until Sys::ProcTable.ps.none?{ |pe| pe.ppid == @pid }
71
80
  end
72
81
  end
73
82
 
74
83
  Then(/^restartable should finish$/) do
75
- Timeout::timeout(5) do
84
+ Timeout.timeout(5) do
76
85
  Process.wait(@pid)
77
86
  end
78
87
  end
data/lib/restartable.rb CHANGED
@@ -3,26 +3,25 @@
3
3
  require 'sys/proctable'
4
4
  require 'colored'
5
5
  require 'thread'
6
+ require 'English'
6
7
 
8
+ # Main interface
7
9
  class Restartable
8
10
  def self.version
9
11
  Gem.loaded_specs['restartable'].version.to_s rescue 'DEV'
10
12
  end
11
13
 
12
14
  def initialize(options = {}, &block)
13
- @options, @block = options, block
15
+ @on_restart = Array(options[:on_restart])
16
+ @block = block
14
17
  run!
15
18
  end
16
19
 
17
20
  private
18
21
 
19
22
  def run!
20
- Signal.trap('INT') do
21
- interrupt!
22
- end
23
- Signal.trap('TERM') do
24
- terminate!
25
- end
23
+ Signal.trap('INT'){ interrupt! }
24
+ Signal.trap('TERM'){ terminate! }
26
25
 
27
26
  until @stop
28
27
  @interrupted = nil
@@ -34,10 +33,11 @@ private
34
33
  end
35
34
  sleep 0.1 until @interrupted
36
35
  kill_children!
37
- unless @stop
38
- $stderr << "Waiting ^C 0.5 second than restart…\n".yellow.bold
39
- sleep 0.5
40
- end
36
+ break if @stop
37
+ $stderr << "Waiting ^C 0.5 second than restart…\n".yellow.bold
38
+ sleep 0.5
39
+ break if @stop
40
+ @on_restart.each(&:call)
41
41
  end
42
42
  end
43
43
 
@@ -58,26 +58,35 @@ private
58
58
  WAIT_SIGNALS = [[5, 'INT'], [3, 'INT'], [1, 'INT'], [3, 'TERM'], [5, 'KILL']]
59
59
 
60
60
  def kill_children!
61
- until (pids = children_pids).empty?
61
+ until children_pids.empty?
62
62
  $stderr << "Killing children…\n".yellow.bold
63
- ripper = Thread.new do
64
- WAIT_SIGNALS.each do |time, signal|
65
- sleep time
66
- $stderr << "…SIG#{signal}…\n".yellow
67
- signal_children!(signal)
63
+
64
+ signal_pair = 0
65
+
66
+ begin
67
+ time, signal = WAIT_SIGNALS[signal_pair]
68
+ Timeout.timeout(time) do
69
+ Process.waitall
70
+ wait_children
68
71
  end
72
+ rescue Timeout::Error
73
+ $stderr << "…SIG#{signal}…\n".yellow
74
+ signal_children!(signal)
75
+ retry if WAIT_SIGNALS[signal_pair += 1]
69
76
  end
70
- Process.waitall
71
- pids.each do |pid|
72
- begin
73
- loop do
74
- Process.kill(0, pid)
75
- sleep 1
76
- end
77
- rescue Errno::ESRCH
77
+ end
78
+ end
79
+
80
+ def wait_children
81
+ children_pids.each do |pid|
82
+ begin
83
+ loop do
84
+ Process.kill(0, pid)
85
+ sleep 1
78
86
  end
87
+ rescue Errno::ESRCH
88
+ next
79
89
  end
80
- ripper.terminate
81
90
  end
82
91
  end
83
92
 
@@ -86,6 +95,7 @@ private
86
95
  begin
87
96
  Process.kill(signal, pid)
88
97
  rescue Errno::ESRCH
98
+ next
89
99
  end
90
100
  end
91
101
  end
@@ -94,15 +104,11 @@ private
94
104
  pgrp = Process.getpgrp
95
105
  Sys::ProcTable.ps.select do |pe|
96
106
  pgrp == case
97
- when pe.respond_to?(:pgid)
98
- pe.pgid
99
- when pe.respond_to?(:pgrp)
100
- pe.pgrp
101
- when pe.respond_to?(:ppid)
102
- pe.ppid
103
- else
104
- raise 'Can\'t find process group id'
107
+ when pe.respond_to?(:pgid) then pe.pgid
108
+ when pe.respond_to?(:pgrp) then pe.pgrp
109
+ when pe.respond_to?(:ppid) then pe.ppid
110
+ else fail 'Can\'t find process group id'
105
111
  end
106
- end.map(&:pid) - [$$]
112
+ end.map(&:pid) - [$PROCESS_ID]
107
113
  end
108
114
  end
data/restartable.gemspec CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  Gem::Specification.new do |s|
4
4
  s.name = 'restartable'
5
- s.version = '0.2.2'
5
+ s.version = '1.0.0'
6
6
  s.summary = %q{Run code, Ctrl-C to restart, once more Ctrl-C to stop}
7
7
  s.homepage = "http://github.com/toy/#{s.name}"
8
8
  s.authors = ['Ivan Kuchin']
@@ -18,5 +18,8 @@ Gem::Specification.new do |s|
18
18
  s.add_dependency 'colored', '~> 1.2'
19
19
  s.add_dependency 'sys-proctable', '~> 0.9.3'
20
20
  s.add_development_dependency 'cucumber'
21
- s.add_development_dependency 'rspec'
21
+ s.add_development_dependency 'rspec', '~> 3.0'
22
+ if Gem::Version.new(RUBY_VERSION) >= Gem::Version.new('1.9.3')
23
+ s.add_development_dependency 'rubocop', '~> 0.27'
24
+ end
22
25
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: restartable
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.2
4
+ version: 1.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ivan Kuchin
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-03-10 00:00:00.000000000 Z
11
+ date: 2014-12-12 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: colored
@@ -56,16 +56,30 @@ dependencies:
56
56
  name: rspec
57
57
  requirement: !ruby/object:Gem::Requirement
58
58
  requirements:
59
- - - ! '>='
59
+ - - ~>
60
60
  - !ruby/object:Gem::Version
61
- version: '0'
61
+ version: '3.0'
62
62
  type: :development
63
63
  prerelease: false
64
64
  version_requirements: !ruby/object:Gem::Requirement
65
65
  requirements:
66
- - - ! '>='
66
+ - - ~>
67
67
  - !ruby/object:Gem::Version
68
- version: '0'
68
+ version: '3.0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: rubocop
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ~>
74
+ - !ruby/object:Gem::Version
75
+ version: '0.27'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ~>
81
+ - !ruby/object:Gem::Version
82
+ version: '0.27'
69
83
  description:
70
84
  email:
71
85
  executables:
@@ -74,11 +88,11 @@ extensions: []
74
88
  extra_rdoc_files: []
75
89
  files:
76
90
  - .gitignore
91
+ - .rubocop.yml
77
92
  - .travis.yml
78
93
  - Gemfile
79
94
  - LICENSE.txt
80
95
  - README.markdown
81
- - TODO
82
96
  - bin/restartable
83
97
  - features/restarting.feature
84
98
  - features/step_definitions/restartable_steps.rb
@@ -104,10 +118,11 @@ required_rubygems_version: !ruby/object:Gem::Requirement
104
118
  version: '0'
105
119
  requirements: []
106
120
  rubyforge_project: restartable
107
- rubygems_version: 2.0.3
121
+ rubygems_version: 2.4.5
108
122
  signing_key:
109
123
  specification_version: 4
110
124
  summary: Run code, Ctrl-C to restart, once more Ctrl-C to stop
111
125
  test_files:
112
126
  - features/restarting.feature
113
127
  - features/step_definitions/restartable_steps.rb
128
+ has_rdoc:
data/TODO DELETED
@@ -1,3 +0,0 @@
1
- add option to run block on restart
2
- bin option to run command on restart
3
- option to auto restart when block finishes