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 ADDED
@@ -0,0 +1,18 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ coverage
6
+ InstalledFiles
7
+ lib/bundler/man
8
+ pkg
9
+ rdoc
10
+ spec/reports
11
+ test/tmp
12
+ test/version_tmp
13
+ tmp
14
+
15
+ # YARD artifacts
16
+ .yardoc
17
+ _yardoc
18
+ doc/
data/Gemfile ADDED
@@ -0,0 +1,5 @@
1
+ source "http://rubygems.org"
2
+
3
+ gemspec
4
+
5
+ gem "rake"
data/Gemfile.lock ADDED
@@ -0,0 +1,16 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ osxsub (0.1.0)
5
+
6
+ GEM
7
+ remote: http://rubygems.org/
8
+ specs:
9
+ rake (10.0.3)
10
+
11
+ PLATFORMS
12
+ ruby
13
+
14
+ DEPENDENCIES
15
+ osxsub!
16
+ rake
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
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env rake
2
+ require "bundler/gem_tasks"
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
@@ -0,0 +1,3 @@
1
+ module OsxSub
2
+ VERSION = "0.1.0"
3
+ end
data/lib/osxsub.rb ADDED
@@ -0,0 +1,6 @@
1
+ module OsxSub
2
+ require 'osxsub/version'
3
+ require 'osxsub/substitution'
4
+ require 'osxsub/plist_buddy'
5
+ require 'osxsub/ns_user_replacement_items'
6
+ end
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
+