@@ -37,68 +37,91 @@ public struct SwiftlyCoreContext: Sendable {
3737 /// The output handler to use, if any.
3838 public var outputHandler : ( any OutputHandler ) ?
3939
40- /// The input probider to use, if any
40+ /// The output handler for error streams
41+ public var errorOutputHandler : ( any OutputHandler ) ?
42+
43+ /// The input provider to use, if any
4144 public var inputProvider : ( any InputProvider ) ?
4245
43- public init ( ) {
46+ /// The terminal info provider
47+ public var terminal : any Terminal
48+
49+ /// The format
50+ public var format : OutputFormat = . text
51+
52+ public init ( format: SwiftlyCore . OutputFormat = . text) {
4453 self . httpClient = SwiftlyHTTPClient ( httpRequestExecutor: HTTPRequestExecutorImpl ( ) )
4554 self . currentDirectory = fs. cwd
55+ self . format = format
56+ self . terminal = SystemTerminal ( )
4657 }
4758
4859 public init ( httpClient: SwiftlyHTTPClient ) {
4960 self . httpClient = httpClient
5061 self . currentDirectory = fs. cwd
62+ self . terminal = SystemTerminal ( )
5163 }
5264
5365 /// Pass the provided string to the set output handler if any.
5466 /// If no output handler has been set, just print to stdout.
55- public func print( _ string: String = " " , terminator : String ? = nil ) async {
67+ public func print( _ string: String = " " ) async {
5668 guard let handler = self . outputHandler else {
57- if let terminator {
58- Swift . print ( string, terminator: terminator)
59- } else {
60- Swift . print ( string)
61- }
69+ Swift . print ( string)
6270 return
6371 }
64- await handler. handleOutputLine ( string + ( terminator ?? " " ) )
72+ await handler. handleOutputLine ( string)
6573 }
6674
6775 public func message( _ string: String = " " , terminator: String ? = nil ) async {
68- // Get terminal size or use default width
69- let terminalWidth = self . getTerminalWidth ( )
70- let wrappedString = string. isEmpty ? string : string. wrapText ( to: terminalWidth)
71- await self . print ( wrappedString, terminator: terminator)
76+ let wrappedString = self . wrappedMessage ( string) + ( terminator ?? " " )
77+
78+ if self . format == . json {
79+ await self . printError ( wrappedString)
80+ return
81+ } else {
82+ await self . print ( wrappedString)
83+ }
7284 }
7385
74- /// Detects the terminal width in columns
75- private func getTerminalWidth( ) -> Int {
76- #if os(macOS) || os(Linux)
77- var size = winsize ( )
78- #if os(OpenBSD)
79- // TIOCGWINSZ is a complex macro, so we need the flattened value.
80- let tiocgwinsz = UInt ( 0x4008_7468 )
81- let result = ioctl ( STDOUT_FILENO, tiocgwinsz, & size)
82- #else
83- let result = ioctl ( STDOUT_FILENO, UInt ( TIOCGWINSZ) , & size)
84- #endif
86+ private func wrappedMessage( _ string: String ) -> String {
87+ let terminalWidth = self . terminal. width ( )
88+ return string. isEmpty ? string : string. wrapText ( to: terminalWidth)
89+ }
8590
86- if result == 0 && Int ( size. ws_col) > 0 {
87- return Int ( size. ws_col)
91+ public func printError( _ string: String = " " ) async {
92+ if let handler = self . errorOutputHandler {
93+ await handler. handleOutputLine ( string)
94+ } else {
95+ if let data = ( string + " \n " ) . data ( using: . utf8) {
96+ try ? FileHandle . standardError. write ( contentsOf: data)
97+ }
8898 }
89- #endif
90- return 80 // Default width if terminal size detection fails
99+ }
100+
101+ public func output( _ data: OutputData ) async {
102+ let formattedOutput : String
103+ switch self . format {
104+ case . text:
105+ formattedOutput = TextOutputFormatter ( ) . format ( data)
106+ case . json:
107+ formattedOutput = JSONOutputFormatter ( ) . format ( data)
108+ }
109+ await self . print ( formattedOutput)
91110 }
92111
93112 public func readLine( prompt: String ) async -> String ? {
94- await self . print ( prompt, terminator: " : \n " )
113+ await self . message ( prompt, terminator: " : \n " )
95114 guard let provider = self . inputProvider else {
96115 return Swift . readLine ( strippingNewline: true )
97116 }
98117 return await provider. readLine ( )
99118 }
100119
101120 public func promptForConfirmation( defaultBehavior: Bool ) async -> Bool {
121+ if self . format == . json {
122+ await self . message ( " Assuming \( defaultBehavior ? " yes " : " no " ) due to JSON format " )
123+ return defaultBehavior
124+ }
102125 let options : String
103126 if defaultBehavior {
104127 options = " (Y/n) "
@@ -112,7 +135,7 @@ public struct SwiftlyCoreContext: Sendable {
112135 ?? ( defaultBehavior ? " y " : " n " ) ) . lowercased ( )
113136
114137 guard [ " y " , " n " , " " ] . contains ( answer) else {
115- await self . print (
138+ await self . message (
116139 " Please input either \" y \" or \" n \" , or press ENTER to use the default. " )
117140 continue
118141 }
0 commit comments