sidekick 0.4.2 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
data/.sidekick CHANGED
@@ -6,7 +6,7 @@
6
6
 
7
7
  # watch '**/*.rb' {|paths| log 'made edits' }
8
8
 
9
- # every(20) { notify sh 'fortune' }
9
+ every(5) { notify sh 'fortune' }
10
10
 
11
11
  auto_compile 'test/fixtures/*.haml', 'test/output/:name.html'
12
12
 
@@ -7,39 +7,24 @@ h3. Use cases
7
7
 
8
8
  * Restart server when code is changed
9
9
  * Compile Sass and CoffeeScript templates when they are updated
10
+ * Periodically run commands
10
11
  * Continuous testing, with notifications and flexible hooks
11
- * Periodically running commands
12
12
 
13
- You typically run Sidekick in the background while coding, to automate all this.
13
+ You typically run Sidekick in the background to automate this while coding.
14
14
 
15
15
  h3. Features
16
16
 
17
- * Simple and powerful DSL. "Examples here.":http://github.com/jbe/sidekick/blob/master/lib/template
18
- * Use triggers to watch files etc.
19
- * Use helpers to compile templates etc.
20
- * Easy to write new triggers and helpers.
21
- * Already compiles many languages, thanks to "Tilt":http://github.com/rtomayko/tilt.
22
-
23
-
17
+ * Simple and powerful DSL ("examples":http://github.com/jbe/sidekick/blob/master/lib/template).
18
+ * Easy to extend
19
+ * Compiles many languages, thanks to "Tilt":http://github.com/rtomayko/tilt.
20
+ * Powered by "EventMachine":http://github.com/eventmachine/eventmachine
21
+ * Short "core library":http://github.com/jbe/sidekick/blob/master/lib/sidekick.rb -- about 70 lines of code.
24
22
 
25
23
  h2. Basic usage
26
24
 
27
25
  Install with @gem install sidekick@ and invoke using the @sidekick@ command in your project folder. If you do not have a @.sidekick@ file, you will be offered a template.
28
26
 
29
- h3. Currently available triggers:
30
-
31
- |@watch(glob) { ... }@|run on file changes matching @glob@. Globs like "Dir[]":http://ruby-doc.org/core/classes/Dir.html#M002323. |
32
- |@every(duration) { ... }@|run every @duration@ seconds|
33
-
34
- h3. Currently available helpers:
35
-
36
- |@sh(cmd)@|run shell command, printing and returning output|
37
- |@log(str)@|log events to @STDOUT@.|
38
- |@notify(message, title='sidekick')@|notify user via growl, libnotify etc.|
39
- |@auto_compile(source_glob, target_path)@|compile on file changes using tilt (language automatically detected -- see example usage in template, and tilt documentation)|
40
- |@restart_passenger@|restart passenger based environments by touching tmp/restart.txt|
41
-
42
- Note how some helpers, like @auto_compile@ set up both triggers and actions for you.
27
+ Read the source to see all available "triggers":http://github.com/jbe/sidekick/blob/master/lib/sidekick/triggers.rb and "helpers":http://github.com/jbe/sidekick/blob/master/lib/sidekick/helpers.rb.
43
28
 
44
29
  h2. Writing extensions
45
30
 
@@ -57,72 +42,18 @@ To add more helpers, simply add methods to @Sidekick::Helpers@, like this:
57
42
  </code>
58
43
  </pre>
59
44
 
60
- They will be available to other helpers and to @.sidekick@ files. Also, if adding a group of methods it may be cleaner to use a separate module under @Sidekick::Helpers@, and then including it.
45
+ They will be available to other helpers and to @.sidekick@ files. When adding a group of methods it may be cleaner to write a module under @Sidekick::Helpers@, and then including it.
61
46
 
62
47
 
63
48
  h3. Writing new triggers
64
49
 
65
- To add new triggers, you have two options: blocks, and classes. Blocks are best for simple stuff, while classes are good for more complex code. Here are some examples of how you set up a new trigger using a block:
66
-
67
- <pre>
68
- <code lang='ruby'>
69
-
70
- Sidekick::Triggers.register(:never) {|callback| }
71
- # available in .sidekick like:
72
- # never { puts 'nobody listens to me' }
73
-
74
- Sidekick::Triggers.register(:on_startup) {|callback| callback.call }
75
- # available like:
76
- # on_startup { puts 'better to do this in the main scope than here' }
77
-
78
- Sidekick::Triggers.register(:every) do |callback, duration|
79
- timeshare(duration) do
80
- callback.call
81
- end
82
- end
83
-
84
- </code>
85
- </pre>
86
-
87
- The last example is the internal implementation of the @every@ trigger seen earlier, which calls the block periodically. @callback@ is the block given to the trigger in the @.sidekick@ file. @timeshare@ is a special method provided by Sidekick to all extensions, to register the supplied block to be called every @duration@ seconds. This is useful for polling something without blocking or doing something out of this world.
88
-
89
- Now, here is the last example written as a class:
90
-
91
- <pre>
92
- <code lang='ruby'>
93
-
94
- class Sidekick::Triggers::Every
95
-
96
- def initialize(callback, duration)
97
- @callback = callback
98
- @duration = duration
99
- end
100
-
101
- def poll
102
- @callback.call
103
- end
104
-
105
- def poll_freq
106
- @duration
107
- end
108
- end
109
-
110
- Sidekick::Triggers.register_class(:watch, Watch)
111
-
112
- </code>
113
- </pre>
114
-
115
- The @poll@ and @poll_freq@ methods are optional. @poll@ functions like the callback block in the previous example, while @poll_freq@ functions like the @duration@ argument, specifying how often to call @poll@. Only integers (whole seconds) are accepted. Finally, notice the last statement, which registers the class.
116
-
117
- h2. Fun facts
118
-
119
- The main code chunk of Sidekick is just under 100 lines, including documentation, excluding the default triggers and helpers.
50
+ Have a look at the "existing triggers":http://github.com/jbe/sidekick/blob/master/lib/sidekick/triggers.rb, and you will get the idea. Basically, you define new triggers by calling @Sidekick::Triggers.register(:trigger_name) { .. }@, and hooking into EventMachine the same way as in @EM.run { .. }@.
120
51
 
121
52
  You can keep your extensions in the @.sidekick@ file itself, or package them in gems, or (better) ask me to merge them into the main repository.
122
53
 
123
- Similar projects:
54
+ h3. Similar projects:
124
55
 
125
- * "Guard":http://github.com/guard/guard (too enterprisey imho)
56
+ * "Guard":http://github.com/guard/guard
126
57
 
127
58
 
128
59
  h3. Copyright
data/Rakefile CHANGED
@@ -6,11 +6,13 @@ begin
6
6
  Jeweler::Tasks.new do |gem|
7
7
  gem.name = "sidekick"
8
8
  gem.summary = %Q{Automatically run common development tasks on events, as defined by a local .sidekick file.}
9
- gem.description = %Q{Automatically run common development tasks on events, as defined by a local .sidekick file. Easy, powerful dsl. Several pre-defined triggers. Helper methods for common tasks. Easy to extend with new triggers and helpers.}
9
+ gem.description = %Q{Automatically run common development tasks on events, as defined by a local .sidekick file. Easy, powerful dsl including useful pre-defined triggers and helper methods for common tasks. Powered by EventMachine and easy to extend.}
10
10
  gem.email = "post@jostein.be"
11
11
  gem.homepage = "http://github.com/jbe/sidekick"
12
12
  gem.authors = ["Jostein Berre Eliassen,"]
13
13
  gem.add_dependency "tilt", ">= 1"
14
+ gem.add_dependency "eventmachine", ">= 0"
15
+ gem.add_dependency "em-dir-watcher", ">= 0"
14
16
  end
15
17
  Jeweler::GemcutterTasks.new
16
18
  rescue LoadError
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.4.2
1
+ 0.5.0
@@ -1,102 +1,68 @@
1
1
  require 'fileutils'
2
+ require 'eventmachine'
2
3
 
3
4
  module Sidekick
4
5
 
5
- # creates a module wrapper, which in turn
6
- # evaluates the .sidekick config file within
7
- # its own scope
8
- def self.run!(conf_path='.sidekick')
9
- unless File.exists?(conf_path)
10
- puts 'Generate new sidekick file? (Y/n)'
11
- unless gets =~ /^N|n/ # 1.8 and 1.9 compatibility
12
- FileUtils.cp(File.expand_path('../template', __FILE__), conf_path)
13
- else
14
- exit
15
- end
16
- end
17
- Context.new(conf_path)
18
- Triggers.enter_loop
19
- end
20
-
21
-
22
- # A new Context is used to run the .sidekick
23
- # config file. It simply extends Module.
24
- class Context < ::Module
25
- def initialize(conf_path)
26
- super()
27
- extend MethodProxy
28
- extend Helpers
29
- code = open(conf_path) { |f| f.read }
30
- module_eval(code, conf_path)
31
- end
32
- end
33
-
34
- # New contexts also extend MethodProxy, so that they
35
- # can forward method calls to the registered sidekicks.
36
- module MethodProxy
6
+ module Triggers
37
7
  @@triggers = {}
38
8
 
39
- # registers a trigger
40
- def self.register(name, block)
9
+ def self.register(name, &block)
41
10
  @@triggers[name] = block
42
11
  end
43
12
 
13
+ def self.log(str) # used by triggers
14
+ puts str
15
+ end
16
+
44
17
  def method_missing(name, *args, &blk)
45
- if @@triggers[name]
46
- @@triggers[name].call(blk, *args)
47
- else
48
- super
49
- end
18
+ @@triggers[name] ?
19
+ @@triggers[name].call(blk, *args) : super
50
20
  end
51
21
 
52
22
  def respond_to?(method)
53
23
  super || !!@@triggers[method]
54
24
  end
25
+
55
26
  end
56
27
 
57
- # contains trigger helpers and logic to register
58
- # triggers, making them available in sidekick
59
- module Triggers
60
- @@timeshare_callbacks = []
61
- @@timeshare_frequencies = []
62
-
63
- class << self
64
-
65
- # registers a sidekick available to .sidekick files
66
- def register(name, &block)
67
- MethodProxy.register(name, block)
68
- end
69
-
70
- # registers a callback for timesharing between
71
- # active sidekicks
72
- def timeshare(freq=1, &callback)
73
- @@timeshare_callbacks << callback
74
- @@timeshare_frequencies << freq
75
- end
76
-
77
- # begins the timesharing loop
78
- def enter_loop
79
- Signal.trap :INT do
80
- exit
81
- end
82
-
83
- 0.upto(1.0/0) do |n|
84
- 0.upto(@@timeshare_callbacks.size - 1) do |i|
85
- if n % @@timeshare_frequencies[i] == 0
86
- @@timeshare_callbacks[i].call(n)
87
- end
88
- end
89
- sleep 1
90
- end
91
- end
92
-
93
- def log(str)
94
- puts str
95
- end
28
+ # default library
29
+ require 'sidekick/triggers'
30
+ require 'sidekick/helpers'
96
31
 
32
+ # context in which to evaluate .sidekick file
33
+ Context = Module.new
34
+ Context.extend Triggers
35
+ Context.extend Helpers
36
+
37
+
38
+ def self.ensure_config_exists(path)
39
+ unless File.exists?(path)
40
+ puts 'Generate new sidekick file? (Y/n)'
41
+ gets =~ /^N|n/ ?
42
+ FileUtils.cp(File.expand_path('../template',
43
+ __FILE__), path) : exit
97
44
  end
98
45
  end
99
- end
100
46
 
101
- require 'sidekick/helpers'
102
- require 'sidekick/triggers'
47
+ # reads and applies the .sidekick file, and begins
48
+ # the event loop.
49
+ def self.run!(path='.sidekick')
50
+
51
+ ensure_config_exists(path)
52
+
53
+ Signal.trap(:INT) { ::Sidekick.stop }
54
+
55
+ EventMachine.run do
56
+ Context.module_eval(
57
+ open(path) {|f| f.read }, path )
58
+ end
59
+ end
60
+
61
+ # stops Sidekick gracefully
62
+ def self.stop(msg=false)
63
+ EventMachine.stop
64
+ puts
65
+ puts msg if msg
66
+ end
67
+
68
+ end
@@ -7,53 +7,33 @@ require 'tilt'
7
7
 
8
8
  module Sidekick::Helpers
9
9
 
10
- module Util
11
- # :linux, :darwin, :other
12
- def platform
13
- [:linux, :darwin].each do |plf|
14
- return plf if Config::CONFIG['target_os'] =~ /#{plf}/i
15
- end; :other
16
- end
17
-
18
- def running?(plf)
19
- plf == platform
20
- end
21
-
22
- def gem_load?(gemname)
23
- @installed ||= begin
24
- require gemname
25
- true
26
- rescue LoadError
27
- puts "Please install the #{gemname} gem to enable all functions."
28
- false
29
- end
30
- end
31
- end
10
+ # system
32
11
 
12
+ require 'sidekick/helpers/util'
33
13
  include Util
34
- extend Util
35
14
 
36
- module Notification
37
- extend ::Sidekick::Helpers::Util
38
- def self.show(message, title='Sidekick')
39
- if running?(:linux) and gem_load?('libnotify')
40
- Libnotify.show :body => message, :summary => title
41
- elsif running?(:darwin) and gem_load?('growl')
42
- Growl.notify message, :title => title, :name => 'Sidekick'
43
- else
44
- puts "Notification: #{title} #{message}"
45
- end
46
- end
15
+ def log(str)
16
+ puts ' -> ' + str
47
17
  end
48
18
 
19
+ def stop(*prms)
20
+ Sidekick.stop(*prms)
21
+ end
49
22
 
23
+ # notifications
50
24
 
51
- def log(str)
52
- puts ' -> ' + str
53
- end
25
+ def notify(message, title='Sidekick')
26
+
27
+ gems = {:linux => 'libnotify', :darwin => 'growl'}
54
28
 
55
- def notify(*args)
56
- Notification.show(*args)
29
+ stop('Notifications not supported.') unless platform_load?(
30
+ gems, 'notifications')
31
+ case platform
32
+ when :linux
33
+ Libnotify.show :body => message, :summary => title
34
+ when :darwin
35
+ Growl.notify message, :title => title, :name => 'Sidekick'
36
+ end
57
37
  end
58
38
 
59
39
  def sh(cmd)
@@ -62,36 +42,30 @@ module Sidekick::Helpers
62
42
  result
63
43
  end
64
44
 
65
-
66
-
67
45
  def restart_passenger
68
46
  FileUtils.touch './tmp/restart.txt'
69
47
  log 'restarted passenger'
70
48
  end
71
49
 
72
-
73
-
74
-
75
50
  # watches for changes matching the source glob,
76
51
  # compiles using the tilt gem, and saves to
77
52
  # target. Target is interpolated for :name
78
53
  def auto_compile(source, target)
79
54
  watch(source) do |files|
80
55
  files.each do |file|
81
- begin
82
- t = target.gsub ':name', File.basename(file, '.*')
83
- File.open(t, 'w') do |f|
84
- f.write(Tilt.new(file).render)
56
+ if File.exists?(file)
57
+ begin
58
+ t = target.gsub(':name', File.basename(file, '.*'))
59
+ File.open(t, 'w') do |f|
60
+ f.write(Tilt.new(file).render)
61
+ end
62
+ log "render #{file} => #{t}"
63
+ rescue Exception => e
64
+ notify "Error in #{file}:\n#{e}"
85
65
  end
86
- log "rendered #{file} => #{target}"
87
- rescue Exception => e
88
- notify "Error in #{file}:\n#{e}"
89
66
  end
90
67
  end
91
68
  end
92
69
  end
93
70
 
94
-
95
-
96
-
97
71
  end
@@ -0,0 +1,26 @@
1
+
2
+
3
+
4
+
5
+ module Util
6
+ # :linux, :darwin, :other
7
+ def platform
8
+ [:linux, :darwin].each do |plf|
9
+ return plf if Config::CONFIG['target_os'] =~ /#{plf}/i
10
+ end; :other
11
+ end
12
+
13
+ def gem_load?(gemname, function='full')
14
+ @installed ||= begin
15
+ require gemname
16
+ true
17
+ rescue LoadError
18
+ "Please gem install #{gemname} for #{function} support."
19
+ ::Sidekick.stop
20
+ end
21
+ end
22
+
23
+ def platform_load?(gems, function='full')
24
+ gem_load?(gems[platform], function)
25
+ end
26
+ end
@@ -1,25 +1,26 @@
1
1
 
2
2
 
3
- module Sidekick::Triggers
3
+ require 'em-dir-watcher'
4
4
 
5
- # registers a class conforming to the sidekick
6
- # trigger class api
7
- def self.register_class(keyword, kls)
8
- register(keyword) do |*prms|
9
- o = kls.new(*prms)
10
- freq = o.respond_to?(:poll_freq) ? o.poll_freq : 1
11
- timeshare(freq) { o.poll } if o.respond_to? :poll
12
- end
13
- end
5
+
6
+ module Sidekick::Triggers
14
7
 
15
8
  # default triggers
16
9
 
17
- require 'sidekick/triggers/watch'
10
+ register :watch do |callback, glob|
11
+ EMDirWatcher.watch(
12
+ File.expand_path('.'),
13
+ :include_only => [glob],
14
+ :grace_period => 0.2
15
+ ) do |paths|
16
+ log "watch #{paths.inspect}"
17
+ callback.call(paths)
18
+ end
19
+ end
18
20
 
19
- register_class(:watch, Watch)
20
-
21
21
  register :every do |callback, duration|
22
- timeshare(duration) do
22
+ EventMachine::PeriodicTimer.new(duration) do
23
+ log "every #{duration} seconds"
23
24
  callback.call
24
25
  end
25
26
  end
@@ -2,7 +2,7 @@
2
2
 
3
3
 
4
4
 
5
- abort "A new .sidekick file was generated -- now you just have to edit it."
5
+ stop "A new .sidekick file was generated -- now you just have to edit it."
6
6
 
7
7
 
8
8
  # now delete the above, and set up some actions.
@@ -5,13 +5,13 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = %q{sidekick}
8
- s.version = "0.4.2"
8
+ s.version = "0.5.0"
9
9
 
10
10
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
11
  s.authors = ["Jostein Berre Eliassen,"]
12
12
  s.date = %q{2010-10-26}
13
13
  s.default_executable = %q{sidekick}
14
- s.description = %q{Automatically run common development tasks on events, as defined by a local .sidekick file. Easy, powerful dsl. Several pre-defined triggers. Helper methods for common tasks. Easy to extend with new triggers and helpers.}
14
+ s.description = %q{Automatically run common development tasks on events, as defined by a local .sidekick file. Easy, powerful dsl including useful pre-defined triggers and helper methods for common tasks. Powered by EventMachine and easy to extend.}
15
15
  s.email = %q{post@jostein.be}
16
16
  s.executables = ["sidekick"]
17
17
  s.extra_rdoc_files = [
@@ -29,8 +29,8 @@ Gem::Specification.new do |s|
29
29
  "bin/sidekick",
30
30
  "lib/sidekick.rb",
31
31
  "lib/sidekick/helpers.rb",
32
+ "lib/sidekick/helpers/util.rb",
32
33
  "lib/sidekick/triggers.rb",
33
- "lib/sidekick/triggers/watch.rb",
34
34
  "lib/template",
35
35
  "sidekick.gemspec",
36
36
  "test/fixtures/page.haml",
@@ -55,11 +55,17 @@ Gem::Specification.new do |s|
55
55
 
56
56
  if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
57
57
  s.add_runtime_dependency(%q<tilt>, [">= 1"])
58
+ s.add_runtime_dependency(%q<eventmachine>, [">= 0"])
59
+ s.add_runtime_dependency(%q<em-dir-watcher>, [">= 0"])
58
60
  else
59
61
  s.add_dependency(%q<tilt>, [">= 1"])
62
+ s.add_dependency(%q<eventmachine>, [">= 0"])
63
+ s.add_dependency(%q<em-dir-watcher>, [">= 0"])
60
64
  end
61
65
  else
62
66
  s.add_dependency(%q<tilt>, [">= 1"])
67
+ s.add_dependency(%q<eventmachine>, [">= 0"])
68
+ s.add_dependency(%q<em-dir-watcher>, [">= 0"])
63
69
  end
64
70
  end
65
71
 
metadata CHANGED
@@ -4,9 +4,9 @@ version: !ruby/object:Gem::Version
4
4
  prerelease: false
5
5
  segments:
6
6
  - 0
7
- - 4
8
- - 2
9
- version: 0.4.2
7
+ - 5
8
+ - 0
9
+ version: 0.5.0
10
10
  platform: ruby
11
11
  authors:
12
12
  - Jostein Berre Eliassen,
@@ -30,7 +30,33 @@ dependencies:
30
30
  version: "1"
31
31
  type: :runtime
32
32
  version_requirements: *id001
33
- description: Automatically run common development tasks on events, as defined by a local .sidekick file. Easy, powerful dsl. Several pre-defined triggers. Helper methods for common tasks. Easy to extend with new triggers and helpers.
33
+ - !ruby/object:Gem::Dependency
34
+ name: eventmachine
35
+ prerelease: false
36
+ requirement: &id002 !ruby/object:Gem::Requirement
37
+ none: false
38
+ requirements:
39
+ - - ">="
40
+ - !ruby/object:Gem::Version
41
+ segments:
42
+ - 0
43
+ version: "0"
44
+ type: :runtime
45
+ version_requirements: *id002
46
+ - !ruby/object:Gem::Dependency
47
+ name: em-dir-watcher
48
+ prerelease: false
49
+ requirement: &id003 !ruby/object:Gem::Requirement
50
+ none: false
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ segments:
55
+ - 0
56
+ version: "0"
57
+ type: :runtime
58
+ version_requirements: *id003
59
+ description: Automatically run common development tasks on events, as defined by a local .sidekick file. Easy, powerful dsl including useful pre-defined triggers and helper methods for common tasks. Powered by EventMachine and easy to extend.
34
60
  email: post@jostein.be
35
61
  executables:
36
62
  - sidekick
@@ -50,8 +76,8 @@ files:
50
76
  - bin/sidekick
51
77
  - lib/sidekick.rb
52
78
  - lib/sidekick/helpers.rb
79
+ - lib/sidekick/helpers/util.rb
53
80
  - lib/sidekick/triggers.rb
54
- - lib/sidekick/triggers/watch.rb
55
81
  - lib/template
56
82
  - sidekick.gemspec
57
83
  - test/fixtures/page.haml
@@ -1,55 +0,0 @@
1
-
2
-
3
- module Sidekick::Triggers::Watch
4
-
5
- def self.new(*args)
6
-
7
- name, adapter_class = {
8
- :linux => [nil, Polling], # Ready to implement fs-event
9
- :darwin => [nil, Polling], # and libnotify. Polling seems
10
- :other => [nil, Polling] # to work perfectly though.
11
- }[::Sidekick::Helpers.platform]
12
-
13
- unless name.nil?
14
- adapter_class = Polling unless Sidekick::Helpers.load_gem?(name)
15
- end
16
- adapter_class.new(*args)
17
- end
18
-
19
- class Polling # todo: check for deletion
20
-
21
- def initialize(callback, glob, ignore_first=false)
22
- ::Sidekick::Triggers.log "polling #{glob} for file changes.."
23
- @path = glob
24
- @file_timestamps = {}
25
- @callback = callback
26
- read_changes if ignore_first # prebuild cache
27
- end
28
-
29
- def poll
30
- if changed?
31
- ::Sidekick::Triggers.log "modified #{@changes.inspect}"
32
- @callback.call(@changes)
33
- end
34
- end
35
-
36
- private
37
-
38
- def changed?
39
- read_changes
40
- !@changes.empty?
41
- end
42
-
43
- def read_changes
44
- @changes = []
45
- Dir[@path].each do |path|
46
- mtime = File.new('./' + path).mtime
47
- unless @file_timestamps[path] == mtime
48
- @changes << path
49
- @file_timestamps[path] = mtime
50
- end
51
- end
52
- end
53
- end
54
-
55
- end