<commit_before>
#if macro

import haxe.Json;
import haxe.macro.Context;
import haxe.macro.Expr;
import sys.FileSystem;
import sys.io.File;

using StringTools;
using haxe.macro.ComplexTypeTools;
using haxe.macro.MacroStringTools;
using haxe.macro.TypeTools;

class ElectronAPI {

	public static function generate( apiFile = 'electron-api.json', destination = 'src', clean = false, addDocumentation = true ) {

		if( !FileSystem.exists( apiFile ) )
			Context.fatalError( 'API description file [$apiFile] not found', Context.currentPos() );

		if( clean ) rmdir( destination );

		var items : Array<Item> = Json.parse( File.getContent( apiFile ) );
		var types = new Gen( ['electron'], addDocumentation ).process( items );
		var printer = new haxe.macro.Printer();
		for( tds in types ) {
			var type = tds[0];
			var code = printer.printTypeDefinition( type );
			if( tds.length > 1 ) {
				for( i in 1...tds.length ) {
					var e = tds[i];
					e.pack = [];
					code += '\n'+printer.printTypeDefinition( e );
				}
			}
			#if (haxe_ver < 4)
			var doc = getTypeDoc( type, items );
			if( doc != null ) {
				var lines = code.split( '\n' );
				code = lines.shift()+'\n';
				code += '/**'+doc+'\n**/\n';
				code += lines.join( '\n' );
			}
			#end
			var dir = destination + '/' + type.pack.join( '/' );
			if( !FileSystem.exists( dir ) ) FileSystem.createDirectory( dir );
			File.saveContent( '$dir/${type.name}.hx', '$code\n' );
		}
	}

	static function rmdir( path : String ) {
		if( FileSystem.exists( path ) ) {
			for( e in FileSystem.readDirectory( path ) ) {
				var p = '$path/$e';
				FileSystem.isDirectory( p ) ? rmdir( p ) : FileSystem.deleteFile( p );
			}
			FileSystem.deleteDirectory( path );
		}
	}

	#if (haxe_ver < 4)
	static function getTypeDoc( type : TypeDefinition, items : Array<Item> ) : String {
		for( item in items ) {
			if( item.name == type.name ) {
				var doc = '';
				if( item.description != null ) doc += '\n\t'+item.description;
				if( item.websiteUrl != null ) doc += '\n\t@see '+item.websiteUrl;
				return doc;
			}
		}
		return null;
	}
	#end
}

private class Gen {

	var root : Array<String>;
	var addDocumentation : Bool;
	var items : Array<Item>;
	var types = new Map<String,TypeDefinition>();
	var extraTypes = new Map<String,Array<TypeDefinition>>();

	public function new( ?root : Array<String>, addDocumentation = true ) {
		this.root = (root != null) ? root : [];
		this.addDocumentation = addDocumentation;
	}

	public function process( items : Array<Item> ) : Map<String,Array<TypeDefinition>> {

		this.items = items;

		// Pre patch
		this.types.set( 'Accelerator', {
			pack: root.copy(),
			name: 'Accelerator',
			kind: TDAbstract( macro:String, [macro:String], [macro:String] ),
			fields: [],
			pos: null
		} );

		for( item in items ) {
			this.types.set( item.name, processItem( item ) );
		}

		var map = new Map<String,Array<TypeDefinition>>();
		for( t in types )
			map.set( t.name, extraTypes.exists( t.name ) ? [t].concat( extraTypes.get( t.name ) ) : [t] );
		return map;
	}

	function processItem( item : Item, ?module : String ) : TypeDefinition {

		var type : TypeDefinition = {
			pack: getItemPack( item ),
			name: item.name,
			isExtern: item.type != Structure,
			kind: null,
			fields: [],
			meta: [],
			pos: null
		};

		if( addDocumentation ) {
			#if haxe4
			type.doc = '';
			if( item.description != null && item.description.length > 0 ) type.doc += item.description+'\n';
			type.doc += '@see '+item.websiteUrl;
			#end
		}

		switch item.type {
		case Class_:
			var sup : TypePath = if( item.instanceEvents == null ) null else {
				createEventEnumAbstract( type.name, type.pack, item.instanceEvents );
				{ pack: ['js','node','events'], name: 'EventEmitter', params: [TPType( TPath( { name: type.name, pack: type.pack } ) )] };
			}
			type.kind = TDClass( sup );
			var jsRequireName = item.name;
			if( module != null ) jsRequireName = module+'.'+jsRequireName;
			type.meta.push( { name: ':jsRequire', params: [macro $v{'electron'}, macro $v{jsRequireName}], pos: null } );
			if( item.staticMethods != null ) for( m in item.staticMethods ) type.fields.push( createFunField( m, [AStatic] ) );
			if( item.instanceProperties != null ) for( p in item.instanceProperties ) type.fields.push( createVarField( p ) );
			if( item.constructorMethod != null ) type.fields.push( createFunField( cast { name: 'new', parameters:  item.constructorMethod.parameters } ) );
			if( item.instanceMethods != null ) for( m in item.instanceMethods ) type.fields.push( createFunField( m ) );
			if( item.staticProperties != null ) {
				for( p in item.staticProperties ) {
					var t = processItem( cast p, type.name );
					if( !this.extraTypes.exists( type.name ) ) this.extraTypes.set( type.name, [t] );
					else this.extraTypes.get( type.name ).push( t );
				}
			}
			mergeTypeItem( type, item );

		case Module:
			type.name = capitalize( item.name );
			type.meta.push({ name: ':jsRequire', params: [macro $v{'electron'}, macro $v{item.name}], pos: null });
			var sup : TypePath = if( item.events == null ) null else {
				createEventEnumAbstract( type.name, type.pack, item.events );
				{ pack: ['js','node','events'], name: 'EventEmitter', params: [TPType( TPath( { name: type.name, pack: type.pack } ) )] };
			}
			type.kind = TDClass( sup );
			if( item.properties != null ) for( p in item.properties ) type.fields.push( createVarField( p, [AStatic] ) );
			if( item.methods != null ) for( m in item.methods ) type.fields.push( createFunField( m, [AStatic] ) );
			mergeTypeItem( type, item );

		case Structure:
			type.kind = TDStructure;
			if( item.properties != null ) for( p in item.properties ) type.fields.push( createVarField( p ) );

		case Element:
			type.name = capitalize( item.name );
			type.kind = TDClass({ pack: ['js','html'], name: 'Element' });
			type.meta.push( { name: ':native', params: [macro $v{item.name}], pos: null } );
			if( item.attributes != null ) for( a in item.attributes ) type.fields.push( createVarField( a ) );
			if( item.methods != null ) for( m in item.methods ) type.fields.push( createFunField( m ) );
			//TODO domEvents
			//if( item.domEvents != null ) {
		}

		// Post patch
		switch type.name {
		case 'App':
			//TODO:
			type.fields.push( {
				name: 'on',
				access: [AStatic],
				kind: FFun( { args: [
					{ name: 'eventType', type: macro : Dynamic },
					{ name: 'callback', type: macro : Dynamic->Void }
				], ret: macro : Void, expr: null } ),
				pos: null
			} );
		case 'Screen':
			// TODO: https://github.com/fponticelli/hxelectron/issues/29
			for( i in 0...type.meta.length ) {
				if( type.meta[i].name == ':jsRequire' ) {
					type.meta.splice( i, 1 );
					type.meta.push( { name: ':native', params: [macro 'require("electron").screen'], pos: null } );<commit_msg>Create @:overload for duplicate method definitions<commit_after>
#if macro

import haxe.Json;
import haxe.macro.Context;
import haxe.macro.Expr;
import sys.FileSystem;
import sys.io.File;

using StringTools;
using haxe.macro.ComplexTypeTools;
using haxe.macro.MacroStringTools;
using haxe.macro.TypeTools;

class ElectronAPI {

	public static function generate( apiFile = 'electron-api.json', destination = 'src', clean = false, addDocumentation = true ) {

		if( !FileSystem.exists( apiFile ) )
			Context.fatalError( 'API description file [$apiFile] not found', Context.currentPos() );

		if( clean ) rmdir( destination );

		var items : Array<Item> = Json.parse( File.getContent( apiFile ) );
		var types = new Gen( ['electron'], addDocumentation ).process( items );
		var printer = new haxe.macro.Printer();
		for( tds in types ) {
			var type = tds[0];
			var code = printer.printTypeDefinition( type );
			if( tds.length > 1 ) {
				for( i in 1...tds.length ) {
					var e = tds[i];
					e.pack = [];
					code += '\n'+printer.printTypeDefinition( e );
				}
			}
			#if (haxe_ver < 4)
			var doc = getTypeDoc( type, items );
			if( doc != null ) {
				var lines = code.split( '\n' );
				code = lines.shift()+'\n';
				code += '/**'+doc+'\n**/\n';
				code += lines.join( '\n' );
			}
			#end
			var dir = destination + '/' + type.pack.join( '/' );
			if( !FileSystem.exists( dir ) ) FileSystem.createDirectory( dir );
			File.saveContent( '$dir/${type.name}.hx', '$code\n' );
		}
	}

	static function rmdir( path : String ) {
		if( FileSystem.exists( path ) ) {
			for( e in FileSystem.readDirectory( path ) ) {
				var p = '$path/$e';
				FileSystem.isDirectory( p ) ? rmdir( p ) : FileSystem.deleteFile( p );
			}
			FileSystem.deleteDirectory( path );
		}
	}

	#if (haxe_ver < 4)
	static function getTypeDoc( type : TypeDefinition, items : Array<Item> ) : String {
		for( item in items ) {
			if( item.name == type.name ) {
				var doc = '';
				if( item.description != null ) doc += '\n\t'+item.description;
				if( item.websiteUrl != null ) doc += '\n\t@see '+item.websiteUrl;
				return doc;
			}
		}
		return null;
	}
	#end
}

private class Gen {

	var root : Array<String>;
	var addDocumentation : Bool;
	var items : Array<Item>;
	var types = new Map<String,TypeDefinition>();
	var extraTypes = new Map<String,Array<TypeDefinition>>();

	public function new( ?root : Array<String>, addDocumentation = true ) {
		this.root = (root != null) ? root : [];
		this.addDocumentation = addDocumentation;
	}

	public function process( items : Array<Item> ) : Map<String,Array<TypeDefinition>> {

		this.items = items;

		// Pre patch
		this.types.set( 'Accelerator', {
			pack: root.copy(),
			name: 'Accelerator',
			kind: TDAbstract( macro:String, [macro:String], [macro:String] ),
			fields: [],
			pos: null
		} );
		this.types.set( 'SaveDialogOptions', {
			pack: root.copy().concat(['main']),
			name: 'SaveDialogOptions',
			kind: TDAlias( macro : Dynamic ),
			fields: [],
			pos: null
		} );

		for( item in items ) {
			this.types.set( item.name, processItem( item ) );
		}

		var map = new Map<String,Array<TypeDefinition>>();
		for( t in types )
			map.set( t.name, extraTypes.exists( t.name ) ? [t].concat( extraTypes.get( t.name ) ) : [t] );
		return map;
	}

	function processItem( item : Item, ?module : String ) : TypeDefinition {

		var type : TypeDefinition = {
			pack: getItemPack( item ),
			name: item.name,
			isExtern: item.type != Structure,
			kind: null,
			fields: [],
			meta: [],
			pos: null
		};

		if( addDocumentation ) {
			#if haxe4
			type.doc = '';
			if( item.description != null && item.description.length > 0 ) type.doc += item.description+'\n';
			type.doc += '@see '+item.websiteUrl;
			#end
		}

		switch item.type {
		case Class_:
			var sup : TypePath = if( item.instanceEvents == null ) null else {
				createEventEnumAbstract( type.name, type.pack, item.instanceEvents );
				{ pack: ['js','node','events'], name: 'EventEmitter', params: [TPType( TPath( { name: type.name, pack: type.pack } ) )] };
			}
			type.kind = TDClass( sup );
			var jsRequireName = item.name;
			if( module != null ) jsRequireName = module+'.'+jsRequireName;
			type.meta.push( { name: ':jsRequire', params: [macro $v{'electron'}, macro $v{jsRequireName}], pos: null } );
			if( item.staticMethods != null ) for( m in item.staticMethods ) type.fields.push( createFunField( m, [AStatic] ) );
			if( item.instanceProperties != null ) for( p in item.instanceProperties ) type.fields.push( createVarField( p ) );
			if( item.constructorMethod != null ) type.fields.push( createFunField( cast { name: 'new', parameters:  item.constructorMethod.parameters } ) );
			if( item.instanceMethods != null ) for( m in item.instanceMethods ) type.fields.push( createFunField( m ) );
			if( item.staticProperties != null ) {
				for( p in item.staticProperties ) {
					var t = processItem( cast p, type.name );
					if( !this.extraTypes.exists( type.name ) ) this.extraTypes.set( type.name, [t] );
					else this.extraTypes.get( type.name ).push( t );
				}
			}
			mergeTypeItem( type, item );

		case Module:
			type.name = capitalize( item.name );
			type.meta.push({ name: ':jsRequire', params: [macro $v{'electron'}, macro $v{item.name}], pos: null });
			var sup : TypePath = if( item.events == null ) null else {
				createEventEnumAbstract( type.name, type.pack, item.events );
				{ pack: ['js','node','events'], name: 'EventEmitter', params: [TPType( TPath( { name: type.name, pack: type.pack } ) )] };
			}
			type.kind = TDClass( sup );
			if( item.properties != null ) for( p in item.properties ) type.fields.push( createVarField( p, [AStatic] ) );
			if( item.methods != null ) for( m in item.methods ) type.fields.push( createFunField( m, [AStatic] ) );
			mergeTypeItem( type, item );

		case Structure:
			type.kind = TDStructure;
			if( item.properties != null ) for( p in item.properties ) type.fields.push( createVarField( p ) );

		case Element:
			type.name = capitalize( item.name );
			type.kind = TDClass({ pack: ['js','html'], name: 'Element' });
			type.meta.push( { name: ':native', params: [macro $v{item.name}], pos: null } );
			if( item.attributes != null ) for( a in item.attributes ) type.fields.push( createVarField( a ) );
			if( item.methods != null ) for( m in item.methods ) type.fields.push( createFunField( m ) );
			//TODO domEvents
			//if( item.domEvents != null ) {
		}

		// Create @:overload for duplicate method definitions
		switch type.kind {
		case TDClass(superClass,interfaces,isInterface,isFinal):
			var i = 0;
			while( i < type.fields.length ) {
				var a = type.fields[i];
				var j = 0;
				while( j < type.fields.length ) {
					if( j != i ) {
						var b = type.fields[j];
						if( a.name == b.name ) {
							type.fields.splice( i, 1 );
							var expr : Expr;
							switch a.kind {
							case FFun(f):
								f.expr = { expr: EBlock([]), pos: Context.currentPos() };
								expr = { expr: EFunction( null, f ), pos: Context.currentPos() };
							default:
							}
							b.meta.push( { name: ':overload', params: [expr], pos: Context.currentPos() } );
						}
					}
					j++;
				}
				i++;
			}
		default:
		}

		// Post patch
		switch type.name {
		case 'App':
			//TODO:
			type.fields.push( {
				name: 'on',
				access: [AStatic],
				kind: FFun( { args: [
					{ name: 'eventType', type: macro : Dynamic },
					{ name: 'callback', type: macro : Dynamic->Void }
				], ret: macro : Void, expr: null } ),
				pos: null
			} );
		case 'Screen':
			// TODO: https://github.com/fponticelli/hxelectron/issues/29
			for( i in 0...type.meta.length ) {
				if( type.meta[i].name == ':jsRequire' ) {
					type.meta.splice( i, 1 );
					type.meta.push( { name: ':native', params: [macro 'require("electron").screen'], pos: null } );
{'fraction_non_alphanumeric': 0.16843039530279239, 'fraction_numerical': 0.0017068341639926265, 'mean_word_length': 3.1542824730572887, 'pattern_counts': {'":': 0, '<': 35, '<?xml version=': 0, '>': 35, 'https://': 2, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '3895342', 'n_tokens_mistral': 5768, 'n_tokens_neox': 4916, 'n_words': 1295}