Skip to content

Commit

Permalink
Merge pull request #5 from dirtyhenry/message-input
Browse files Browse the repository at this point in the history
Support messages and journal of messages
  • Loading branch information
dirtyhenry authored Feb 1, 2020
2 parents 3fe5736 + aa98b08 commit 8a8a162
Show file tree
Hide file tree
Showing 30 changed files with 1,509 additions and 236 deletions.
16 changes: 8 additions & 8 deletions Brewfile.lock.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,22 +19,22 @@
}
},
"swiftformat": {
"version": "0.44.0",
"version": "0.44.1",
"bottle": {
"cellar": ":any_skip_relocation",
"prefix": "/usr/local",
"files": {
"catalina": {
"url": "https://homebrew.bintray.com/bottles/swiftformat-0.44.0.catalina.bottle.tar.gz",
"sha256": "07c72ee82ff59ad84a4ddca7dea83ac94ba76c29758b9602f9afb478213e3bc2"
"url": "https://homebrew.bintray.com/bottles/swiftformat-0.44.1.catalina.bottle.tar.gz",
"sha256": "95cec024e6ace171b3a7ea88487ed7cb614e3d7db7cf47cbdd8e4875888f7485"
},
"mojave": {
"url": "https://homebrew.bintray.com/bottles/swiftformat-0.44.0.mojave.bottle.tar.gz",
"sha256": "1650e00f1ba45795d397e12332a82e3a5926f184ef19d217b680b281e1e30565"
"url": "https://homebrew.bintray.com/bottles/swiftformat-0.44.1.mojave.bottle.tar.gz",
"sha256": "635691a03bd533c59c42b8b9e11af652d2c582179098ff422df409fff5c5d96d"
},
"high_sierra": {
"url": "https://homebrew.bintray.com/bottles/swiftformat-0.44.0.high_sierra.bottle.tar.gz",
"sha256": "e7034ee588615738dac8a9b2ebeb2dfd2ce58e1c59b51fb869ebb9cd2b30442c"
"url": "https://homebrew.bintray.com/bottles/swiftformat-0.44.1.high_sierra.bottle.tar.gz",
"sha256": "15c494f50d4b770451b4205fc17e477a74300252df72dd0520c94f49d8e012af"
}
}
}
Expand All @@ -46,7 +46,7 @@
"catalina": {
"HOMEBREW_VERSION": "2.2.4",
"HOMEBREW_PREFIX": "/usr/local",
"Homebrew/homebrew-core": "e8a9116f3e1e8de84357203d749f1a6935d939d9",
"Homebrew/homebrew-core": "f157e41880e1d35967e2e7c1aa264d6578fd7864",
"CLT": "",
"Xcode": "11.3.1",
"macOS": "10.15.1"
Expand Down
5 changes: 3 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -20,16 +20,17 @@ lint:
swiftlint

run-test:
.build/debug/PomodoroCLI --duration 10
.build/debug/PomodoroCLI --duration 5

deploy:
swift build -c release --disable-sandbox
install ".build/release/PomodoroCLI" "$(bindir)/pomodoro-cli"
mkdir -p "$(HOME)/.pomodoro-cli"
touch "$(HOME)/.pomodoro-cli/journal.yml"
cp Resources/SampleHooks/did*.sh "$(HOME)/.pomodoro-cli"

docs:
./scripts/generateDocs.sh

clean:
rm -rf .build
rm -rf .build
17 changes: 4 additions & 13 deletions Package.resolved
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,12 @@
"object": {
"pins": [
{
"package": "Commander",
"repositoryURL": "https://github.com/kylef/Commander.git",
"package": "SwiftCLI",
"repositoryURL": "https://github.com/jakeheis/SwiftCLI",
"state": {
"branch": null,
"revision": "4b6133c3071d521489a80c38fb92d7983f19d438",
"version": "0.9.1"
}
},
{
"package": "Spectre",
"repositoryURL": "https://github.com/kylef/Spectre.git",
"state": {
"branch": null,
"revision": "f14ff47f45642aa5703900980b014c2e9394b6e5",
"version": "0.9.0"
"revision": "c72c4564f8c0a24700a59824880536aca45a4cae",
"version": "6.0.1"
}
}
]
Expand Down
8 changes: 5 additions & 3 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,17 @@ import PackageDescription

let package = Package(
name: "Pomodoro",
platforms: [
.macOS(.v10_12),
],
dependencies: [
// Dependencies declare other packages that this package depends on.
// .package(url: /* package url */, from: "1.0.0"),
.package(url: "https://github.com/kylef/Commander.git", from: "0.8.0"),
.package(url: "https://github.com/jakeheis/SwiftCLI", from: "6.0.0"),
],
targets: [
// Targets are the basic building blocks of a package. A target can define a module or a test suite.
// Targets can depend on other targets in this package, and on products in packages which this package depends on.
.target(name: "Pomodoro", dependencies: ["Commander"]),
.target(name: "Pomodoro", dependencies: ["SwiftCLI"]),
.target(name: "PomodoroCLI", dependencies: ["Pomodoro"]),
.testTarget(name: "PomodoroTests", dependencies: ["Pomodoro"]),
]
Expand Down
13 changes: 9 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,14 @@ When I started this project, I wrote a [blog post][blog-post] on my motivations
</div>

```
➜ pomodoro-cli --help
> Usage:
➜ Usage: pomodoro-cli [options]
$ pomodoro-cli
CLI pomodoro
Options:
--duration [default: 25m] - The duration of the pomodoro in seconds (100) or in minutes (10m).
-d, --duration <value> The duration of the pomodoro in seconds (100) or in minutes (10m) (default to 25m)
-h, --help Show help information
-m, --message <value> The intent of the pomodoro (example: email zero)
```

## Hooks
Expand All @@ -27,6 +28,10 @@ Pomodoro can optionnaly run shell scripts when a pomodoro starts and/or finished

Sample scripts can be found in [the `SampleHooks` directory](https://github.com/dirtyhenry/pomodoro-cli/blob/master/Resources/SampleHooks).

## Journal

A journal of pomodoros is created in `~/.pomodoro-cli/journal.yml`.

## Installation

To install from sources, [Swift](https://swift.org/getting-started/) is required.
Expand Down
11 changes: 11 additions & 0 deletions Sources/Pomodoro/Environment.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import Foundation

struct Environment {
static let dotDirectoryName = ".pomodoro-cli"

static let dotDirectory = FileManager.default
.homeDirectoryForCurrentUser
.appendingPathComponent(dotDirectoryName)

static let hooksOn = false
}
40 changes: 16 additions & 24 deletions Sources/Pomodoro/Hook.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,26 +8,19 @@ enum Hook {

extension Hook {
// MARK: - Script Support of Hooks
static let dotDirectoryName = ".pomodoro-cli"

static let didStartScript = "didStart.sh"
static let didFinishScript = "didFinish.sh"

@available(OSX 10.12, *)
private var scriptURL: URL {
let dotDirectory = FileManager.default
.homeDirectoryForCurrentUser
.appendingPathComponent(Hook.dotDirectoryName)

switch self {
case .didStart:
return dotDirectory.appendingPathComponent(Hook.didStartScript)
return Environment.dotDirectory.appendingPathComponent(Hook.didStartScript)
case .didFinish:
return dotDirectory.appendingPathComponent(Hook.didFinishScript)
return Environment.dotDirectory.appendingPathComponent(Hook.didFinishScript)
}
}

@available(OSX 10.12, *)
private func canBeExecuted(completionHandler: (Bool, String?) -> Void) {
_ = scriptURL.withUnsafeFileSystemRepresentation { cString in
if let scriptPath = cString.map({ String(cString: $0) }) {
Expand All @@ -41,29 +34,28 @@ extension Hook {
}

func execute(completionHandler: (Result<Void, HookError>) -> Void) {
if #available(OSX 10.12, *) {
self.canBeExecuted { executable, path in
if executable, let path = path {
let task = Process.launchedProcess(launchPath: path, arguments: [])
task.waitUntilExit()
let status = task.terminationStatus
if status == 0 {
completionHandler(.success(()))
} else {
completionHandler(.failure(.hookExecutedWithErroredTerminationStatus(status)))
}
guard Environment.hooksOn else {
return
}

canBeExecuted { executable, path in
if executable, let path = path {
let task = Process.launchedProcess(launchPath: path, arguments: [])
task.waitUntilExit()
let status = task.terminationStatus
if status == 0 {
completionHandler(.success(()))
} else {
completionHandler(.failure(.noExecutableFileAtPath(path)))
completionHandler(.failure(.hookExecutedWithErroredTerminationStatus(status)))
}
} else {
completionHandler(.failure(.noExecutableFileAtPath(path)))
}
} else {
completionHandler(.failure(.hookExecutionNotAvailable))
}
}
}

enum HookError: Error {
case hookExecutedWithErroredTerminationStatus(Int32)
case noExecutableFileAtPath(String?)
case hookExecutionNotAvailable
}
17 changes: 17 additions & 0 deletions Sources/Pomodoro/LogWriter.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import Foundation

struct LogWriter {
static let journalFile = "journal.yml"

func writeLog(pomodoroDescription: PomodoroDescription) {
do {
let journalFileHandle = try FileHandle(forWritingTo: Environment.dotDirectory.appendingPathComponent(LogWriter.journalFile))

journalFileHandle.seekToEndOfFile()
journalFileHandle.write(string: pomodoroDescription.description)
journalFileHandle.closeFile()
} catch {
print("An error happened: \(error)")
}
}
}
52 changes: 52 additions & 0 deletions Sources/Pomodoro/PomodoroDescription.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import Foundation

/// A data struct encapsulating a full description of a Pomodoro.
public struct PomodoroDescription {
static let dateFormatter: DateFormatter = {
let result = DateFormatter()
result.dateStyle = .short
result.timeStyle = .medium
return result
}()

// MARK: - Creating a pomodoro

/// Creates a new pomodoro.
/// - Parameters:
/// - duration: the duration of the pomodoro.
/// - message: a message describing the intent of the pomodoro.
public init(duration: TimeInterval, message: String?) {
startDate = Date()
self.duration = duration
self.message = message
}

let startDate: Date
let duration: TimeInterval
let message: String?

var endDate: Date {
return startDate.addingTimeInterval(duration)
}

var formattedStartDate: String {
return PomodoroDescription.dateFormatter.string(from: startDate)
}

var formattedEndDate: String {
return PomodoroDescription.dateFormatter.string(from: endDate)
}
}

extension PomodoroDescription: CustomStringConvertible {
/// The description of the Pomodoro, as logged in the journal.
public var description: String {
let lines = [
"-",
" - startDate: \(formattedStartDate)",
" - endDate: \(formattedEndDate)",
" - message: \(message ?? "n/a")",
]
return lines.joined(separator: "\n").appending("\n")
}
}
10 changes: 9 additions & 1 deletion Sources/Pomodoro/TimeIntervalExtension.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,15 @@ enum TimeIntervalConversionError: Error {
}

extension TimeInterval {
static func fromHumanReadableString(_ string: String) throws -> TimeInterval {
// MARK: - Creating a time internal from a human readable expression.

/// Interprets a duration from a human readable string.
///
/// Two kinds of strings are supported:
///
/// * A string **with digits only** will be parsed as a number of **seconds** (example: `123`);
/// * A string **finishing with `m`** will be parsed as a number of **minutes** (example: `123m` or `123 m`).
public static func fromHumanReadableString(_ string: String) throws -> TimeInterval {
let normalizedInput = string.replacingOccurrences(of: " ", with: "")

guard let lastChar = normalizedInput.last else {
Expand Down
45 changes: 10 additions & 35 deletions Sources/Pomodoro/TimerViewCLI.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,6 @@ public class TimerViewCLI {
var sleepTime: TimeInterval = 1
let intervalBeforePuttingDisplayToSleep: TimeInterval = 10

let dateFormatter: DateFormatter = {
let result = DateFormatter()
result.dateStyle = .short
result.timeStyle = .medium
return result
}()

// MARK: - Creating a timer.

/// Creates a new instance of a timer.
Expand All @@ -32,44 +25,26 @@ public class TimerViewCLI {

// MARK: - Starting the timer

/// Starts the timer for a duration described as a time interval string.
///
/// - Parameter durationAsString: the duration of the timer. Two kinds of strings are supported:
/// * A string **with digits only** will be parsed as a number of **seconds**;
/// * A string **finishing with `m`** will be parsed as a number of **minutes**.
/// (example: `123` or `123m` or `123 m`)
public func start(durationAsString: String) {
do {
start(duration: try TimeInterval.fromHumanReadableString(durationAsString))
} catch {
output.write(string: "Could not start the timer with interval \(durationAsString)")
}
}

/// Starts the timer for the specified duration.
///
/// - Parameter duration: the duration of the string. Two kinds of strings are supported:
/// * A string **with digits only** will be parsed as a number of **seconds**;
/// * A string **finishing with `m`** will be parsed as a number of **minutes**.
/// (example: `123` or `123m` or `123 m`)
public func start(duration: TimeInterval) {
let timerViewModel = TimerViewModel(timeInterval: duration)
/// - Parameter pomodoro: the description of the Pomodoro.
public func start(pomodoro: PomodoroDescription) {
let timerViewModel = TimerViewModel(timeInterval: pomodoro.duration)
self.timerViewModel = timerViewModel

Hook.didStart.execute(completionHandler: hookCompletionHandler)

let beginning = dateFormatter.string(from: timerViewModel.outputs.startDate)
let end = dateFormatter.string(from: timerViewModel.outputs.endDate)
output.write(string: "🍅 from \(beginning) to \(end)\n")
sleepTime = duration / TimeInterval(outputLength)
output.write(string: "🍅 from \(pomodoro.formattedStartDate) to \(pomodoro.formattedEndDate)\n")
sleepTime = pomodoro.duration / TimeInterval(outputLength)
while !timerViewModel.outputs.progress.isFinished {
outputLine(for: timerViewModel.outputs.progress.fractionCompleted)
Thread.sleep(forTimeInterval: sleepTime)
}
outputLine(for: 1.0)
output.write(string: "\nTimer ended\n")
output.write(string: "\nPomodoro ended\n")

Hook.didFinish.execute(completionHandler: hookCompletionHandler)
LogWriter().writeLog(pomodoroDescription: pomodoro)

exit(EXIT_SUCCESS)
}
Expand All @@ -92,9 +67,9 @@ public class TimerViewCLI {
}
}
#if DEBUG
if case .success = result {
debugPrint("✌️ Hook completed successfully.")
}
if case .success = result {
debugPrint("✌️ Hook completed successfully.")
}
#endif
}
}
Loading

0 comments on commit 8a8a162

Please sign in to comment.