sidekick 0.4.2 → 0.5.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.
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