launchd_tools 0.5.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: 7a74ee78c83f4de133e4d2ae13f21ead304210ef
4
+ data.tar.gz: ca34827f49bf559be77e8c250471cfa7aa6c2eca
5
+ SHA512:
6
+ metadata.gz: 03f429b7f92e27e6aa3db2ab370ae418d1a1d0a8d53f18edbb15d926e42b2e4e36a4f2088c919b8733e86eb89abd8808c089b6bc80b034a532000bbc6398edea
7
+ data.tar.gz: 65c19da4950484a251c349c244e57cad2b76f115e5650f65eb747de9441c6bcf5545c14645cdbf29001f477c0b93ef9feb907fa86aab2f86f3e6d7ea3c770630
data/.gitignore ADDED
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+ --format progress
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in launchd_tools.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2013 Kyle Crawford
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,108 @@
1
+ # Launchd Tools
2
+
3
+ Convert from command to launchd plist or from launchd plist to command
4
+
5
+ ## Installation
6
+
7
+ $ gem install launchd_tools
8
+
9
+ ## Command Usage
10
+
11
+ Output the command and arguments from a launchd plist:
12
+
13
+ $ launchd2cmd /path/to/launchd.plist [another/launchd.plist]
14
+
15
+ Create a launchd plist from a command and arguments:
16
+
17
+ $ cmd2launchd /path/to/daemon [arg] [-option]
18
+
19
+ ## Examples
20
+
21
+ ####launchd2cmd with multiple directories:
22
+
23
+ $ launchd2cmd /System/Library/LaunchAgents /System/Library/LaunchDaemons
24
+ ...
25
+ # /System/Library/LaunchDaemons/tftp.plist
26
+ /usr/libexec/tftpd -i /private/tftpboot
27
+
28
+ ####launchd2cmd wildcards/globbing:
29
+
30
+ $ launchd2cmd /Library/Launch*/com.googlecode.munki.*
31
+ ...
32
+ # /Library/LaunchDaemons/com.googlecode.munki.managedsoftwareupdate-manualcheck.plist
33
+ /usr/local/munki/supervisor --timeout 43200 -- /usr/local/munki/managedsoftwareupdate --manualcheck
34
+
35
+ ####cmd2launchd creating a launchd plist skeleton:
36
+
37
+ $ cmd2launchd /usr/local/bin/daemond -d --mode foreground
38
+ <?xml version='1.0' encoding='UTF-8'?>
39
+ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
40
+ <plist version='1.0'>
41
+ <dict>
42
+ <key>ProgramArguments</key>
43
+ <array>
44
+ <string>/usr/local/bin/daemond</string>
45
+ <string>-d</string>
46
+ <string>--mode</string>
47
+ <string>foreground</string>
48
+ </array>
49
+ </dict>
50
+ </plist>
51
+
52
+ ## Usage from Ruby
53
+
54
+ ### Parsing a launchd plist
55
+
56
+ ```ruby
57
+ >> require 'launchd_tools/path'
58
+ => false
59
+ >> sshd_plist_path =
60
+ LaunchdTools::Path.new("/System/Library/LaunchDaemons/ssh.plist")
61
+ => #<LaunchdTools::Path:0x007f9a7b1a5f50
62
+ @path="/System/Library/LaunchDaemons/ssh.plist">
63
+ >> sshd_job = sshd_plist_path.parse
64
+ => #<LaunchdTools::LaunchdJob:0x007f9a7aa6cc70
65
+ @attributes={"EnvironmentVariables"=>[],
66
+ "ProgramArguments"=>["/usr/sbin/sshd", "-i"]}>
67
+ >> sshd_job.program_arguments
68
+ => ["/usr/sbin/sshd", "-i"]
69
+ ```
70
+
71
+ ### Creating a launchd plist
72
+
73
+ ```ruby
74
+ >> require 'launchd_tools/launchd_plist'
75
+ => true
76
+ >> plist = LaunchdTools::LaunchdPlist.new
77
+ => #<LaunchdTools::LaunchdPlist:0x007fde1c434c78 @doc=<UNDEFINED> ...
78
+ </>, @program_args_array_element=<array/>>
79
+ >> plist.add_program_args(["/usr/local/bin/daemon", "-f", "-o",
80
+ "/var/log/daemon.log"])
81
+ => ["/usr/local/bin/daemon", "-f", "-o", "/var/log/daemon.log"]
82
+ >> puts plist.to_s
83
+ <?xml version='1.0' encoding='UTF-8'?>
84
+ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN"
85
+ "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
86
+ <plist version='1.0'>
87
+ <dict>
88
+ <key>ProgramArguments</key>
89
+ <array>
90
+ <string>/usr/local/bin/daemon</string>
91
+ <string>-f</string>
92
+ <string>-o</string>
93
+ <string>/var/log/daemon.log</string>
94
+ </array>
95
+ </dict>
96
+ </plist>
97
+ ```
98
+
99
+
100
+ ## Contributing
101
+
102
+ 1. Fork it
103
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
104
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
105
+ 4. Push to the branch (`git push origin my-new-feature`)
106
+ 5. Create new Pull Request
107
+
108
+
data/Rakefile ADDED
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
data/TODO ADDED
@@ -0,0 +1,3 @@
1
+ Release package made with fpm
2
+ Output errors to stderr
3
+ Test for exit statuses
data/bin/cmd2launchd ADDED
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'launchd_tools/cmd2launchd_cli'
4
+
5
+ LaunchdTools::Cmd2LaunchdCli.new(ARGV).run
data/bin/launchd2cmd ADDED
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'launchd_tools/launchd2cmd_cli'
4
+
5
+ LaunchdTools::Launchd2CmdCli.new(ARGV).run
@@ -0,0 +1,24 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'launchd_tools/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "launchd_tools"
8
+ spec.version = LaunchdTools::VERSION
9
+ spec.authors = ["Kyle Crawford"]
10
+ spec.email = ["kcrwfrd@gmail.com"]
11
+ spec.description = %q{Provides tools for converting from command line arguments to a formatted launchd plist and vice versa}
12
+ spec.summary = %q{launchd tools convert from command to launchd and launchd to command}
13
+ spec.homepage = ""
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files`.split($/)
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_development_dependency "bundler", "~> 1.3"
22
+ spec.add_development_dependency "rspec"
23
+ spec.add_development_dependency "rake"
24
+ end
@@ -0,0 +1,38 @@
1
+ require 'launchd_tools/launchd_plist'
2
+
3
+ module LaunchdTools
4
+ class Cmd2LaunchdCli
5
+
6
+ attr_reader :args
7
+ def initialize(args)
8
+ if args.empty?
9
+ show_usage
10
+ exit 0
11
+ elsif args.length == 1
12
+ case args.first
13
+ when "--help"
14
+ show_usage
15
+ exit 0
16
+ when "-h"
17
+ show_usage
18
+ exit 0
19
+ when "--version"
20
+ puts LaunchdTools::VERSION
21
+ exit 0
22
+ end
23
+ end
24
+ @args = args
25
+ end
26
+
27
+ def show_usage
28
+ puts "Usage: #{$0} command [arg1] [arg2]"
29
+ end
30
+
31
+ def run
32
+ plist = LaunchdPlist.new
33
+ plist.add_program_args(args)
34
+ puts plist.to_s
35
+ end
36
+ end
37
+ end
38
+
@@ -0,0 +1,31 @@
1
+ require 'rexml/document'
2
+ include REXML
3
+ module LaunchdTools
4
+ class EnvironmentParser
5
+ attr_reader :xml_doc, :element
6
+ def initialize(xml_doc)
7
+ @xml_doc = xml_doc
8
+ end
9
+
10
+ def element
11
+ @element ||= REXML::XPath.first(xml_doc, "plist/dict/key[text()='EnvironmentVariables']/following-sibling::dict")
12
+ end
13
+
14
+ def extract_env
15
+ env = {}
16
+ REXML::XPath.match(element, 'key').each do |environment_key|
17
+ env[environment_key.text] = environment_key.next_sibling.next_sibling.text
18
+ end
19
+ env
20
+ end
21
+
22
+ def parse
23
+ if element
24
+ extract_env
25
+ else
26
+ {}
27
+ end
28
+ end
29
+
30
+ end
31
+ end
@@ -0,0 +1,17 @@
1
+ module LaunchdTools
2
+ class EnvironmentVariables
3
+ attr_reader :variables
4
+ def initialize(variables = {})
5
+ @variables = variables
6
+ end
7
+
8
+ def to_a
9
+ env_items = []
10
+ variables.each do |key,value|
11
+ env_items << "#{key}=#{value}"
12
+ end
13
+ env_items
14
+ end
15
+ end
16
+ end
17
+
@@ -0,0 +1,82 @@
1
+ require 'launchd_tools/path'
2
+ require 'optparse'
3
+ module LaunchdTools
4
+ class Launchd2CmdCli
5
+ attr_reader :paths, :args
6
+ def initialize(args)
7
+ showing_help = false
8
+ opt_parser = OptionParser.new do |opt|
9
+ opt.banner = "Usage: #{$0} path/to/launchd.plist"
10
+ opt.separator ""
11
+ opt.separator "Options"
12
+ opt.on("--help", "-h", "Displays this help message") do
13
+ showing_help = true
14
+ end
15
+ opt.on("--version", "outputs version information for this tool") do
16
+ puts LaunchdTools::VERSION
17
+ end
18
+ opt.separator ""
19
+ end
20
+
21
+ opt_parser.parse!(args)
22
+
23
+ if args.empty? || showing_help
24
+ puts opt_parser
25
+ else
26
+ @args = args
27
+ @paths = extract_paths(args)
28
+ end
29
+ end
30
+
31
+ def extract_paths(path_args)
32
+ path_args = path_args.map do |path_arg|
33
+ if File.directory?(path_arg)
34
+ path_arg += "/" if path_arg[-1] != "/"
35
+ path_arg += "*"
36
+ end
37
+ path_arg
38
+ end
39
+ Dir.glob(path_args)
40
+ end
41
+
42
+ def run
43
+ errors = 0
44
+ if paths
45
+ if paths.length > 0
46
+ errors = process_each_path
47
+ else
48
+ args.each {|arg| puts "No launchd job found at '#{arg}'" }
49
+ exit 1
50
+ end
51
+ end
52
+ exit 2 if errors > 0
53
+ end
54
+
55
+ def process_path(path_string)
56
+ error_count = 0
57
+ begin
58
+ path = Path.new(path_string).validate
59
+ puts "# #{path.expanded}"
60
+ puts path.parse.to_s
61
+ rescue LaunchdTools::Path::UnparsablePlist
62
+ puts "Error: unable to parse launchd job\n"
63
+ error_count = 1
64
+ rescue LaunchdTools::Path::PermissionsError
65
+ require 'etc'
66
+ username = Etc.getpwuid(Process.euid).name
67
+ puts "Error: user #{username} does not have access to read launchd job\n"
68
+ error_count = 1
69
+ end
70
+ return error_count
71
+ end
72
+
73
+ def process_each_path
74
+ error_count = 0
75
+ puts
76
+ paths.each do |path_string|
77
+ error_count += process_path(path_string)
78
+ end
79
+ return error_count
80
+ end
81
+ end
82
+ end
@@ -0,0 +1,45 @@
1
+ module LaunchdTools
2
+ class LaunchdJob
3
+ attr_reader :attributes
4
+ def initialize(attributes = {})
5
+ @attributes = attributes
6
+ end
7
+
8
+ def environment_variables
9
+ attributes.fetch('EnvironmentVariables', {})
10
+ end
11
+
12
+ def program_arguments
13
+ attributes.fetch('ProgramArguments', [])
14
+ end
15
+
16
+ def to_s
17
+ escaped_args = program_arguments.map {|arg| shellescape(arg) }
18
+ puts (environment_variables + escaped_args).join(" ")
19
+ end
20
+
21
+ # from ruby's shellescape
22
+ # included here for ruby 1.8 compatibility
23
+ def shellescape(str)
24
+ str = str.to_s
25
+
26
+ # An empty argument will be skipped, so return empty quotes.
27
+ return "''" if str.empty?
28
+
29
+ str = str.dup
30
+
31
+ # Treat multibyte characters as is. It is caller's responsibility
32
+ # to encode the string in the right encoding for the shell
33
+ # environment.
34
+ str.gsub!(/([^A-Za-z0-9_\-.,:\/@\n])/, "\\\\\\1")
35
+
36
+ # A LF cannot be escaped with a backslash because a backslash + LF
37
+ # combo is regarded as line continuation and simply ignored.
38
+ str.gsub!(/\n/, "'\n'")
39
+
40
+ return str
41
+ end
42
+
43
+ end
44
+ end
45
+
@@ -0,0 +1,44 @@
1
+ require 'rexml/document'
2
+ include REXML
3
+ module LaunchdTools
4
+ class LaunchdPlist
5
+ attr_reader :doc, :program_args_array_element
6
+
7
+ def initialize
8
+ base = %q[<?xml version='1.0' encoding='UTF-8'?>
9
+ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">]
10
+ @doc = Document.new(base)
11
+ plist_element = Element.new('plist')
12
+ doc.add_element(plist_element, {"version" => "1.0"})
13
+ base_dictionary = Element.new('dict')
14
+ plist_element.add_element(base_dictionary)
15
+ program_args_key_element = Element.new('key')
16
+ program_args_key_element.text = 'ProgramArguments'
17
+ base_dictionary.add_element(program_args_key_element)
18
+ @program_args_array_element = Element.new "array"
19
+ base_dictionary.add_element(program_args_array_element)
20
+ end
21
+
22
+
23
+ def add_program_arg(arg)
24
+ string_element = Element.new "string"
25
+ string_element.text = arg
26
+ program_args_array_element << string_element
27
+ end
28
+
29
+ def add_program_args(args)
30
+ args.each do |arg|
31
+ add_program_arg(arg)
32
+ end
33
+ end
34
+
35
+ def to_s
36
+ formatter = REXML::Formatters::Pretty.new # (2)
37
+ formatter.compact = true
38
+ xml_string = String.new
39
+ formatter.write(doc, xml_string)
40
+ xml_string
41
+ end
42
+ end
43
+ end
44
+
@@ -0,0 +1,47 @@
1
+ require 'launchd_tools/path_parser'
2
+ require 'launchd_tools/path_content'
3
+
4
+ module LaunchdTools
5
+ class Path
6
+
7
+ class UnparsablePlist < Exception
8
+ end
9
+
10
+ class PermissionsError < Exception
11
+ end
12
+
13
+ attr_reader :path
14
+
15
+ def initialize(path)
16
+ @path = path
17
+ end
18
+
19
+ def validate
20
+ raise PathMissingError unless File.exist?(path)
21
+ self
22
+ end
23
+
24
+ def content
25
+ PathContent.new(path).to_s
26
+ end
27
+
28
+ def parse
29
+ begin
30
+ path_parser.parse
31
+ rescue Errno::EACCES
32
+ raise PermissionsError.new
33
+ rescue
34
+ raise UnparsablePlist.new
35
+ end
36
+ end
37
+
38
+ def path_parser
39
+ PathParser.new(self)
40
+ end
41
+
42
+ def expanded
43
+ File.expand_path(path)
44
+ end
45
+
46
+ end
47
+ end
@@ -0,0 +1,24 @@
1
+ module LaunchdTools
2
+ class PathContent
3
+ attr_reader :path
4
+ def initialize(path)
5
+ @path = path
6
+ end
7
+
8
+ def to_s
9
+ if binary?
10
+ `plutil -convert xml1 -o /dev/stdout '#{path}'`
11
+ else
12
+ File.read(path)
13
+ end
14
+ end
15
+
16
+ def binary?
17
+ f = File.open(path, "r")
18
+ f.read(6) == "bplist"
19
+ ensure
20
+ f.close if f
21
+ end
22
+ end
23
+
24
+ end
@@ -0,0 +1,34 @@
1
+ require 'rexml/document'
2
+ require 'launchd_tools/launchd_job'
3
+ require 'launchd_tools/environment_parser'
4
+ require 'launchd_tools/environment_variables'
5
+ require 'launchd_tools/program_args_parser'
6
+
7
+ include REXML
8
+ module LaunchdTools
9
+ class PathParser
10
+ attr_reader :path, :xml_doc
11
+ def initialize(path)
12
+ @path = path
13
+ end
14
+
15
+ # returns a parsed launchd job
16
+ def parse
17
+ LaunchdJob.new({ 'EnvironmentVariables' => parse_env, 'ProgramArguments' => parse_program_args })
18
+ end
19
+
20
+ def parse_env
21
+ variables = EnvironmentParser.new(xml_doc).parse
22
+ EnvironmentVariables.new(variables).to_a
23
+ end
24
+
25
+ def parse_program_args
26
+ ProgramArgsParser.new(xml_doc).parse
27
+ end
28
+
29
+ def xml_doc
30
+ @xml_doc ||= Document.new(path.content)
31
+ end
32
+
33
+ end
34
+ end
@@ -0,0 +1,21 @@
1
+ require 'rexml/document'
2
+ include REXML
3
+ module LaunchdTools
4
+ class ProgramArgsParser
5
+ attr_reader :xml_doc, :element
6
+ def initialize(xml_doc)
7
+ @xml_doc = xml_doc
8
+ end
9
+
10
+ def parse
11
+ element = REXML::XPath.first(xml_doc, "plist/dict/key[text()='ProgramArguments']/following-sibling::array")
12
+ if element
13
+ args_strings = XPath.match(element, 'string')
14
+ args_strings.map {|e| e.text }
15
+ else
16
+ program_string_element = REXML::XPath.first(xml_doc, "plist/dict/key[text()='Program']/following-sibling::string")
17
+ [program_string_element.text]
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,3 @@
1
+ module LaunchdTools
2
+ VERSION = "0.5.0"
3
+ end
@@ -0,0 +1,5 @@
1
+ require "launchd_tools/version"
2
+
3
+ module LaunchdTools
4
+ # Your code goes here...
5
+ end
@@ -0,0 +1,21 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
3
+ <plist version="1.0">
4
+ <dict>
5
+ <key>Label</key>
6
+ <string>com.apple.dt.CommandLineTools.installondemand</string>
7
+ <key>MachServices</key>
8
+ <dict>
9
+ <key>com.apple.dt.CommandLineTools.installondemand</key>
10
+ <true/>
11
+ </dict>
12
+ <key>OnDemand</key>
13
+ <true/>
14
+ <key>EnableTransactions</key>
15
+ <true/>
16
+ <key>Program</key>
17
+ <string>/System/Library/CoreServices/Install Command Line Developer Tools.app/Contents/MacOS/Install Command Line Developer Tools</string>
18
+ <key>LimitLoadToSessionType</key>
19
+ <string>Aqua</string>
20
+ </dict>
21
+ </plist>
@@ -0,0 +1,50 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
3
+ <plist version="1.0">
4
+ <dict>
5
+ <key>Label</key>
6
+ <string>com.apple.storeagent</string>
7
+ <key>MachServices</key>
8
+ <dict>
9
+ <key>com.apple.storeagent</key>
10
+ <true/>
11
+ <key>com.apple.storeagent-xpc</key>
12
+ <true/>
13
+ <key>com.apple.storeagent.storekit</key>
14
+ <string></string>
15
+ </dict>
16
+ <key>OnDemand</key>
17
+ <true/>
18
+ <key>Program</key>
19
+ <string>/System/Library/PrivateFrameworks/CommerceKit.framework/Versions/A/Resources/storeagent</string>
20
+ <key>EnableTransactions</key>
21
+ <true/>
22
+ <key>LimitLoadToSessionType</key>
23
+ <array>
24
+ <string>LoginWindow</string>
25
+ <string>Aqua</string>
26
+ </array>
27
+ <key>LaunchEvents</key>
28
+ <dict>
29
+ <key>com.apple.usernotificationcenter.matching</key>
30
+ <dict>
31
+ <key>store</key>
32
+ <dict>
33
+ <key>bundleid</key>
34
+ <string>com.apple.appstore</string>
35
+ <key>delay registration</key>
36
+ <true/>
37
+ <key>events</key>
38
+ <array>
39
+ <string>didDismissAlert</string>
40
+ <string>didActivateNotification</string>
41
+ <string>didDeliverNotification</string>
42
+ <string>didSnoozeAlert</string>
43
+ <string>didRemoveDeliveredNotifications</string>
44
+ <string>didExpireNotifications</string>
45
+ </array>
46
+ </dict>
47
+ </dict>
48
+ </dict>
49
+ </dict>
50
+ </plist>
@@ -0,0 +1,41 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
3
+ <plist version="1.0">
4
+ <dict>
5
+ <key>Disabled</key>
6
+ <true/>
7
+ <key>Label</key>
8
+ <string>com.openssh.sshd</string>
9
+ <key>Program</key>
10
+ <string>/usr/libexec/sshd-keygen-wrapper</string>
11
+ <key>ProgramArguments</key>
12
+ <array>
13
+ <string>/usr/sbin/sshd</string>
14
+ <string>-i</string>
15
+ </array>
16
+ <key>Sockets</key>
17
+ <dict>
18
+ <key>Listeners</key>
19
+ <dict>
20
+ <key>SockServiceName</key>
21
+ <string>ssh</string>
22
+ <key>Bonjour</key>
23
+ <array>
24
+ <string>ssh</string>
25
+ <string>sftp-ssh</string>
26
+ </array>
27
+ </dict>
28
+ </dict>
29
+ <key>inetdCompatibility</key>
30
+ <dict>
31
+ <key>Wait</key>
32
+ <false/>
33
+ </dict>
34
+ <key>StandardErrorPath</key>
35
+ <string>/dev/null</string>
36
+ <key>SHAuthorizationRight</key>
37
+ <string>system.preferences</string>
38
+ <key>POSIXSpawnType</key>
39
+ <string>Interactive</string>
40
+ </dict>
41
+ </plist>
@@ -0,0 +1,25 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
3
+ <plist version="1.0">
4
+ <dict>
5
+ <key>Disabled</key>
6
+ <true/>
7
+ <key>EnvironmentVariables</key>
8
+ <dict>
9
+ <key>XPC_SERVICES_UNAVAILABLE</key>
10
+ <string>1</string>
11
+ </dict>
12
+ <key>Label</key>
13
+ <string>org.apache.httpd</string>
14
+ <key>OnDemand</key>
15
+ <false/>
16
+ <key>ProgramArguments</key>
17
+ <array>
18
+ <string>/usr/sbin/httpd</string>
19
+ <string>-D</string>
20
+ <string>FOREGROUND</string>
21
+ </array>
22
+ <key>SHAuthorizationRight</key>
23
+ <string>system.preferences</string>
24
+ </dict>
25
+ </plist>
@@ -0,0 +1,25 @@
1
+ require 'tempfile'
2
+ describe 'running cmd2launchd' do
3
+ it "converts right back to original commmand using launchd2cmd" do
4
+ t = Tempfile.new(File.basename(__FILE__))
5
+ temp_file_path = t.path
6
+ t.close
7
+ output = `bundle exec bin/cmd2launchd ls -la /tmp > #{temp_file_path} && bundle exec bin/launchd2cmd #{temp_file_path}`
8
+ expect(output.chomp).to include("ls -la /tmp")
9
+ File.unlink(temp_file_path)
10
+ end
11
+
12
+ context "no arguments" do
13
+ it "outputs usage info" do
14
+ output = `bundle exec bin/cmd2launchd`
15
+ expect(output.chomp).to include("Usage: ")
16
+ end
17
+ end
18
+
19
+ context "help option" do
20
+ it "outputs usage info" do
21
+ output = `bundle exec bin/cmd2launchd -h`
22
+ expect(output.chomp).to include("Usage: ")
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,82 @@
1
+ describe 'running launchd2cmd' do
2
+
3
+ context 'launch agent' do
4
+ it 'outputs the command' do
5
+ output = `bundle exec bin/launchd2cmd spec/fixtures/LaunchAgents/com.apple.storeagent.plist`
6
+ expect(output.chomp).to include("/System/Library/PrivateFrameworks/CommerceKit.framework/Versions/A/Resources/storeagent")
7
+ end
8
+ it "outputs a comment line with path to the plist" do
9
+ output = `bundle exec bin/launchd2cmd spec/fixtures/LaunchAgents/com.apple.storeagent.plist`
10
+ expect(output.chomp).to include("# #{File.expand_path("spec/fixtures/LaunchAgents/com.apple.storeagent.plist")}")
11
+ end
12
+ end
13
+
14
+ context "no arguments" do
15
+ it "outputs usage info" do
16
+ output = `bundle exec bin/launchd2cmd`
17
+ expect(output.chomp).to include("Usage: ")
18
+ end
19
+ end
20
+
21
+ context "help option" do
22
+ it "outputs usage info" do
23
+ output = `bundle exec bin/launchd2cmd -h`
24
+ expect(output.chomp).to include("Usage: ")
25
+ end
26
+ end
27
+
28
+ context "inaccessible file" do
29
+ it "outputs relevant error message" do
30
+ inaccessible_path = 'spec/fixtures/LaunchDaemons/inaccessible.plist'
31
+ File.chmod(0000, inaccessible_path)
32
+ output = `bundle exec bin/launchd2cmd #{inaccessible_path}`
33
+ File.chmod(0644, inaccessible_path)
34
+ require 'etc'
35
+ username = Etc.getpwuid(Process.euid).name
36
+ expect(output).to include("Error: user #{username} does not have access to read launchd job")
37
+ end
38
+ end
39
+
40
+ context "unparsable file" do
41
+ it "outputs relevant error message" do
42
+ output = `bundle exec bin/launchd2cmd #{__FILE__}`
43
+ expect(output).to include("Error: unable to parse launchd job")
44
+ end
45
+ end
46
+
47
+ context 'bad file paths' do
48
+ it "outputs relevant error message" do
49
+ output = `bundle exec bin/launchd2cmd not/a/real/path and/another/bad/path`
50
+ expect(output).to include("No launchd job found at 'not/a/real/path'")
51
+ expect(output).to include("No launchd job found at 'and/another/bad/path'")
52
+ end
53
+ end
54
+ context 'launch daemon' do
55
+ context 'with environment variables' do
56
+ it 'includes argument and environment variables in output' do
57
+ output = `bundle exec bin/launchd2cmd spec/fixtures/LaunchDaemons/org.apache.httpd.plist`
58
+ expect(output.chomp).to include("XPC_SERVICES_UNAVAILABLE=1 /usr/sbin/httpd -D FOREGROUND")
59
+ end
60
+ end
61
+ end
62
+ context "binary plist" do
63
+ it "handles it without error" do
64
+ output = `bundle exec bin/launchd2cmd spec/fixtures/LaunchDaemons/com.apple.opendirectoryd.plist`
65
+ expect(output.chomp).to include("__CFPREFERENCES_AVOID_DAEMON=1 __CF_USER_TEXT_ENCODING=0x0:0:0 /usr/libexec/opendirectoryd")
66
+ end
67
+ end
68
+
69
+ context "args are directories" do
70
+ it "lists contents of directory" do
71
+ command = "bundle exec bin/launchd2cmd spec/fixtures/LaunchAgents > /dev/null"
72
+ expect(system(command)).to be_true
73
+ end
74
+ end
75
+
76
+ context "program arguments contain spaces" do
77
+ it "escapes them for use in the shell" do
78
+ output = `bundle exec bin/launchd2cmd spec/fixtures/LaunchAgents/com.apple.dt.CommandLineTools.installondemand.plist`
79
+ expect(output.chomp).to include("/System/Library/CoreServices/Install\\ Command\\ Line\\ Developer\\ Tools.app/Contents/MacOS/Install\\ Command\\ Line\\ Developer\\ Tools")
80
+ end
81
+ end
82
+ end
@@ -0,0 +1,17 @@
1
+ # This file was generated by the `rspec --init` command. Conventionally, all
2
+ # specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`.
3
+ # Require this file using `require "spec_helper"` to ensure that it is only
4
+ # loaded once.
5
+ #
6
+ # See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
7
+ RSpec.configure do |config|
8
+ config.treat_symbols_as_metadata_keys_with_true_values = true
9
+ config.run_all_when_everything_filtered = true
10
+ config.filter_run :focus
11
+
12
+ # Run specs in random order to surface order dependencies. If you find an
13
+ # order dependency and want to debug it, you can fix the order by providing
14
+ # the seed, which is printed after each run.
15
+ # --seed 1234
16
+ config.order = 'random'
17
+ end
metadata ADDED
@@ -0,0 +1,127 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: launchd_tools
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.5.0
5
+ platform: ruby
6
+ authors:
7
+ - Kyle Crawford
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-02-23 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.3'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.3'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rspec
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rake
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ description: Provides tools for converting from command line arguments to a formatted
56
+ launchd plist and vice versa
57
+ email:
58
+ - kcrwfrd@gmail.com
59
+ executables:
60
+ - cmd2launchd
61
+ - launchd2cmd
62
+ extensions: []
63
+ extra_rdoc_files: []
64
+ files:
65
+ - ".gitignore"
66
+ - ".rspec"
67
+ - Gemfile
68
+ - LICENSE.txt
69
+ - README.md
70
+ - Rakefile
71
+ - TODO
72
+ - bin/cmd2launchd
73
+ - bin/launchd2cmd
74
+ - launchd_tools.gemspec
75
+ - lib/launchd_tools.rb
76
+ - lib/launchd_tools/cmd2launchd_cli.rb
77
+ - lib/launchd_tools/environment_parser.rb
78
+ - lib/launchd_tools/environment_variables.rb
79
+ - lib/launchd_tools/launchd2cmd_cli.rb
80
+ - lib/launchd_tools/launchd_job.rb
81
+ - lib/launchd_tools/launchd_plist.rb
82
+ - lib/launchd_tools/path.rb
83
+ - lib/launchd_tools/path_content.rb
84
+ - lib/launchd_tools/path_parser.rb
85
+ - lib/launchd_tools/program_args_parser.rb
86
+ - lib/launchd_tools/version.rb
87
+ - spec/fixtures/LaunchAgents/com.apple.dt.CommandLineTools.installondemand.plist
88
+ - spec/fixtures/LaunchAgents/com.apple.storeagent.plist
89
+ - spec/fixtures/LaunchDaemons/com.apple.opendirectoryd.plist
90
+ - spec/fixtures/LaunchDaemons/inaccessible.plist
91
+ - spec/fixtures/LaunchDaemons/org.apache.httpd.plist
92
+ - spec/integration/cmd2launchd_spec.rb
93
+ - spec/integration/launchd2cmd_spec.rb
94
+ - spec/spec_helper.rb
95
+ homepage: ''
96
+ licenses:
97
+ - MIT
98
+ metadata: {}
99
+ post_install_message:
100
+ rdoc_options: []
101
+ require_paths:
102
+ - lib
103
+ required_ruby_version: !ruby/object:Gem::Requirement
104
+ requirements:
105
+ - - ">="
106
+ - !ruby/object:Gem::Version
107
+ version: '0'
108
+ required_rubygems_version: !ruby/object:Gem::Requirement
109
+ requirements:
110
+ - - ">="
111
+ - !ruby/object:Gem::Version
112
+ version: '0'
113
+ requirements: []
114
+ rubyforge_project:
115
+ rubygems_version: 2.2.1
116
+ signing_key:
117
+ specification_version: 4
118
+ summary: launchd tools convert from command to launchd and launchd to command
119
+ test_files:
120
+ - spec/fixtures/LaunchAgents/com.apple.dt.CommandLineTools.installondemand.plist
121
+ - spec/fixtures/LaunchAgents/com.apple.storeagent.plist
122
+ - spec/fixtures/LaunchDaemons/com.apple.opendirectoryd.plist
123
+ - spec/fixtures/LaunchDaemons/inaccessible.plist
124
+ - spec/fixtures/LaunchDaemons/org.apache.httpd.plist
125
+ - spec/integration/cmd2launchd_spec.rb
126
+ - spec/integration/launchd2cmd_spec.rb
127
+ - spec/spec_helper.rb