Skip to content

Commit 1f98da2

Browse files
committed
Modify code for Linux and add tests.
1 parent 204adfc commit 1f98da2

13 files changed

+126
-42
lines changed

.travis.yml

+7
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,13 @@ jobs:
3333
name: macOS / Swift 5.1
3434
osx_image: xcode11
3535

36+
- name: Linux / Swift 5.1
37+
os: linux
38+
env: SWIFT_VERSION=5.1
39+
language: generic
40+
install: eval "$(curl -sL https://swiftenv.fuller.li/install.sh)"
41+
script: swift test --parallel
42+
3643
- stage: deploy
3744
name: Create documentation
3845
install: gem install jazzy

README.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -313,7 +313,7 @@ Feel free to [open an issue][new-issue], or find me [@hejki on Twitter](https://
313313
[badge-swift]: https://img.shields.io/badge/Swift-5.1-orange.svg?logo=swift?style=flat
314314
[badge-spm]: https://img.shields.io/badge/spm-compatible-brightgreen.svg?style=flat
315315
[spm-link]: https://swift.org/package-manager
316-
[badge-platforms]: https://img.shields.io/badge/platform-mac-lightgray.svg?style=flat
316+
[badge-platforms]: https://img.shields.io/badge/platform-mac+linux-lightgray.svg?style=flat
317317
[badge-ci]: https://travis-ci.com/Hejki/CommandLineAPI.svg
318318
[ci]: https://travis-ci.com/Hejki/CommandLineAPI
319319
[badge-licence]: https://img.shields.io/badge/license-MIT-black.svg?style=flat

Sources/CLI.swift

+9-2
Original file line numberDiff line numberDiff line change
@@ -51,11 +51,18 @@ public enum CLI {
5151

5252
/**
5353
Holds global instance of `ProcessBuilder` that is used for create
54-
`Process` instances for `Command` execution. Default is `CLI.Shell.zsh`.
54+
`Process` instances for `Command` execution. Default is `CLI.Shell.zsh` on macOS
55+
and `CLI.Shell.bash` on Linux.
5556

5657
- SeeAlso: `CLI.Shell`
5758
*/
58-
public static var processBuilder: ProcessBuilder = CLI.Shell.zsh
59+
public static var processBuilder: ProcessBuilder = {
60+
#if os(macOS)
61+
return CLI.Shell.zsh
62+
#else
63+
return CLI.Shell.bash
64+
#endif
65+
}()
5966
}
6067

6168
// MARK: - String Style

Sources/Path.swift

+26-7
Original file line numberDiff line numberDiff line change
@@ -589,8 +589,12 @@ public extension Path {
589589
target = destination
590590
}
591591

592-
if target.exist && overwrite && target.type != .directory {
593-
try target.delete()
592+
if target.exist && target.type != .directory {
593+
if overwrite {
594+
try target.delete()
595+
} else {
596+
throw Error.targetFileExist(target.path)
597+
}
594598
}
595599
return target
596600
}
@@ -613,7 +617,7 @@ public extension Path {
613617
throw CocoaError.error(.fileWriteUnknown)
614618
}
615619
} else {
616-
try FileManager.default.setAttributes([.modificationDate: Date()], ofItemAtPath: destinationPath.path)
620+
try? FileManager.default.setAttributes([.modificationDate: Date()], ofItemAtPath: destinationPath.path)
617621
}
618622
return destinationPath
619623
}
@@ -729,7 +733,8 @@ extension Path: Codable {
729733
- Parameter decoder: The decoder to read data from.
730734
*/
731735
public init(from decoder: Decoder) throws {
732-
try self.init(decoder.singleValueContainer().decode(String.self))
736+
let container = try decoder.singleValueContainer()
737+
try self.init(container.decode(String.self))
733738
}
734739

735740
/**
@@ -781,9 +786,13 @@ public extension Path {
781786
/**
782787
The filesystem item's creation date.
783788
*/
784-
public var creationDate: Date {
785-
get { fileAttributes[.creationDate] as! Date }
786-
set { setAttribute(.creationDate, to: newValue) }
789+
public var creationDate: Date? {
790+
get { fileAttributes[.creationDate] as? Date }
791+
set {
792+
if let val = newValue {
793+
setAttribute(.creationDate, to: val)
794+
}
795+
}
787796
}
788797

789798
#if os(macOS)
@@ -994,6 +1003,14 @@ public extension Path {
9941003
*/
9951004
case invalidArgumentValue(arg: String, _ description: String)
9961005

1006+
/**
1007+
An indication that destination *path* represents existing file.
1008+
Tihs is used when you copy or move something to that location.
1009+
1010+
- Parameter path: The path of existing file.
1011+
*/
1012+
case targetFileExist(_ path: String)
1013+
9971014
/// Retrieve the localized description for this error.
9981015
public var localizedDescription: String {
9991016
switch self {
@@ -1003,6 +1020,8 @@ public extension Path {
10031020
return "URL scheme: '\(scheme)' is not supported. Only 'file' can be used."
10041021
case let .invalidArgumentValue(arg, description):
10051022
return "Invalid argument: '\(arg)' value. \(description)"
1023+
case let .targetFileExist(path):
1024+
return "Cannot move/copy to destination path '\(path)\' because this path represents existing file. Use overwrite parameter or delete it."
10061025
}
10071026
}
10081027
}

Tests/CommandLineAPITests/CLI/ChooseTests.swift

+2
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,9 @@ final class ChooseTests: XCTestCase {
6363
}
6464

6565
func testChoose_noChoices() {
66+
#if canImport(Darwin)
6667
expect { _ = CLI.choose("", choices: []) }.to(throwAssertion())
6768
expect { _ = CLI.choose("", choices: [:]) }.to(throwAssertion())
69+
#endif
6870
}
6971
}

Tests/CommandLineAPITests/CLI/RunTests.swift

+10-9
Original file line numberDiff line numberDiff line change
@@ -79,13 +79,12 @@ final class RunTests: XCTestCase {
7979
}
8080

8181
func testRun_pipe() throws {
82-
var result = try CLI.run("echo -n", "Hi! | base64")
82+
let result = try CLI.run("echo -n", "Hi! | base64")
8383
expect(result) == "SGkh\n"
84+
}
8485

85-
result = try CLI.run("echo -n", "Hi! | base64 | base64 -D")
86-
expect(result) == "Hi!"
87-
88-
result = try CLI.run("echo", "Hi!\nHello\ntest".quoted, "|", "grep H")
86+
func testRun_pipe2() throws {
87+
let result = try CLI.run("echo", "Hi!\nHello\ntest".quoted, "|", "grep H")
8988
expect(result) == "Hi!\nHello\n"
9089
}
9190

@@ -100,25 +99,27 @@ final class RunTests: XCTestCase {
10099
try CLI.run("swift", "--version")
101100
} catch let error as CLI.CommandExecutionError {
102101
expect(error.terminationStatus) == 127
103-
expect(error.stderr) == "env: swift --version: No such file or directory\n"
102+
expect(error.stderr).to(contain("env", "swift --version", "No such file or directory"))
104103
expect(error.stdout) == ""
105104
}
106105

107106
CLI.processBuilder = CLI.Shell.bash
108107
expect(try CLI.run("swift --version")).to(contain("Swift"))
109108

109+
#if os(macOS)
110110
CLI.processBuilder = CLI.Shell.zsh
111111
expect(try CLI.run("swift --version")).to(contain("Swift"))
112+
#endif
112113
}
113114

114115
func testRun_pipeInString() throws {
115116
try Path.temporary { tmp in
116-
try tmp.touch("a")
117+
try tmp.touch("ab")
117118
try tmp.touch("b")
118-
try tmp.touch("_a")
119+
try tmp.touch("ba")
119120

120121
let result = try CLI.Command(["ls | grep a | sort -r"], workingDirectory: tmp.path).execute()
121-
expect(result) == "a\n_a\n"
122+
expect(result) == "ba\nab\n"
122123
}
123124
}
124125
}

Tests/CommandLineAPITests/CLI/StringStyleTests.swift

+7-2
Original file line numberDiff line numberDiff line change
@@ -68,20 +68,24 @@ final class StringStyleTests: XCTestCase {
6868
func testBright() {
6969
expect("t".styled(.bright(.bgBlack))) == "\u{001B}[40;1mt\u{001B}[0m"
7070
expect("t".styled(.bright(.fgBlack))) == "\u{001B}[30;1mt\u{001B}[0m"
71+
#if canImport(Darwin)
7172
expect { _ = Style.bright(.strikethrough) }.to(throwAssertion())
7273
expect { _ = Style.bright(.fg(0)) }.to(throwAssertion())
74+
#endif
7375
}
7476

7577
func testCustomColor() {
7678
expect("t".styled(.fg(0))) == "\u{001B}[38;5;0mt\u{001B}[0m"
7779
expect("t".styled(.fg(255))) == "\u{001B}[38;5;255mt\u{001B}[0m"
7880
expect("t".styled(.fg(r: 0, g: 92, b: 255))) == "\u{001B}[38;2;0;92;255mt\u{001B}[0m"
79-
expect { _ = Style.fg(-1) }.to(throwAssertion())
80-
expect { _ = Style.fg(256) }.to(throwAssertion())
8181

8282
expect("t".styled(.bg(0))) == "\u{001B}[48;5;0mt\u{001B}[0m"
8383
expect("t".styled(.bg(255))) == "\u{001B}[48;5;255mt\u{001B}[0m"
8484
expect("t".styled(.bg(r: 0, g: 64, b: 255))) == "\u{001B}[48;2;0;64;255mt\u{001B}[0m"
85+
86+
#if canImport(Darwin)
87+
expect { _ = Style.fg(-1) }.to(throwAssertion())
88+
expect { _ = Style.fg(256) }.to(throwAssertion())
8589
expect { _ = Style.bg(-1) }.to(throwAssertion())
8690
expect { _ = Style.bg(256) }.to(throwAssertion())
8791

@@ -93,6 +97,7 @@ final class StringStyleTests: XCTestCase {
9397
expect { _ = Style.bg(r: 0, g: i, b: 0) }.to(throwAssertion())
9498
expect { _ = Style.bg(r: 0, g: 0, b: i) }.to(throwAssertion())
9599
}
100+
#endif
96101
}
97102

98103
func testDisableStyles() {

Tests/CommandLineAPITests/Path/AttributesTests.swift

+20-8
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,6 @@ import XCTest
2929
final class AttributesTests: XCTestCase {
3030

3131
func testType() throws {
32-
expect(Path.root.appending("/tmp").type) == .symlink
33-
3432
try Path.temporary { dir in
3533
expect(dir.type) == .directory
3634
expect(dir.appending("nonexist").type) == .unknown
@@ -41,6 +39,10 @@ final class AttributesTests: XCTestCase {
4139
let pipe = dir.appending("pipe")
4240
try CLI.run("mkfifo", pipe.path.quoted)
4341
expect(pipe.type) == .pipe
42+
43+
let symlink = dir.appending("link")
44+
try CLI.run("ln -s", file.path.quoted, symlink.path.quoted)
45+
expect(symlink.type) == .symlink
4446
}
4547
}
4648

@@ -51,8 +53,12 @@ final class AttributesTests: XCTestCase {
5153

5254
try file.touch()
5355
expect(attributes).notTo(beNil())
56+
expect(attributes.modificationDate).notTo(beNil())
57+
58+
#if os(macOS)
5459
expect(attributes.modificationDate) < file.attributes!.modificationDate
5560
expect(attributes.creationDate) < file.attributes!.modificationDate
61+
#endif
5662
}
5763
}
5864

@@ -64,17 +70,20 @@ final class AttributesTests: XCTestCase {
6470
let attributes = file.attributes!
6571

6672
expect(attributes).notTo(beNil())
67-
expect(attributes.extensionHidden) == false
68-
expect(attributes.groupName) == "staff"
69-
if #available(OSX 10.12, *) {
70-
expect(attributes.userName) == ProcessInfo.processInfo.userName
71-
}
72-
expect(attributes.permissions.rawValue) == 0o644
73+
try expect(attributes.groupName) == CLI.run("groups $(whoami) | cut -d' ' -f1 | tr -d $'\n'")
74+
try expect(attributes.userName) == CLI.run("whoami | tr -d $'\n'")
7375
expect(attributes.size) == 1
76+
77+
#if os(macOS)
78+
expect(attributes.permissions.rawValue) == 0o644
79+
#else
80+
expect(attributes.permissions.rawValue) == 0o600
81+
#endif
7482
}
7583
}
7684

7785
func testModifyAttributes() throws {
86+
#if os(macOS)
7887
try Path.temporary { dir in
7988
let file = try dir.touch("data").write(text: "a")
8089
var attributes = file.attributes!
@@ -97,9 +106,11 @@ final class AttributesTests: XCTestCase {
97106
expect(attributes.permissions.rawValue) == 0o777
98107
expect(attributes.groupName) == "staff"
99108
}
109+
#endif
100110
}
101111

102112
func testModifyAttributes_macOS() throws {
113+
#if os(macOS)
103114
try Path.temporary { dir in
104115
let file = try dir.touch("data").write(text: "a")
105116
var attributes = file.attributes!
@@ -111,5 +122,6 @@ final class AttributesTests: XCTestCase {
111122
try attributes.reload()
112123
expect(attributes.extensionHidden) == true
113124
}
125+
#endif
114126
}
115127
}

Tests/CommandLineAPITests/Path/FileManagementTests.swift

+7-1
Original file line numberDiff line numberDiff line change
@@ -220,16 +220,22 @@ final class FileManagementTests: XCTestCase {
220220
try p.parent.delete()
221221
expect(p.exist) == false
222222
expect(p.parent.exist) == false
223+
}
224+
}
223225

226+
func testTrash() throws {
227+
#if os(macOS)
228+
try Path.temporary { dir in
224229
let trash = try Path(url: FileManager.default.url(for: .trashDirectory, in: .userDomainMask, appropriateFor: nil, create: false)).appending("a")
225-
p = try dir.touch("a")
230+
let p = try dir.touch("a")
226231

227232
if trash.exist { try trash.delete() }
228233
expect(trash.exist) == false
229234
try p.delete(useTrash: true)
230235
expect(p.exist) == false
231236
expect(trash.exist) == true
232237
}
238+
#endif
233239
}
234240

235241
func testRename() throws {

Tests/CommandLineAPITests/Path/PathInitTests.swift

+6-2
Original file line numberDiff line numberDiff line change
@@ -32,13 +32,17 @@ final class PathInitTests: XCTestCase {
3232
expect(Path.root.path) == "/"
3333
try expect(Path.current.path) == Path(FileManager.default.currentDirectoryPath).path
3434
expect(Path.home.path) == NSHomeDirectory()
35+
36+
#if os(macOS)
3537
expect(Path.temporary.path.hasPrefix("/var/folders/")) == true
38+
#else
39+
expect(Path.temporary.path.hasPrefix("/tmp")) == true
40+
#endif
3641
}
3742

38-
@available(OSX 10.12, *)
3943
func testInit() throws {
4044
let currentHome = NSHomeDirectory()
41-
let userName = ProcessInfo.processInfo.userName
45+
let userName = try CLI.run("whoami | tr -d $'\n'")
4246

4347
expectInit(path: "/", toBe: "/")
4448
expectInit(path: "/tmp", toBe: "/tmp")

Tests/CommandLineAPITests/Path/PathOtherTests.swift

+20-10
Original file line numberDiff line numberDiff line change
@@ -80,27 +80,37 @@ final class PathOtherTests: XCTestCase {
8080

8181
func testEncodable() throws {
8282
let encoder = JSONEncoder()
83-
let fullPath = "/mypath/file.txt"
83+
let obj = CodableStruct(path: Path.root.appending("/mypath/file.txt"))
8484

85-
let encoded = try encoder.encode(Path.root.appending(fullPath))
86-
expect(String(data: encoded, encoding: .utf8)) == "\"\\/mypath\\/file.txt\""
85+
encoder.outputFormatting = [.prettyPrinted]
86+
87+
let encoded = try encoder.encode(obj)
88+
expect(String(data: encoded, encoding: .utf8)) == """
89+
{
90+
"path" : "\\/mypath\\/file.txt"
91+
}
92+
"""
8793
}
8894

8995
func testCodable() throws {
9096
let encoder = JSONEncoder()
9197
let decoder = JSONDecoder()
92-
let fullPath = "/mypath/file.txt"
98+
let obj = CodableStruct(path: Path.root.appending("/mypath/file.txt"))
9399

94-
let encoded = try encoder.encode(Path.root.appending(fullPath))
95-
let decoded = try decoder.decode(Path.self, from: encoded)
96-
expect(decoded.path) == fullPath
100+
let encoded = try encoder.encode(obj)
101+
let decoded = try decoder.decode(CodableStruct.self, from: encoded)
102+
expect(decoded) == obj
97103
}
98104

99105
func testDecodable() throws {
100106
let decoder = JSONDecoder()
101-
let fullPath = "/mypath/file.txt"
107+
let json = "{\"path\":\"/mypath/file.txt\"}".data(using: .utf8)!
108+
109+
let decoded = try decoder.decode(CodableStruct.self, from: json)
110+
expect(decoded.path) == Path.root.appending("mypath/file.txt")
111+
}
102112

103-
let decoded = try decoder.decode(Path.self, from: "\"/mypath/file.txt\"".data(using: .utf8)!)
104-
expect(decoded.path) == fullPath
113+
struct CodableStruct: Codable, Equatable {
114+
let path: Path
105115
}
106116
}

0 commit comments

Comments
 (0)