fork of hey-api/openapi-ts because I need some additional things
at feat/skip-token 249 lines 8.2 kB view raw
1import type { BindingKind } from '@hey-api/codegen-core'; 2// import type { BindingKind, NodeScope, Symbol } from '@hey-api/codegen-core'; 3// import { isSymbol } from '@hey-api/codegen-core'; 4import type { MaybeFunc } from '@hey-api/types'; 5 6import type { DollarPyDsl } from '../../py-dsl'; 7import type { py } from '../../ts-python'; 8// import { $, PythonRenderer } from '../../py-dsl'; 9import type { PyDsl } from '../base'; 10import type { CallArgs } from '../expr/call'; 11 12export type NodeChain = ReadonlyArray<PyDsl>; 13 14export interface AccessOptions { 15 /** The access context. */ 16 context?: 'example'; 17 /** Enable debug mode. */ 18 debug?: boolean; 19 /** Transform function for each node in the access chain. */ 20 transform?: (node: PyDsl, index: number, chain: NodeChain) => PyDsl; 21} 22 23// export type AccessResult = ReturnType<typeof $.expr | typeof $.attr | typeof $.call | typeof $.new>; 24 25export interface ExampleOptions { 26 /** Import kind for the root node. */ 27 importKind?: BindingKind; 28 /** Import name for the root node. */ 29 importName?: string; 30 /** Setup to run before calling the example. */ 31 importSetup?: MaybeFunc< 32 ( 33 ctx: DollarPyDsl & { 34 /** The imported expression. */ 35 node: PyDsl<py.Expression>; 36 }, 37 ) => PyDsl<py.Expression> 38 >; 39 /** Module to import from. */ 40 moduleName?: string; 41 /** Example request payload. */ 42 payload?: MaybeFunc<(ctx: DollarPyDsl) => CallArgs | CallArgs[number]>; 43 /** Variable name for setup node. */ 44 setupName?: string; 45} 46 47// function accessChainToNode<T = AccessResult>(accessChain: NodeChain): T { 48// let result!: AccessResult; 49// accessChain.forEach((node, index) => { 50// if (index === 0) { 51// // assume correct node 52// result = node as typeof result; 53// } else { 54// result = result.attr(node.name); 55// } 56// }); 57// return result as T; 58// } 59 60// function getAccessChainForNode(node: PyDsl): NodeChain { 61// const structuralChain = [...getStructuralChainForNode(node, new Set())]; 62// const accessChain = structuralToAccessChain(structuralChain); 63// if (accessChain.length === 0) { 64// // I _think_ this should not happen, but it does and this fix works for now. 65// // I assume this will cause issues with imports in some cases, investigate 66// // when it actually happens. 67// return [node.clone()]; 68// } 69// return accessChain.map((node) => node.clone()); 70// } 71 72// function getScope(node: PyDsl): NodeScope { 73// return node.scope ?? 'value'; 74// } 75 76// function getStructuralChainForNode(node: PyDsl, visited: Set<PyDsl>): NodeChain { 77// if (visited.has(node)) return []; 78// visited.add(node); 79 80// if (isStopNode(node)) return []; 81 82// if (node.structuralParents) { 83// for (const [parent] of node.structuralParents) { 84// if (getScope(parent) !== getScope(node)) continue; 85 86// const chain = getStructuralChainForNode(parent, visited); 87// if (chain.length > 0) return [...chain, node]; 88// } 89// } 90 91// if (!node.root) return []; 92 93// return [node]; 94// } 95 96// function isAccessorNode(node: PyDsl): boolean { 97// return ( 98// node['~dsl'] === 'FieldPyDsl' || 99// node['~dsl'] === 'GetterPyDsl' || 100// node['~dsl'] === 'MethodPyDsl' 101// ); 102// } 103 104// function isStopNode(node: PyDsl): boolean { 105// return node['~dsl'] === 'FuncPyDsl' || node['~dsl'] === 'TemplatePyDsl'; 106// } 107 108/** 109 * Fold a structural chain to an access chain by removing 110 * non-accessor nodes. 111 */ 112// function structuralToAccessChain(structuralChain: NodeChain): NodeChain { 113// const accessChain: Array<PyDsl> = []; 114// structuralChain.forEach((node, index) => { 115// // assume first node is always included 116// if (index === 0) { 117// accessChain.push(node); 118// } else if (isAccessorNode(node)) { 119// accessChain.push(node); 120// } 121// }); 122// return accessChain; 123// } 124 125// function transformAccessChain(accessChain: NodeChain, options: AccessOptions = {}): NodeChain { 126// return accessChain.map((node, index) => { 127// const transformedNode = options.transform?.(node, index, accessChain); 128// if (transformedNode) return transformedNode; 129// const accessNode = node.toAccessNode?.(node, options, { 130// chain: accessChain, 131// index, 132// isLeaf: index === accessChain.length - 1, 133// isRoot: index === 0, 134// length: accessChain.length, 135// }); 136// if (accessNode) return accessNode; 137// if (index === 0) { 138// if (node['~dsl'] === 'ClassPyDsl') { 139// const nextNode = accessChain[index + 1]; 140// if (nextNode && isAccessorNode(nextNode)) { 141// if ((nextNode as ReturnType<typeof $.field>).hasModifier('static')) { 142// return $(node.name); 143// } 144// } 145// return $.new(node.name).args(); 146// } 147// return $(node.name); 148// } 149// return node; 150// }); 151// } 152 153export class PyDslContext { 154 /** 155 * Build an expression for accessing the node. 156 * 157 * @param node - The node or symbol to build access for 158 * @param options - Access options 159 * @returns Expression for accessing the node 160 * 161 * @example 162 * ```ts 163 * ctx.access(node); // → Expression for accessing the node 164 * ``` 165 */ 166 // access<T = AccessResult>(node: PyDsl | Symbol<PyDsl>, options?: AccessOptions): T { 167 // const n = isSymbol(node) ? node.node : node; 168 // if (!n) { 169 // throw new Error(`Symbol ${node.name} is not resolved to a node.`); 170 // } 171 // const accessChain = getAccessChainForNode(n); 172 // const finalChain = transformAccessChain(accessChain, options); 173 // return accessChainToNode<T>(finalChain); 174 // } 175 /** 176 * Build an example. 177 * 178 * @param node - The node to generate an example for 179 * @param options - Example options 180 * @returns Full example string 181 * 182 * @example 183 * ```ts 184 * ctx.example(node, { moduleName: 'my-sdk' }); // → Full example string 185 * ``` 186 */ 187 // example( 188 // node: PyDsl, 189 // options?: ExampleOptions, 190 // astOptions?: Parameters<typeof PythonRenderer.astToString>[0], 191 // ): string { 192 // if (astOptions) { 193 // return PythonRenderer.astToString(astOptions); 194 // } 195 // options ||= {}; 196 // const accessChain = getAccessChainForNode(node); 197 // if (options.importName) { 198 // accessChain[0]!.name.set(options.importName); 199 // } 200 // const importNode = $(accessChain[0]!.name.toString()); // must store name before transform 201 // const finalChain = transformAccessChain(accessChain, { 202 // context: 'example', 203 // }); 204 // const setupNode = options.importSetup 205 // ? typeof options.importSetup === 'function' 206 // ? options.importSetup({ $, node: importNode }) 207 // : options.importSetup 208 // : (finalChain[0]! as PyDsl<py.Expression>); 209 // const setupName = options.setupName; 210 // let payload = typeof options.payload === 'function' ? options.payload({ $ }) : options.payload; 211 // payload = payload instanceof Array ? payload : payload ? [payload] : []; 212 // let nodes: Array<PyDsl> = []; 213 // if (setupName) { 214 // nodes = [ 215 // $.const(setupName).assign(setupNode), 216 // $.await(accessChainToNode([$(setupName), ...finalChain.slice(1)]).call(...payload)), 217 // ]; 218 // } else { 219 // nodes = [$.await(accessChainToNode([setupNode, ...finalChain.slice(1)]).call(...payload))]; 220 // } 221 // const localName = importNode.name.toString(); 222 // return PythonRenderer.astToString({ 223 // imports: [ 224 // [ 225 // { 226 // imports: 227 // !options.importKind || options.importKind === 'named' 228 // ? [ 229 // { 230 // isTypeOnly: false, 231 // localName, 232 // sourceName: localName, 233 // }, 234 // ] 235 // : [], 236 // isTypeOnly: false, 237 // kind: options.importKind ?? 'named', 238 // localName: options.importKind !== 'named' ? localName : undefined, 239 // modulePath: options.moduleName ?? 'your-package', 240 // }, 241 // ], 242 // ], 243 // nodes, 244 // trailingNewline: false, 245 // }); 246 // } 247} 248 249export const ctx = new PyDslContext();