@@ -11,7 +11,7 @@ type ExportsOptions = {
1111 preferExportAll ?: boolean ;
1212} ;
1313type Header = MaybeArray < string > | null | undefined ;
14- type Imports = ReadonlyArray < ReadonlyArray < ModuleImport > > ;
14+ type Imports = Array < ReadonlyArray < ModuleImport > > ;
1515
1616function headerToLines ( header : Header ) : ReadonlyArray < string > {
1717 if ( ! header ) return [ ] ;
@@ -69,7 +69,7 @@ export class PythonRenderer implements Renderer {
6969 render ( ctx : RenderContext < PyDsl > ) : string {
7070 const header = typeof this . _header === 'function' ? this . _header ( ctx ) : this . _header ;
7171 return PythonRenderer . astToString ( {
72- // exports: this.getExports(ctx),
72+ exports : this . getExports ( ctx ) ,
7373 exportsOptions : {
7474 preferExportAll : this . _preferExportAll ,
7575 } ,
@@ -101,31 +101,62 @@ export class PythonRenderer implements Renderer {
101101 text += `${ header } \n` ;
102102 }
103103
104+ const argsImports = args . imports ?? [ ] ;
105+
106+ for ( const group of args . exports ?? [ ] ) {
107+ for ( const exp of group ) {
108+ let found = false ;
109+ for ( const impGroup of argsImports ) {
110+ if ( found ) break ;
111+ for ( const imp of impGroup ) {
112+ if ( imp . modulePath === exp . modulePath ) {
113+ // TODO: merge imports and exports from the same module
114+ found = true ;
115+ break ;
116+ }
117+ }
118+ }
119+ if ( ! found ) {
120+ argsImports . push ( [
121+ {
122+ imports : exp . exports . map ( ( exp ) => ( {
123+ isTypeOnly : exp . isTypeOnly ,
124+ localName : exp . exportedName ,
125+ sourceName : exp . exportedName ,
126+ } ) ) ,
127+ isTypeOnly : false ,
128+ kind : 'named' ,
129+ modulePath : exp . modulePath ,
130+ } ,
131+ ] ) ;
132+ }
133+ }
134+ }
135+
104136 let imports = '' ;
105- for ( const group of args . imports ?? [ ] ) {
137+ for ( const group of argsImports ) {
106138 if ( imports ) imports += '\n' ;
107139 for ( const imp of group ) {
108- imports += `${ astToString ( PythonRenderer . toImportAst ( imp ) ) } \n ` ;
140+ imports += `${ astToString ( PythonRenderer . toImportAst ( imp ) ) } ` ;
109141 }
110142 }
111143 text = `${ text } ${ text && imports ? '\n' : '' } ${ imports } ` ;
112144
145+ let exports = '' ;
146+ for ( const group of args . exports ?? [ ] ) {
147+ if ( exports ) exports += '\n' ;
148+ for ( const exp of group ) {
149+ exports += `${ astToString ( PythonRenderer . toExportAst ( exp , args . exportsOptions ) ) } ` ;
150+ }
151+ }
152+ text = `${ text } ${ text && exports ? '\n' : '' } ${ exports } ` ;
153+
113154 let nodes = '' ;
114155 for ( const node of args . nodes ?? [ ] ) {
115- if ( nodes ) nodes += '\n' ;
116- nodes += `${ astToString ( node . toAst ( ) ) } \n ` ;
156+ if ( nodes ) nodes += '\n\n ' ;
157+ nodes += `${ astToString ( node . toAst ( ) ) } ` ;
117158 }
118- text = `${ text } ${ text && nodes ? '\n' : '' } ${ nodes } ` ;
119-
120- const exports = '' ;
121- // let exports = '';
122- // for (const group of args.exports ?? []) {
123- // if ((!exports && nodes) || exports) exports += '\n';
124- // for (const exp of group) {
125- // exports += `${astToString(PythonRenderer.toExportAst(exp, args.exportsOptions))}\n`;
126- // }
127- // }
128- text = `${ text } ${ text && exports ? '\n' : '' } ${ exports } ` ;
159+ text = `${ text } ${ text && nodes ? '\n\n' : '' } ${ nodes } ` ;
129160
130161 if ( args . trailingNewline === false && text . endsWith ( '\n' ) ) {
131162 text = text . slice ( 0 , - 1 ) ;
@@ -134,27 +165,15 @@ export class PythonRenderer implements Renderer {
134165 return text ;
135166 }
136167
137- // static toExportAst(group: ModuleExport, options?: ExportsOptions): ts.ExportDeclaration {
138- // const specifiers = group.exports.map((exp) => {
139- // const specifier = ts.factory.createExportSpecifier(
140- // exp.isTypeOnly,
141- // exp.sourceName !== exp.exportedName ? $.id(exp.sourceName).toAst() : undefined,
142- // $.id(exp.exportedName).toAst(),
143- // );
144- // return specifier;
145- // });
146- // const exportClause = group.namespaceExport
147- // ? ts.factory.createNamespaceExport($.id(group.namespaceExport).toAst())
148- // : (!group.canExportAll || !options?.preferExportAll) && specifiers.length
149- // ? ts.factory.createNamedExports(specifiers)
150- // : undefined;
151- // return ts.factory.createExportDeclaration(
152- // undefined,
153- // group.isTypeOnly,
154- // exportClause,
155- // $.literal(group.modulePath).toAst(),
156- // );
157- // }
168+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
169+ static toExportAst ( group : ModuleExport , options ?: ExportsOptions ) : py . Assignment {
170+ const specifiers = group . exports . map ( ( exp ) => py . factory . createLiteral ( exp . exportedName ) ) ;
171+ return py . factory . createAssignment (
172+ py . factory . createIdentifier ( '__all__' ) ,
173+ undefined ,
174+ py . factory . createListExpression ( specifiers ) ,
175+ ) ;
176+ }
158177
159178 static toImportAst ( group : ModuleImport ) : py . ImportStatement {
160179 const names : Array < {
@@ -167,71 +186,71 @@ export class PythonRenderer implements Renderer {
167186 return py . factory . createImportStatement ( group . modulePath , names , group . imports . length > 0 ) ;
168187 }
169188
170- // private getExports(ctx: RenderContext): Exports {
171- // type ModuleEntry = {
172- // group: ModuleExport;
173- // sortKey: SortKey;
174- // };
175-
176- // const groups = new Map<SortGroup, Map<SortModule, ModuleEntry>>();
177-
178- // for (const exp of ctx.file.exports) {
179- // const sortKey = moduleSortKey({
180- // file: ctx.file,
181- // fromFile: exp.from,
182- // preferFileExtension: this._preferFileExtension,
183- // root: ctx.project.root,
184- // });
185- // const modulePath = this._resolveModuleName?.(sortKey[2]) ?? sortKey[2];
186- // const [groupIndex] = sortKey;
187-
188- // if (!groups.has(groupIndex)) groups.set(groupIndex, new Map());
189- // const moduleMap = groups.get(groupIndex)!;
190-
191- // if (!moduleMap.has(modulePath)) {
192- // moduleMap.set(modulePath, {
193- // group: {
194- // canExportAll: exp.canExportAll,
195- // exports: exp.exports,
196- // isTypeOnly: exp.isTypeOnly,
197- // modulePath,
198- // namespaceExport: exp.namespaceExport,
199- // },
200- // sortKey,
201- // });
202- // }
203- // }
204-
205- // const exports: Array<Array<ModuleExport>> = Array.from(groups.entries())
206- // .sort((a, b) => a[0] - b[0])
207- // .map(([, moduleMap]) => {
208- // const entries = Array.from(moduleMap.values());
209-
210- // entries.sort((a, b) => {
211- // const d = a.sortKey[1] - b.sortKey[1];
212- // return d !== 0 ? d : a.group.modulePath.localeCompare(b.group.modulePath);
213- // });
214-
215- // return entries.map((e) => {
216- // const group = e.group;
217- // if (group.namespaceExport) {
218- // group.exports = [];
219- // } else {
220- // const isTypeOnly = !group.exports.find((exp) => !exp.isTypeOnly);
221- // if (isTypeOnly) {
222- // group.isTypeOnly = true;
223- // for (const exp of group.exports) {
224- // exp.isTypeOnly = false;
225- // }
226- // }
227- // group.exports.sort((a, b) => a.exportedName.localeCompare(b.exportedName));
228- // }
229- // return group;
230- // });
231- // });
232-
233- // return exports;
234- // }
189+ private getExports ( ctx : RenderContext ) : Exports {
190+ type ModuleEntry = {
191+ group : ModuleExport ;
192+ sortKey : SortKey ;
193+ } ;
194+
195+ const groups = new Map < SortGroup , Map < SortModule , ModuleEntry > > ( ) ;
196+
197+ for ( const exp of ctx . file . exports ) {
198+ const sortKey = moduleSortKey ( {
199+ file : ctx . file ,
200+ fromFile : exp . from ,
201+ preferFileExtension : this . _preferFileExtension ,
202+ root : ctx . project . root ,
203+ } ) ;
204+ const modulePath = this . _resolveModuleName ?.( sortKey [ 2 ] ) ?? sortKey [ 2 ] ;
205+ const [ groupIndex ] = sortKey ;
206+
207+ if ( ! groups . has ( groupIndex ) ) groups . set ( groupIndex , new Map ( ) ) ;
208+ const moduleMap = groups . get ( groupIndex ) ! ;
209+
210+ if ( ! moduleMap . has ( modulePath ) ) {
211+ moduleMap . set ( modulePath , {
212+ group : {
213+ canExportAll : exp . canExportAll ,
214+ exports : exp . exports ,
215+ isTypeOnly : exp . isTypeOnly ,
216+ modulePath,
217+ namespaceExport : exp . namespaceExport ,
218+ } ,
219+ sortKey,
220+ } ) ;
221+ }
222+ }
223+
224+ const exports : Array < Array < ModuleExport > > = Array . from ( groups . entries ( ) )
225+ . sort ( ( a , b ) => a [ 0 ] - b [ 0 ] )
226+ . map ( ( [ , moduleMap ] ) => {
227+ const entries = Array . from ( moduleMap . values ( ) ) ;
228+
229+ entries . sort ( ( a , b ) => {
230+ const d = a . sortKey [ 1 ] - b . sortKey [ 1 ] ;
231+ return d !== 0 ? d : a . group . modulePath . localeCompare ( b . group . modulePath ) ;
232+ } ) ;
233+
234+ return entries . map ( ( e ) => {
235+ const group = e . group ;
236+ if ( group . namespaceExport ) {
237+ group . exports = [ ] ;
238+ } else {
239+ const isTypeOnly = ! group . exports . find ( ( exp ) => ! exp . isTypeOnly ) ;
240+ if ( isTypeOnly ) {
241+ group . isTypeOnly = true ;
242+ for ( const exp of group . exports ) {
243+ exp . isTypeOnly = false ;
244+ }
245+ }
246+ group . exports . sort ( ( a , b ) => a . exportedName . localeCompare ( b . exportedName ) ) ;
247+ }
248+ return group ;
249+ } ) ;
250+ } ) ;
251+
252+ return exports ;
253+ }
235254
236255 private getImports ( ctx : RenderContext ) : Imports {
237256 type ModuleEntry = {
0 commit comments