autorespawn 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: ea386cb98108e534b9f65033e7f664ec13321893
4
+ data.tar.gz: 7a8e260d15d67f91563da5505f6c42878548301e
5
+ SHA512:
6
+ metadata.gz: 56804a4ae5f3410c656a0e95d35c1504a215c2bdee3769f65b51496df699c4c4238c1c2dba2490a5c5bca524f7b4ed752f4a32b14f70758c2184bf37f18bb7d2
7
+ data.tar.gz: 7d2735d95211f6e70b4ad28ca5204e292bd2775dfdebdff8961332d6b5b84c4d37680d63af48a657fb617f11bb206d967201a70b287888b5182b6ce7946fcba1
data/.gitignore ADDED
@@ -0,0 +1,10 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
10
+ /vendor/
data/.travis.yml ADDED
@@ -0,0 +1,4 @@
1
+ language: ruby
2
+ rvm:
3
+ - 2.1.2
4
+ before_install: gem install bundler -v 1.10.5
data/Gemfile ADDED
@@ -0,0 +1,5 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gem 'flexmock', github: 'doudou/flexmock', branch: 'master'
4
+ # Specify your gem's dependencies in autorespawn.gemspec
5
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2015 Sylvain Joyeux
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,59 @@
1
+ # Autorespawn
2
+
3
+ Autorespawn is an implementation of the popular autoreload scheme, which reloads
4
+ Ruby program files when they change, but instead execs/spawns the underlying
5
+ program again. This avoids common issues related to the load mechanism.
6
+
7
+ ## Usage
8
+
9
+ Require all the files you need autorespawn to watch and then do
10
+
11
+ ~~~
12
+ Autorespawn.autorespawn do
13
+ # Add the program's functionality here
14
+ end
15
+ ~~~
16
+
17
+ If you touch ARGV and $0, you will want to pass the program and arguments
18
+ explicitely
19
+
20
+ ~~~
21
+ Autorespawn.autorespawn 'program', 'argument0', 'argument1' do
22
+ end
23
+ ~~~
24
+
25
+ ## Installation
26
+
27
+ Add this line to your application's Gemfile:
28
+
29
+ ```ruby
30
+ gem 'autorespawn'
31
+ ```
32
+
33
+ And then execute:
34
+
35
+ $ bundle
36
+
37
+ Or install it yourself as:
38
+
39
+ $ gem install autorespawn
40
+
41
+ ## Usage
42
+
43
+ TODO: Write usage instructions here
44
+
45
+ ## Development
46
+
47
+ After checking out the repo, run `bundle install` to install dependencies. Then, run `rake test` to run the tests.
48
+
49
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
50
+
51
+ ## Contributing
52
+
53
+ Bug reports and pull requests are welcome on GitHub at https://github.com/doudou/autorespawn.
54
+
55
+
56
+ ## License
57
+
58
+ The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
59
+
data/Rakefile ADDED
@@ -0,0 +1,10 @@
1
+ require "bundler/gem_tasks"
2
+ require "rake/testtask"
3
+
4
+ Rake::TestTask.new(:test) do |t|
5
+ t.libs << "test"
6
+ t.libs << "lib"
7
+ t.test_files = FileList['test/**/*_test.rb']
8
+ end
9
+
10
+ task :default => :test
@@ -0,0 +1,31 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'autorespawn/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "autorespawn"
8
+ spec.version = Autorespawn::VERSION
9
+ spec.authors = ["Sylvain Joyeux"]
10
+ spec.email = ["sylvain.joyeux@m4x.org"]
11
+
12
+ spec.summary = "functionality to respawn a Ruby program when its source changes"
13
+ spec.description =<<-EOD
14
+ This gem implements the functionality to take a signature of the current Ruby
15
+ program (i.e. the current process) and respawn it whenever the source code or
16
+ libraries change
17
+ EOD
18
+ spec.homepage = "https://github.com/doudou/autorespawn"
19
+ spec.license = "MIT"
20
+
21
+ spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
22
+ spec.bindir = "exe"
23
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
24
+ spec.require_paths = ["lib"]
25
+
26
+ spec.add_development_dependency "bundler", "~> 1.10"
27
+ spec.add_development_dependency "rake", "~> 10.0"
28
+ spec.add_development_dependency "minitest", ">= 5.0", "~> 5.0"
29
+ spec.add_development_dependency "fakefs", ">= 0.6", "~> 0.6.0"
30
+ spec.add_development_dependency 'flexmock', ">= 2.0", '~> 2.0'
31
+ end
@@ -0,0 +1,14 @@
1
+ module Autorespawn
2
+ # Exception raised when a path cannot be resolved to a file on disk
3
+ class FileNotFound < RuntimeError
4
+ # @return [Pathname] the path to resolve
5
+ attr_reader :path
6
+ # @return [Array<Pathname>] the search path that was provided to resolve
7
+ # {#path}. It is always empty if {#path} is absolute
8
+ attr_reader :search_path
9
+
10
+ def initialize(path, search_path)
11
+ @path, @search_path = path, search_path
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,165 @@
1
+ require 'autorespawn/program_id'
2
+ require 'pathname'
3
+ require 'digest/sha1'
4
+
5
+ module Autorespawn
6
+ # Management of the ID of a complete Ruby program
7
+ #
8
+ # It basically stores information about all the files that form this
9
+ # program.
10
+ class ProgramID
11
+ FileInfo = Struct.new :require_path, :path, :mtime, :size, :id
12
+
13
+ # Information about the files that form this program
14
+ #
15
+ # @return [Hash<Pathname,FileInfo>]
16
+ attr_reader :files
17
+
18
+ def initialize
19
+ @files = Hash.new
20
+ end
21
+
22
+ # Compute ID information abou thte current Ruby process
23
+ def self.for_self
24
+ id = ProgramID.new
25
+ id.register_loaded_features
26
+ id
27
+ end
28
+
29
+ # Registers the file information for all loaded features
30
+ #
31
+ # @return [void]
32
+ def register_loaded_features
33
+ search_path = ruby_load_path
34
+ $LOADED_FEATURES.each do |file|
35
+ # enumerator.so is listed in $LOADED_FEATURES but is not present
36
+ # on disk ... no idea
37
+ begin
38
+ begin
39
+ register_file(Pathname.new(file))
40
+ rescue FileNotFound => e
41
+ STDERR.puts "WARN: could not find #{e.path} in ruby search path, ignored"
42
+ end
43
+ end
44
+ end
45
+ end
46
+
47
+ # Registers file information for one file
48
+ #
49
+ # @param [Pathname] file the path to the file
50
+ # @return [FileInfo] the file's information
51
+ def register_file(file, search_path = ruby_load_path)
52
+ info = file_info(file, search_path)
53
+ files[info.path] = info
54
+ @id = nil
55
+ info
56
+ end
57
+
58
+ # Returns a string that can ID this program
59
+ def id
60
+ return @id if @id
61
+
62
+ complete_id = files.keys.sort.map do |p|
63
+ files[p].id
64
+ end.join("")
65
+ @id = Digest::SHA1.hexdigest(complete_id)
66
+ end
67
+
68
+ # Whether the state on disk is different than the state stored in self
69
+ def changed?
70
+ files.each_value do |info|
71
+ return true if !info.path.exist?
72
+ stat = info.path.stat
73
+ if stat.mtime != info.mtime || stat.size != info.size
74
+ new_id = compute_file_id(info.path)
75
+ return new_id != info.id
76
+ end
77
+ end
78
+ false
79
+ end
80
+
81
+ # @api private
82
+ #
83
+ # Given a path that may be relative, computes the full path to the
84
+ # corresponding file
85
+ #
86
+ # @param [Pathname] path the file path
87
+ # @param [Array<Pathname>] search_path the search path to use to resolve
88
+ # relative paths
89
+ # @return [void]
90
+ # @raise FileNotFound when a relative path cannot be resolved into a
91
+ # global one
92
+ def resolve_file_path(path, search_path = Array.new)
93
+ if !path.absolute?
94
+ search_path.each do |search_p|
95
+ full = search_p + path
96
+ if full.exist?
97
+ return full
98
+ end
99
+ end
100
+ raise FileNotFound.new(path, search_path), "cannot find #{path} in #{search_path.join(", ")}"
101
+ elsif !path.exist?
102
+ raise FileNotFound.new(path, []), "#{path} does not exist"
103
+ else
104
+ return path
105
+ end
106
+ end
107
+
108
+ # The ruby load path
109
+ #
110
+ # @param [Array<Pathname>]
111
+ def ruby_load_path
112
+ $LOAD_PATH.map { |p| Pathname.new(p) }
113
+ end
114
+
115
+ # Resolve file information about a single file
116
+ #
117
+ # @param [Pathname] path the path to the file
118
+ # @param [Array<Pathname>] search_path the search path to use to resolve
119
+ # 'path' if it is relative
120
+ # @return [FileInfo]
121
+ def file_info(path, search_path = ruby_load_path)
122
+ resolved = resolve_file_path(path, search_path)
123
+ stat = resolved.stat
124
+ id = compute_file_id(resolved)
125
+ return FileInfo.new(path, resolved, stat.mtime, stat.size, id)
126
+ end
127
+
128
+ # @api private
129
+ #
130
+ # Compute the content ID of a text (code) file
131
+ def compute_text_file_id(file)
132
+ sanitized = file.readlines.map do |line|
133
+ # Remove unnecessary spaces
134
+ line = line.strip
135
+ line = line.gsub(/\s\s+/, ' ')
136
+ if !line.empty?
137
+ line
138
+ end
139
+ end.compact
140
+ Digest::SHA1.hexdigest(sanitized.join("\n"))
141
+ end
142
+
143
+ # @api private
144
+ #
145
+ # Compute the content ID of a binary file
146
+ def compute_binary_file_id(file)
147
+ Digest::SHA1.hexdigest(file.read(enc: 'BINARY'))
148
+ end
149
+
150
+ # Compute a SHA1 that is representative of the file's contents
151
+ #
152
+ # It does some whitespace cleanup, but is not meant to be super-robust
153
+ # to changes that are irrelevant to the end program
154
+ #
155
+ # @param [Pathname] file the path to the file
156
+ # @return [String] an ID string
157
+ def compute_file_id(file)
158
+ if file.extname == ".rb"
159
+ compute_text_file_id(file)
160
+ else
161
+ compute_binary_file_id(file)
162
+ end
163
+ end
164
+ end
165
+ end
@@ -0,0 +1,3 @@
1
+ module Autorespawn
2
+ VERSION = "0.1.0"
3
+ end
@@ -0,0 +1,85 @@
1
+ module Autorespawn
2
+ # Functionality to watch a program for change
3
+ class Watch
4
+ # Create a pipe and dump the program ID state of the current program
5
+ # there
6
+ def self.dump_self_id
7
+ r, w = IO.pipe
8
+ Marshal.dump(ProgramID.for_self, w)
9
+ w.flush
10
+ return r, w
11
+ end
12
+
13
+ # Automatically autoreload this program when one of the source file
14
+ # changes
15
+ #
16
+ # Call this method from the entry point of your program, giving it the
17
+ # actual program functionality as a block. The method will exec and
18
+ # spawn subprocesses at will, when needed, and call the block in these
19
+ # subprocesses as required.
20
+ #
21
+ # At the point of call, all of the program's dependencies must be
22
+ # already required, as it is on this basis that the auto-reloading will
23
+ # be done
24
+ #
25
+ # This method does NOT return
26
+ #
27
+ # @param [Array<String>] command the command to be executed. It is
28
+ # passed as-is to Kernel.spawn and Kernel.exec
29
+ # @param options keyword options to pass to Kernel.spawn and Kernel.exec
30
+ def self.autoreload(*command, **options)
31
+ if !block_given?
32
+ raise ArgumentError, "you must provide the actions to perform on reload as a block"
33
+ end
34
+
35
+ # Check if we're being called by an autoreload call already
36
+ if ENV['autorespawn_AUTORELOAD']
37
+ program_id = Marshal.load(STDIN)
38
+ if !program_id.changed?
39
+ # We can do what is required of us and wait for changes
40
+ yield
41
+ new(program_id).wait
42
+ end
43
+
44
+ r, w = dump_self_id
45
+ exec(Hash['autorespawn_AUTORELOAD' => '1'], *command,
46
+ in: r, **options)
47
+ else
48
+ begin
49
+ r, w = dump_self_id
50
+ pid = spawn(Hash['autorespawn_AUTORELOAD' => '1'], *command,
51
+ in: r, pgroup: true, **options)
52
+ w.close
53
+ r.close
54
+ _, status = Process.waitpid2(pid)
55
+ exit status.exitcode
56
+ ensure
57
+ if pid
58
+ Process.kill 'TERM', pid
59
+ end
60
+ if !$!
61
+ exit 0
62
+ end
63
+ end
64
+ end
65
+ end
66
+
67
+ # @return [ProgramID] the reference state
68
+ attr_reader :current_state
69
+
70
+ def initialize(current_state)
71
+ @current_state = current_state
72
+ end
73
+
74
+ # Wait for changes
75
+ def wait
76
+ loop do
77
+ if current_state.changed?
78
+ return
79
+ end
80
+ sleep 1
81
+ end
82
+ end
83
+ end
84
+ end
85
+
@@ -0,0 +1,8 @@
1
+ require "autorespawn/version"
2
+ require "autorespawn/exceptions"
3
+ require "autorespawn/program_id"
4
+ require "autorespawn/watch"
5
+
6
+ module Autorespawn
7
+ # Your code goes here...
8
+ end
metadata ADDED
@@ -0,0 +1,147 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: autorespawn
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Sylvain Joyeux
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2015-09-12 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.10'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.10'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '10.0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '10.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: minitest
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '5.0'
48
+ - - "~>"
49
+ - !ruby/object:Gem::Version
50
+ version: '5.0'
51
+ type: :development
52
+ prerelease: false
53
+ version_requirements: !ruby/object:Gem::Requirement
54
+ requirements:
55
+ - - ">="
56
+ - !ruby/object:Gem::Version
57
+ version: '5.0'
58
+ - - "~>"
59
+ - !ruby/object:Gem::Version
60
+ version: '5.0'
61
+ - !ruby/object:Gem::Dependency
62
+ name: fakefs
63
+ requirement: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - ">="
66
+ - !ruby/object:Gem::Version
67
+ version: '0.6'
68
+ - - "~>"
69
+ - !ruby/object:Gem::Version
70
+ version: 0.6.0
71
+ type: :development
72
+ prerelease: false
73
+ version_requirements: !ruby/object:Gem::Requirement
74
+ requirements:
75
+ - - ">="
76
+ - !ruby/object:Gem::Version
77
+ version: '0.6'
78
+ - - "~>"
79
+ - !ruby/object:Gem::Version
80
+ version: 0.6.0
81
+ - !ruby/object:Gem::Dependency
82
+ name: flexmock
83
+ requirement: !ruby/object:Gem::Requirement
84
+ requirements:
85
+ - - ">="
86
+ - !ruby/object:Gem::Version
87
+ version: '2.0'
88
+ - - "~>"
89
+ - !ruby/object:Gem::Version
90
+ version: '2.0'
91
+ type: :development
92
+ prerelease: false
93
+ version_requirements: !ruby/object:Gem::Requirement
94
+ requirements:
95
+ - - ">="
96
+ - !ruby/object:Gem::Version
97
+ version: '2.0'
98
+ - - "~>"
99
+ - !ruby/object:Gem::Version
100
+ version: '2.0'
101
+ description: |
102
+ This gem implements the functionality to take a signature of the current Ruby
103
+ program (i.e. the current process) and respawn it whenever the source code or
104
+ libraries change
105
+ email:
106
+ - sylvain.joyeux@m4x.org
107
+ executables: []
108
+ extensions: []
109
+ extra_rdoc_files: []
110
+ files:
111
+ - ".gitignore"
112
+ - ".travis.yml"
113
+ - Gemfile
114
+ - LICENSE.txt
115
+ - README.md
116
+ - Rakefile
117
+ - autorespawn.gemspec
118
+ - lib/autorespawn.rb
119
+ - lib/autorespawn/exceptions.rb
120
+ - lib/autorespawn/program_id.rb
121
+ - lib/autorespawn/version.rb
122
+ - lib/autorespawn/watch.rb
123
+ homepage: https://github.com/doudou/autorespawn
124
+ licenses:
125
+ - MIT
126
+ metadata: {}
127
+ post_install_message:
128
+ rdoc_options: []
129
+ require_paths:
130
+ - lib
131
+ required_ruby_version: !ruby/object:Gem::Requirement
132
+ requirements:
133
+ - - ">="
134
+ - !ruby/object:Gem::Version
135
+ version: '0'
136
+ required_rubygems_version: !ruby/object:Gem::Requirement
137
+ requirements:
138
+ - - ">="
139
+ - !ruby/object:Gem::Version
140
+ version: '0'
141
+ requirements: []
142
+ rubyforge_project:
143
+ rubygems_version: 2.2.3
144
+ signing_key:
145
+ specification_version: 4
146
+ summary: functionality to respawn a Ruby program when its source changes
147
+ test_files: []