launchdr 3
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/.manifest +7 -0
- data/README.markdown +66 -0
- data/Rakefile.rb +40 -0
- data/launchdr.gemspec +43 -0
- data/lib/launchdr.rb +36 -0
- data/lib/launchdr/launchd.rb +43 -0
- data/lib/launchdr/property_list.rb +38 -0
- data/lib/launchdr/task.rb +33 -0
- metadata +146 -0
data/.manifest
ADDED
data/README.markdown
ADDED
@@ -0,0 +1,66 @@
|
|
1
|
+
LaunchDoctor
|
2
|
+
============
|
3
|
+
I've got a PhD in [launchd][], and I'm not afraid to use it!
|
4
|
+
|
5
|
+
[launchd]: <http://en.wikipedia.org/wiki/Launchd> "launchd on Wikipedia"
|
6
|
+
|
7
|
+
Usage
|
8
|
+
-----
|
9
|
+
LaunchDoctor is really simple. It is essentially an easy interface to
|
10
|
+
[launchd][]'s [property list][plist] files.
|
11
|
+
|
12
|
+
There are three ways to use LaunchDoctor - the simplest being directly
|
13
|
+
creating property lists and treating them as hashes:
|
14
|
+
|
15
|
+
require 'launchdr'
|
16
|
+
plist = LaunchDr::Launchd.new "name.elliottcable.launchdr.test"
|
17
|
+
plist[:program_arguments] = ['/Applications/Calculator.app/Contents/MacOS/Calculator']
|
18
|
+
plist.dump LaunchDr::Launchd::Paths[:user_agent]
|
19
|
+
|
20
|
+
The second is to use the common block idiom, provided by the `LaunchDr()`
|
21
|
+
method:
|
22
|
+
|
23
|
+
require 'launchdr'
|
24
|
+
LaunchDr "name.elliottcable.launchdr.test" do |plist|
|
25
|
+
plist[:program_arguments] = ['/Applications/Calculator.app/Contents/MacOS/Calculator']
|
26
|
+
end
|
27
|
+
|
28
|
+
LaunchDoctor will automatically add the property list to `launchctl`, and then
|
29
|
+
start it running. Once you run the above snippet, the target will immediately
|
30
|
+
be launched for the first time.
|
31
|
+
|
32
|
+
LaunchDoctor can write (`dump`) the property lists to any place on your disk,
|
33
|
+
but the idiom method assumes you're going to want to use one of the
|
34
|
+
directories that launchd checks for property lists. These are stored in the
|
35
|
+
`Launchd::Paths` array. The default is to place it in the user-owned agents
|
36
|
+
directory at `~/Library/LaunchAgents`.
|
37
|
+
|
38
|
+
LaunchDoctor also preforms some 'prettification' on the keys provided by
|
39
|
+
launchd's property list structure. All of the keys on the
|
40
|
+
[`launchd.plist` manpage][manpage] are available, but they can also be used as
|
41
|
+
true 'Ruby-ish' symbol keys. All of the following are legal:
|
42
|
+
|
43
|
+
plist["UserName"] = "elliottcable"
|
44
|
+
plist[:UserName] = "elliottcable"
|
45
|
+
plist[:user_name] = "elliottcable"
|
46
|
+
|
47
|
+
Finally, you can use the Rake task interface to the last method. It simply
|
48
|
+
wraps the last method inside a rake task. This method is really great if you
|
49
|
+
want to provide a way to let users make your gem's binary run all the time:
|
50
|
+
|
51
|
+
require 'launchdr/task'
|
52
|
+
LaunchDr::Task.new :launchd, :bin => 'jello', :arguments => ['-D', 'shortener', 'grabup']
|
53
|
+
|
54
|
+
This isn't very flexible, but it's not very complicated either. If you need
|
55
|
+
more control over the plist, just use the second method inside a `task` block.
|
56
|
+
This method defaults to making your gem's binary `run_at_load` and be
|
57
|
+
`keep_alive` as well, so it won't die.
|
58
|
+
|
59
|
+
[plist]: <http://en.wikipedia.org/wiki/Property_list> "Property list on Wikipedia"
|
60
|
+
[manpage]: <http://developer.apple.com/DOCUMENTATION/DARWIN/Reference/ManPages/man5/launchd.plist.5.html> "Mac OS X Manual Page for launchd.plist(5)"
|
61
|
+
[rake]: <http://rake.rubyforge.org/> "Rake's RDocs"
|
62
|
+
|
63
|
+
Requirements
|
64
|
+
------------
|
65
|
+
- Plist - `gem install plist`
|
66
|
+
- Ruby Facets - `gem install facets`
|
data/Rakefile.rb
ADDED
@@ -0,0 +1,40 @@
|
|
1
|
+
($:.unshift File.expand_path(File.join( File.dirname(__FILE__), 'lib' ))).uniq!
|
2
|
+
require 'launchdr'
|
3
|
+
|
4
|
+
# =======================
|
5
|
+
# = Gem packaging tasks =
|
6
|
+
# =======================
|
7
|
+
begin
|
8
|
+
require 'echoe'
|
9
|
+
|
10
|
+
task :package => :'package:package'
|
11
|
+
task :install => :'package:install'
|
12
|
+
task :manifest => :'package:manifest'
|
13
|
+
task :clobber => :'package:clobber'
|
14
|
+
namespace :package do
|
15
|
+
Echoe.new('launchdr', LaunchDr::Version) do |g|
|
16
|
+
g.author = ['elliottcable']
|
17
|
+
g.email = ['launchdr@elliottcable.com']
|
18
|
+
g.summary = "One stop shop for launchd property list creation. The doctor is *in*!"
|
19
|
+
g.url = 'http://github.com/elliottcable/launchdr'
|
20
|
+
g.dependencies = ['plist', 'facets', 'uuid']
|
21
|
+
g.development_dependencies = ['echoe >= 3.0.2']
|
22
|
+
g.manifest_name = '.manifest' # I don't want this showing up <,<
|
23
|
+
g.retain_gemspec = true # perfect for GitHub
|
24
|
+
g.rakefile_name = 'Rakefile.rb' # It's a Ruby file, why not have .rb?
|
25
|
+
g.ignore_pattern = /^\.git\/|\.gemspec/
|
26
|
+
end
|
27
|
+
|
28
|
+
desc 'tests packaged files to ensure they are all present'
|
29
|
+
task :verify => :package do
|
30
|
+
# An error message will be displayed if files are missing
|
31
|
+
if system %(ruby -e "require 'rubygems'; require 'pkg/launchdr-#{LaunchDr::Version}/lib/launchdr'")
|
32
|
+
puts "\nThe library files are present"
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
rescue LoadError
|
38
|
+
desc 'You need the `echoe` gem to package LaunchDr'
|
39
|
+
task :package
|
40
|
+
end
|
data/launchdr.gemspec
ADDED
@@ -0,0 +1,43 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
|
3
|
+
Gem::Specification.new do |s|
|
4
|
+
s.name = %q{launchdr}
|
5
|
+
s.version = "3"
|
6
|
+
|
7
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 1.2") if s.respond_to? :required_rubygems_version=
|
8
|
+
s.authors = ["elliottcable"]
|
9
|
+
s.date = %q{2009-02-11}
|
10
|
+
s.description = %q{One stop shop for launchd property list creation. The doctor is *in*!}
|
11
|
+
s.email = ["launchdr@elliottcable.com"]
|
12
|
+
s.extra_rdoc_files = ["lib/launchdr/launchd.rb", "lib/launchdr/property_list.rb", "lib/launchdr/task.rb", "lib/launchdr.rb", "README.markdown"]
|
13
|
+
s.files = ["lib/launchdr/launchd.rb", "lib/launchdr/property_list.rb", "lib/launchdr/task.rb", "lib/launchdr.rb", "Rakefile.rb", "README.markdown", ".manifest", "launchdr.gemspec"]
|
14
|
+
s.has_rdoc = true
|
15
|
+
s.homepage = %q{http://github.com/elliottcable/launchdr}
|
16
|
+
s.rdoc_options = ["--line-numbers", "--inline-source", "--title", "Launchdr", "--main", "README.markdown"]
|
17
|
+
s.require_paths = ["lib"]
|
18
|
+
s.rubyforge_project = %q{launchdr}
|
19
|
+
s.rubygems_version = %q{1.3.1}
|
20
|
+
s.summary = %q{One stop shop for launchd property list creation. The doctor is *in*!}
|
21
|
+
|
22
|
+
if s.respond_to? :specification_version then
|
23
|
+
current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
|
24
|
+
s.specification_version = 2
|
25
|
+
|
26
|
+
if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
|
27
|
+
s.add_runtime_dependency(%q<plist>, [">= 0"])
|
28
|
+
s.add_runtime_dependency(%q<facets>, [">= 0"])
|
29
|
+
s.add_runtime_dependency(%q<uuid>, [">= 0"])
|
30
|
+
s.add_development_dependency(%q<echoe>, [">= 0", "= 3.0.2"])
|
31
|
+
else
|
32
|
+
s.add_dependency(%q<plist>, [">= 0"])
|
33
|
+
s.add_dependency(%q<facets>, [">= 0"])
|
34
|
+
s.add_dependency(%q<uuid>, [">= 0"])
|
35
|
+
s.add_dependency(%q<echoe>, [">= 0", "= 3.0.2"])
|
36
|
+
end
|
37
|
+
else
|
38
|
+
s.add_dependency(%q<plist>, [">= 0"])
|
39
|
+
s.add_dependency(%q<facets>, [">= 0"])
|
40
|
+
s.add_dependency(%q<uuid>, [">= 0"])
|
41
|
+
s.add_dependency(%q<echoe>, [">= 0", "= 3.0.2"])
|
42
|
+
end
|
43
|
+
end
|
data/lib/launchdr.rb
ADDED
@@ -0,0 +1,36 @@
|
|
1
|
+
require 'launchdr/property_list'
|
2
|
+
require 'launchdr/launchd'
|
3
|
+
|
4
|
+
|
5
|
+
def LaunchDr label, opts = {}
|
6
|
+
LaunchDr.create label, opts, &Proc.new
|
7
|
+
end
|
8
|
+
module LaunchDr
|
9
|
+
Version = 3
|
10
|
+
Options = Hash.new
|
11
|
+
|
12
|
+
def self.create label, opts = {}
|
13
|
+
plist = Launchd.new label
|
14
|
+
|
15
|
+
yield plist if block_given?
|
16
|
+
|
17
|
+
raise "You must run as a root user and allow_system_agents! if you wish to preform the dangerous act of writing to #{Launchd::Paths[:system_agent]}!" unless !(opts[:type] == :system_agent) or Options[:system_agents_allowed]
|
18
|
+
raise "You must run as a root user, allow_system_agents!, *and* allow_system_daemons! if you wish to preform the very dangerous act of writing to #{Launchd::Paths[:system_daemon]}!" unless !(opts[:type] == :system_daemon) or Options[:system_daemon_allowed]
|
19
|
+
path = Launchd::Paths[opts[:type] || :user_agent]
|
20
|
+
raise "Type error! Must be one of #{Launchd::Paths.keys.join(", ")}" unless path
|
21
|
+
|
22
|
+
plist.dump File.expand_path(path)
|
23
|
+
plist.load!
|
24
|
+
end
|
25
|
+
|
26
|
+
def self.allow_system_agents!
|
27
|
+
Options[:system_agents_allowed] = true
|
28
|
+
end
|
29
|
+
|
30
|
+
def self.allow_system_daemons!
|
31
|
+
raise "You must allow System-owned agents to allow System-owned daemons!" unless Options[:system_agents_allowed]
|
32
|
+
Options[:system_daemon_allowed] = true
|
33
|
+
end
|
34
|
+
|
35
|
+
|
36
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
require 'facets/string'
|
2
|
+
|
3
|
+
module LaunchDr
|
4
|
+
|
5
|
+
class Launchd < PropertyList
|
6
|
+
Paths = {
|
7
|
+
:user_agent => "~/Library/LaunchAgents",
|
8
|
+
:agent => "/Library/LaunchAgents",
|
9
|
+
:daemon => "/Library/LaunchDaemons",
|
10
|
+
:system_agent => "/System/Library/LaunchAgents",
|
11
|
+
:system_daemon => "/System/Library/LaunchDaemons"
|
12
|
+
}
|
13
|
+
|
14
|
+
def [] key
|
15
|
+
super(key.to_s.titlecase.camelcase)
|
16
|
+
end
|
17
|
+
|
18
|
+
def []= key, value
|
19
|
+
super(key.to_s.titlecase.camelcase, value)
|
20
|
+
end
|
21
|
+
|
22
|
+
# Adds the property list to `launchctl`'s indexes, and starts it (unless
|
23
|
+
# disabled).
|
24
|
+
def load!
|
25
|
+
system "launchctl load #{file}"
|
26
|
+
end
|
27
|
+
|
28
|
+
# Removes the propertly list from `launchctl`'s indexes.
|
29
|
+
def unload!
|
30
|
+
system "launchctl unload #{file}"
|
31
|
+
end
|
32
|
+
|
33
|
+
# Finds the plist file, if it's been created
|
34
|
+
def file
|
35
|
+
Paths.values.each do |dir|
|
36
|
+
file = File.expand_path(File.join(dir, self[:label] + '.plist'))
|
37
|
+
return file if File.file? file
|
38
|
+
end
|
39
|
+
raise "#{self[:label]} doesn't exist in any of the default locations. Did you #dump it?"
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
require 'plist'
|
2
|
+
require 'uuid'
|
3
|
+
|
4
|
+
module LaunchDr
|
5
|
+
|
6
|
+
class PropertyList
|
7
|
+
attr_reader :elements
|
8
|
+
|
9
|
+
def initialize label = nil, elements = {}
|
10
|
+
@elements = elements
|
11
|
+
@elements['Label'] = label || "rb.launchdr.#{UUID.new.generate}"
|
12
|
+
end
|
13
|
+
|
14
|
+
def [] key
|
15
|
+
@elements[key.to_s]
|
16
|
+
end
|
17
|
+
|
18
|
+
def []= key, value
|
19
|
+
@elements[key] = value
|
20
|
+
end
|
21
|
+
|
22
|
+
def to_plist
|
23
|
+
@elements.to_plist
|
24
|
+
end
|
25
|
+
|
26
|
+
def dump path
|
27
|
+
out = File.new(File.expand_path(File.join(path, @elements['Label'] + '.plist')), 'w+')
|
28
|
+
out.puts Plist::Emit.dump @elements
|
29
|
+
out.close
|
30
|
+
end
|
31
|
+
|
32
|
+
def self.load filename
|
33
|
+
elements = Plist::parse_xml File.new(File.expand_path filename, 'r')
|
34
|
+
new elements[:label], elements
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
require 'launchdr'
|
2
|
+
|
3
|
+
module LaunchDr
|
4
|
+
|
5
|
+
module Task
|
6
|
+
def self.new name = :launchd, opts = {}
|
7
|
+
raise "`LaunchDr.task`'s options must include the name of the binary for your gem!" unless opts[:bin]
|
8
|
+
opts = {:desc => 'Creates a launchd property list for this gem', :arguments => []}.merge opts
|
9
|
+
|
10
|
+
desc opts[:desc] unless opts[:no_desc]
|
11
|
+
task name do
|
12
|
+
path = File.expand_path File.join('', opts[:bin])
|
13
|
+
unless File.file? path
|
14
|
+
path = File.expand_path File.join(Gem::default_bindir, opts[:bin])
|
15
|
+
unless File.file? path
|
16
|
+
path = File.expand_path %x[which "#{opts[:bin]}"].chomp
|
17
|
+
end
|
18
|
+
end
|
19
|
+
unless File.file? path
|
20
|
+
raise "** Unable to locate binary #{opts[:bin]}"
|
21
|
+
end
|
22
|
+
LaunchDr "rb.launchdr.#{opts[:bin]}" do |plist|
|
23
|
+
plist[:program_arguments] = [path, opts[:arguments]].flatten
|
24
|
+
plist[:keep_alive] = true
|
25
|
+
plist[:run_at_load] = true
|
26
|
+
end
|
27
|
+
puts "** `#{[path, opts[:arguments]].flatten.join(' ')}` will now be run on startup!"
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
metadata
ADDED
@@ -0,0 +1,146 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: launchdr
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: -124656629444251359
|
5
|
+
prerelease: false
|
6
|
+
segments:
|
7
|
+
- 3
|
8
|
+
version: "3"
|
9
|
+
platform: ruby
|
10
|
+
authors:
|
11
|
+
- elliottcable
|
12
|
+
autorequire:
|
13
|
+
bindir: bin
|
14
|
+
cert_chain: []
|
15
|
+
|
16
|
+
date: 2009-02-11 00:00:00 -09:00
|
17
|
+
default_executable:
|
18
|
+
dependencies:
|
19
|
+
- !ruby/object:Gem::Dependency
|
20
|
+
name: plist
|
21
|
+
prerelease: false
|
22
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
23
|
+
none: false
|
24
|
+
requirements:
|
25
|
+
- - ">="
|
26
|
+
- !ruby/object:Gem::Version
|
27
|
+
hash: 134023568094289244
|
28
|
+
segments:
|
29
|
+
- 0
|
30
|
+
version: "0"
|
31
|
+
type: :runtime
|
32
|
+
version_requirements: *id001
|
33
|
+
- !ruby/object:Gem::Dependency
|
34
|
+
name: facets
|
35
|
+
prerelease: false
|
36
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
37
|
+
none: false
|
38
|
+
requirements:
|
39
|
+
- - ">="
|
40
|
+
- !ruby/object:Gem::Version
|
41
|
+
hash: 134023568094289244
|
42
|
+
segments:
|
43
|
+
- 0
|
44
|
+
version: "0"
|
45
|
+
type: :runtime
|
46
|
+
version_requirements: *id002
|
47
|
+
- !ruby/object:Gem::Dependency
|
48
|
+
name: uuid
|
49
|
+
prerelease: false
|
50
|
+
requirement: &id003 !ruby/object:Gem::Requirement
|
51
|
+
none: false
|
52
|
+
requirements:
|
53
|
+
- - ">="
|
54
|
+
- !ruby/object:Gem::Version
|
55
|
+
hash: 134023568094289244
|
56
|
+
segments:
|
57
|
+
- 0
|
58
|
+
version: "0"
|
59
|
+
type: :runtime
|
60
|
+
version_requirements: *id003
|
61
|
+
- !ruby/object:Gem::Dependency
|
62
|
+
name: echoe
|
63
|
+
prerelease: false
|
64
|
+
requirement: &id004 !ruby/object:Gem::Requirement
|
65
|
+
none: false
|
66
|
+
requirements:
|
67
|
+
- - ">="
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
hash: 134023568094289244
|
70
|
+
segments:
|
71
|
+
- 0
|
72
|
+
version: "0"
|
73
|
+
- - "="
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
hash: 1556383981505405372
|
76
|
+
segments:
|
77
|
+
- 3
|
78
|
+
- 0
|
79
|
+
- 2
|
80
|
+
version: 3.0.2
|
81
|
+
type: :development
|
82
|
+
version_requirements: *id004
|
83
|
+
description: One stop shop for launchd property list creation. The doctor is *in*!
|
84
|
+
email:
|
85
|
+
- launchdr@elliottcable.com
|
86
|
+
executables: []
|
87
|
+
|
88
|
+
extensions: []
|
89
|
+
|
90
|
+
extra_rdoc_files:
|
91
|
+
- lib/launchdr/launchd.rb
|
92
|
+
- lib/launchdr/property_list.rb
|
93
|
+
- lib/launchdr/task.rb
|
94
|
+
- lib/launchdr.rb
|
95
|
+
- README.markdown
|
96
|
+
files:
|
97
|
+
- lib/launchdr/launchd.rb
|
98
|
+
- lib/launchdr/property_list.rb
|
99
|
+
- lib/launchdr/task.rb
|
100
|
+
- lib/launchdr.rb
|
101
|
+
- Rakefile.rb
|
102
|
+
- README.markdown
|
103
|
+
- .manifest
|
104
|
+
- launchdr.gemspec
|
105
|
+
has_rdoc: true
|
106
|
+
homepage: http://github.com/elliottcable/launchdr
|
107
|
+
licenses: []
|
108
|
+
|
109
|
+
post_install_message:
|
110
|
+
rdoc_options:
|
111
|
+
- --line-numbers
|
112
|
+
- --inline-source
|
113
|
+
- --title
|
114
|
+
- Launchdr
|
115
|
+
- --main
|
116
|
+
- README.markdown
|
117
|
+
require_paths:
|
118
|
+
- lib
|
119
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
120
|
+
none: false
|
121
|
+
requirements:
|
122
|
+
- - ">="
|
123
|
+
- !ruby/object:Gem::Version
|
124
|
+
hash: 134023568094289244
|
125
|
+
segments:
|
126
|
+
- 0
|
127
|
+
version: "0"
|
128
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
129
|
+
none: false
|
130
|
+
requirements:
|
131
|
+
- - ">="
|
132
|
+
- !ruby/object:Gem::Version
|
133
|
+
hash: 1922007725579590311
|
134
|
+
segments:
|
135
|
+
- 1
|
136
|
+
- 2
|
137
|
+
version: "1.2"
|
138
|
+
requirements: []
|
139
|
+
|
140
|
+
rubyforge_project: launchdr
|
141
|
+
rubygems_version: 1.3.7
|
142
|
+
signing_key:
|
143
|
+
specification_version: 2
|
144
|
+
summary: One stop shop for launchd property list creation. The doctor is *in*!
|
145
|
+
test_files: []
|
146
|
+
|