Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Transforms raw text input for to be used by text editors. Also refactored send as a subcommand.
- Loading branch information
Showing
6 changed files
with
218 additions
and
65 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,73 @@ | ||
import ArgumentParser | ||
import Foundation | ||
|
||
@available(OSX 10.11, *) | ||
public struct Sender: ParsableCommand { | ||
public static let configuration = CommandConfiguration( | ||
commandName: "send", | ||
abstract: "Sends keystroke and mouse event commands." | ||
) | ||
|
||
@Option(name: .shortAndLong, help: "Name of a running application to send keys to.") | ||
var applicationName: String? | ||
|
||
@Option(name: .shortAndLong, help: "Default delay between keystrokes in seconds.") | ||
var delay: Double = 0.1 | ||
|
||
@Option(name: .shortAndLong, help: "Initial delay before sending commands in seconds.") | ||
var initialDelay: Double = 1 | ||
|
||
@Option(name: NameSpecification([.customShort("f"), .long ]), help: "File containing keystroke instructions.") | ||
var inputFile: String? | ||
|
||
@Option(name: .shortAndLong, help: "String of characters to send.") | ||
var characters: String? | ||
|
||
public init() { } | ||
|
||
public mutating func run() throws { | ||
let accessEnabled = AXIsProcessTrustedWithOptions([kAXTrustedCheckOptionPrompt.takeUnretainedValue() as String: true] as CFDictionary) | ||
|
||
if !accessEnabled { | ||
fputs("WARNING: Accessibility preferences must be enabled to use this tool. If running from the terminal, make sure that your terminal app has accessibility permissiions enabled.\n\n", stderr) | ||
} | ||
|
||
let commandProcessor = CommandsProcessor(defaultPause: delay) | ||
var commandString: String? | ||
|
||
if !(inputFile ?? "").isEmpty { | ||
if let data = FileManager.default.contents(atPath: inputFile!) { | ||
commandString = String(data: data, encoding: .utf8) | ||
} else { | ||
fatalError("Could not read file \(inputFile!)\n") | ||
} | ||
} else if !(characters ?? "").isEmpty { | ||
commandString = characters | ||
} | ||
|
||
if !(applicationName ?? "").isEmpty { | ||
try AppActivator(appName: applicationName!).activate() | ||
} | ||
|
||
if (initialDelay > 0) { | ||
Sleeper.sleep(seconds: initialDelay) | ||
} | ||
|
||
if !(commandString ?? "").isEmpty { | ||
commandProcessor.process(commandString!) | ||
} else if !isTty() { | ||
var data: Data | ||
|
||
repeat { | ||
data = FileHandle.standardInput.availableData | ||
|
||
if data.count > 0 { | ||
commandString = String(data: data, encoding: .utf8) | ||
commandProcessor.process(commandString!) | ||
} | ||
} while data.count > 0 | ||
} else { | ||
print(SendKeysCli.helpMessage(for: Self.self)) | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,79 @@ | ||
import ArgumentParser | ||
import Foundation | ||
|
||
@available(OSX 10.11, *) | ||
class Transformer: ParsableCommand { | ||
public static let configuration = CommandConfiguration( | ||
commandName: "transform", | ||
abstract: "Transforms raw text input into application friendly character sequences. Examples include accounting for applications that automatically indent source code and insert closing brackets." | ||
) | ||
|
||
@Option(name: .shortAndLong, help: "Determines if the application automatically inserts indentation.") | ||
var indent = true | ||
|
||
@Option(name: .shortAndLong, help: "Specifies which brackets are automatically closed by the application and don't need to be explicitly closed.") | ||
var autoClose = "}])" | ||
|
||
@Option(name: NameSpecification([.customShort("f"), .long ]), help: "File containing keystroke instructions to transform.") | ||
var inputFile: String? | ||
|
||
@Option(name: .shortAndLong, help: "String of characters to transform.") | ||
var characters: String? | ||
|
||
public init(indent: Bool, autoClose: String = "}])") { | ||
self.indent = indent | ||
self.autoClose = autoClose | ||
} | ||
|
||
required init() { | ||
} | ||
|
||
func run() { | ||
var commandString: String? | ||
|
||
if !(inputFile ?? "").isEmpty { | ||
if let data = FileManager.default.contents(atPath: inputFile!) { | ||
commandString = String(data: data, encoding: .utf8) | ||
} else { | ||
fatalError("Could not read file \(inputFile!)\n") | ||
} | ||
} else if !(characters ?? "").isEmpty { | ||
commandString = characters | ||
} | ||
|
||
if !(commandString ?? "").isEmpty { | ||
fputs(transform(commandString!), stdout) | ||
} else if !isTty() { | ||
var data: Data | ||
|
||
repeat { | ||
data = FileHandle.standardInput.availableData | ||
|
||
if data.count > 0 { | ||
commandString = String(data: data, encoding: .utf8) | ||
fputs(transform(commandString!), stdout) | ||
} | ||
} while data.count > 0 | ||
} else { | ||
print(SendKeysCli.helpMessage(for: Self.self)) | ||
} | ||
} | ||
|
||
func transform(_ input: String) -> String { | ||
var output = input | ||
|
||
if indent { | ||
let removeIndentExpression = try! NSRegularExpression(pattern: "^[\\t ]+", options: .anchorsMatchLines) | ||
let range = NSRange(location: 0, length: output.count) | ||
output = removeIndentExpression.stringByReplacingMatches(in: output, options: [], range: range, withTemplate: "") | ||
} | ||
|
||
if !autoClose.isEmpty { | ||
let removeBracketExpression = try! NSRegularExpression(pattern: "\\n[\\t ]*[\(NSRegularExpression.escapedPattern(for: autoClose).replacingOccurrences(of: "]", with: "\\]"))]+") | ||
let range = NSRange(location: 0, length: output.count) | ||
output = removeBracketExpression.stringByReplacingMatches(in: output, options: .withoutAnchoringBounds, range: range, withTemplate: "<\\\\>\n<c:down><p:0><c:right:command>") | ||
} | ||
|
||
return output | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
import Foundation | ||
|
||
func isTty() -> Bool { | ||
return isatty(FileHandle.standardInput.fileDescriptor) == 1 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
@testable import SendKeysLib | ||
|
||
import XCTest | ||
|
||
final class TransformerTests: XCTestCase { | ||
func testShouldNoteTransformSingleLine() { | ||
let transformer = Transformer(indent: true) | ||
let result = transformer.transform("hello world") | ||
|
||
XCTAssertEqual(result, "hello world") | ||
} | ||
|
||
func testShouldNotTransformCurlyBraceOnSameLine() { | ||
let transformer = Transformer(indent: true) | ||
let result = transformer.transform("{}") | ||
|
||
XCTAssertEqual(result, "{}") | ||
} | ||
|
||
func testTransformCurlyBraceOnDifferentLine() { | ||
let transformer = Transformer(indent: true) | ||
let result = transformer.transform("{\n}") | ||
|
||
XCTAssertEqual(result, "{<\\>\n<c:down><p:0><c:right:command>") | ||
} | ||
|
||
func testTransformCurlyBraceWithBasicContent() { | ||
let transformer = Transformer(indent: true) | ||
let result = transformer.transform("hello {\n world\n}") | ||
|
||
XCTAssertEqual(result, "hello {\nworld<\\>\n<c:down><p:0><c:right:command>") | ||
} | ||
|
||
func testTransformBracketAndCurlyBraceWithBasicContent() { | ||
let transformer = Transformer(indent: true) | ||
let result = transformer.transform("hello ({\n world\n})") | ||
|
||
XCTAssertEqual(result, "hello ({\nworld<\\>\n<c:down><p:0><c:right:command>") | ||
} | ||
} |