int32 UGatherTextFromSourceCommandlet::Main( const FString& Params )
{
    // Parse command line - we're interested in the param vals
    TArray<FString> Tokens;
    TArray<FString> Switches;
    TMap<FString, FString> ParamVals;
    UCommandlet::ParseCommandLine(*Params, Tokens, Switches, ParamVals);

    //Set config file
    const FString* ParamVal = ParamVals.Find(FString(TEXT("Config")));
    FString GatherTextConfigPath;

    if ( ParamVal )
    {
        GatherTextConfigPath = *ParamVal;
    }
    else
    {
        UE_LOG(LogGatherTextFromSourceCommandlet, Error, TEXT("No config specified."));
        return -1;
    }

    //Set config section
    ParamVal = ParamVals.Find(FString(TEXT("Section")));
    FString SectionName;

    if ( ParamVal )
    {
        SectionName = *ParamVal;
    }
    else
    {
        UE_LOG(LogGatherTextFromSourceCommandlet, Error, TEXT("No config section specified."));
        return -1;
    }

    // SearchDirectoryPaths
    TArray<FString> SearchDirectoryPaths;
    GetPathArrayFromConfig(*SectionName, TEXT("SearchDirectoryPaths"), SearchDirectoryPaths, GatherTextConfigPath);

    // IncludePaths (DEPRECATED)
    {
        TArray<FString> IncludePaths;
        GetPathArrayFromConfig(*SectionName, TEXT("IncludePaths"), IncludePaths, GatherTextConfigPath);
        if (IncludePaths.Num())
        {
            SearchDirectoryPaths.Append(IncludePaths);
            UE_LOG(LogGatherTextFromSourceCommandlet, Warning, TEXT("IncludePaths detected in section %s. IncludePaths is deprecated, please use SearchDirectoryPaths."), *SectionName);
        }
    }

    if (SearchDirectoryPaths.Num() == 0)
    {
        UE_LOG(LogGatherTextFromSourceCommandlet, Error, TEXT("No search directory paths in section %s."), *SectionName);
        return -1;
    }

    // ExcludePathFilters
    TArray<FString> ExcludePathFilters;
    GetPathArrayFromConfig(*SectionName, TEXT("ExcludePathFilters"), ExcludePathFilters, GatherTextConfigPath);

    // ExcludePaths (DEPRECATED)
    {
        TArray<FString> ExcludePaths;
        GetPathArrayFromConfig(*SectionName, TEXT("ExcludePaths"), ExcludePaths, GatherTextConfigPath);
        if (ExcludePaths.Num())
        {
            ExcludePathFilters.Append(ExcludePaths);
            UE_LOG(LogGatherTextFromSourceCommandlet, Warning, TEXT("ExcludePaths detected in section %s. ExcludePaths is deprecated, please use ExcludePathFilters."), *SectionName);
        }
    }

    // FileNameFilters
    TArray<FString> FileNameFilters;
    GetStringArrayFromConfig(*SectionName, TEXT("FileNameFilters"), FileNameFilters, GatherTextConfigPath);

    // SourceFileSearchFilters (DEPRECATED)
    {
        TArray<FString> SourceFileSearchFilters;
        GetStringArrayFromConfig(*SectionName, TEXT("SourceFileSearchFilters"), SourceFileSearchFilters, GatherTextConfigPath);
        if (SourceFileSearchFilters.Num())
        {
            FileNameFilters.Append(SourceFileSearchFilters);
            UE_LOG(LogGatherTextFromSourceCommandlet, Warning, TEXT("SourceFileSearchFilters detected in section %s. SourceFileSearchFilters is deprecated, please use FileNameFilters."), *SectionName);
        }
    }

    if (FileNameFilters.Num() == 0)
    {
        UE_LOG(LogGatherTextFromSourceCommandlet, Error, TEXT("No source filters in section %s"), *SectionName);
        return -1;
    }

    //Ensure all filters are unique.
    TArray<FString> UniqueSourceFileSearchFilters;
    for (const FString& SourceFileSearchFilter : FileNameFilters)
    {
        UniqueSourceFileSearchFilters.AddUnique(SourceFileSearchFilter);
    }

    // Search in the root folder for each of the wildcard filters specified and build a list of files
    TArray<FString> AllFoundFiles;

    for (FString& SearchDirectoryPath : SearchDirectoryPaths)
    {
        for (const FString& UniqueSourceFileSearchFilter : UniqueSourceFileSearchFilters)
        {
            TArray<FString> RootSourceFiles;

            IFileManager::Get().FindFilesRecursive(RootSourceFiles, *SearchDirectoryPath, *UniqueSourceFileSearchFilter, true, false,false);

            for (FString& RootSourceFile : RootSourceFiles)
            {
                if (FPaths::IsRelative(RootSourceFile))
                {
                    RootSourceFile = FPaths::ConvertRelativePathToFull(RootSourceFile);
                }
            }

            AllFoundFiles.Append(RootSourceFiles);
        }
    }

    TArray<FString> FilesToProcess;
    TArray<FString> RemovedList;

    //Run through all the files found and add any that pass the exclude and filter constraints to PackageFilesToProcess
    for (const FString& FoundFile : AllFoundFiles)
    {
        bool bExclude = false;

        //Ensure it does not match the exclude paths if there are some.
        for (FString& ExcludePath : ExcludePathFilters)
        {
            if (FoundFile.MatchesWildcard(ExcludePath) )
            {
                bExclude = true;
                RemovedList.Add(FoundFile);
                break;
            }
        }

        //If we haven't failed any checks, add it to the array of files to process.
        if( !bExclude )
        {
            FilesToProcess.Add(FoundFile);
        }
    }

    // Return if no source files were found
    if( FilesToProcess.Num() == 0 )
    {
        FString SpecifiedDirectoriesString;
        for (FString& SearchDirectoryPath : SearchDirectoryPaths)
        {
            SpecifiedDirectoriesString.Append(FString(SpecifiedDirectoriesString.IsEmpty() ? TEXT("") : TEXT("\n")) + FString::Printf(TEXT("+ %s"), *SearchDirectoryPath));
        }
        for (FString& ExcludePath : ExcludePathFilters)
        {
            SpecifiedDirectoriesString.Append(FString(SpecifiedDirectoriesString.IsEmpty() ? TEXT("") : TEXT("\n")) + FString::Printf(TEXT("- %s"), *ExcludePath));
        }

        FString SourceFileSearchFiltersString;
        for (const auto& Filter : UniqueSourceFileSearchFilters)
        {
            SourceFileSearchFiltersString += FString(SourceFileSearchFiltersString.IsEmpty() ? TEXT("") : TEXT(", ")) + Filter;
        }

        UE_LOG(LogGatherTextFromSourceCommandlet, Error, TEXT("The GatherTextFromSource commandlet couldn't find any source files matching (%s) in the specified directories:\n%s"), *SourceFileSearchFiltersString, *SpecifiedDirectoriesString);
        return -1;
    }

    // Add any manifest dependencies if they were provided
    TArray<FString> ManifestDependenciesList;
    GetPathArrayFromConfig(*SectionName, TEXT("ManifestDependencies"), ManifestDependenciesList, GatherTextConfigPath);


    if( !ManifestInfo->AddManifestDependencies( ManifestDependenciesList ) )
    {
        UE_LOG(LogGatherTextFromSourceCommandlet, Error, TEXT("The GatherTextFromSource commandlet couldn't find all the specified manifest dependencies."));
        return -1;
    }

    // Get the loc macros and their syntax
    TArray<FParsableDescriptor*> Parsables;

    Parsables.Add(new FDefineDescriptor());

    Parsables.Add(new FUndefDescriptor());

    Parsables.Add(new FCommandMacroDescriptor());

    // New Localization System with Namespace as literal argument.
    Parsables.Add(new FStringMacroDescriptor( FString(TEXT("NSLOCTEXT")),
                  FMacroDescriptor::FMacroArg(FMacroDescriptor::MAS_Namespace, true),
                  FMacroDescriptor::FMacroArg(FMacroDescriptor::MAS_Identifier, true),
                  FMacroDescriptor::FMacroArg(FMacroDescriptor::MAS_SourceText, true)));

    // New Localization System with Namespace as preprocessor define.
    Parsables.Add(new FStringMacroDescriptor( FString(TEXT("LOCTEXT")),
                  FMacroDescriptor::FMacroArg(FMacroDescriptor::MAS_Identifier, true),
                  FMacroDescriptor::FMacroArg(FMacroDescriptor::MAS_SourceText, true)));

    Parsables.Add(new FIniNamespaceDescriptor());

    // Init a parse context to track the state of the file parsing
    FSourceFileParseContext ParseCtxt;
    ParseCtxt.ManifestInfo = ManifestInfo;

    // Parse all source files for macros and add entries to SourceParsedEntries
    for ( FString& SourceFile : FilesToProcess)
    {
        FString ProjectBasePath;
        if (!FPaths::GameDir().IsEmpty())
        {
            ProjectBasePath = FPaths::GameDir();
        }
        else
        {
            ProjectBasePath = FPaths::EngineDir();
        }

        ParseCtxt.Filename = SourceFile;
        FPaths::MakePathRelativeTo(ParseCtxt.Filename, *ProjectBasePath);
        ParseCtxt.LineNumber = 0;
        ParseCtxt.LineText.Empty();
        ParseCtxt.Namespace.Empty();
        ParseCtxt.ExcludedRegion = false;
        ParseCtxt.WithinBlockComment = false;
        ParseCtxt.WithinLineComment = false;
        ParseCtxt.WithinStringLiteral = false;
        ParseCtxt.WithinNamespaceDefine = false;
        ParseCtxt.WithinStartingLine.Empty();

        FString SourceFileText;
        if (!FFileHelper::LoadFileToString(SourceFileText, *SourceFile))
        {
            UE_LOG(LogGatherTextFromSourceCommandlet, Error, TEXT("GatherTextSource failed to open file %s"), *ParseCtxt.Filename);
        }
        else
        {
            if (!ParseSourceText(SourceFileText, Parsables, ParseCtxt))
            {
                UE_LOG(LogGatherTextFromSourceCommandlet, Warning, TEXT("GatherTextSource error(s) parsing source file %s"), *ParseCtxt.Filename);
            }
            else
            {
                if (ParseCtxt.WithinNamespaceDefine == true)
                {
                    UE_LOG(LogGatherTextFromSourceCommandlet, Warning, TEXT("Non-matching LOCTEXT_NAMESPACE defines found in %s"), *ParseCtxt.Filename);
                }
            }
        }
    }

    // Clear parsables list safely
    for (int32 i=0; i<Parsables.Num(); i++)
    {
        delete Parsables[i];
    }

    return 0;
}
int32 UGatherTextFromSourceCommandlet::Main( const FString& Params )
{
	// Parse command line - we're interested in the param vals
	TArray<FString> Tokens;
	TArray<FString> Switches;
	TMap<FString, FString> ParamVals;
	UCommandlet::ParseCommandLine(*Params, Tokens, Switches, ParamVals);

	//Set config file
	const FString* ParamVal = ParamVals.Find(FString(TEXT("Config")));
	FString GatherTextConfigPath;
	
	if ( ParamVal )
	{
		GatherTextConfigPath = *ParamVal;
	}
	else
	{
		UE_LOG(LogGatherTextFromSourceCommandlet, Error, TEXT("No config specified."));
		return -1;
	}

	//Set config section
	ParamVal = ParamVals.Find(FString(TEXT("Section")));
	FString SectionName;

	if ( ParamVal )
	{
		SectionName = *ParamVal;
	}
	else
	{
		UE_LOG(LogGatherTextFromSourceCommandlet, Error, TEXT("No config section specified."));
		return -1;
	}


	//Include paths
	TArray<FString> IncludePaths;
	GetConfigArray(*SectionName, TEXT("IncludePaths"), IncludePaths, GatherTextConfigPath);

	if (IncludePaths.Num() == 0)
	{
		UE_LOG(LogGatherTextFromSourceCommandlet, Error, TEXT("No include paths in section %s"), *SectionName);
		return -1;
	}

	//Exclude paths
	TArray<FString> ExcludePaths;
	GetConfigArray(*SectionName, TEXT("ExcludePaths"), ExcludePaths, GatherTextConfigPath);

	// Check the config section for source filters. e.g. *.cpp
	TArray<FString> SourceFileSearchFilters;
	GetConfigArray(*SectionName, TEXT("SourceFileSearchFilters"), SourceFileSearchFilters, GatherTextConfigPath);

	if (SourceFileSearchFilters.Num() == 0)
	{
		UE_LOG(LogGatherTextFromSourceCommandlet, Error, TEXT("No source filters in section %s"), *SectionName);
		return -1;
	}

	//Ensure all filters are unique.
	TArray<FString> UniqueSourceFileSearchFilters;
	for (int32 Idx=0; Idx<SourceFileSearchFilters.Num(); Idx++)
	{
		UniqueSourceFileSearchFilters.AddUnique(SourceFileSearchFilters[Idx]);
	}

	// Search in the root folder for each of the wildcard filters specified and build a list of files
	TArray<FString> AllFoundFiles;

	for(int32 i = 0; i < IncludePaths.Num(); i++)
	{
		for (int32 SourceFileSearchIdx=0; SourceFileSearchIdx < UniqueSourceFileSearchFilters.Num(); SourceFileSearchIdx++)
		{
			TArray<FString> RootSourceFiles;

			IFileManager::Get().FindFilesRecursive(RootSourceFiles, *(FPaths::RootDir() + IncludePaths[i]), *UniqueSourceFileSearchFilters[SourceFileSearchIdx], true, false,false);

			AllFoundFiles.Append(RootSourceFiles);
		}
	}

	TArray<FString> FilesToProcess;
	TArray<FString> RemovedList;

	//Run through all the files found and add any that pass the include, exclude and filter constraints to PackageFilesToProcess
	for( int32 FileIdx=0; FileIdx < AllFoundFiles.Num(); ++FileIdx )
	{
		bool bExclude = false;

		//Ensure it does not match the exclude paths if there are some.
		for( int32 ExcludePathIdx=0; ExcludePathIdx < ExcludePaths.Num() ; ++ExcludePathIdx )
		{
			if( AllFoundFiles[FileIdx].MatchesWildcard( ExcludePaths[ExcludePathIdx] ) )
			{
				bExclude = true;
				RemovedList.Add( AllFoundFiles[FileIdx] );
				break;
			}
		}

		//If we haven't failed any checks, add it to the array of files to process.
		if( !bExclude )
		{
			FilesToProcess.Add( AllFoundFiles[FileIdx] );
		}
	}
	
	// Return if no source files were found
	if( FilesToProcess.Num() == 0 )
	{
		UE_LOG(LogGatherTextFromSourceCommandlet, Error, TEXT("The GatherTextFromSource commandlet couldn't find any source files in the specified directories."));
		return -1;
	}

	// Add any manifest dependencies if they were provided
	TArray<FString> ManifestDependenciesList;
	GetConfigArray(*SectionName, TEXT("ManifestDependencies"), ManifestDependenciesList, GatherTextConfigPath);
	
	if( !ManifestInfo->AddManifestDependencies( ManifestDependenciesList ) )
	{
		UE_LOG(LogGatherTextFromSourceCommandlet, Error, TEXT("The GatherTextFromSource commandlet couldn't find all the specified manifest dependencies."));
		return -1;
	}

	// Get the loc macros and their syntax
	TArray<FParsableDescriptor*> Parsables;

	Parsables.Add(new FDefineDescriptor());

	Parsables.Add(new FUndefDescriptor());

	Parsables.Add(new FCommandMacroDescriptor());

	// New Localization System with Namespace as literal argument.
	Parsables.Add(new FStringMacroDescriptor( FString(TEXT("NSLOCTEXT")),
		FMacroDescriptor::FMacroArg(FMacroDescriptor::MAS_Namespace, true),
		FMacroDescriptor::FMacroArg(FMacroDescriptor::MAS_Identifier, true),
		FMacroDescriptor::FMacroArg(FMacroDescriptor::MAS_SourceText, true)));
	
	// New Localization System with Namespace as preprocessor define.
	Parsables.Add(new FStringMacroDescriptor( FString(TEXT("LOCTEXT")),
		FMacroDescriptor::FMacroArg(FMacroDescriptor::MAS_Identifier, true),
		FMacroDescriptor::FMacroArg(FMacroDescriptor::MAS_SourceText, true)));

	Parsables.Add(new FIniNamespaceDescriptor());

	// Init a parse context to track the state of the file parsing 
	FSourceFileParseContext ParseCtxt;
	ParseCtxt.ManifestInfo = ManifestInfo;

	// Parse all source files for macros and add entries to SourceParsedEntries
	for (int32 SourceFileIdx=0; SourceFileIdx < FilesToProcess.Num(); SourceFileIdx++)
	{
		ParseCtxt.Filename = FilesToProcess[SourceFileIdx];
		ParseCtxt.LineNumber = 0;
		ParseCtxt.ExcludedRegion = false;
		ParseCtxt.WithinBlockComment = false;
		ParseCtxt.WithinLineComment = false;

		FString SourceFileText;
		if (!FFileHelper::LoadFileToString(SourceFileText, *ParseCtxt.Filename))
		{
			UE_LOG(LogGatherTextFromSourceCommandlet, Error, TEXT("GatherTextSource failed to open file %s"), *ParseCtxt.Filename);
		}
		else
		{
			if (!ParseSourceText(SourceFileText, Parsables, ParseCtxt))
			{
				UE_LOG(LogGatherTextFromSourceCommandlet, Warning, TEXT("GatherTextSource error(s) parsing source file %s"), *ParseCtxt.Filename);
			}
		}
	}
	
	// Clear parsables list safely
	for (int32 i=0; i<Parsables.Num(); i++)
	{
		delete Parsables[i];
	}

	return 0;
}