|
- /*
- ==============================================================================
-
- This file is part of the JUCE library - "Jules' Utility Class Extensions"
- Copyright 2004-9 by Raw Material Software Ltd.
-
- ------------------------------------------------------------------------------
-
- JUCE can be redistributed and/or modified under the terms of the GNU General
- Public License (Version 2), as published by the Free Software Foundation.
- A copy of the license is included in the JUCE distribution, or can be found
- online at www.gnu.org/licenses.
-
- JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
- WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
- A PARTICULAR PURPOSE. See the GNU General Public License for more details.
-
- ------------------------------------------------------------------------------
-
- To release a closed-source product which uses JUCE, commercial licenses are
- available: visit www.rawmaterialsoftware.com/juce for more information.
-
- ==============================================================================
- */
-
- #include "../JuceLibraryCode/JuceHeader.h"
-
-
- //==============================================================================
- static const char* newLine = "\n";
-
- static bool matchesWildcard (const String& filename, const StringArray& wildcards)
- {
- for (int i = wildcards.size(); --i >= 0;)
- if (filename.matchesWildcard (wildcards[i], true))
- return true;
-
- return false;
- }
-
- static bool canFileBeReincluded (const File& f)
- {
- String content (f.loadFileAsString());
-
- for (;;)
- {
- content = content.trimStart();
-
- if (content.startsWith ("//"))
- content = content.fromFirstOccurrenceOf ("\n", false, false);
- else if (content.startsWith ("/*"))
- content = content.fromFirstOccurrenceOf ("*/", false, false);
- else
- break;
- }
-
- StringArray lines;
- lines.addLines (content);
- lines.trim();
- lines.removeEmptyStrings();
-
- const String l1 (lines[0].removeCharacters (" \t").trim());
- const String l2 (lines[1].removeCharacters (" \t").trim());
-
- if (l1.replace ("#ifndef", "#define") == l2)
- return false;
-
- return true;
- }
-
- static int64 calculateStreamHashCode (InputStream& in)
- {
- int64 t = 0;
-
- const int bufferSize = 4096;
- HeapBlock <uint8> buffer;
- buffer.malloc (bufferSize);
-
- for (;;)
- {
- const int num = in.read (buffer, bufferSize);
-
- if (num <= 0)
- break;
-
- for (int i = 0; i < num; ++i)
- t = t * 65599 + buffer[i];
- }
-
- return t;
- }
-
- static int64 calculateFileHashCode (const File& file)
- {
- ScopedPointer <FileInputStream> stream (file.createInputStream());
- return stream != 0 ? calculateStreamHashCode (*stream) : 0;
- }
-
-
- //==============================================================================
- static bool parseFile (const File& rootFolder,
- const File& newTargetFile,
- OutputStream& dest,
- const File& file,
- StringArray& alreadyIncludedFiles,
- const StringArray& includesToIgnore,
- const StringArray& wildcards,
- bool isOuterFile,
- bool stripCommentBlocks)
- {
- if (! file.exists())
- {
- std::cout << "!! ERROR - file doesn't exist!";
- return false;
- }
-
- StringArray lines;
- lines.addLines (file.loadFileAsString());
-
- if (lines.size() == 0)
- {
- std::cout << "!! ERROR - input file was empty: " << file.getFullPathName();
- return false;
- }
-
- bool lastLineWasBlank = true;
-
- for (int i = 0; i < lines.size(); ++i)
- {
- String line (lines[i]);
- String trimmed (line.trimStart());
-
- if ((! isOuterFile) && trimmed.startsWith ("//================================================================"))
- line = String::empty;
-
- if (trimmed.startsWithChar ('#')
- && trimmed.removeCharacters (" \t").startsWithIgnoreCase ("#include\""))
- {
- const int endOfInclude = line.indexOfChar (line.indexOfChar ('\"') + 1, '\"') + 1;
- const String lineUpToEndOfInclude (line.substring (0, endOfInclude));
- const String lineAfterInclude (line.substring (endOfInclude));
-
- const String filename (line.fromFirstOccurrenceOf ("\"", false, false)
- .upToLastOccurrenceOf ("\"", false, false));
- const File targetFile (file.getSiblingFile (filename));
-
- if (targetFile.exists() && targetFile.isAChildOf (rootFolder))
- {
- if (matchesWildcard (filename.replaceCharacter ('\\', '/'), wildcards)
- && ! includesToIgnore.contains (targetFile.getFileName()))
- {
- if (line.containsIgnoreCase ("FORCE_AMALGAMATOR_INCLUDE")
- || ! alreadyIncludedFiles.contains (targetFile.getFullPathName()))
- {
- if (! canFileBeReincluded (targetFile))
- alreadyIncludedFiles.add (targetFile.getFullPathName());
-
- dest << newLine << "/*** Start of inlined file: " << targetFile.getFileName() << " ***/" << newLine;
-
- if (! parseFile (rootFolder, newTargetFile,
- dest, targetFile, alreadyIncludedFiles, includesToIgnore,
- wildcards, false, stripCommentBlocks))
- {
- return false;
- }
-
- dest << "/*** End of inlined file: " << targetFile.getFileName() << " ***/" << newLine << newLine;
-
- line = lineAfterInclude;
- }
- else
- {
- line = String::empty;
- }
- }
- else
- {
- line = lineUpToEndOfInclude.upToFirstOccurrenceOf ("\"", true, false)
- + targetFile.getRelativePathFrom (newTargetFile.getParentDirectory())
- .replaceCharacter ('\\', '/')
- + "\""
- + lineAfterInclude;
- }
- }
- }
-
- if ((stripCommentBlocks || i == 0) && trimmed.startsWith ("/*") && (i > 10 || ! isOuterFile))
- {
- int originalI = i;
- String originalLine = line;
-
- for (;;)
- {
- int end = line.indexOf ("*/");
-
- if (end >= 0)
- {
- line = line.substring (end + 2);
-
- // If our comment appeared just before an assertion, leave it in, as it
- // might be useful..
- if (lines [i + 1].contains ("assert")
- || lines [i + 2].contains ("assert"))
- {
- i = originalI;
- line = originalLine;
- }
-
- break;
- }
-
- line = lines [++i];
-
- if (i >= lines.size())
- break;
- }
-
- line = line.trimEnd();
- if (line.isEmpty())
- continue;
- }
-
- line = line.trimEnd();
-
- {
- // Turn initial spaces into tabs..
- int numIntialSpaces = 0;
- int len = line.length();
- while (numIntialSpaces < len && line [numIntialSpaces] == ' ')
- ++numIntialSpaces;
-
- if (numIntialSpaces > 0)
- {
- int tabSize = 4;
- int numTabs = numIntialSpaces / tabSize;
- line = String::repeatedString ("\t", numTabs) + line.substring (numTabs * tabSize);
- }
-
- if (! line.containsChar ('"'))
- {
- // turn large areas of spaces into tabs - this will mess up alignment a bit, but
- // it's only the amalgamated file, so doesn't matter...
- line = line.replace (" ", "\t", false);
- line = line.replace (" ", "\t", false);
- }
- }
-
- if (line.isNotEmpty() || ! lastLineWasBlank)
- dest << line << newLine;
-
- lastLineWasBlank = line.isEmpty();
- }
-
- return true;
- }
-
- //==============================================================================
- static bool munge (const File& templateFile, const File& targetFile, const String& wildcard,
- StringArray& alreadyIncludedFiles, const StringArray& includesToIgnore)
- {
- if (! templateFile.existsAsFile())
- {
- std::cout << " The template file doesn't exist!\n\n";
- return false;
- }
-
- StringArray wildcards;
- wildcards.addTokens (wildcard, ";,", "'\"");
- wildcards.trim();
- wildcards.removeEmptyStrings();
-
- std::cout << "Building: " << targetFile.getFullPathName() << "...\n";
-
- TemporaryFile temp (targetFile);
- ScopedPointer <FileOutputStream> out (temp.getFile().createOutputStream (1024 * 128));
-
- if (out == 0)
- {
- std::cout << "\n!! ERROR - couldn't write to the target file: "
- << temp.getFile().getFullPathName() << "\n\n";
- return false;
- }
-
- if (! parseFile (targetFile.getParentDirectory(),
- targetFile,
- *out, templateFile,
- alreadyIncludedFiles,
- includesToIgnore,
- wildcards,
- true, false))
- {
- return false;
- }
-
- out = 0;
-
- if (calculateFileHashCode (targetFile) == calculateFileHashCode (temp.getFile()))
- {
- std::cout << " -- No need to write - new file is identical\n";
- return true;
- }
-
- if (! temp.overwriteTargetFileWithTemporary())
- {
- std::cout << "\n!! ERROR - couldn't write to the target file: "
- << targetFile.getFullPathName() << "\n\n";
- return false;
- }
-
- return true;
- }
-
- static void findAllFilesIncludedIn (const File& hppTemplate, StringArray& alreadyIncludedFiles)
- {
- StringArray lines;
- lines.addLines (hppTemplate.loadFileAsString());
-
- for (int i = 0; i < lines.size(); ++i)
- {
- String line (lines[i]);
-
- if (line.removeCharacters (" \t").startsWithIgnoreCase ("#include\""))
- {
- const String filename (line.fromFirstOccurrenceOf ("\"", false, false)
- .upToLastOccurrenceOf ("\"", false, false));
- const File targetFile (hppTemplate.getSiblingFile (filename));
-
- if (! alreadyIncludedFiles.contains (targetFile.getFullPathName()))
- {
- alreadyIncludedFiles.add (targetFile.getFullPathName());
-
- if (targetFile.getFileName().containsIgnoreCase ("juce_") && targetFile.exists())
- findAllFilesIncludedIn (targetFile, alreadyIncludedFiles);
- }
- }
- }
- }
-
- //==============================================================================
- static void mungeJuce (const File& juceFolder)
- {
- if (! juceFolder.isDirectory())
- {
- std::cout << " The folder supplied must be the root of your Juce directory!\n\n";
- return;
- }
-
- const File hppTemplate (juceFolder.getChildFile ("amalgamation/juce_amalgamated_template.h"));
- const File cppTemplate (juceFolder.getChildFile ("amalgamation/juce_amalgamated_template.cpp"));
-
- const File hppTarget (juceFolder.getChildFile ("juce_amalgamated.h"));
- const File cppTarget (juceFolder.getChildFile ("juce_amalgamated.cpp"));
-
- StringArray alreadyIncludedFiles, includesToIgnore;
-
- if (! munge (hppTemplate, hppTarget, "*.h", alreadyIncludedFiles, includesToIgnore))
- {
- return;
- }
-
- findAllFilesIncludedIn (hppTemplate, alreadyIncludedFiles);
- includesToIgnore.add (hppTarget.getFileName());
-
- munge (cppTemplate, cppTarget, "*.cpp;*.c;*.h;*.mm;*.m", alreadyIncludedFiles, includesToIgnore);
- }
-
- //==============================================================================
- int main (int argc, char* argv[])
- {
- // This object makes sure that Juce is initialised and shut down correctly
- // for the scope of this function call. Make sure this declaration is the
- // first statement of this function.
- const ScopedJuceInitialiser_NonGUI juceSystemInitialiser;
-
- std::cout << "\n*** The C++ Amalgamator! Written for Juce - www.rawmaterialsoftware.com\n";
-
- if (argc == 4)
- {
- const File templateFile (File::getCurrentWorkingDirectory().getChildFile (String (argv[1]).unquoted()));
- const File targetFile (File::getCurrentWorkingDirectory().getChildFile (String (argv[2]).unquoted()));
- const String wildcard (String (argv[3]).unquoted());
- StringArray alreadyIncludedFiles, includesToIgnore;
-
- munge (templateFile, targetFile, wildcard, alreadyIncludedFiles, includesToIgnore);
- }
- else if (argc == 2)
- {
- const File juceFolder (File::getCurrentWorkingDirectory().getChildFile (String (argv[1]).unquoted()));
- mungeJuce (juceFolder);
- }
- else
- {
- std::cout << " Usage: amalgamator TemplateFile TargetFile \"FileToReplaceWildcard\"\n\n";
- " amalgamator will run through a C++ file and replace any\n"
- " #include statements with the contents of the file they refer to.\n"
- " It'll only do this for files that are within the same parent\n"
- " directory as the target file, and will ignore include statements\n"
- " that use '<>' instead of quotes. It'll also only include a file once,\n"
- " ignoring any repeated instances of it.\n\n"
- " The wildcard lets you specify what kind of files will be replaced, so\n"
- " \"*.cpp;*.h\" would replace only includes that reference a .cpp or .h file.\n\n"
- " Or: just run 'amalgamator YourJuceDirectory' to rebuild the juce files.";
- }
-
- std::cout << "\n";
- return 0;
- }
|