Skip to content

Commit 092dc4a

Browse files
committed
some new app commands
1 parent 9cf0158 commit 092dc4a

File tree

6 files changed

+156
-37
lines changed

6 files changed

+156
-37
lines changed

ReadMe.md

Lines changed: 64 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -15,43 +15,60 @@ You can use `utiluti` to inspect and modify default apps for url schemes and fil
1515

1616
- macOS connects the `http` and `https` url schemes and the `public.html` UTI. You can only set the default app for `http`. Then the default app for `https` and the `public.html` type will be set to the same app. Attempting to change the default apps for `https` or `public.html` independently will result in an error.
1717

18+
- many commands require the bundle identifier to specify an app. You can determine an app's bundle identifier with `utiluti` itself, `mdls`, `osascript`/AppleScript, or a GUI tool like [Apparency](https://www.mothersruin.com/software/Apparency/)
19+
20+
```
21+
$ utiluti app id /Applications/Safari.app
22+
com.apple.Safari
23+
```
24+
25+
```
26+
$ mdls -n kMDItemCFBundleIdentifier /Applications/Safari.app
27+
kMDItemCFBundleIdentifier = "com.apple.Safari"
28+
```
29+
30+
```
31+
$ osascript -e 'id of app "Safari"'
32+
com.apple.Safari
33+
```
34+
1835
## URL schemes
1936

2037
URL schemes are the part of the URL before the colon `:` which identify which app or protocol to use. E.g. `http`, `mailto`, `ssh`, etc.
2138

2239
Get the current default app for a given url scheme:
2340

24-
```sh
41+
```
2542
$ utiluti url mailto
2643
/System/Applications/Mail.app
2744
```
2845

2946
Use the `--bundle-id` flag to receive the app's bundle identifier instead:
3047

31-
```sh
48+
```
3249
$ utiluti url mailto --bundle-id
3350
com.apple.mail
3451
```
3552

3653
List all apps registered for a given url scheme:
3754

38-
```sh
55+
```
3956
$ utiluti url list mailto
4057
/System/Applications/Mail.app
4158
/Applications/Microsoft Outlook.app
4259
```
4360

4461
Use the `--bundle-id` flag to receive the apps' bundle identifiers instead:
4562

46-
```sh
63+
```
4764
$ utiluti url list mailto --bundle-id
4865
com.apple.mail
4966
com.microsoft.Outlook
5067
```
5168

5269
Set the default app for a given URL scheme:
5370

54-
```sh
71+
```
5572
$ utiluti url set mailto com.microsoft.Outlook
5673
set com.microsoft.Outlook for mailto
5774
```
@@ -62,21 +79,21 @@ set com.microsoft.Outlook for mailto
6279

6380
To get the UTI associated with a file extension, use `get-uti`:
6481

65-
```sh
82+
```
6683
$ utiluti get-uti txt
6784
public.plain-text
6885
```
6986

7087
Get the default application for a UTI:
7188

72-
```sh
89+
```
7390
$ utiluti type public.plain-text
7491
/System/Applications/TextEdit.app
7592
```
7693

7794
List all applications registered for the given UTI:
7895

79-
```sh
96+
```
8097
$ utiluti type list public.plain-text
8198
/System/Applications/TextEdit.app
8299
/Applications/Numbers.app
@@ -92,18 +109,18 @@ Add the `--bundle-id` flag to receive bundle identifiers instead of paths.
92109

93110
Set the the default app for a given UTI:
94111

95-
```sh
112+
```
96113
$ utiluti type set public.plain-text com.barebones.bbedit
97114
set com.barebones.bbedit for public.plain-text
98115
```
99116

100-
## Getting an App's declarations
117+
## Getting an App's declarations and other information
101118

102119
`utiluti` can list the UTIs and url schemes an app has declared in their Info.plist:
103120

104121
List the URL schemes for a given app with `app schemes`:
105122

106-
```sh
123+
```
107124
$ utiluti app schemes com.apple.safari
108125
http
109126
https
@@ -113,7 +130,7 @@ x-safari-https
113130

114131
List the UTIs and file extensions for a given app with `app types`
115132

116-
```sh
133+
```
117134
$ utiluti app types com.apple.TextEdit
118135
public.rtf
119136
com.apple.rtfd
@@ -131,7 +148,7 @@ public.data
131148

132149
Some apps declare file extensions instead of UTIs. In this case `utiluti` will prepend `file extension:`. If there is an associated UTI, it will be shown in parenthesis:
133150

134-
```sh
151+
```
135152
$ utiluti app types com.apple.safari
136153
file extension: css (public.css)
137154
file extension: pdf (com.adobe.pdf)
@@ -171,27 +188,52 @@ file extension: heic (public.heic)
171188
file extension: jxl (public.jpeg-xl)
172189
```
173190

191+
Show an app's bundle identifier:
192+
193+
```
194+
$ utiluti app id /Applications/Safari.app
195+
com.apple.Safari
196+
```
197+
198+
Show an app's version:
199+
200+
```
201+
$ utiluti app id /Applications/Safari.app
202+
18.5
203+
```
204+
205+
List paths to applications for a given bundle identifier: (note that the output might have multiple lines, when there are multiple copies of the app, or be empty when there are no apps matching the identifier)
206+
207+
```
208+
$ utiluti app for-id com.apple.notes
209+
/System/Applications/Notes.app
210+
```
211+
212+
213+
214+
215+
174216
## Default app for specific files
175217

176218
macOS allows for a file to be assigned to an app different from the general default app for that file type. `utiluti` has the `file` verb to inspect or set the default app for a specific file.
177219

178220
Get the UTI for a given file:
179221

180-
```sh
222+
```
181223
$ utiluti file get-uti ReadMe.md
182224
net.daringfireball.markdown
183225
```
184226

185227
Get the app that will open the file when double-clicked:
186228

187-
```sh
229+
```
188230
$utiluti file app ReadMe.md
189231
/System/Applications/TextEdit.app
190232
```
191233

192234
List all apps that can open the file:
193235

194-
```sh
236+
```
195237
$ utiluti file list-apps ReadMe.md
196238
/System/Applications/TextEdit.app
197239
/Applications/Xcode.app
@@ -201,7 +243,7 @@ $ utiluti file list-apps ReadMe.md
201243

202244
Set the default app for this file:
203245

204-
```sh
246+
```
205247
$ utiluti file set ReadMe.md com.apple.dt.xcode
206248
set com.apple.dt.xcode for ReadMe.md
207249
```
@@ -216,20 +258,20 @@ The file format is an XML Property list. You will need two separate files for as
216258

217259
The root object of the property list is a `dict`, each key will be the url scheme or UTI, respectively. The value is the application bundle identifier for the default app that should be set.
218260

219-
```sh
261+
```
220262
$ utiluti manage --type-file types.plist
221263
set com.fatcatsoftware.pledpro for com.apple.property-list
222264
set com.barebones.BBEdit for public.plain-text
223265
set com.barebones.BBEdit for public.shell-script
224266
```
225267

226-
```sh
268+
```
227269
$ utiluti manage --url-file urls.plist
228270
set com.microsoft.Outlook for mailto
229271
set com.ranchero.NetNewsWire-Evergreen for feed
230272
```
231273

232-
```sh
274+
```
233275
$ utiluti manage --type-file types.plist --url-file urls.plist
234276
set com.fatcatsoftware.pledpro for com.apple.property-list
235277
set com.barebones.BBEdit for public.plain-text
@@ -278,7 +320,7 @@ Example (UTIs):
278320

279321
For managed deployments, the settings can be read from a configuration profile.
280322

281-
```sh
323+
```
282324
$ utiluti manage
283325
set com.fatcatsoftware.pledpro for com.apple.property-list
284326
set com.barebones.BBEdit for public.plain-text
@@ -349,7 +391,7 @@ Example (configuration profile):
349391

350392
By default, `utiluti manage` will _ignore_ unmanaged defaults, i.e. defaults that come from local settings rather than configuration profiles. You can override this behavior with the `--include-unmanaged` option.
351393

352-
```sh
394+
```
353395
$ defaults write com.scriptingosx.utiluti.type net.daringfireball.markdown com.barebones.BBEdit
354396
$ defaults read com.scriptingosx.utiluti.type
355397
{

Sources/utiluti/AppCommands.swift

Lines changed: 74 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,14 +11,20 @@ import UniformTypeIdentifiers
1111
import AppKit // for NSWorkspace
1212

1313
struct AppCommands: AsyncParsableCommand {
14+
15+
static var subCommands: [ParsableCommand.Type] {
16+
if #available(macOS 12.0, *) {
17+
[Types.self, Schemes.self, BundleID.self, ForBundleID.self, Version.self]
18+
} else {
19+
[Types.self, Schemes.self, BundleID.self, Version.self]
20+
}
21+
}
22+
1423
static let configuration = CommandConfiguration(
1524
commandName: "app",
1625
abstract: "list uniform types identifiers and url schemes associated with an app",
17-
subcommands: [
18-
Types.self,
19-
Schemes.self
20-
]
21-
)
26+
subcommands: subCommands
27+
)
2228

2329
struct Types: AsyncParsableCommand {
2430
static let configuration
@@ -118,4 +124,67 @@ struct AppCommands: AsyncParsableCommand {
118124
}
119125
}
120126
}
127+
128+
struct BundleID: AsyncParsableCommand {
129+
static let configuration
130+
= CommandConfiguration(
131+
commandName: "identifier",
132+
abstract: "Show the bundle identifier for an app at the path",
133+
aliases: ["id"]
134+
)
135+
136+
@Argument(help:ArgumentHelp("path to the app", valueName: "path"))
137+
var path: String
138+
139+
func run() async {
140+
guard
141+
let bundle = Bundle(path: path),
142+
let bundleID = bundle.bundleIdentifier
143+
else {
144+
Self.exit(withError: ExitCode(11))
145+
}
146+
print(bundleID)
147+
}
148+
}
149+
150+
struct Version: AsyncParsableCommand {
151+
static let configuration
152+
= CommandConfiguration(
153+
commandName: "version",
154+
abstract: "Show the version for an app at the path",
155+
)
156+
157+
@Argument(help:ArgumentHelp("path to the app", valueName: "path"))
158+
var path: String
159+
160+
func run() async {
161+
guard
162+
let bundle = Bundle(path: path),
163+
let version = bundle.infoDictionary?["CFBundleShortVersionString"] as? String ?? bundle.infoDictionary?["CFBundleVersion"] as? String
164+
else {
165+
Self.exit(withError: ExitCode(12))
166+
}
167+
print(version)
168+
}
169+
}
170+
171+
@available(macOS 12, *)
172+
struct ForBundleID: AsyncParsableCommand {
173+
static let configuration
174+
= CommandConfiguration(
175+
commandName: "for-identifier",
176+
abstract: "Show apps with the given bundle identifier",
177+
aliases: ["for-id"]
178+
)
179+
180+
@Argument(help:ArgumentHelp("the app identifier", valueName: "app-identifier"))
181+
var appID: String
182+
183+
func run() async throws {
184+
let apps = NSWorkspace.shared.urlsForApplications(withBundleIdentifier: appID)
185+
for app in apps {
186+
print(app.path)
187+
}
188+
}
189+
}
121190
}

Sources/utiluti/FileCommands.swift

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -74,9 +74,12 @@ struct FileCommands: AsyncParsableCommand {
7474

7575
@available(macOS 12, *)
7676
struct ListApps: AsyncParsableCommand {
77-
static let configuration
78-
= CommandConfiguration(abstract: "get all app that can open this file")
79-
77+
static let configuration = CommandConfiguration(
78+
commandName: "list-apps",
79+
abstract: "get all app that can open this file",
80+
aliases: ["ls-apps", "list", "ls"]
81+
)
82+
8083
@Argument(help:ArgumentHelp("file path", valueName: "path"))
8184
var path: String
8285

Sources/utiluti/GetUTI.swift

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,11 @@ import ArgumentParser
1010
import UniformTypeIdentifiers
1111

1212
struct GetUTI: AsyncParsableCommand {
13-
static let configuration
14-
= CommandConfiguration(abstract: "Get the type identifier (UTI) for a file extension")
15-
13+
static let configuration = CommandConfiguration(
14+
commandName: "get-uti",
15+
abstract: "Get the type identifier (UTI) for a file extension"
16+
)
17+
1618
@Argument(help: "file extension")
1719
var fileExtension: String
1820

Sources/utiluti/TypeCommands.swift

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -62,9 +62,12 @@ struct TypeCommands: AsyncParsableCommand {
6262
}
6363

6464
struct List: AsyncParsableCommand {
65-
static let configuration
66-
= CommandConfiguration(abstract: "List all applications that can handle this type identifier.")
67-
65+
static let configuration = CommandConfiguration(
66+
commandName: "list",
67+
abstract: "List all applications that can handle this type identifier.",
68+
aliases: ["ls"]
69+
)
70+
6871
@OptionGroup var utidentifier: UTIdentifier
6972
@OptionGroup var bundleID: IdentifierFlag
7073

Sources/utiluti/utiluti.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ struct UtilUTI: AsyncParsableCommand {
99
static let configuration = CommandConfiguration(
1010
commandName: "utiluti",
1111
abstract: "Read and set default URL scheme and file type handlers.",
12-
version: "1.2",
12+
version: "1.3",
1313
subcommands: [
1414
URLCommands.self,
1515
TypeCommands.self,

0 commit comments

Comments
 (0)