Various fixes to improve our drilling
This commit is contained in:
parent
ff3f16a125
commit
b5ba7c66c9
@ -92,7 +92,4 @@ extension AXorcist {
|
||||
return HandlerResponse(data: AnyCodable(axElement), error: nil)
|
||||
}
|
||||
|
||||
// TODO: Add remaining command handler methods here...
|
||||
// This is a placeholder file to demonstrate the refactoring approach
|
||||
// The complete implementation would include all handle* methods
|
||||
}
|
||||
|
||||
@ -1,4 +1,9 @@
|
||||
import Foundation
|
||||
import CoreGraphics // Import for CGPoint, CGSize, CGRect
|
||||
import Accessibility // Import for AXTextMarker, AXTextMarkerRange
|
||||
// It's likely AXorcist module, where this file lives, already imports Accessibility or AppKit,
|
||||
// which would make AXTextMarker and AXTextMarkerRange available.
|
||||
// If not, a more dynamic type check might be needed, or this file needs to import them.
|
||||
|
||||
// For encoding/decoding 'Any' type in JSON, especially for element attributes.
|
||||
// Note: @unchecked Sendable is used because 'Any' cannot guarantee thread safety
|
||||
@ -112,6 +117,16 @@ public struct AnyCodable: Codable, @unchecked Sendable {
|
||||
try container.encode(float)
|
||||
case let string as String:
|
||||
try container.encode(string)
|
||||
case let point as CGPoint:
|
||||
try container.encode(["x": point.x, "y": point.y])
|
||||
case let size as CGSize:
|
||||
try container.encode(["width": size.width, "height": size.height])
|
||||
case let url as NSURL:
|
||||
try container.encode(url.absoluteString)
|
||||
case let rect as CGRect:
|
||||
try container.encode(["x": rect.origin.x, "y": rect.origin.y, "width": rect.size.width, "height": rect.size.height])
|
||||
case let val where String(describing: type(of: val)) == "__NSCFType":
|
||||
try container.encode(String(describing: val))
|
||||
case let array as [AnyCodable]:
|
||||
try container.encode(array)
|
||||
case let array as [Any?]:
|
||||
@ -121,6 +136,8 @@ public struct AnyCodable: Codable, @unchecked Sendable {
|
||||
case let dictionary as [String: Any?]:
|
||||
try container.encode(dictionary.mapValues { AnyCodable($0) })
|
||||
default:
|
||||
// DEBUG: Print the type of unhandled values
|
||||
print("AnyCodable unhandled type: \(String(describing: type(of: value))), value: \(String(describing: value))")
|
||||
return false
|
||||
}
|
||||
return true
|
||||
|
||||
@ -6,16 +6,19 @@ import Foundation
|
||||
|
||||
extension Element {
|
||||
@MainActor
|
||||
public func children() -> [Element]? { // Removed logging params
|
||||
public func children(strict: Bool = false) -> [Element]? { // Added strict parameter
|
||||
// Logging for this top-level call
|
||||
// self.briefDescription() is assumed to be refactored and available
|
||||
axDebugLog("Getting children for element: \(self.briefDescription(option: .default))")
|
||||
axDebugLog("Getting children for element: \(self.briefDescription(option: .default)), strict: \(strict)")
|
||||
|
||||
var childCollector = ChildCollector() // ChildCollector will use GlobalAXLogger internally
|
||||
|
||||
collectDirectChildren(collector: &childCollector)
|
||||
collectAlternativeChildren(collector: &childCollector)
|
||||
collectApplicationWindows(collector: &childCollector)
|
||||
|
||||
if !strict { // Only collect alternatives if not strict
|
||||
collectAlternativeChildren(collector: &childCollector)
|
||||
collectApplicationWindows(collector: &childCollector)
|
||||
}
|
||||
|
||||
let result = childCollector.finalizeResults()
|
||||
axDebugLog("Final children count: \(result?.count ?? 0)")
|
||||
|
||||
@ -77,7 +77,7 @@ extension AXorcist {
|
||||
requestedAttributes: [String]?,
|
||||
outputFormat: OutputFormat?,
|
||||
commandId: String?
|
||||
) -> String {
|
||||
) async -> String {
|
||||
SearchVisitor.resetGlobalVisitCount()
|
||||
|
||||
let params = CollectAllParameters(
|
||||
@ -95,7 +95,7 @@ extension AXorcist {
|
||||
|
||||
// Get app element
|
||||
guard let appElement = applicationElement(for: params.appIdentifier) else {
|
||||
return createErrorResponse(
|
||||
return await createErrorResponse(
|
||||
commandId: params.effectiveCommandId,
|
||||
appIdentifier: params.appIdentifier,
|
||||
error: "Failed to get app element for identifier: \(params.appIdentifier)"
|
||||
@ -103,7 +103,7 @@ extension AXorcist {
|
||||
}
|
||||
|
||||
// Determine start element
|
||||
let startElementResult = determineStartElement(
|
||||
let startElementResult = await determineStartElement(
|
||||
appElement: appElement,
|
||||
pathHint: pathHint,
|
||||
locator: locator,
|
||||
@ -111,7 +111,7 @@ extension AXorcist {
|
||||
)
|
||||
|
||||
guard let startElement = startElementResult.element else {
|
||||
return createErrorResponse(
|
||||
return await createErrorResponse(
|
||||
commandId: params.effectiveCommandId,
|
||||
appIdentifier: params.appIdentifier,
|
||||
error: startElementResult.error ?? "Failed to determine start element"
|
||||
@ -119,13 +119,13 @@ extension AXorcist {
|
||||
}
|
||||
|
||||
// Perform collection
|
||||
let collectedElements = performCollectionTraversal(
|
||||
let collectedElements = await performCollectionTraversal(
|
||||
startElement: startElement,
|
||||
appElement: appElement,
|
||||
params: params
|
||||
)
|
||||
|
||||
return createSuccessResponse(
|
||||
return await createSuccessResponse(
|
||||
commandId: params.effectiveCommandId,
|
||||
appIdentifier: params.appIdentifier,
|
||||
collectedElements: collectedElements
|
||||
@ -191,45 +191,52 @@ extension AXorcist {
|
||||
pathHint: [String]?,
|
||||
locator: Locator?,
|
||||
params: CollectAllParameters
|
||||
) -> (element: Element?, error: String?) {
|
||||
) async -> (element: Element?, error: String?) {
|
||||
var startElement = appElement
|
||||
var pathNavigated = false
|
||||
|
||||
// Navigate to path hint if provided
|
||||
if let hint = pathHint, !hint.isEmpty {
|
||||
let pathHintString = hint.joined(separator: " -> ")
|
||||
axDebugLog("Navigating to path hint: \(pathHintString)")
|
||||
axDebugLog("[CollectAll] Navigating to path hint: \(pathHintString)")
|
||||
|
||||
guard let navigatedElement = navigateToElement(
|
||||
from: appElement,
|
||||
pathHint: hint,
|
||||
maxDepth: AXMiscConstants.defaultMaxDepthSearch
|
||||
maxDepth: AXMiscConstants.defaultMaxDepthSearch
|
||||
) else {
|
||||
return (nil, "Failed to navigate to path: \(pathHintString)")
|
||||
}
|
||||
startElement = navigatedElement
|
||||
pathNavigated = true
|
||||
axDebugLog("[CollectAll] Path navigation successful. Current startElement: \(startElement.briefDescription())")
|
||||
} else {
|
||||
axDebugLog("Using app element as start element")
|
||||
axDebugLog("[CollectAll] No pathHint provided. Current startElement: \(startElement.briefDescription()) (app root)")
|
||||
}
|
||||
|
||||
// Apply locator if provided
|
||||
if let locator = locator {
|
||||
if !pathNavigated, let loc = locator, !loc.criteria.isEmpty {
|
||||
axDebugLog("[CollectAll] Path navigation did not occur. Trying locator.criteria from startElement: \(startElement.briefDescription())")
|
||||
if let locatedElement = findElementByLocator(
|
||||
startElement: startElement,
|
||||
locator: locator
|
||||
locator: loc
|
||||
) {
|
||||
axDebugLog(
|
||||
"Locator found element: \(locatedElement.briefDescription()). " +
|
||||
"[CollectAll] Locator (criteria-only) found element: \(locatedElement.briefDescription()). " +
|
||||
"This will be the root for collectAll recursion."
|
||||
)
|
||||
startElement = locatedElement
|
||||
} else {
|
||||
let locatorDescription = String(describing: locator.criteria)
|
||||
let elementDescription = startElement.briefDescription()
|
||||
let locatorDescription = String(describing: loc.criteria)
|
||||
let currentStartDesc = startElement.briefDescription()
|
||||
axWarningLog(
|
||||
"Locator provided but no element found for: \(locatorDescription). " +
|
||||
"CollectAll will proceed from the current start element: \(elementDescription)."
|
||||
"[CollectAll] Locator (criteria-only) provided but no element found for: \(locatorDescription) from \(currentStartDesc). " +
|
||||
"CollectAll will proceed from \(currentStartDesc)."
|
||||
)
|
||||
}
|
||||
} else if pathNavigated {
|
||||
axDebugLog("[CollectAll] Path navigation occurred. Using element from path as definitive root: \(startElement.briefDescription()). Locator.criteria (if any) will not be used to further refine this root.")
|
||||
} else if let loc = locator, loc.criteria.isEmpty {
|
||||
axDebugLog("[CollectAll] Locator provided with empty criteria and no path hint. Using current startElement: \(startElement.briefDescription()) as root.")
|
||||
}
|
||||
|
||||
return (startElement, nil)
|
||||
@ -259,7 +266,7 @@ extension AXorcist {
|
||||
startElement: Element,
|
||||
appElement: Element,
|
||||
params: CollectAllParameters
|
||||
) -> [AXElement] {
|
||||
) async -> [AXElement] {
|
||||
var traverser = TreeTraverser()
|
||||
let visitor = CollectAllVisitor(
|
||||
attributesToFetch: params.attributesToFetch,
|
||||
@ -269,10 +276,11 @@ extension AXorcist {
|
||||
|
||||
var traversalState = TraversalState(
|
||||
maxDepth: params.recursionDepthLimit,
|
||||
startElement: startElement
|
||||
startElement: startElement,
|
||||
strictChildren: true
|
||||
)
|
||||
|
||||
axDebugLog("Starting unified tree traversal from start element: \(startElement.briefDescription())")
|
||||
axDebugLog("[Pre-Traverse PCT] Handler: validStartElement is: \(startElement.briefDescription(option: .default)) with strictChildren=true")
|
||||
_ = traverser.traverse(from: startElement, visitor: visitor, state: &traversalState)
|
||||
|
||||
let collectedElementsData = visitor.collectedElements
|
||||
@ -293,15 +301,17 @@ extension AXorcist {
|
||||
commandId: String,
|
||||
appIdentifier: String,
|
||||
error: String
|
||||
) -> String {
|
||||
) async -> String {
|
||||
axErrorLog(error)
|
||||
// Fetch logs
|
||||
let logs = await GlobalAXLogger.shared.getLogsAsStrings(format: .text)
|
||||
return encode(CollectAllOutput(
|
||||
commandId: commandId,
|
||||
success: false,
|
||||
command: "collectAll",
|
||||
collectedElements: [],
|
||||
appBundleId: appIdentifier,
|
||||
debugLogs: [],
|
||||
debugLogs: logs,
|
||||
errorMessage: error
|
||||
))
|
||||
}
|
||||
@ -311,14 +321,16 @@ extension AXorcist {
|
||||
commandId: String,
|
||||
appIdentifier: String,
|
||||
collectedElements: [AXElement]
|
||||
) -> String {
|
||||
) async -> String {
|
||||
// Fetch logs
|
||||
let logs = await GlobalAXLogger.shared.getLogsAsStrings(format: .text)
|
||||
let output = CollectAllOutput(
|
||||
commandId: commandId,
|
||||
success: true,
|
||||
command: "collectAll",
|
||||
collectedElements: collectedElements,
|
||||
appBundleId: appIdentifier,
|
||||
debugLogs: [],
|
||||
debugLogs: logs,
|
||||
errorMessage: nil
|
||||
)
|
||||
return encode(output)
|
||||
@ -350,11 +362,11 @@ extension AXorcist {
|
||||
|
||||
var traversalState = TraversalState(
|
||||
maxDepth: recursionDepthLimit,
|
||||
startElement: startElement
|
||||
startElement: startElement,
|
||||
strictChildren: true
|
||||
)
|
||||
|
||||
axDebugLog("Beginning tree traversal for collectAll (CommandEnvelope) from: \(startElement.briefDescription())")
|
||||
|
||||
axDebugLog("[Pre-Traverse CGHEH] Handler: validStartElement is: \(startElement.briefDescription(option: .default)) with strictChildren=true")
|
||||
_ = traverser.traverse(from: startElement, visitor: visitor, state: &traversalState)
|
||||
|
||||
let collectedElementsData = visitor.collectedElements
|
||||
|
||||
1
Sources/AXorcist/Matching/AXElementMatcher.swift
Normal file
1
Sources/AXorcist/Matching/AXElementMatcher.swift
Normal file
@ -0,0 +1 @@
|
||||
|
||||
@ -16,11 +16,13 @@ public enum TraversalAction {
|
||||
public struct TraversalState {
|
||||
public let maxDepth: Int
|
||||
public let startElement: Element // Could be useful for context if GlobalAXLogger needs it indirectly
|
||||
public let strictChildren: Bool // New flag
|
||||
// Add other non-logging state if needed by visitors, e.g., a shared Set for specific tracking.
|
||||
|
||||
public init(maxDepth: Int, startElement: Element) {
|
||||
public init(maxDepth: Int, startElement: Element, strictChildren: Bool = false) { // Default to false
|
||||
self.maxDepth = maxDepth
|
||||
self.startElement = startElement
|
||||
self.strictChildren = strictChildren
|
||||
}
|
||||
}
|
||||
|
||||
@ -45,12 +47,19 @@ public struct TreeTraverser {
|
||||
|
||||
public init() {}
|
||||
|
||||
public mutating func traverse(from element: Element, visitor: TreeVisitor, state: inout TraversalState) -> Element? {
|
||||
public mutating func traverse(from startNode: Element, visitor: TreeVisitor, state: inout TraversalState) -> Element? {
|
||||
let startNodeDesc = startNode.briefDescription(option: .default)
|
||||
let logMaxDepth = state.maxDepth // Capture value for logging
|
||||
let logStrictChildren = state.strictChildren // Capture value for logging
|
||||
axDebugLog("[Traverse Entry] TreeTraverser.traverse starting from: \(startNodeDesc). MaxDepth: \(logMaxDepth), StrictChildren: \(logStrictChildren)")
|
||||
visitedElements.removeAll()
|
||||
return _traverse(currentElement: element, depth: 0, visitor: visitor, state: &state)
|
||||
return _traverse(currentElement: startNode, depth: 0, visitor: visitor, state: &state)
|
||||
}
|
||||
|
||||
private mutating func _traverse(currentElement: Element, depth: Int, visitor: TreeVisitor, state: inout TraversalState) -> Element? {
|
||||
let currentDesc = currentElement.briefDescription(option: .default) // Corrected label
|
||||
axDebugLog("[_Traverse Entry] Visiting \(currentDesc) at depth \(depth)") // MODIFIED LOG
|
||||
|
||||
if depth > state.maxDepth {
|
||||
let maxDepth = state.maxDepth
|
||||
axDebugLog("Max depth (\(maxDepth)) reached at \(currentElement.briefDescription())")
|
||||
@ -75,7 +84,7 @@ public struct TreeTraverser {
|
||||
break
|
||||
}
|
||||
|
||||
guard let children = currentElement.children() else {
|
||||
guard let children = currentElement.children(strict: state.strictChildren) else {
|
||||
axDebugLog("No children for \(currentElement.briefDescription()) or error fetching them.")
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -81,7 +81,7 @@ struct AXORCCommand: AsyncParsableCommand {
|
||||
// Parse JSON command
|
||||
guard let jsonData = jsonString.data(using: .utf8) else {
|
||||
// Clear logs after error
|
||||
await GlobalAXLogger.shared.clearLogs()
|
||||
await axClearLogs()
|
||||
print("{\"error\": \"Failed to convert JSON string to data\"}")
|
||||
return
|
||||
}
|
||||
@ -109,7 +109,7 @@ struct AXORCCommand: AsyncParsableCommand {
|
||||
|
||||
// Stop collecting logs after successful execution
|
||||
// Clear logs after error
|
||||
await GlobalAXLogger.shared.clearLogs()
|
||||
await axClearLogs()
|
||||
|
||||
} catch {
|
||||
axErrorLog("DECODE_ERROR_DEBUG: Original jsonString that led to this error: [\(jsonString)]")
|
||||
@ -125,7 +125,7 @@ struct AXORCCommand: AsyncParsableCommand {
|
||||
await GlobalAXLogger.shared.setDetailLevel(.verbose)
|
||||
}
|
||||
let collectedLogs = await GlobalAXLogger.shared.getLogsAsStrings(format: .text, includeTimestamps: true, includeLevels: true, includeDetails: true)
|
||||
await GlobalAXLogger.shared.clearLogs()
|
||||
await axClearLogs()
|
||||
|
||||
let errorResponse = ErrorResponse(
|
||||
commandId: "decode_error",
|
||||
|
||||
@ -38,7 +38,7 @@ struct CommandExecutor {
|
||||
|
||||
// Clear logs for this specific operation after processing,
|
||||
// so next command in a batch (if any) or next CLI call starts fresh for its specific logs.
|
||||
await GlobalAXLogger.shared.clearLogs()
|
||||
await axClearLogs()
|
||||
await GlobalAXLogger.shared.updateOperationDetails(commandID: nil, appName: nil) // Reset context
|
||||
|
||||
return responseString
|
||||
@ -77,7 +77,16 @@ struct CommandExecutor {
|
||||
return await handleSimpleCommand(command: command, axorcist: axorcist, executor: executeExtractText)
|
||||
|
||||
case .collectAll:
|
||||
return await handleCollectAllCommand(command: command, axorcist: axorcist)
|
||||
// Directly await the call to the now async axorcist.handleCollectAll
|
||||
return await axorcist.handleCollectAll(
|
||||
for: command.application,
|
||||
locator: command.locator,
|
||||
pathHint: command.pathHint, // from CommandEnvelope
|
||||
maxDepth: command.maxDepth,
|
||||
requestedAttributes: command.attributes,
|
||||
outputFormat: command.outputFormat,
|
||||
commandId: command.commandId
|
||||
)
|
||||
|
||||
case .batch:
|
||||
return await handleBatchCommand(command: command, axorcist: axorcist)
|
||||
@ -128,20 +137,6 @@ struct CommandExecutor {
|
||||
)
|
||||
}
|
||||
|
||||
private static func handleCollectAllCommand(command: CommandEnvelope, axorcist: AXorcist) async -> String {
|
||||
// handleCollectAll itself was refactored to use GlobalAXLogger.
|
||||
// It returns a JSON string directly.
|
||||
return await axorcist.handleCollectAll(
|
||||
for: command.application,
|
||||
locator: command.locator,
|
||||
pathHint: command.pathHint,
|
||||
maxDepth: command.maxDepth,
|
||||
requestedAttributes: command.attributes,
|
||||
outputFormat: command.outputFormat,
|
||||
commandId: command.commandId
|
||||
)
|
||||
}
|
||||
|
||||
private static func handleBatchCommand(command: CommandEnvelope, axorcist: AXorcist) async -> String {
|
||||
let batchResponse = await executeBatch(
|
||||
command: command,
|
||||
|
||||
@ -36,9 +36,9 @@ struct InputHandler {
|
||||
if trimmedJsonString.isEmpty {
|
||||
let error = "Error: --json argument was provided but the string is empty or only whitespace."
|
||||
axErrorLog(error)
|
||||
return (nil, "--json argument", error)
|
||||
return ParseResult(jsonString: nil, sourceDescription: "--json argument", error: error)
|
||||
}
|
||||
return (trimmedJsonString, "--json argument", nil)
|
||||
return ParseResult(jsonString: trimmedJsonString, sourceDescription: "--json argument", error: nil)
|
||||
} else if positionalPayloadProvided {
|
||||
return handleDirectPayload(directPayload: directPayload)
|
||||
} else {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user