sidekick 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,5 @@
1
+ README.rdoc
2
+ lib/**/*.rb
3
+ bin/*
4
+ features/**/*.feature
5
+ LICENSE
@@ -0,0 +1,21 @@
1
+ ## MAC OS
2
+ .DS_Store
3
+
4
+ ## TEXTMATE
5
+ *.tmproj
6
+ tmtags
7
+
8
+ ## EMACS
9
+ *~
10
+ \#*
11
+ .\#*
12
+
13
+ ## VIM
14
+ *.swp
15
+
16
+ ## PROJECT::GENERAL
17
+ coverage
18
+ rdoc
19
+ pkg
20
+
21
+ ## PROJECT::SPECIFIC
@@ -0,0 +1,17 @@
1
+
2
+ # This is an example sidekick file that i use for testing
3
+ # by doing. Proper tests are welcome.
4
+
5
+ watch '**/*.rb' do |paths|
6
+ sh 'echo files updated'
7
+ end
8
+
9
+ every(5) do
10
+ log 'Five seconds passed!'
11
+ end
12
+
13
+ every(20) do
14
+ notify 'Are you tired of being notified yet?'
15
+ end
16
+
17
+ # vim:ft=ruby
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2010 Jostein Berre Eliassen,
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,126 @@
1
+ h1. Sidekick [Draft]
2
+
3
+ Sidekick is a command line tool to automatically trigger certain tasks following certain events, as defined in a @.sidekick@ file in your project folder.
4
+
5
+ You would typically run it in the background while coding, to automatize things like restarting development servers on file changes, recompiling assets, continously running tests or periodically running commands.
6
+
7
+ It is very easy to set up and extend.
8
+
9
+
10
+ h2. Basic usage
11
+
12
+ Install with @gem install sidekick@ and invoke it using the @sidekick@ shell command in your project folder. You should have a @.sidekick@ file in the active directory. Here is a sample:
13
+
14
+ <pre>
15
+ <code lang='ruby'>
16
+
17
+ # restarts passenger on file changes and
18
+ # notifies via growl or libnotify
19
+ watch '**/*.rb' do |paths|
20
+ restart_passenger
21
+ notify 'Are you tired of being notified yet?'
22
+ end
23
+
24
+ # shows a new fortune every 10 seconds
25
+ every(10) do
26
+ sh 'fortune'
27
+ end
28
+
29
+ </code>
30
+ </pre>
31
+
32
+ The @watch@ and @every@ commands are are triggers, while @notify@, @sh@, and @restart_passenger@ are helper methods. The currently available triggers and helpers are:
33
+
34
+ Triggers:
35
+
36
+ |@watch@|run on file changes|
37
+ |@every(duration)@|run every @duration@ of seconds|
38
+
39
+ Helpers:
40
+
41
+ |@sh@|run shell command, showing output, like in rake|
42
+ |@log@|log events to screen. cleaner than puts.|
43
+ |@notify@|notify user via growl, libnotify etc|
44
+ |@running?(platform)@|os can be one of @[:linux, :darwin, :other]@|
45
+ |@load_gem?(name)@|tries to load gem, true if success. otherwise false. informs user that more functions are available when it is installed|
46
+ |@restart_passenger@|restart passenger based environments by touching tmp/restart.txt|
47
+
48
+ h2. Writing extensions
49
+
50
+ Sidekick is very easy to extend.
51
+
52
+ h3. Writing new helpers
53
+
54
+ To add more helpers, simply add methods to @Sidekick::Helpers@, like this:
55
+
56
+ <pre>
57
+ <code lang='ruby'>
58
+ module Sidekick::Helpers
59
+ def make_pickle
60
+ PickeFactory.manufacture!
61
+ end
62
+ end
63
+ </code>
64
+ </pre>
65
+
66
+ When adding a group it is cleaner to use a separate module under @Sidekick::Helpers@, and then include it.
67
+
68
+
69
+ h3. Writing new triggers
70
+
71
+ To add new triggers, you have two options: block or class interface. Blocks are best for simple stuff, while classes are good for more complex code. This is how you set up a new trigger using a block:
72
+
73
+ <pre>
74
+ <code lang='ruby'>
75
+
76
+ register :every do |callback, duration|
77
+ timeshare(duration) do
78
+ callback.call
79
+ end
80
+ end
81
+
82
+ </code>
83
+ </pre>
84
+
85
+ This is the implementation of the @every@ function mentioned earlier. The @timeshare@ method is provided by Sidekick, and will call the supplied block with the specified duration in seconds. This is also useful for polling something without blocking other triggers.
86
+
87
+ Triggers can also be written as a class interface. Here is an example of the above, translated as a class:
88
+
89
+ <pre>
90
+ <code lang='ruby'>
91
+
92
+ class Sidekick::Triggers::Every
93
+
94
+ def initialize(callback, duration)
95
+ @callback = callback
96
+ @duration = duration
97
+ end
98
+
99
+ def poll
100
+ @callback.call
101
+ end
102
+
103
+ def poll_freq
104
+ @duration
105
+ end
106
+ end
107
+
108
+ </code>
109
+ </pre>
110
+
111
+ Here, the optional @poll@ method functions like the callback block in the previous example. The @poll_freq@ method is also optional, and determines the frequency in seconds with which to call @poll@.
112
+
113
+ h3. When writing your own extensions
114
+
115
+ You can keep your extensions in the @.sidekick@ file, or in a separate gem, or (better yet) ask me to merge them into the main repository.
116
+
117
+ h2. One more feature
118
+
119
+ Fascinatingly, the main code chunk is just under 100 lines including documentation, excluding the default triggers and helpers. Why not read it..?
120
+
121
+ I got a bit of inspiration from the somewhat similar project "Guard":http://github.com/guard/guard
122
+
123
+
124
+ h3. Copyright
125
+
126
+ Copyright (c) 2010 Jostein Berre Eliassen. See LICENSE for details.
@@ -0,0 +1,53 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+
4
+ begin
5
+ require 'jeweler'
6
+ Jeweler::Tasks.new do |gem|
7
+ gem.name = "sidekick"
8
+ gem.summary = %Q{Automatically run common project tasks according to triggers defined in .sidekick}
9
+ gem.description = %Q{Automatically run common project tasks according to triggers defined in a .sidekick file. Easy, powerful configuration per project. Provides pre-defined sidekicks for things like watching for file changes, and allows easy definition of new ones. Contains helpers for common tasks such as compiling assets or triggering development server restarts.}
10
+ gem.email = "post@jostein.be"
11
+ gem.homepage = "http://github.com/jbe/sidekick"
12
+ gem.authors = ["Jostein Berre Eliassen,"]
13
+ gem.add_development_dependency "thoughtbot-shoulda", ">= 0"
14
+ # gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
15
+ end
16
+ Jeweler::GemcutterTasks.new
17
+ rescue LoadError
18
+ puts "Jeweler (or a dependency) not available. Install it with: gem install jeweler"
19
+ end
20
+
21
+ require 'rake/testtask'
22
+ Rake::TestTask.new(:test) do |test|
23
+ test.libs << 'lib' << 'test'
24
+ test.pattern = 'test/**/test_*.rb'
25
+ test.verbose = true
26
+ end
27
+
28
+ begin
29
+ require 'rcov/rcovtask'
30
+ Rcov::RcovTask.new do |test|
31
+ test.libs << 'test'
32
+ test.pattern = 'test/**/test_*.rb'
33
+ test.verbose = true
34
+ end
35
+ rescue LoadError
36
+ task :rcov do
37
+ abort "RCov is not available. In order to run rcov, you must: sudo gem install spicycode-rcov"
38
+ end
39
+ end
40
+
41
+ task :test => :check_dependencies
42
+
43
+ task :default => :test
44
+
45
+ require 'rake/rdoctask'
46
+ Rake::RDocTask.new do |rdoc|
47
+ version = File.exist?('VERSION') ? File.read('VERSION') : ""
48
+
49
+ rdoc.rdoc_dir = 'rdoc'
50
+ rdoc.title = "sidekick #{version}"
51
+ rdoc.rdoc_files.include('README*')
52
+ rdoc.rdoc_files.include('lib/**/*.rb')
53
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.2.0
@@ -0,0 +1,10 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ begin
4
+ require 'sidekick'
5
+ rescue LoadError
6
+ require 'rubygems'
7
+ require 'sidekick'
8
+ end
9
+
10
+ Sidekick.run!
@@ -0,0 +1,96 @@
1
+
2
+
3
+ module Sidekick
4
+
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
+ abort('No .sidekick file!') unless File.exists?(conf_path)
10
+ Context.new(conf_path)
11
+ Triggers.enter_loop
12
+ end
13
+
14
+
15
+ # A new Context is used to run the .sidekick
16
+ # config file. It simply extends Module.
17
+ class Context < ::Module
18
+ def initialize(conf_path)
19
+ super()
20
+ extend MethodProxy
21
+ extend Helpers
22
+ code = open(conf_path) { |f| f.read }
23
+ module_eval(code, conf_path)
24
+ end
25
+ end
26
+
27
+ # New contexts also extend MethodProxy, so that they
28
+ # can forward method calls to the registered sidekicks.
29
+ module MethodProxy
30
+ @@triggers = {}
31
+
32
+ # registers a trigger
33
+ def self.register(name, block)
34
+ @@triggers[name] = block
35
+ end
36
+
37
+ def method_missing(name, *args, &blk)
38
+ if @@triggers[name]
39
+ @@triggers[name].call(blk, *args)
40
+ else
41
+ super
42
+ end
43
+ end
44
+
45
+ def respond_to?(method)
46
+ super || !!@@triggers[method]
47
+ end
48
+ end
49
+
50
+ # contains trigger helpers and logic to register
51
+ # triggers, making them available in sidekick
52
+ module Triggers
53
+ @@timeshare_callbacks = []
54
+ @@timeshare_frequencies = []
55
+
56
+ class << self
57
+
58
+ # registers a sidekick available to .sidekick files
59
+ def register(name, &block)
60
+ MethodProxy.register(name, block)
61
+ end
62
+
63
+ # registers a callback for timesharing between
64
+ # active sidekicks
65
+ def timeshare(freq=1, &callback)
66
+ @@timeshare_callbacks << callback
67
+ @@timeshare_frequencies << freq
68
+ end
69
+
70
+ # begins the timesharing loop
71
+ def enter_loop
72
+ Signal.trap :INT do
73
+ exit
74
+ end
75
+
76
+ 0.upto(1.0/0) do |n|
77
+ 0.upto(@@timeshare_callbacks.size - 1) do |i|
78
+ if n % @@timeshare_frequencies[i] == 0
79
+ @@timeshare_callbacks[i].call(n)
80
+ end
81
+ end
82
+ sleep 1
83
+ end
84
+ end
85
+
86
+ def log(str)
87
+ puts str
88
+ end
89
+
90
+ end
91
+ end
92
+ end
93
+
94
+ require 'sidekick/helpers'
95
+ require 'sidekick/triggers'
96
+
@@ -0,0 +1,70 @@
1
+ require 'fileutils'
2
+ require 'rbconfig'
3
+
4
+
5
+ # default helpers
6
+
7
+ module Sidekick::Helpers
8
+
9
+ module Util
10
+ # :linux, :darwin, :other
11
+ def platform
12
+ [:linux, :darwin].each do |plf|
13
+ return plf if Config::CONFIG['target_os'] =~ /#{plf}/i
14
+ end; :other
15
+ end
16
+
17
+ def running?(plf)
18
+ plf == platform
19
+ end
20
+
21
+ def gem_load?(gemname)
22
+ @installed ||= begin
23
+ require gemname
24
+ true
25
+ rescue LoadError
26
+ puts "Please install the #{gemname} gem to enable all functions."
27
+ false
28
+ end
29
+ end
30
+ end
31
+
32
+ include Util
33
+ extend Util
34
+
35
+ module Notification
36
+ extend ::Sidekick::Helpers::Util
37
+ def self.show(message, title='Sidekick')
38
+ if running?(:linux) and gem_load?('libnotify')
39
+ Libnotify.show :body => message, :summary => title
40
+ elsif running?(:darwin) and gem_load?('growl')
41
+ Growl.notify message, :title => title, :name => 'Sidekick'
42
+ else
43
+ puts "Notification: #{title} #{message}"
44
+ end
45
+ end
46
+ end
47
+
48
+
49
+
50
+ def log(str)
51
+ puts ' -> ' + str
52
+ end
53
+
54
+ def notify(*args)
55
+ Notification.show(*args)
56
+ end
57
+
58
+ def sh(cmd)
59
+ log cmd
60
+ puts `#{cmd}`
61
+ end
62
+
63
+
64
+
65
+ def restart_passenger
66
+ FileUtils.touch './tmp/restart.txt'
67
+ log 'restarted passenger'
68
+ end
69
+
70
+ end
@@ -0,0 +1,30 @@
1
+
2
+
3
+ module Sidekick::Triggers
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
14
+
15
+ # default triggers
16
+
17
+ require 'sidekick/triggers/watch'
18
+
19
+ register_class(:watch, Watch)
20
+
21
+ register :every do |callback, duration|
22
+ timeshare(duration) do
23
+ callback.call
24
+ end
25
+ end
26
+
27
+ end
28
+
29
+
30
+
@@ -0,0 +1,53 @@
1
+
2
+
3
+ module Sidekick::Triggers::Watch
4
+
5
+ def self.new(*args)
6
+ case ::Sidekick::Helpers.platform
7
+ when :linux then Polling
8
+ when :darwin then Polling
9
+ else Polling
10
+ end.new(*args)
11
+ end
12
+
13
+ class Polling
14
+
15
+ def initialize(callback, glob, ignore_first=false)
16
+ ::Sidekick::Triggers.log "polling #{glob} for file changes.."
17
+ @path = glob
18
+ @file_timestamps = {}
19
+ @callback = callback
20
+ read_changes if ignore_first # prebuild cache
21
+ end
22
+
23
+ def poll
24
+ if changed?
25
+ ::Sidekick::Triggers.log "modified #{@changes.inspect}"
26
+ @callback.call(@changes)
27
+ end
28
+ end
29
+
30
+ private
31
+
32
+ def changed?
33
+ read_changes
34
+ !@changes.empty?
35
+ end
36
+
37
+ def read_changes
38
+ @changes = []
39
+ Dir[@path].each do |path|
40
+ mtime = File.new('./' + path).mtime
41
+ unless @file_timestamps[path] == mtime
42
+ @changes << path
43
+ @file_timestamps[path] = mtime
44
+ end
45
+ end
46
+ end
47
+ end
48
+
49
+ # TODO inotify and mac support
50
+
51
+
52
+
53
+ end
@@ -0,0 +1,61 @@
1
+ # Generated by jeweler
2
+ # DO NOT EDIT THIS FILE DIRECTLY
3
+ # Instead, edit Jeweler::Tasks in Rakefile, and run the gemspec command
4
+ # -*- encoding: utf-8 -*-
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = %q{sidekick}
8
+ s.version = "0.2.0"
9
+
10
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
+ s.authors = ["Jostein Berre Eliassen,"]
12
+ s.date = %q{2010-10-24}
13
+ s.default_executable = %q{sidekick}
14
+ s.description = %q{Automatically run common project tasks according to triggers defined in a .sidekick file. Easy, powerful configuration per project. Provides pre-defined sidekicks for things like watching for file changes, and allows easy definition of new ones. Contains helpers for common tasks such as compiling assets or triggering development server restarts.}
15
+ s.email = %q{post@jostein.be}
16
+ s.executables = ["sidekick"]
17
+ s.extra_rdoc_files = [
18
+ "LICENSE",
19
+ "README.textile"
20
+ ]
21
+ s.files = [
22
+ ".document",
23
+ ".gitignore",
24
+ ".sidekick",
25
+ "LICENSE",
26
+ "README.textile",
27
+ "Rakefile",
28
+ "VERSION",
29
+ "bin/sidekick",
30
+ "lib/sidekick.rb",
31
+ "lib/sidekick/helpers.rb",
32
+ "lib/sidekick/triggers.rb",
33
+ "lib/sidekick/triggers/watch.rb",
34
+ "sidekick.gemspec",
35
+ "test/helper.rb",
36
+ "test/test_sidekick.rb"
37
+ ]
38
+ s.homepage = %q{http://github.com/jbe/sidekick}
39
+ s.rdoc_options = ["--charset=UTF-8"]
40
+ s.require_paths = ["lib"]
41
+ s.rubygems_version = %q{1.3.7}
42
+ s.summary = %q{Automatically run common project tasks according to triggers defined in .sidekick}
43
+ s.test_files = [
44
+ "test/helper.rb",
45
+ "test/test_sidekick.rb"
46
+ ]
47
+
48
+ if s.respond_to? :specification_version then
49
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
50
+ s.specification_version = 3
51
+
52
+ if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
53
+ s.add_development_dependency(%q<thoughtbot-shoulda>, [">= 0"])
54
+ else
55
+ s.add_dependency(%q<thoughtbot-shoulda>, [">= 0"])
56
+ end
57
+ else
58
+ s.add_dependency(%q<thoughtbot-shoulda>, [">= 0"])
59
+ end
60
+ end
61
+
@@ -0,0 +1,10 @@
1
+ require 'rubygems'
2
+ require 'test/unit'
3
+ require 'shoulda'
4
+
5
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
6
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
7
+ require 'sidekick'
8
+
9
+ class Test::Unit::TestCase
10
+ end
@@ -0,0 +1,7 @@
1
+ require 'helper'
2
+
3
+ class TestSidekick < Test::Unit::TestCase
4
+ should "probably rename this file and start testing for real" do
5
+ flunk "hey buddy, you should probably rename this file and start testing for real"
6
+ end
7
+ end
metadata ADDED
@@ -0,0 +1,92 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: sidekick
3
+ version: !ruby/object:Gem::Version
4
+ prerelease: false
5
+ segments:
6
+ - 0
7
+ - 2
8
+ - 0
9
+ version: 0.2.0
10
+ platform: ruby
11
+ authors:
12
+ - Jostein Berre Eliassen,
13
+ autorequire:
14
+ bindir: bin
15
+ cert_chain: []
16
+
17
+ date: 2010-10-24 00:00:00 +02:00
18
+ default_executable: sidekick
19
+ dependencies:
20
+ - !ruby/object:Gem::Dependency
21
+ name: thoughtbot-shoulda
22
+ prerelease: false
23
+ requirement: &id001 !ruby/object:Gem::Requirement
24
+ none: false
25
+ requirements:
26
+ - - ">="
27
+ - !ruby/object:Gem::Version
28
+ segments:
29
+ - 0
30
+ version: "0"
31
+ type: :development
32
+ version_requirements: *id001
33
+ description: Automatically run common project tasks according to triggers defined in a .sidekick file. Easy, powerful configuration per project. Provides pre-defined sidekicks for things like watching for file changes, and allows easy definition of new ones. Contains helpers for common tasks such as compiling assets or triggering development server restarts.
34
+ email: post@jostein.be
35
+ executables:
36
+ - sidekick
37
+ extensions: []
38
+
39
+ extra_rdoc_files:
40
+ - LICENSE
41
+ - README.textile
42
+ files:
43
+ - .document
44
+ - .gitignore
45
+ - .sidekick
46
+ - LICENSE
47
+ - README.textile
48
+ - Rakefile
49
+ - VERSION
50
+ - bin/sidekick
51
+ - lib/sidekick.rb
52
+ - lib/sidekick/helpers.rb
53
+ - lib/sidekick/triggers.rb
54
+ - lib/sidekick/triggers/watch.rb
55
+ - sidekick.gemspec
56
+ - test/helper.rb
57
+ - test/test_sidekick.rb
58
+ has_rdoc: true
59
+ homepage: http://github.com/jbe/sidekick
60
+ licenses: []
61
+
62
+ post_install_message:
63
+ rdoc_options:
64
+ - --charset=UTF-8
65
+ require_paths:
66
+ - lib
67
+ required_ruby_version: !ruby/object:Gem::Requirement
68
+ none: false
69
+ requirements:
70
+ - - ">="
71
+ - !ruby/object:Gem::Version
72
+ segments:
73
+ - 0
74
+ version: "0"
75
+ required_rubygems_version: !ruby/object:Gem::Requirement
76
+ none: false
77
+ requirements:
78
+ - - ">="
79
+ - !ruby/object:Gem::Version
80
+ segments:
81
+ - 0
82
+ version: "0"
83
+ requirements: []
84
+
85
+ rubyforge_project:
86
+ rubygems_version: 1.3.7
87
+ signing_key:
88
+ specification_version: 3
89
+ summary: Automatically run common project tasks according to triggers defined in .sidekick
90
+ test_files:
91
+ - test/helper.rb
92
+ - test/test_sidekick.rb