Skip to content

Commit a8ab904

Browse files
committed
working version
1 parent a18830b commit a8ab904

File tree

7 files changed

+238
-88
lines changed

7 files changed

+238
-88
lines changed

utiluti.xcodeproj/project.pbxproj

Lines changed: 21 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,11 @@
88

99
/* Begin PBXBuildFile section */
1010
C6C84541291AB2CF00AF407A /* main.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6C84540291AB2CF00AF407A /* main.swift */; };
11-
C6C84549291AB2E800AF407A /* Algorithms in Frameworks */ = {isa = PBXBuildFile; productRef = C6C84548291AB2E800AF407A /* Algorithms */; };
12-
C6C8454B291AB30100AF407A /* LSKit.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6C8454A291AB30100AF407A /* LSKit.swift */; };
13-
C6C8454E291AB58200AF407A /* ArgumentParser in Frameworks */ = {isa = PBXBuildFile; productRef = C6C8454D291AB58200AF407A /* ArgumentParser */; };
1411
C6C84552291CEF1500AF407A /* URLScheme.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6C84551291CEF1500AF407A /* URLScheme.swift */; };
1512
C6C84554291CEFC200AF407A /* TypeCommands.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6C84553291CEFC200AF407A /* TypeCommands.swift */; };
13+
C6C84557291CF0C600AF407A /* LSKit.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6C8454A291AB30100AF407A /* LSKit.swift */; };
14+
C6C84559291D64B100AF407A /* GetUTI.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6C84558291D64B100AF407A /* GetUTI.swift */; };
15+
C6C84563291E570C00AF407A /* ArgumentParser in Frameworks */ = {isa = PBXBuildFile; productRef = C6C84562291E570C00AF407A /* ArgumentParser */; };
1616
/* End PBXBuildFile section */
1717

1818
/* Begin PBXCopyFilesBuildPhase section */
@@ -33,15 +33,15 @@
3333
C6C8454A291AB30100AF407A /* LSKit.swift */ = {isa = PBXFileReference; fileEncoding = 4; indentWidth = 2; lastKnownFileType = sourcecode.swift; path = LSKit.swift; sourceTree = "<group>"; tabWidth = 2; };
3434
C6C84551291CEF1500AF407A /* URLScheme.swift */ = {isa = PBXFileReference; indentWidth = 2; lastKnownFileType = sourcecode.swift; path = URLScheme.swift; sourceTree = "<group>"; tabWidth = 2; };
3535
C6C84553291CEFC200AF407A /* TypeCommands.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TypeCommands.swift; sourceTree = "<group>"; };
36+
C6C84558291D64B100AF407A /* GetUTI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GetUTI.swift; sourceTree = "<group>"; };
3637
/* End PBXFileReference section */
3738

3839
/* Begin PBXFrameworksBuildPhase section */
3940
C6C8453A291AB2CF00AF407A /* Frameworks */ = {
4041
isa = PBXFrameworksBuildPhase;
4142
buildActionMask = 2147483647;
4243
files = (
43-
C6C8454E291AB58200AF407A /* ArgumentParser in Frameworks */,
44-
C6C84549291AB2E800AF407A /* Algorithms in Frameworks */,
44+
C6C84563291E570C00AF407A /* ArgumentParser in Frameworks */,
4545
);
4646
runOnlyForDeploymentPostprocessing = 0;
4747
};
@@ -53,6 +53,7 @@
5353
children = (
5454
C6C8453F291AB2CF00AF407A /* utiluti */,
5555
C6C8453E291AB2CF00AF407A /* Products */,
56+
C6C8455F291E568500AF407A /* Frameworks */,
5657
);
5758
indentWidth = 2;
5859
sourceTree = "<group>";
@@ -72,11 +73,19 @@
7273
C6C84540291AB2CF00AF407A /* main.swift */,
7374
C6C84553291CEFC200AF407A /* TypeCommands.swift */,
7475
C6C84551291CEF1500AF407A /* URLScheme.swift */,
76+
C6C84558291D64B100AF407A /* GetUTI.swift */,
7577
C6C8454A291AB30100AF407A /* LSKit.swift */,
7678
);
7779
path = utiluti;
7880
sourceTree = "<group>";
7981
};
82+
C6C8455F291E568500AF407A /* Frameworks */ = {
83+
isa = PBXGroup;
84+
children = (
85+
);
86+
name = Frameworks;
87+
sourceTree = "<group>";
88+
};
8089
/* End PBXGroup section */
8190

8291
/* Begin PBXNativeTarget section */
@@ -94,8 +103,7 @@
94103
);
95104
name = utiluti;
96105
packageProductDependencies = (
97-
C6C84548291AB2E800AF407A /* Algorithms */,
98-
C6C8454D291AB58200AF407A /* ArgumentParser */,
106+
C6C84562291E570C00AF407A /* ArgumentParser */,
99107
);
100108
productName = utiluti;
101109
productReference = C6C8453D291AB2CF00AF407A /* utiluti */;
@@ -126,8 +134,7 @@
126134
);
127135
mainGroup = C6C84534291AB2CF00AF407A;
128136
packageReferences = (
129-
C6C84547291AB2E800AF407A /* XCRemoteSwiftPackageReference "swift-algorithms" */,
130-
C6C8454C291AB58200AF407A /* XCRemoteSwiftPackageReference "swift-argument-parser" */,
137+
C6C8455A291D727D00AF407A /* XCRemoteSwiftPackageReference "swift-argument-parser" */,
131138
);
132139
productRefGroup = C6C8453E291AB2CF00AF407A /* Products */;
133140
projectDirPath = "";
@@ -144,9 +151,10 @@
144151
buildActionMask = 2147483647;
145152
files = (
146153
C6C84554291CEFC200AF407A /* TypeCommands.swift in Sources */,
154+
C6C84557291CF0C600AF407A /* LSKit.swift in Sources */,
147155
C6C84541291AB2CF00AF407A /* main.swift in Sources */,
148156
C6C84552291CEF1500AF407A /* URLScheme.swift in Sources */,
149-
C6C8454B291AB30100AF407A /* LSKit.swift in Sources */,
157+
C6C84559291D64B100AF407A /* GetUTI.swift in Sources */,
150158
);
151159
runOnlyForDeploymentPostprocessing = 0;
152160
};
@@ -316,15 +324,7 @@
316324
/* End XCConfigurationList section */
317325

318326
/* Begin XCRemoteSwiftPackageReference section */
319-
C6C84547291AB2E800AF407A /* XCRemoteSwiftPackageReference "swift-algorithms" */ = {
320-
isa = XCRemoteSwiftPackageReference;
321-
repositoryURL = "https://github.com/apple/swift-algorithms.git";
322-
requirement = {
323-
kind = upToNextMajorVersion;
324-
minimumVersion = 1.0.0;
325-
};
326-
};
327-
C6C8454C291AB58200AF407A /* XCRemoteSwiftPackageReference "swift-argument-parser" */ = {
327+
C6C8455A291D727D00AF407A /* XCRemoteSwiftPackageReference "swift-argument-parser" */ = {
328328
isa = XCRemoteSwiftPackageReference;
329329
repositoryURL = "https://github.com/apple/swift-argument-parser.git";
330330
requirement = {
@@ -335,14 +335,9 @@
335335
/* End XCRemoteSwiftPackageReference section */
336336

337337
/* Begin XCSwiftPackageProductDependency section */
338-
C6C84548291AB2E800AF407A /* Algorithms */ = {
339-
isa = XCSwiftPackageProductDependency;
340-
package = C6C84547291AB2E800AF407A /* XCRemoteSwiftPackageReference "swift-algorithms" */;
341-
productName = Algorithms;
342-
};
343-
C6C8454D291AB58200AF407A /* ArgumentParser */ = {
338+
C6C84562291E570C00AF407A /* ArgumentParser */ = {
344339
isa = XCSwiftPackageProductDependency;
345-
package = C6C8454C291AB58200AF407A /* XCRemoteSwiftPackageReference "swift-argument-parser" */;
340+
package = C6C8455A291D727D00AF407A /* XCRemoteSwiftPackageReference "swift-argument-parser" */;
346341
productName = ArgumentParser;
347342
};
348343
/* End XCSwiftPackageProductDependency section */

utiluti.xcodeproj/xcuserdata/armin.xcuserdatad/xcschemes/xcschememanagement.plist

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,13 @@
44
<dict>
55
<key>SchemeUserState</key>
66
<dict>
7+
<key>Tools for plug-in “Generate Manual”.xcscheme</key>
8+
<dict>
9+
<key>isShown</key>
10+
<false/>
11+
<key>orderHint</key>
12+
<integer>1</integer>
13+
</dict>
714
<key>utiluti.xcscheme_^#shared#^_</key>
815
<dict>
916
<key>orderHint</key>

utiluti/GetUTI.swift

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
//
2+
// GetIdentifier.swift
3+
// utiluti
4+
//
5+
// Created by Armin Briegel on 2022-11-10.
6+
//
7+
8+
import Foundation
9+
import ArgumentParser
10+
import UniformTypeIdentifiers
11+
12+
@available(macOS 11.0, *)
13+
struct GetUTI: ParsableCommand {
14+
static var configuration
15+
= CommandConfiguration(abstract: "Get the type identifier (UTI) for a file extension")
16+
17+
@Argument(help: "file extension") var fileExtension: String
18+
19+
func run() {
20+
guard let utype = UTType(filenameExtension: fileExtension) else {
21+
Self.exit(withError: ExitCode(3))
22+
}
23+
24+
if utype.identifier.hasPrefix("dyn.") {
25+
print("<unknown extension>")
26+
Self.exit(withError: ExitCode(1))
27+
}
28+
29+
print(utype.identifier)
30+
}
31+
}

utiluti/LSKit.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import Foundation
99
import AppKit
1010
import UniformTypeIdentifiers
1111

12-
class LSKit {
12+
struct LSKit {
1313

1414
/**
1515
returns a list of URLs to applications that can open URLs starting with the scheme

utiluti/TypeCommands.swift

Lines changed: 92 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -7,61 +7,138 @@
77

88
import Foundation
99
import ArgumentParser
10+
import UniformTypeIdentifiers
1011

1112
struct TypeCommands: ParsableCommand {
13+
14+
static var subCommands: [ParsableCommand.Type] {
15+
if #available(macOS 11.0, *) {
16+
return [Get.self, List.self, Set.self, Info.self, FileExtensions.self]
17+
} else {
18+
return[Get.self, List.self, Set.self]
19+
}
20+
}
21+
1222
static var configuration = CommandConfiguration(
1323
commandName: "type",
1424
abstract: "Manipulate default file type handlers",
15-
subcommands: [Get.self, List.self, Set.self],
25+
subcommands: subCommands,
1626
defaultSubcommand: Get.self
1727
)
1828

1929
struct UTIdentifier: ParsableArguments {
20-
@Argument(help: "universal type identifier, e.g. 'public.html'") var utidentifier: String
30+
@Argument(help: ArgumentHelp(
31+
"universal type identifier, e.g. 'public.html'",
32+
valueName: "uti"))
33+
var value: String
2134
}
2235

36+
struct IdentifierFlag: ParsableArguments {
37+
@Flag(help: ArgumentHelp(
38+
"list bundle identifiers instead of paths",
39+
valueName: "bundleID"))
40+
var bundleID = false
41+
}
2342

2443
struct Get: ParsableCommand {
2544
static var configuration
2645
= CommandConfiguration(abstract: "Get the path to the default application.")
27-
28-
@OptionGroup var args: UTIdentifier
46+
47+
@OptionGroup var utidentifier: UTIdentifier
48+
@OptionGroup var bundleID: IdentifierFlag
2949

3050
func run() {
31-
let appURL = LSKit.defaultAppURL(forTypeIdentifier: args.utidentifier)
32-
print(appURL?.path ?? "no default app found")
51+
guard let appURL = LSKit.defaultAppURL(forTypeIdentifier: utidentifier.value) else {
52+
print("<no default app found>")
53+
return
54+
}
55+
if bundleID.bundleID {
56+
guard let appBundle = Bundle(url: appURL) else {
57+
Self.exit(withError: ExitCode(6))
58+
}
59+
print(appBundle.bundleIdentifier ?? "<no identifier>")
60+
} else {
61+
print(appURL.path)
62+
}
3363
}
3464
}
3565

3666
struct List: ParsableCommand {
3767
static var configuration
3868
= CommandConfiguration(abstract: "List all applications that can handle this type identifier.")
39-
40-
@OptionGroup var args: UTIdentifier
4169

70+
@OptionGroup var utidentifier: UTIdentifier
71+
@OptionGroup var bundleID: IdentifierFlag
72+
4273
func run() {
43-
let appURLs = LSKit.appURLs(forTypeIdentifier: args.utidentifier)
74+
let appURLs = LSKit.appURLs(forTypeIdentifier: utidentifier.value)
4475

4576
for appURL in appURLs {
46-
print(appURL.path)
77+
if bundleID.bundleID {
78+
if let appBundle = Bundle(url: appURL) {
79+
print(appBundle.bundleIdentifier ?? "<no identifier>")
80+
} else {
81+
print("<'\(appURL.path)' is not a bundle>")
82+
}
83+
} else {
84+
print(appURL.path)
85+
}
4786
}
4887
}
4988
}
5089

5190
struct Set: ParsableCommand {
5291
static var configuration
5392
= CommandConfiguration(abstract: "Set the default app for this type identifier.")
54-
55-
@OptionGroup var args: UTIdentifier
93+
94+
@OptionGroup var utidentifier: UTIdentifier
5695
@Argument var identifier: String
5796

5897
func run() {
59-
let result = LSKit.setDefaultApp(identifier: identifier, forTypeIdentifier: args.utidentifier)
98+
let result = LSKit.setDefaultApp(identifier: identifier, forTypeIdentifier: utidentifier.value)
6099

61100
if result == 0 {
62-
print("set \(identifier) for \(args.utidentifier)")
101+
print("set \(identifier) for \(utidentifier.value)")
63102
} else {
64-
print("cannot set default app for \(args.utidentifier) (error \(result))")
103+
print("cannot set default app for \(utidentifier.value) (error \(result))")
104+
TypeCommands.exit(withError: ExitCode(result))
105+
}
106+
}
107+
}
108+
109+
@available(macOS 11.0, *)
110+
struct FileExtensions: ParsableCommand {
111+
static var configuration
112+
= CommandConfiguration(abstract: "prints the file extensions for the given type identifier")
113+
114+
@OptionGroup var utidentifier: UTIdentifier
115+
116+
func run() {
117+
guard let utype = UTType(utidentifier.value) else {
118+
print("<none>")
119+
TypeCommands.exit(withError: ExitCode(3))
120+
}
121+
122+
let extensions = utype.tags[.filenameExtension] ?? []
123+
print(extensions.joined(separator: " "))
124+
}
125+
}
126+
127+
@available(macOS 11.0, *)
128+
struct Info: ParsableCommand {
129+
static var configuration
130+
= CommandConfiguration(abstract: "prints information for the given type identifier")
131+
132+
@OptionGroup var utidentifier: UTIdentifier
133+
134+
func run() {
135+
guard let utype = UTType(utidentifier.value) else {
136+
print("<none>")
137+
TypeCommands.exit(withError: ExitCode(3))
138+
}
139+
140+
for (key, value) in utype.tags {
141+
print("\(key): \(value)")
65142
}
66143
}
67144
}

0 commit comments

Comments
 (0)