# CutBox.app

## Coverage Report

#### Created: 2024-03-12 03:40

    .../Source/App/Services/JSFuncService.swift

    Line

    Count

    Source (jump to first uncovered line)

    1

    //

    2

    //  JSFuncService.swift

    3

    //  CutBox

    4

    //

    5

    //  Created by Jason Milkins on 17/5/18.

    6

    //  Copyright © 2018-2023 ocodo. All rights reserved.

    7

    //

    8

    9

    import JavaScriptCore

    10

    11

    class JSFuncService: NSObject {

    12

        static var context: JSContext = JSContext()

    13

    14

        var prefs: CutBoxPreferencesService?

    15

    16

    396

        let require: @convention(block) (String) -> JSValue? = { path in

    17

    396

            let expandedPath = NSString(string: path).expandingTildeInPath

    18

    396

    19

    396

            guard FileManager.default.fileExists(atPath: expandedPath)

    20

    396

                else {

    21

    1

                    notifyUser(

    22

    1

                        title: "Require: filename \(expandedPath)",

    23

    1

                        info: " does not exist")

    24

    1

                    return nil

    25

    3

            }

    26

    3

    27

    3

            guard let fileContent = getStringFromFile(expandedPath)

    28

    3

                else {

    29

    1

                    notifyUser(

    30

    1

                        title: "Cannot read: \(expandedPath)",

    31

    1

                        info: "unknown error processing file")

    32

    1

                    return nil

    33

    2

            }

    34

    2

    35

    2

            return JSFuncService.context.evaluateScript(fileContent)

    36

    3

        }

    $s15CutBoxUnitTests13JSFuncServiceC7requireySo7JSValueCSgSSXBvpfi

    Line

    Count

    Source

    16

    392

        let require: @convention(block) (String) -> JSValue? = { path in

    17

    392

            let expandedPath = NSString(string: path).expandingTildeInPath

    18

    392

    19

    392

            guard FileManager.default.fileExists(atPath: expandedPath)

    20

    392

                else {

    21

    392

                    notifyUser(

    22

    392

                        title: "Require: filename \(expandedPath)",

    23

    392

                        info: " does not exist")

    24

    392

                    return nil

    25

    392

            }

    26

    392

    27

    392

            guard let fileContent = getStringFromFile(expandedPath)

    28

    392

                else {

    29

    392

                    notifyUser(

    30

    392

                        title: "Cannot read: \(expandedPath)",

    31

    392

                        info: "unknown error processing file")

    32

    392

                    return nil

    33

    392

            }

    34

    392

    35

    392

            return JSFuncService.context.evaluateScript(fileContent)

    36

    392

        }

    $s15CutBoxUnitTests13JSFuncServiceC7requireySo7JSValueCSgSSXBvpfiAGSScfU_

    Line

    Count

    Source

    16

    4

        let require: @convention(block) (String) -> JSValue? = { path in

    17

    4

            let expandedPath = NSString(string: path).expandingTildeInPath

    18

    4

    19

    4

            guard FileManager.default.fileExists(atPath: expandedPath)

    20

    4

                else {

    21

    1

                    notifyUser(

    22

    1

                        title: "Require: filename \(expandedPath)",

    23

    1

                        info: " does not exist")

    24

    1

                    return nil

    25

    3

            }

    26

    3

    27

    3

            guard let fileContent = getStringFromFile(expandedPath)

    28

    3

                else {

    29

    1

                    notifyUser(

    30

    1

                        title: "Cannot read: \(expandedPath)",

    31

    1

                        info: "unknown error processing file")

    32

    1

                    return nil

    33

    2

            }

    34

    2

    35

    2

            return JSFuncService.context.evaluateScript(fileContent)

    36

    3

        }

    37

    38

    393

        let shellCommand: @convention(block) (String) -> String = { command in

    39

    393

            let task = Process()

    40

    393

            task.launchPath = "/bin/bash"

    41

    393

            task.arguments = ["-c", command]

    42

    393

    43

    393

            let pipe = Pipe()

    44

    393

            task.standardOutput = pipe

    45

    393

            task.launch()

    46

    393

    47

    393

            let data = pipe.fileHandleForReading.readDataToEndOfFile()

    48

    393

            return String(data: data, encoding: .utf8) ?? ""

    49

    393

        }

    $s15CutBoxUnitTests13JSFuncServiceC12shellCommandyS2SXBvpfi

    Line

    Count

    Source

    38

    392

        let shellCommand: @convention(block) (String) -> String = { command in

    39

    392

            let task = Process()

    40

    392

            task.launchPath = "/bin/bash"

    41

    392

            task.arguments = ["-c", command]

    42

    392

    43

    392

            let pipe = Pipe()

    44

    392

            task.standardOutput = pipe

    45

    392

            task.launch()

    46

    392

    47

    392

            let data = pipe.fileHandleForReading.readDataToEndOfFile()

    48

    392

            return String(data: data, encoding: .utf8) ?? ""

    49

    392

        }

    $s15CutBoxUnitTests13JSFuncServiceC12shellCommandyS2SXBvpfiS2ScfU_

    Line

    Count

    Source

    38

    1

        let shellCommand: @convention(block) (String) -> String = { command in

    39

    1

            let task = Process()

    40

    1

            task.launchPath = "/bin/bash"

    41

    1

            task.arguments = ["-c", command]

    42

    1

    43

    1

            let pipe = Pipe()

    44

    1

            task.standardOutput = pipe

    45

    1

            task.launch()

    46

    1

    47

    1

            let data = pipe.fileHandleForReading.readDataToEndOfFile()

    48

    1

            return String(data: data, encoding: .utf8) ?? ""

    49

    1

        }

    50

    51

        let shell: @convention(block)

    52

    394

        (String, String?) -> [String] = { command, stdin -> [String] in

    53

    394

            let task = Process()

    54

    394

    55

    394

            if let stdinString = stdin {

    56

    2

                let stdinPipe = Pipe()

    57

    2

                task.standardInput = stdinPipe

    58

    2

                if let stdinData = stdinString.data(using: .utf8) {

    59

    2

                    stdinPipe.fileHandleForWriting.write(stdinData)

    60

    2

                    stdinPipe.fileHandleForWriting.closeFile()

    61

    2

                }

    62

    2

            }

    63

    2

    64

    2

            task.launchPath = "/bin/bash"

    65

    2

            task.arguments = ["-c", command]

    66

    2

    67

    2

            let stdoutPipe = Pipe()

    68

    2

            let stderrPipe = Pipe()

    69

    2

            task.standardOutput = stdoutPipe

    70

    2

            task.standardError = stderrPipe

    71

    2

    72

    2

            do {

    73

    2

                try task.run()

    74

    2

            } catch {

    75

    0

                return ["", "Error running: shell(command: \(command), stdin: \(String(describing: stdin))"]

    76

    2

            }

    77

    2

    78

    2

            let stdoutData = stdoutPipe.fileHandleForReading.readDataToEndOfFile()

    79

    2

            let stderrData = stderrPipe.fileHandleForReading.readDataToEndOfFile()

    80

    2

    81

    2

            let stdout = String(data: stdoutData, encoding: .utf8) ?? ""

    82

    2

            let stderr = String(data: stderrData, encoding: .utf8) ?? ""

    83

    2

    84

    2

            return [stdout, stderr]

    85

    2

        }

    $s15CutBoxUnitTests13JSFuncServiceC5shellySaySSGSS_SSSgtXBvpfi

    Line

    Count

    Source

    52

    392

        (String, String?) -> [String] = { command, stdin -> [String] in

    53

    392

            let task = Process()

    54

    392

    55

    392

            if let stdinString = stdin {

    56

    392

                let stdinPipe = Pipe()

    57

    392

                task.standardInput = stdinPipe

    58

    392

                if let stdinData = stdinString.data(using: .utf8) {

    59

    392

                    stdinPipe.fileHandleForWriting.write(stdinData)

    60

    392

                    stdinPipe.fileHandleForWriting.closeFile()

    61

    392

                }

    62

    392

            }

    63

    392

    64

    392

            task.launchPath = "/bin/bash"

    65

    392

            task.arguments = ["-c", command]

    66

    392

    67

    392

            let stdoutPipe = Pipe()

    68

    392

            let stderrPipe = Pipe()

    69

    392

            task.standardOutput = stdoutPipe

    70

    392

            task.standardError = stderrPipe

    71

    392

    72

    392

            do {

    73

    392

                try task.run()

    74

    392

            } catch {

    75

    392

                return ["", "Error running: shell(command: \(command), stdin: \(String(describing: stdin))"]

    76

    392

            }

    77

    392

    78

    392

            let stdoutData = stdoutPipe.fileHandleForReading.readDataToEndOfFile()

    79

    392

            let stderrData = stderrPipe.fileHandleForReading.readDataToEndOfFile()

    80

    392

    81

    392

            let stdout = String(data: stdoutData, encoding: .utf8) ?? ""

    82

    392

            let stderr = String(data: stderrData, encoding: .utf8) ?? ""

    83

    392

    84

    392

            return [stdout, stderr]

    85

    392

        }

    $s15CutBoxUnitTests13JSFuncServiceC5shellySaySSGSS_SSSgtXBvpfiAESS_AFtcfU_

    Line

    Count

    Source

    52

    2

        (String, String?) -> [String] = { command, stdin -> [String] in

    53

    2

            let task = Process()

    54

    2

    55

    2

            if let stdinString = stdin {

    56

    2

                let stdinPipe = Pipe()

    57

    2

                task.standardInput = stdinPipe

    58

    2

                if let stdinData = stdinString.data(using: .utf8) {

    59

    2

                    stdinPipe.fileHandleForWriting.write(stdinData)

    60

    2

                    stdinPipe.fileHandleForWriting.closeFile()

    61

    2

                }

    62

    2

            }

    63

    2

    64

    2

            task.launchPath = "/bin/bash"

    65

    2

            task.arguments = ["-c", command]

    66

    2

    67

    2

            let stdoutPipe = Pipe()

    68

    2

            let stderrPipe = Pipe()

    69

    2

            task.standardOutput = stdoutPipe

    70

    2

            task.standardError = stderrPipe

    71

    2

    72

    2

            do {

    73

    2

                try task.run()

    74

    2

            } catch {

    75

    0

                return ["", "Error running: shell(command: \(command), stdin: \(String(describing: stdin))"]

    76

    2

            }

    77

    2

    78

    2

            let stdoutData = stdoutPipe.fileHandleForReading.readDataToEndOfFile()

    79

    2

            let stderrData = stderrPipe.fileHandleForReading.readDataToEndOfFile()

    80

    2

    81

    2

            let stdout = String(data: stdoutData, encoding: .utf8) ?? ""

    82

    2

            let stderr = String(data: stderrData, encoding: .utf8) ?? ""

    83

    2

    84

    2

            return [stdout, stderr]

    85

    2

        }

    86

    87

        var filterText: String = ""

    88

    89

    66

        var funcs: [(String, Int)] {

    90

    66

            guard let funcsDict: [[String: Any]] = js["cutboxFunctions"]

    91

    66

                .toArray() as? [[String: Any]] else {

    92

    40

                    return []

    93

    40

            }

    94

    26

    95

    26

            var idx = 0

    96

    27

            return funcsDict.map { (dict: [String: Any]) -> (String, Int)? in

    97

    27

                if let name = dict["name"] as? String {

    98

    13

                    let index = (name, idx)

    99

    13

                    idx += 1

    100

    13

                    return index

    101

    14

                } else {

    102

    14

                    return nil

    103

    14

                }

    104

    0

            }

    105

    27

            .compactMap { $0 }

    106

    66

    107

    66

        }

    108

    109

    59

        var funcList: [String] {

    110

    59

            let names: [String] = funcs.map { (t: (String, Int)) -> String in t.0 }

    111

    59

            if filterText.isEmpty {

    112

    58

                return names

    113

    58

            }

    114

    1

            return names.substringSearchFiltered(search: filterText)

    115

    59

        }

    116

    117

    4

        func selected(name: String) -> (String, Int)? {

    118

    4

            guard let found = funcs.first(where: { $0.0 == name })

    119

    4

                else { return nil }

    120

    3

            return found

    121

    4

        }

    122

    123

    392

        var js: JSContext = JSFuncService.context

    124

    125

    77

        var count: Int {

    126

    77

            return self.funcList.count

    127

    77

        }

    128

    129

    5

        var isEmpty: Bool {

    130

    5

            return self.funcList.isEmpty

    131

    5

        }

    132

    133

        var helpers = """

    134

            help = `

    135

            CutBox JS Help:

    136

              cutboxFunctions - Array of loaded functions

    137

    138

            ${ls()}`

    139

    140

            var ls = () => cutboxFunctions.map(e => e.name).join(`\n`)

    141

            var list = () => JSON.stringify(cutboxFunctions, null, 2)

    142

            """

    143

    144

        var noCutboxJSHelp = """

    145

            var help = `CutBox JS Help:\n

    146

              ~/.cutbox.js does not exist\n

    147

            Add and reload to paste through JS.

    148

            See the wiki for more advanced info:

    149

            https://github.com/cutbox/CutBox/wiki`

    150

            """

    151

    152

    27

        func setup() {

    153

    27

            self.js["require"] = self.require

    154

    27

            self.js["shellCommand"] = self.shellCommand

    155

    27

            self.js["shell"] = self.shell

    156

    27

        }

    157

    158

    5

        func reload() {

    159

    5

            setup()

    160

    5

            repl(noCutboxJSHelp)

    161

    5

    162

    5

            guard let cutBoxJSLocation = prefs?.cutBoxJSLocation,

    163

    5

                  let cutboxJS = getStringFromFile(cutBoxJSLocation) else {

    164

    2

                return

    165

    3

            }

    166

    3

    167

    3

            repl(helpers)

    168

    3

            repl(cutboxJS)

    169

    3

    170

    3

            if self.isEmpty {

    171

    2

                notifyUser(title: "Problem in cutbox js",

    172

    2

                           info: "Error: no function objects in cutboxFunctions")

    173

    3

            } else {

    174

    1

                notifyUser(title: "Javascript loaded",

    175

    1

                           info: "cutbox js loaded \(self.count) function(s)")

    176

    3

            }

    177

    3

        }

    178

    179

        @discardableResult

    180

    45

        func repl(_ line: String) -> String {

    181

    45

            return js.evaluateScript(line).toString()

    182

    45

        }

    183

    184

    1

        func replValue(_ line: String) -> JSValue? {

    185

    1

            return js.evaluateScript(line)

    186

    1

        }

    187

    188

    3

        func process(_ name: String, items: [String]) -> String {

    189

    3

            guard let index = selected(name: name)?.1

    190

    3

                else { return "" }

    191

    2

            return self.process(index, items: items)

    192

    3

        }

    193

    194

    2

        func process(_ fnIndex: Int, items: [String]) -> String {

    195

    2

            return js.evaluateScript("cutboxFunctions[\(fnIndex)].fn")

    196

    2

                .call(withArguments: [items]).toString()!

    197

    2

        }

    198

    }

    199

    200

    9

    func getStringFromFile(_ expandedFilename: String) -> String? {

    201

    9

        guard let fileContent = try? String(contentsOfFile: expandedFilename)

    202

    9

        else { return nil }

    203

    6

        return fileContent

    204

    9

    }