Skip to content

Commit 17ace7b

Browse files
committed
update plutil to parse options more robustly and add display function and a placeholder for conversion
1 parent 1333852 commit 17ace7b

File tree

1 file changed

+299
-45
lines changed

1 file changed

+299
-45
lines changed

Diff for: Tools/plutil/main.swift

+299-45
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import Foundation
1414
import Glibc
1515
#endif
1616

17-
func help() {
17+
func help() -> Int32 {
1818
print("plutil: [command_option] [other_options] file...\n" +
1919
"The file '-' means stdin\n" +
2020
"Command options are (-lint is the default):\n" +
@@ -33,49 +33,128 @@ func help() {
3333
" -e extension specify alternate extension for converted files\n" +
3434
" -r if writing JSON, output in human-readable form\n" +
3535
" -- specifies that all further arguments are file names\n")
36+
return EXIT_SUCCESS
37+
}
38+
39+
enum ExecutionMode {
40+
case Help
41+
case Lint
42+
case Convert
43+
case Print
44+
}
45+
46+
enum ConversionFormat {
47+
case XML1
48+
case Binary1
49+
case JSON
50+
}
51+
52+
struct Options {
53+
var mode: ExecutionMode = .Lint
54+
var silent: Bool = false
55+
var output: String?
56+
var fileExtension: String?
57+
var humanReadable: Bool?
58+
var conversionFormat: ConversionFormat?
59+
var inputs = [String]()
60+
}
61+
62+
enum OptionParseError : ErrorType {
63+
case UnrecognizedArgument(String)
64+
case MissingArgument(String)
65+
case InvalidFormat(String)
3666
}
3767

38-
func lint(args : [String]) {
39-
var silent = false
40-
// Be nice and filter the rest of the arguments for other optional arguments
41-
let filteredArgs = args.filter { arg in
68+
func parseArguments(args: [String]) throws -> Options {
69+
var opts = Options()
70+
var iterator = args.generate()
71+
while let arg = iterator.next() {
4272
switch arg {
4373
case "--":
44-
return false
45-
74+
while let path = iterator.next() {
75+
opts.inputs.append(path)
76+
}
77+
break
4678
case "-s":
47-
silent = true
48-
return false
49-
79+
opts.silent = true
80+
break
5081
case "-o":
51-
print("-o is not used with -lint")
52-
help()
53-
exit(EXIT_FAILURE)
54-
82+
if let path = iterator.next() {
83+
opts.output = path
84+
} else {
85+
throw OptionParseError.MissingArgument("-o requires a path argument")
86+
}
87+
break
88+
case "-convert":
89+
opts.mode = ExecutionMode.Convert
90+
if let format = iterator.next() {
91+
switch format {
92+
case "xml1":
93+
opts.conversionFormat = ConversionFormat.XML1
94+
break
95+
case "binary1":
96+
opts.conversionFormat = ConversionFormat.Binary1
97+
break
98+
case "json":
99+
opts.conversionFormat = ConversionFormat.JSON
100+
break
101+
default:
102+
throw OptionParseError.InvalidFormat(format)
103+
}
104+
} else {
105+
throw OptionParseError.MissingArgument("-convert requires a format argument of xml1 binary1 json")
106+
}
107+
break
55108
case "-e":
56-
print("-e is not used with -lint")
57-
help()
58-
exit(EXIT_FAILURE)
59-
109+
if let ext = iterator.next() {
110+
opts.fileExtension = ext
111+
} else {
112+
throw OptionParseError.MissingArgument("-e requires an extension argument")
113+
}
114+
case "-help":
115+
opts.mode = ExecutionMode.Help
116+
break
117+
case "-lint":
118+
opts.mode = ExecutionMode.Lint
119+
break
120+
case "-p":
121+
opts.mode = ExecutionMode.Print
122+
break
60123
default:
61124
if arg.hasPrefix("-") && arg.utf8.count > 1 {
62-
print("unrecognized option \(arg)")
63-
help()
64-
exit(EXIT_FAILURE)
125+
throw OptionParseError.UnrecognizedArgument(arg)
65126
}
66-
return true
127+
break
67128
}
68-
return true
69129
}
70130

71-
if filteredArgs.count < 1 {
131+
return opts
132+
}
133+
134+
135+
func lint(options: Options) -> Int32 {
136+
if options.output != nil {
137+
print("-o is not used with -lint")
138+
help()
139+
return EXIT_FAILURE
140+
}
141+
142+
if options.fileExtension != nil {
143+
print("-e is not used with -lint")
144+
help()
145+
return EXIT_FAILURE
146+
}
147+
148+
if options.inputs.count < 1 {
72149
print("No files specified.")
73150
help()
74-
exit(EXIT_FAILURE)
151+
return EXIT_FAILURE
75152
}
76153

154+
let silent = options.silent
155+
77156
var doError = false
78-
for file in filteredArgs {
157+
for file in options.inputs {
79158
let data : NSData?
80159
if file == "-" {
81160
// stdin
@@ -103,7 +182,174 @@ func lint(args : [String]) {
103182
}
104183

105184
if doError {
106-
exit(EXIT_FAILURE)
185+
return EXIT_FAILURE
186+
} else {
187+
return EXIT_SUCCESS
188+
}
189+
}
190+
191+
func convert(options: Options) -> Int32 {
192+
print("Unimplemented")
193+
return EXIT_FAILURE
194+
}
195+
196+
enum DisplayType {
197+
case Primary
198+
case Key
199+
case Value
200+
}
201+
202+
extension Dictionary {
203+
func display(indent: Int = 0, type: DisplayType = .Primary) {
204+
let indentation = String(count: indent * 2, repeatedValue: Character(" "))
205+
if type == .Primary || type == .Key {
206+
print("\(indentation)[\n", terminator: "")
207+
} else {
208+
print("[\n", terminator: "")
209+
}
210+
211+
forEach() {
212+
if let key = $0.0 as? String {
213+
key.display(indent + 1, type: .Key)
214+
} else {
215+
fatalError("plists should have strings as keys but got a \($0.0.dynamicType)")
216+
}
217+
print(" => ", terminator: "")
218+
displayPlist($0.1, indent: indent + 1, type: .Value)
219+
}
220+
221+
print("\(indentation)]\n", terminator: "")
222+
}
223+
}
224+
225+
extension Array {
226+
func display(indent: Int = 0, type: DisplayType = .Primary) {
227+
let indentation = String(count: indent * 2, repeatedValue: Character(" "))
228+
if type == .Primary || type == .Key {
229+
print("\(indentation)[\n", terminator: "")
230+
} else {
231+
print("[\n", terminator: "")
232+
}
233+
234+
for idx in 0..<count {
235+
print("\(indentation) \(idx) => ", terminator: "")
236+
displayPlist(self[idx], indent: indent + 1, type: .Value)
237+
}
238+
239+
print("\(indentation)]\n", terminator: "")
240+
}
241+
}
242+
243+
extension String {
244+
func display(indent: Int = 0, type: DisplayType = .Primary) {
245+
let indentation = String(count: indent * 2, repeatedValue: Character(" "))
246+
if type == .Primary {
247+
print("\(indentation)\"\(self)\"\n", terminator: "")
248+
}
249+
else if type == .Key {
250+
print("\(indentation)\"\(self)\"", terminator: "")
251+
} else {
252+
print("\"\(self)\"\n", terminator: "")
253+
}
254+
}
255+
}
256+
257+
extension Bool {
258+
func display(indent: Int = 0, type: DisplayType = .Primary) {
259+
let indentation = String(count: indent * 2, repeatedValue: Character(" "))
260+
if type == .Primary {
261+
print("\(indentation)\"\(self ? "1" : "0")\"\n", terminator: "")
262+
}
263+
else if type == .Key {
264+
print("\(indentation)\"\(self ? "1" : "0")\"", terminator: "")
265+
} else {
266+
print("\"\(self ? "1" : "0")\"\n", terminator: "")
267+
}
268+
}
269+
}
270+
271+
extension NSNumber {
272+
func display(indent: Int = 0, type: DisplayType = .Primary) {
273+
let indentation = String(count: indent * 2, repeatedValue: Character(" "))
274+
if type == .Primary {
275+
print("\(indentation)\"\(self)\"\n", terminator: "")
276+
}
277+
else if type == .Key {
278+
print("\(indentation)\"\(self)\"", terminator: "")
279+
} else {
280+
print("\"\(self)\"\n", terminator: "")
281+
}
282+
}
283+
}
284+
285+
extension NSData {
286+
func display(indent: Int = 0, type: DisplayType = .Primary) {
287+
let indentation = String(count: indent * 2, repeatedValue: Character(" "))
288+
if type == .Primary {
289+
print("\(indentation)\"\(self)\"\n", terminator: "")
290+
}
291+
else if type == .Key {
292+
print("\(indentation)\"\(self)\"", terminator: "")
293+
} else {
294+
print("\"\(self)\"\n", terminator: "")
295+
}
296+
}
297+
}
298+
299+
func displayPlist(plist: Any, indent: Int = 0, type: DisplayType = .Primary) {
300+
if let val = plist as? Dictionary<String, Any> {
301+
val.display(indent, type: type)
302+
} else if let val = plist as? Array<Any> {
303+
val.display(indent, type: type)
304+
} else if let val = plist as? String {
305+
val.display(indent, type: type)
306+
} else if let val = plist as? Bool {
307+
val.display(indent, type: type)
308+
} else if let val = plist as? NSNumber {
309+
val.display(indent, type: type)
310+
} else if let val = plist as? NSData {
311+
val.display(indent, type: type)
312+
} else {
313+
fatalError("unhandled type \(plist.dynamicType)")
314+
}
315+
}
316+
317+
func display(options: Options) -> Int32 {
318+
if options.inputs.count < 1 {
319+
print("No files specified.")
320+
help()
321+
return EXIT_FAILURE
322+
}
323+
324+
var doError = false
325+
for file in options.inputs {
326+
let data : NSData?
327+
if file == "-" {
328+
// stdin
329+
data = NSFileHandle.fileHandleWithStandardInput().readDataToEndOfFile()
330+
} else {
331+
data = NSData(contentsOfFile: file)
332+
}
333+
334+
if let d = data {
335+
do {
336+
let plist = try NSPropertyListSerialization.propertyListWithData(d, options: [], format: nil)
337+
displayPlist(plist)
338+
} catch {
339+
print("\(file): \(error)")
340+
}
341+
342+
} else {
343+
print("\(file) does not exists or is not readable or is not a regular file")
344+
doError = true
345+
continue
346+
}
347+
}
348+
349+
if doError {
350+
return EXIT_FAILURE
351+
} else {
352+
return EXIT_SUCCESS
107353
}
108354
}
109355

@@ -117,25 +363,33 @@ func main() -> Int32 {
117363

118364
// Throw away process path
119365
args.removeFirst()
120-
121-
let command = args[0]
122-
switch command {
123-
case "-help":
124-
help()
125-
return EXIT_SUCCESS
126-
127-
case "-lint":
128-
// Throw away command arg
129-
args.removeFirst()
130-
lint(args)
131-
132-
default:
133-
// Default is to lint
134-
lint(args)
366+
do {
367+
let opts = try parseArguments(args)
368+
switch opts.mode {
369+
case .Lint:
370+
return lint(opts)
371+
case .Convert:
372+
return convert(opts)
373+
case .Print:
374+
return display(opts)
375+
case .Help:
376+
return help()
377+
}
378+
} catch let err {
379+
switch err as! OptionParseError {
380+
case .UnrecognizedArgument(let arg):
381+
print("unrecognized option: \(arg)")
382+
help()
383+
break
384+
case .InvalidFormat(let format):
385+
print("unrecognized format \(format)\nformat should be one of: xml1 binary1 json")
386+
break
387+
case .MissingArgument(let errorStr):
388+
print(errorStr)
389+
break
390+
}
391+
return EXIT_FAILURE
135392
}
136-
137-
return EXIT_SUCCESS
138-
139393
}
140394

141395
exit(main())

0 commit comments

Comments
 (0)