osxsub 0.1.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/.gitignore +18 -0
- data/Gemfile +5 -0
- data/Gemfile.lock +16 -0
- data/LICENSE +20 -0
- data/README.md +59 -0
- data/Rakefile +2 -0
- data/bin/osxsub +94 -0
- data/lib/osxsub/ns_user_replacement_items.rb +107 -0
- data/lib/osxsub/plist_buddy.rb +50 -0
- data/lib/osxsub/substitution.rb +20 -0
- data/lib/osxsub/version.rb +3 -0
- data/lib/osxsub.rb +6 -0
- data/osxsub.gemspec +17 -0
- metadata +80 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright © 2013 Mark Wunsch
|
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
|
9
|
+
to 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 NONINFRINGEMENT.
|
17
|
+
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR
|
18
|
+
ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
|
19
|
+
CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,59 @@
|
|
1
|
+
`osxsub`
|
2
|
+
|
3
|
+
A command line utility for using the OS X Text Substitution System Preferences in a unix pipeline. Like `sed` but for Mac users who are taking advantage of a mostly hidden system preference.
|
4
|
+
|
5
|
+
Basic usage is like this:
|
6
|
+
|
7
|
+
$ echo "Copyright (c) 2013 Mark Wunsch" | osxsub
|
8
|
+
Copyright © 2013 Mark Wunsch
|
9
|
+
|
10
|
+
Text Substitutions are available for a handful of Mac applications, and can be set up by going to `System Preferences -> Language & Text` and selecting the `Text` tab.
|
11
|
+
|
12
|
+
Read more about OS X Text Substitution:
|
13
|
+
|
14
|
+
+ [_How to use text substitution in Snow Leopard_ -- Macworld](http://www.macworld.com/article/1142708/slsubstitutions.html)
|
15
|
+
+ [_Mac 101: Making Text Replacement Work_ -- TUAW](http://www.tuaw.com/2009/12/31/mac-101-making-text-replacement-work/)
|
16
|
+
+ [_Do Yourself a Favor: Set Up Mountain Lion’s Built-in Text Expansion with These Shortcuts_ -- Lifehacker](http://lifehacker.com/5931337/do-yourself-a-favor-set-up-mountain-lions-built+in-text-expansion-with-these-shortcuts)
|
17
|
+
|
18
|
+
The goals for this program are:
|
19
|
+
|
20
|
+
+ Backup your text substitution preferences, along with the ability to
|
21
|
+
+ Load new preferences, so that you can
|
22
|
+
+ Share substitution preferences across machines, and
|
23
|
+
+ Have access to them in a greater set of applications, by
|
24
|
+
+ Allowing Text Substitutions to be a part of your command line toolkit.
|
25
|
+
|
26
|
+
Some more options can be given to the tool to make managing your Preferences easy:
|
27
|
+
|
28
|
+
$ osxsub --print
|
29
|
+
## Print a plist of your substition preferences
|
30
|
+
|
31
|
+
$ osxsub --merge PATH_TO_PLIST
|
32
|
+
## Merge another plist into your preferences.
|
33
|
+
|
34
|
+
$ osxsub --add REPLACE,WITH
|
35
|
+
## Add a new pair of substitutions (they're turned on by default).
|
36
|
+
|
37
|
+
$ osxsub --clear
|
38
|
+
## Clear all your preferences.
|
39
|
+
|
40
|
+
$ osxsub --repl
|
41
|
+
## Start an interactive session to test out substitutions.
|
42
|
+
|
43
|
+
## Installation
|
44
|
+
|
45
|
+
A Homebrew Formula is available, but has yet to be moved to the master Homebrew repo: https://gist.github.com/4657560
|
46
|
+
|
47
|
+
Alternatively, `osxsub` is available as a Rubygem.
|
48
|
+
|
49
|
+
$ gem install osxsub
|
50
|
+
|
51
|
+
Or clone the repository and put the `bin` directory somewhere on your `$PATH`.
|
52
|
+
|
53
|
+
## TODO
|
54
|
+
|
55
|
+
+ Manpages
|
56
|
+
+ Documentation
|
57
|
+
+ Tests
|
58
|
+
|
59
|
+
Copyright © 2013 Mark Wunsch.
|
data/Rakefile
ADDED
data/bin/osxsub
ADDED
@@ -0,0 +1,94 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
require 'optparse'
|
3
|
+
|
4
|
+
libdir = File.expand_path File.join(File.dirname(__FILE__), "/../lib")
|
5
|
+
$:.unshift libdir unless $:.include? libdir
|
6
|
+
require 'osxsub'
|
7
|
+
|
8
|
+
options = ARGV.options do |opts|
|
9
|
+
opts.banner = <<-BANNER
|
10
|
+
osxsub: OS X Text Substitution Preferences on the command line
|
11
|
+
|
12
|
+
Usage: Run STDIN through Text Substitions set in System Preferences -> Language & Text
|
13
|
+
|
14
|
+
echo "Copyright (c) 2013 Mark Wunsch" | osxsub
|
15
|
+
#=> Copyright © 2013 Mark Wunsch
|
16
|
+
|
17
|
+
BANNER
|
18
|
+
opts.version = OsxSub::VERSION
|
19
|
+
|
20
|
+
opts.on('-p', '--print', "Print an XML Plist of the Text Substitution Preferences") do
|
21
|
+
puts OsxSub::NSUserReplacementItems.from_plist_buddy.to_plist
|
22
|
+
exit
|
23
|
+
end
|
24
|
+
|
25
|
+
opts.on('-m', '--merge [PATH]', "Merge an XML Plist (from either PATH or STDIN) into the Preferences") do |path|
|
26
|
+
xml = if path && !path.empty?
|
27
|
+
full_path = File.expand_path(path)
|
28
|
+
opts.abort("Plist file does not exist!") unless File.exists? full_path
|
29
|
+
IO.read(full_path)
|
30
|
+
elsif !STDIN.tty?
|
31
|
+
STDIN.read
|
32
|
+
else
|
33
|
+
opts.abort("Must give either a PATH or a Plist in STDIN")
|
34
|
+
end
|
35
|
+
subs = OsxSub::NSUserReplacementItems.from_xml(xml)
|
36
|
+
subs.merge!
|
37
|
+
puts subs.to_plist
|
38
|
+
exit
|
39
|
+
end
|
40
|
+
|
41
|
+
opts.on('-a', '--add REPLACE,WITH', Array, "Add a new pair of substitions to the list") do |list|
|
42
|
+
opts.abort "REPLACE,WITH must have a pair and they can't be the same" if list.first == list.last
|
43
|
+
sub = OsxSub::Substitution.new(list.first, list.last)
|
44
|
+
replacement_item = OsxSub::NSUserReplacementItems.new([sub])
|
45
|
+
replacement_item.merge!
|
46
|
+
puts replacement_item.to_plist
|
47
|
+
exit
|
48
|
+
end
|
49
|
+
|
50
|
+
opts.on('-r', '--repl', "Start a REPL (read-eval-print loop)") do
|
51
|
+
opts.abort("Can't start REPL unless STDIN is a tty") unless STDIN.tty?
|
52
|
+
opts.warn("Send an EOT signal (Ctrl-D) to end")
|
53
|
+
print ">> "
|
54
|
+
while STDIN.gets
|
55
|
+
puts " => " + OsxSub::NSUserReplacementItems.from_plist_buddy.sub($_)
|
56
|
+
print ">> "
|
57
|
+
end
|
58
|
+
puts ""
|
59
|
+
exit
|
60
|
+
end
|
61
|
+
|
62
|
+
opts.on('-c', '--clear', "Clear out the set of substitution preferences") do
|
63
|
+
plist_buddy = OsxSub::PlistBuddy.instance
|
64
|
+
begin
|
65
|
+
plist_buddy.delete
|
66
|
+
plist_buddy.add
|
67
|
+
rescue OsxSub::PlistBuddyError => e
|
68
|
+
plist_buddy.add unless e.exists?
|
69
|
+
end
|
70
|
+
puts plist_buddy.print
|
71
|
+
exit
|
72
|
+
end
|
73
|
+
|
74
|
+
opts.separator ""
|
75
|
+
|
76
|
+
opts.on_tail('-h', '--help') { puts opts ; exit }
|
77
|
+
opts.on_tail('--version', "Print version information and exit") { puts opts.version ; exit }
|
78
|
+
begin
|
79
|
+
opts.parse!
|
80
|
+
rescue OptionParser::ParseError
|
81
|
+
opts.warn $!
|
82
|
+
end
|
83
|
+
opts
|
84
|
+
end
|
85
|
+
|
86
|
+
if !STDIN.tty?
|
87
|
+
STDIN.each_line do |line|
|
88
|
+
STDOUT.write OsxSub::NSUserReplacementItems.from_plist_buddy.sub(line)
|
89
|
+
end
|
90
|
+
exit
|
91
|
+
end
|
92
|
+
|
93
|
+
abort options.help
|
94
|
+
|
@@ -0,0 +1,107 @@
|
|
1
|
+
require 'rexml/document'
|
2
|
+
|
3
|
+
module OsxSub
|
4
|
+
class NSUserReplacementItems
|
5
|
+
PLIST_TEMPLATE = <<-PLIST
|
6
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
7
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
8
|
+
<plist version="1.0">
|
9
|
+
<array>
|
10
|
+
<% substitutions.each do |substitution| %>
|
11
|
+
<dict>
|
12
|
+
<% if substitution.on? %>
|
13
|
+
<key>on</key>
|
14
|
+
<integer>1</integer>
|
15
|
+
<% end %>
|
16
|
+
<key>replace</key>
|
17
|
+
<string><%= substitution.replace %></string>
|
18
|
+
<key>with</key>
|
19
|
+
<string><%= substitution.with %></string>
|
20
|
+
</dict>
|
21
|
+
<% end %>
|
22
|
+
</array>
|
23
|
+
</plist>
|
24
|
+
PLIST
|
25
|
+
|
26
|
+
def self.from_plist_buddy
|
27
|
+
plist_buddy = PlistBuddy.instance
|
28
|
+
self.from_xml plist_buddy.print
|
29
|
+
rescue PlistBuddyError => e
|
30
|
+
unless e.exists?
|
31
|
+
warn("#{e}Adding and Merging default preferences")
|
32
|
+
plist_buddy.add
|
33
|
+
self.defaults.merge!
|
34
|
+
retry
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def self.from_xml(xml)
|
39
|
+
doc = REXML::Document.new(xml, :ignore_whitespace_nodes => :all)
|
40
|
+
plist = doc.elements['plist/array'].map do |dict|
|
41
|
+
tuple = dict.each.select {|node| node.has_name?("key") }.map do |key|
|
42
|
+
[key.text, key.next_element.text]
|
43
|
+
end
|
44
|
+
Hash[tuple]
|
45
|
+
end
|
46
|
+
self.new plist.map {|dict| Substitution.new(dict['replace'], dict['with'], dict['on']) }
|
47
|
+
end
|
48
|
+
|
49
|
+
def self.defaults
|
50
|
+
default_substitutions = [
|
51
|
+
Substitution.new("(c)", "\xC2\xA9"),
|
52
|
+
Substitution.new("(r)", "\xC2\xAE"),
|
53
|
+
Substitution.new("(p)", "\xE2\x84\x97"),
|
54
|
+
Substitution.new("TM", "\xE2\x84\xA2"),
|
55
|
+
Substitution.new("c/o", "\xE2\x84\x85"),
|
56
|
+
Substitution.new("...", "\xE2\x80\xA6"),
|
57
|
+
Substitution.new("1/2", "\xC2\xBD", false),
|
58
|
+
Substitution.new("1/3", "\xE2\x85\x93", false),
|
59
|
+
Substitution.new("2/3", "\xE2\x85\x94", false),
|
60
|
+
Substitution.new("1/4", "\xC2\xBC", false),
|
61
|
+
Substitution.new("3/4", "\xC2\xBE", false),
|
62
|
+
Substitution.new("1/8", "\xE2\x85\x9B", false),
|
63
|
+
Substitution.new("3/8", "\xE2\x85\x9C", false),
|
64
|
+
Substitution.new("5/8", "\xE2\x85\x9D", false),
|
65
|
+
Substitution.new("7/8", "\xE2\x85\x9E", false)
|
66
|
+
]
|
67
|
+
self.new(default_substitutions)
|
68
|
+
end
|
69
|
+
|
70
|
+
attr_reader :substitutions
|
71
|
+
|
72
|
+
def initialize(substitutions)
|
73
|
+
@substitutions = substitutions
|
74
|
+
end
|
75
|
+
|
76
|
+
def sub(text)
|
77
|
+
substitutions.select(&:on?).inject(text) {|memo, obj| obj.sub(memo) }
|
78
|
+
end
|
79
|
+
|
80
|
+
def to_plist
|
81
|
+
require 'erb'
|
82
|
+
template = ERB.new(PLIST_TEMPLATE)
|
83
|
+
template.result(binding)
|
84
|
+
end
|
85
|
+
|
86
|
+
def merge!
|
87
|
+
tmp_file = temp_plist_file
|
88
|
+
PlistBuddy.instance.merge tmp_file.path
|
89
|
+
ensure
|
90
|
+
if tmp_file
|
91
|
+
tmp_file.close
|
92
|
+
tmp_file.unlink
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
private
|
97
|
+
|
98
|
+
def temp_plist_file
|
99
|
+
require 'tempfile'
|
100
|
+
tmp_file = Tempfile.new("nsuser_replacement_items_plist")
|
101
|
+
tmp_file.write to_plist
|
102
|
+
tmp_file.rewind
|
103
|
+
tmp_file
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
@@ -0,0 +1,50 @@
|
|
1
|
+
module OsxSub
|
2
|
+
class PlistBuddyError < StandardError
|
3
|
+
def exists?
|
4
|
+
!message.match(/Does Not Exist$/)
|
5
|
+
end
|
6
|
+
end
|
7
|
+
|
8
|
+
class PlistBuddy
|
9
|
+
require 'singleton'
|
10
|
+
|
11
|
+
include Singleton
|
12
|
+
|
13
|
+
PLIST_BUDDY = '/usr/libexec/PlistBuddy'
|
14
|
+
GLOBAL_PREFERENCES_PLIST = File.expand_path('~/Library/Preferences/.GlobalPreferences.plist')
|
15
|
+
NSUSER_REPLACEMENT_ITEMS = 'NSUserReplacementItems'
|
16
|
+
|
17
|
+
def print
|
18
|
+
execute plist_buddy(:print)
|
19
|
+
end
|
20
|
+
|
21
|
+
def delete
|
22
|
+
execute plist_buddy(:delete)
|
23
|
+
end
|
24
|
+
|
25
|
+
def add
|
26
|
+
execute plist_buddy(:add, "array")
|
27
|
+
end
|
28
|
+
|
29
|
+
def merge(path)
|
30
|
+
execute plist_buddy("Merge #{path}")
|
31
|
+
end
|
32
|
+
|
33
|
+
def plist_buddy(command, *extras)
|
34
|
+
%Q[#{PLIST_BUDDY} -x -c "#{command.to_s.capitalize} #{NSUSER_REPLACEMENT_ITEMS} #{extras.join(' ')}" #{GLOBAL_PREFERENCES_PLIST}]
|
35
|
+
end
|
36
|
+
|
37
|
+
def execute(command)
|
38
|
+
require 'stringio'
|
39
|
+
require 'open3'
|
40
|
+
|
41
|
+
out, err = ::StringIO.new, ::StringIO.new
|
42
|
+
::Open3.popen3(command) do |stdin, stdout, stderr|
|
43
|
+
out.write stdout.read
|
44
|
+
err.write stderr.read
|
45
|
+
end
|
46
|
+
raise PlistBuddyError, err.string unless err.string.empty?
|
47
|
+
out.string
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module OsxSub
|
2
|
+
class Substitution
|
3
|
+
attr_reader :replace, :with
|
4
|
+
|
5
|
+
def initialize(replace, with, on = true)
|
6
|
+
@replace = replace
|
7
|
+
@with = with
|
8
|
+
@on = on
|
9
|
+
end
|
10
|
+
|
11
|
+
def on?
|
12
|
+
return false if @on.is_a? String and @on.to_i <= 0
|
13
|
+
true & @on
|
14
|
+
end
|
15
|
+
|
16
|
+
def sub(string)
|
17
|
+
string.gsub(replace, with)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
data/lib/osxsub.rb
ADDED
data/osxsub.gemspec
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
require File.expand_path('../lib/osxsub/version', __FILE__)
|
3
|
+
|
4
|
+
Gem::Specification.new do |gem|
|
5
|
+
gem.authors = ["Mark Wunsch"]
|
6
|
+
gem.email = ["mark@markwunsch.com"]
|
7
|
+
gem.description = %q{Command line utility for OS X Text Substition Preferences.}
|
8
|
+
gem.summary = %q{Command line utility for OS X Text Substition Preferences.}
|
9
|
+
gem.homepage = %q{http://github.com/mwunsch/osxsub}
|
10
|
+
|
11
|
+
gem.files = `git ls-files`.split($\)
|
12
|
+
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
13
|
+
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
14
|
+
gem.name = "osxsub"
|
15
|
+
gem.require_paths = ["lib"]
|
16
|
+
gem.version = OsxSub::VERSION
|
17
|
+
end
|
metadata
ADDED
@@ -0,0 +1,80 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: osxsub
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 27
|
5
|
+
prerelease: false
|
6
|
+
segments:
|
7
|
+
- 0
|
8
|
+
- 1
|
9
|
+
- 0
|
10
|
+
version: 0.1.0
|
11
|
+
platform: ruby
|
12
|
+
authors:
|
13
|
+
- Mark Wunsch
|
14
|
+
autorequire:
|
15
|
+
bindir: bin
|
16
|
+
cert_chain: []
|
17
|
+
|
18
|
+
date: 2013-01-28 00:00:00 -05:00
|
19
|
+
default_executable:
|
20
|
+
dependencies: []
|
21
|
+
|
22
|
+
description: Command line utility for OS X Text Substition Preferences.
|
23
|
+
email:
|
24
|
+
- mark@markwunsch.com
|
25
|
+
executables:
|
26
|
+
- osxsub
|
27
|
+
extensions: []
|
28
|
+
|
29
|
+
extra_rdoc_files: []
|
30
|
+
|
31
|
+
files:
|
32
|
+
- .gitignore
|
33
|
+
- Gemfile
|
34
|
+
- Gemfile.lock
|
35
|
+
- LICENSE
|
36
|
+
- README.md
|
37
|
+
- Rakefile
|
38
|
+
- bin/osxsub
|
39
|
+
- lib/osxsub.rb
|
40
|
+
- lib/osxsub/ns_user_replacement_items.rb
|
41
|
+
- lib/osxsub/plist_buddy.rb
|
42
|
+
- lib/osxsub/substitution.rb
|
43
|
+
- lib/osxsub/version.rb
|
44
|
+
- osxsub.gemspec
|
45
|
+
has_rdoc: true
|
46
|
+
homepage: http://github.com/mwunsch/osxsub
|
47
|
+
licenses: []
|
48
|
+
|
49
|
+
post_install_message:
|
50
|
+
rdoc_options: []
|
51
|
+
|
52
|
+
require_paths:
|
53
|
+
- lib
|
54
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
55
|
+
none: false
|
56
|
+
requirements:
|
57
|
+
- - ">="
|
58
|
+
- !ruby/object:Gem::Version
|
59
|
+
hash: 3
|
60
|
+
segments:
|
61
|
+
- 0
|
62
|
+
version: "0"
|
63
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
64
|
+
none: false
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
hash: 3
|
69
|
+
segments:
|
70
|
+
- 0
|
71
|
+
version: "0"
|
72
|
+
requirements: []
|
73
|
+
|
74
|
+
rubyforge_project:
|
75
|
+
rubygems_version: 1.3.7
|
76
|
+
signing_key:
|
77
|
+
specification_version: 3
|
78
|
+
summary: Command line utility for OS X Text Substition Preferences.
|
79
|
+
test_files: []
|
80
|
+
|