[aseprite] 111/134: Add --filename-format option (fix #519)

Tobias Hansen thansen at moszumanska.debian.org
Sat Mar 14 17:10:14 UTC 2015


This is an automated email from the git hooks/post-receive script.

thansen pushed a commit to branch master
in repository aseprite.

commit 72e2040c58d6120c1fcd08970422ea35002c24b6
Author: David Capello <davidcapello at gmail.com>
Date:   Sun Jan 25 22:36:32 2015 -0300

    Add --filename-format option (fix #519)
---
 src/app/CMakeLists.txt               |   1 +
 src/app/app.cpp                      |  48 ++++++----
 src/app/app_options.cpp              |   1 +
 src/app/app_options.h                |   2 +
 src/app/commands/cmd_save_file.cpp   | 178 ++++++++++++++++++-----------------
 src/app/commands/cmd_save_file.h     |   5 +-
 src/app/document_exporter.cpp        |  35 ++++---
 src/app/document_exporter.h          |   5 +
 src/app/file/file.cpp                |  67 +++++++++----
 src/app/file/file.h                  |   2 +-
 src/app/filename_formatter.cpp       | 105 +++++++++++++++++++++
 src/app/filename_formatter.h         |  44 +++++++++
 src/app/filename_formatter_tests.cpp | 142 ++++++++++++++++++++++++++++
 src/base/CMakeLists.txt              |   1 +
 src/base/LICENSE.txt                 |   2 +-
 src/base/README.md                   |   2 +-
 src/base/replace_string.cpp          |  33 +++++++
 src/base/replace_string.h            |  22 +++++
 src/base/replace_string_tests.cpp    |  31 ++++++
 19 files changed, 586 insertions(+), 140 deletions(-)

diff --git a/src/app/CMakeLists.txt b/src/app/CMakeLists.txt
index 65f79fb..9e7d050 100644
--- a/src/app/CMakeLists.txt
+++ b/src/app/CMakeLists.txt
@@ -154,6 +154,7 @@ add_library(app-lib
   file/tga_format.cpp
   file_selector.cpp
   file_system.cpp
+  filename_formatter.cpp
   flatten.cpp
   gui_xml.cpp
   handle_anidir.cpp
diff --git a/src/app/app.cpp b/src/app/app.cpp
index 62aaa2b..9119a06 100644
--- a/src/app/app.cpp
+++ b/src/app/app.cpp
@@ -37,6 +37,7 @@
 #include "app/file/file_formats_manager.h"
 #include "app/file/palette_file.h"
 #include "app/file_system.h"
+#include "app/filename_formatter.h"
 #include "app/find_widget.h"
 #include "app/gui_xml.h"
 #include "app/ini_file.h"
@@ -220,6 +221,7 @@ void App::initialize(const AppOptions& options)
     bool splitLayersSaveAs = false;
     std::string importLayer;
     std::string importLayerSaveAs;
+    std::string filenameFormat;
 
     for (const auto& value : options.values()) {
       const AppOptions::Option* opt = value.option();
@@ -265,6 +267,10 @@ void App::initialize(const AppOptions& options)
         else if (opt == &options.ignoreEmpty()) {
           ignoreEmpty = true;
         }
+        // --filename-format
+        else if (opt == &options.filenameFormat()) {
+          filenameFormat = value.value();
+        }
         // --save-as <filename>
         else if (opt == &options.saveAs()) {
           Document* doc = NULL;
@@ -277,30 +283,35 @@ void App::initialize(const AppOptions& options)
           else {
             ctx->setActiveDocument(doc);
 
+            std::string format = filenameFormat;
+
             Command* command = CommandsModule::instance()->getCommandByName(CommandId::SaveFileCopyAs);
             if (splitLayersSaveAs) {
               std::vector<Layer*> layers;
               doc->sprite()->getLayersList(layers);
 
+              std::string fn, fmt;
+              if (format.empty()) {
+                if (doc->sprite()->totalFrames() > FrameNumber(1))
+                  format = "{path}/{title} ({layer}) {frame}.{extension}";
+                else
+                  format = "{path}/{title} ({layer}).{extension}";
+              }
+
               // For each layer, hide other ones and save the sprite.
               for (Layer* show : layers) {
                 for (Layer* hide : layers)
                   hide->setReadable(hide == show);
 
-                std::string frameStr;
-                if (doc->sprite()->totalFrames() > FrameNumber(1))
-                  frameStr += " 1";
-
-                std::string fn = value.value();
-                fn =
-                  base::join_path(
-                    base::get_file_path(fn),
-                    base::get_file_title(fn))
-                  + " (" + show->name() + ")" + frameStr + "." +
-                  base::get_file_extension(fn);
-
-                static_cast<SaveFileBaseCommand*>(command)->setFilename(fn);
-                ctx->executeCommand(command);
+                fn = filename_formatter(format,
+                  value.value(), show->name());
+                fmt = filename_formatter(format,
+                  value.value(), show->name(), -1, false);
+
+                Params params;
+                params.set("filename", fn.c_str());
+                params.set("filename-format", fmt.c_str());
+                ctx->executeCommand(command, &params);
               }
             }
             else {
@@ -313,8 +324,10 @@ void App::initialize(const AppOptions& options)
                   layer->setReadable(layer->name() == importLayerSaveAs);
               }
 
-              static_cast<SaveFileBaseCommand*>(command)->setFilename(value.value());
-              ctx->executeCommand(command);
+              Params params;
+              params.set("filename", value.value().c_str());
+              params.set("filename-format", format.c_str());
+              ctx->executeCommand(command, &params);
             }
           }
         }
@@ -381,6 +394,9 @@ void App::initialize(const AppOptions& options)
           splitLayers = false;
       }
     }
+
+    if (m_exporter && !filenameFormat.empty())
+      m_exporter->setFilenameFormat(filenameFormat);
   }
 
   // Export
diff --git a/src/app/app_options.cpp b/src/app/app_options.cpp
index 7f78773..0557450 100644
--- a/src/app/app_options.cpp
+++ b/src/app/app_options.cpp
@@ -49,6 +49,7 @@ AppOptions::AppOptions(int argc, const char* argv[])
   , m_splitLayers(m_po.add("split-layers").description("Import each layer of the next given sprite as\na separated image in the sheet"))
   , m_importLayer(m_po.add("import-layer").requiresValue("<name>").description("Import just one layer of the next given sprite"))
   , m_ignoreEmpty(m_po.add("ignore-empty").description("Do not export empty frames/cels"))
+  , m_filenameFormat(m_po.add("filename-format").requiresValue("<fmt>").description("Special format to generate filenames"))
   , m_verbose(m_po.add("verbose").description("Explain what is being done"))
   , m_help(m_po.add("help").mnemonic('?').description("Display this help and exits"))
   , m_version(m_po.add("version").description("Output version information and exit"))
diff --git a/src/app/app_options.h b/src/app/app_options.h
index 19901d8..a9c5b8b 100644
--- a/src/app/app_options.h
+++ b/src/app/app_options.h
@@ -57,6 +57,7 @@ public:
   const Option& splitLayers() const { return m_splitLayers; }
   const Option& importLayer() const { return m_importLayer; }
   const Option& ignoreEmpty() const { return m_ignoreEmpty; }
+  const Option& filenameFormat() const { return m_filenameFormat; }
 
   bool hasExporterParams() const;
 
@@ -84,6 +85,7 @@ private:
   Option& m_splitLayers;
   Option& m_importLayer;
   Option& m_ignoreEmpty;
+  Option& m_filenameFormat;
 
   Option& m_verbose;
   Option& m_help;
diff --git a/src/app/commands/cmd_save_file.cpp b/src/app/commands/cmd_save_file.cpp
index 0defd78..8e66ec9 100644
--- a/src/app/commands/cmd_save_file.cpp
+++ b/src/app/commands/cmd_save_file.cpp
@@ -20,6 +20,8 @@
 #include "config.h"
 #endif
 
+#include "app/commands/cmd_save_file.h"
+
 #include "app/app.h"
 #include "app/commands/command.h"
 #include "app/commands/params.h"
@@ -79,9 +81,11 @@ private:
   FileOp* m_fop;
 };
 
-static void save_document_in_background(Context* context, Document* document, bool mark_as_saved)
+static void save_document_in_background(Context* context, Document* document,
+  bool mark_as_saved, const std::string& fn_format)
 {
-  base::UniquePtr<FileOp> fop(fop_to_save_document(context, document));
+  base::UniquePtr<FileOp> fop(fop_to_save_document(context, document,
+      fn_format.c_str()));
   if (!fop)
     return;
 
@@ -113,111 +117,112 @@ static void save_document_in_background(Context* context, Document* document, bo
 
 //////////////////////////////////////////////////////////////////////
 
-class SaveFileBaseCommand : public Command {
-public:
-  SaveFileBaseCommand(const char* short_name, const char* friendly_name, CommandFlags flags)
-    : Command(short_name, friendly_name, flags) {
-  }
+SaveFileBaseCommand::SaveFileBaseCommand(const char* short_name, const char* friendly_name, CommandFlags flags)
+  : Command(short_name, friendly_name, flags)
+{
+}
 
-protected:
-  void onLoadParams(Params* params) override {
-    m_filename = params->get("filename");
-  }
+void SaveFileBaseCommand::onLoadParams(Params* params)
+{
+  m_filename = params->get("filename");
+  m_filenameFormat = params->get("filename-format");
+}
 
-  // Returns true if there is a current sprite to save.
-  // [main thread]
-  bool onEnabled(Context* context) override {
-    return context->checkFlags(ContextFlags::ActiveDocumentIsWritable);
+// Returns true if there is a current sprite to save.
+// [main thread]
+bool SaveFileBaseCommand::onEnabled(Context* context)
+{
+  return context->checkFlags(ContextFlags::ActiveDocumentIsWritable);
+}
+
+void SaveFileBaseCommand::saveAsDialog(const ContextReader& reader, const char* dlgTitle, bool markAsSaved)
+{
+  const Document* document = reader.document();
+  std::string filename;
+
+  if (!m_filename.empty()) {
+    filename = m_filename;
   }
+  else {
+    filename = document->filename();
 
-  void saveAsDialog(const ContextReader& reader, const char* dlgTitle, bool markAsSaved)
-  {
-    const Document* document = reader.document();
-    std::string filename;
+    char exts[4096];
+    get_writable_extensions(exts, sizeof(exts));
 
-    if (!m_filename.empty()) {
-      filename = m_filename;
-    }
-    else {
-      filename = document->filename();
-
-      char exts[4096];
-      get_writable_extensions(exts, sizeof(exts));
-
-      for (;;) {
-        std::string newfilename = app::show_file_selector(dlgTitle, filename, exts);
-        if (newfilename.empty())
-          return;
-
-        filename = newfilename;
-
-        // Ask if the user wants overwrite the existent file.
-        int ret = 0;
-        if (base::is_file(filename)) {
-          ret = ui::Alert::show("Warning<<The file already exists, overwrite it?<<%s||&Yes||&No||&Cancel",
-            base::get_file_name(filename).c_str());
-
-          // Check for read-only attribute.
-          if (ret == 1) {
-            if (!confirmReadonly(filename))
-              ret = 2;              // Select file again.
-            else
-              break;
-          }
-        }
-        else
-          break;
+    for (;;) {
+      std::string newfilename = app::show_file_selector(dlgTitle, filename, exts);
+      if (newfilename.empty())
+        return;
+
+      filename = newfilename;
+
+      // Ask if the user wants overwrite the existent file.
+      int ret = 0;
+      if (base::is_file(filename)) {
+        ret = ui::Alert::show("Warning<<The file already exists, overwrite it?<<%s||&Yes||&No||&Cancel",
+          base::get_file_name(filename).c_str());
 
-        // "yes": we must continue with the operation...
+        // Check for read-only attribute.
         if (ret == 1) {
-          break;
+          if (!confirmReadonly(filename))
+            ret = 2;              // Select file again.
+          else
+            break;
         }
-        // "cancel" or <esc> per example: we back doing nothing
-        else if (ret != 2)
-          return;
+      }
+      else
+        break;
 
-        // "no": we must back to select other file-name
+      // "yes": we must continue with the operation...
+      if (ret == 1) {
+        break;
       }
+      // "cancel" or <esc> per example: we back doing nothing
+      else if (ret != 2)
+        return;
+
+      // "no": we must back to select other file-name
     }
+  }
 
-    {
-      ContextWriter writer(reader);
-      Document* documentWriter = writer.document();
-      std::string oldFilename = documentWriter->filename();
+  {
+    ContextWriter writer(reader);
+    Document* documentWriter = writer.document();
+    std::string oldFilename = documentWriter->filename();
 
-      // Change the document file name
-      documentWriter->setFilename(filename.c_str());
-      m_selectedFilename = filename;
+    // Change the document file name
+    documentWriter->setFilename(filename.c_str());
+    m_selectedFilename = filename;
 
-      // Save the document
-      save_document_in_background(writer.context(), documentWriter, markAsSaved);
+    // Save the document
+    save_document_in_background(writer.context(), documentWriter,
+      markAsSaved, m_filenameFormat);
 
-      if (documentWriter->isModified())
-        documentWriter->setFilename(oldFilename);
+    if (documentWriter->isModified())
+      documentWriter->setFilename(oldFilename);
 
-      update_screen_for_document(documentWriter);
-    }
+    update_screen_for_document(documentWriter);
   }
+}
 
-  static bool confirmReadonly(const std::string& filename)
-  {
-    if (!base::has_readonly_attr(filename))
-      return true;
+//static
+bool SaveFileBaseCommand::confirmReadonly(const std::string& filename)
+{
+  if (!base::has_readonly_attr(filename))
+    return true;
 
-    int ret = ui::Alert::show("Warning<<The file is read-only, do you really want to overwrite it?<<%s||&Yes||&No",
-      base::get_file_name(filename).c_str());
+  int ret = ui::Alert::show("Warning<<The file is read-only, do you really want to overwrite it?<<%s||&Yes||&No",
+    base::get_file_name(filename).c_str());
 
-    if (ret == 1) {
-      base::remove_readonly_attr(filename);
-      return true;
-    }
-    else
-      return false;
+  if (ret == 1) {
+    base::remove_readonly_attr(filename);
+    return true;
   }
+  else
+    return false;
+}
 
-  std::string m_filename;
-  std::string m_selectedFilename;
-};
+//////////////////////////////////////////////////////////////////////
 
 class SaveFileCommand : public SaveFileBaseCommand {
 public:
@@ -249,7 +254,8 @@ void SaveFileCommand::onExecute(Context* context)
     if (!confirmReadonly(documentWriter->filename()))
       return;
 
-    save_document_in_background(context, documentWriter, true);
+    save_document_in_background(context, documentWriter, true,
+      m_filenameFormat.c_str());
     update_screen_for_document(documentWriter);
   }
   // If the document isn't associated to a file, we must to show the
diff --git a/src/app/commands/cmd_save_file.h b/src/app/commands/cmd_save_file.h
index f400979..f329dd1 100644
--- a/src/app/commands/cmd_save_file.h
+++ b/src/app/commands/cmd_save_file.h
@@ -35,10 +35,6 @@ namespace app {
       return m_selectedFilename;
     }
 
-    void setFilename(const std::string& fn) {
-      m_filename = fn;
-    }
-
   protected:
     void onLoadParams(Params* params) override;
     bool onEnabled(Context* context) override;
@@ -48,6 +44,7 @@ namespace app {
     static bool confirmReadonly(const std::string& filename);
 
     std::string m_filename;
+    std::string m_filenameFormat;
     std::string m_selectedFilename;
   };
 
diff --git a/src/app/document_exporter.cpp b/src/app/document_exporter.cpp
index e15d792..dcbfd20 100644
--- a/src/app/document_exporter.cpp
+++ b/src/app/document_exporter.cpp
@@ -26,6 +26,7 @@
 #include "app/document.h"
 #include "app/document_api.h"
 #include "app/file/file.h"
+#include "app/filename_formatter.h"
 #include "app/ui_context.h"
 #include "base/convert_to.h"
 #include "base/path.h"
@@ -273,24 +274,28 @@ void DocumentExporter::captureSamples(Samples& samples)
     Document* doc = item.doc;
     Sprite* sprite = doc->sprite();
     Layer* layer = item.layer;
+    bool hasFrames = (doc->sprite()->totalFrames() > FrameNumber(1));
+    bool hasLayer = (layer != NULL);
+
+    std::string format = m_filenameFormat;
+    if (format.empty()) {
+      if (hasFrames && hasLayer)
+        format = "{title} ({layer}) {frame}.{extension}";
+      else if (hasFrames)
+        format = "{title} {frame}.{extension}";
+      else if (hasLayer)
+        format = "{title} ({layer}).{extension}";
+      else
+        format = "{fullname}";
+    }
 
     for (FrameNumber frame=FrameNumber(0);
          frame<sprite->totalFrames(); ++frame) {
-      std::string filename = doc->filename();
-
-      if (sprite->totalFrames() > FrameNumber(1)) {
-        std::string path = base::get_file_path(filename);
-        std::string title = base::get_file_title(filename);
-        if (layer) {
-          title += " (";
-          title += layer->name();
-          title += ") ";
-        }
-
-        filename = base::join_path(path, title +
-            base::convert_to<std::string>((int)frame + 1)
-            + "." + base::get_file_extension(filename));
-      }
+      std::string filename =
+        filename_formatter(format,
+          doc->filename(),
+          layer ? layer->name(): "",
+          (sprite->totalFrames() > FrameNumber(1)) ? frame: FrameNumber(-1));
 
       Sample sample(doc, sprite, layer, frame, filename);
 
diff --git a/src/app/document_exporter.h b/src/app/document_exporter.h
index 1224821..7fce95a 100644
--- a/src/app/document_exporter.h
+++ b/src/app/document_exporter.h
@@ -93,6 +93,10 @@ namespace app {
       m_ignoreEmptyCels = ignore;
     }
 
+    void setFilenameFormat(const std::string& format) {
+      m_filenameFormat = format;
+    }
+
     void addDocument(Document* document, raster::Layer* layer = NULL) {
       m_documents.push_back(Item(document, layer));
     }
@@ -133,6 +137,7 @@ namespace app {
     ScaleMode m_scaleMode;
     bool m_ignoreEmptyCels;
     Items m_documents;
+    std::string m_filenameFormat;
 
     DISABLE_COPYING(DocumentExporter);
   };
diff --git a/src/app/file/file.cpp b/src/app/file/file.cpp
index d61e27a..a9609df 100644
--- a/src/app/file/file.cpp
+++ b/src/app/file/file.cpp
@@ -28,6 +28,7 @@
 #include "app/file/file_format.h"
 #include "app/file/file_formats_manager.h"
 #include "app/file/format_options.h"
+#include "app/filename_formatter.h"
 #include "app/modules/gui.h"
 #include "app/modules/palettes.h"
 #include "app/ui/status_bar.h"
@@ -122,7 +123,8 @@ int save_document(Context* context, doc::Document* document)
   ASSERT(dynamic_cast<app::Document*>(document));
 
   int ret;
-  FileOp* fop = fop_to_save_document(context, static_cast<app::Document*>(document));
+  FileOp* fop = fop_to_save_document(context,
+    static_cast<app::Document*>(document), "");
   if (!fop)
     return -1;
 
@@ -235,7 +237,7 @@ done:;
   return fop;
 }
 
-FileOp* fop_to_save_document(Context* context, Document* document)
+FileOp* fop_to_save_document(Context* context, Document* document, const char* fn_format_arg)
 {
   FileOp *fop;
   bool fatal;
@@ -359,26 +361,59 @@ FileOp* fop_to_save_document(Context* context, Document* document)
   if (fop->format->support(FILE_SUPPORT_SEQUENCES)) {
     fop_prepare_for_sequence(fop);
 
-    // To save one frame
+    std::string fn = fop->document->filename();
+    std::string fn_format = fn_format_arg;
+    bool default_format = false;
+
+    if (fn_format.empty()) {
+      if (fop->document->sprite()->totalFrames() == 1)
+        fn_format = "{fullname}";
+      else {
+        fn_format = "{path}/{title}{frame}.{extension}";
+        default_format = true;
+      }
+    }
+
+    // Save one frame
     if (fop->document->sprite()->totalFrames() == 1) {
-      fop->seq.filename_list.push_back(fop->document->filename());
+      fn = filename_formatter(fn_format, fn);
+      fop->seq.filename_list.push_back(fn);
     }
-    // To save more frames
+    // Save multiple frames
     else {
-      char left[256], right[256];
-      int width, start_from;
-
-      start_from = split_filename(fop->document->filename().c_str(), left, right, &width);
-      if (start_from < 0) {
-        start_from = 1;
-        width = 1;
+      int width = 0;
+      int start_from = 0;
+
+      if (default_format) {
+        char left[256], right[256];
+        start_from = split_filename(fn.c_str(), left, right, &width);
+        if (start_from < 0) {
+          start_from = 1;
+          width = 1;
+        }
+        else {
+          fn = left;
+          fn += right;
+        }
       }
 
+      std::vector<char> buf(32);
+      std::sprintf(&buf[0], "{frame%0*d}", width, 0);
+      if (default_format)
+        fn_format = set_frame_format(fn_format, &buf[0]);
+      else if (fop->document->sprite()->totalFrames() > 1)
+        fn_format = add_frame_format(fn_format, &buf[0]);
+
+      printf("start_from = %d\n", start_from);
+      printf("fn_format = %s\n", fn_format.c_str());
+      printf("fn = %s\n", fn.c_str());
+
       for (FrameNumber frame(0); frame<fop->document->sprite()->totalFrames(); ++frame) {
-        // Get the name for this frame
-        char buf[4096];
-        sprintf(buf, "%s%0*d%s", left, width, start_from+frame, right);
-        fop->seq.filename_list.push_back(buf);
+        std::string frame_fn =
+          filename_formatter(fn_format, fn, "", start_from+frame);
+
+        printf("frame_fn = %s\n", frame_fn.c_str());
+        fop->seq.filename_list.push_back(frame_fn);
       }
     }
   }
diff --git a/src/app/file/file.h b/src/app/file/file.h
index 35dd778..99ba0ba 100644
--- a/src/app/file/file.h
+++ b/src/app/file/file.h
@@ -133,7 +133,7 @@ namespace app {
   // Low-level routines to load/save documents.
 
   FileOp* fop_to_load_document(Context* context, const char* filename, int flags);
-  FileOp* fop_to_save_document(Context* context, Document* document);
+  FileOp* fop_to_save_document(Context* context, Document* document, const char* fn_format);
   void fop_operate(FileOp* fop, IFileOpProgress* progress);
   void fop_done(FileOp* fop);
   void fop_stop(FileOp* fop);
diff --git a/src/app/filename_formatter.cpp b/src/app/filename_formatter.cpp
new file mode 100644
index 0000000..028beb0
--- /dev/null
+++ b/src/app/filename_formatter.cpp
@@ -0,0 +1,105 @@
+/* Aseprite
+ * Copyright (C) 2001-2015  David Capello
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "app/filename_formatter.h"
+
+#include "base/convert_to.h"
+#include "base/path.h"
+#include "base/replace_string.h"
+
+#include <vector>
+
+namespace app {
+
+std::string filename_formatter(
+  const std::string& format,
+  const std::string& filename,
+  const std::string& layerName,
+  int frame, bool replaceFrame)
+{
+  std::string output = format;
+  base::replace_string(output, "{fullname}", filename);
+  base::replace_string(output, "{path}", base::get_file_path(filename));
+  base::replace_string(output, "{name}", base::get_file_name(filename));
+  base::replace_string(output, "{title}", base::get_file_title(filename));
+  base::replace_string(output, "{extension}", base::get_file_extension(filename));
+  base::replace_string(output, "{layer}", layerName);
+
+  if (replaceFrame) {
+    size_t i = output.find("{frame");
+    if (i != std::string::npos) {
+      size_t j = output.find("}", i+6);
+      if (j != std::string::npos) {
+        std::string from = output.substr(i, j - i + 1);
+        if (frame >= 0) {
+          std::vector<char> to(32);
+          int offset = std::strtol(from.c_str()+6, NULL, 10);
+
+          std::sprintf(&to[0], "%0*d", (int(j)-int(i+6)), frame + offset);
+          base::replace_string(output, from, &to[0]);
+        }
+        else
+          base::replace_string(output, from, "");
+      }
+    }
+  }
+
+  return output;
+}
+
+std::string set_frame_format(
+  const std::string& format,
+  const std::string& newFrameFormat)
+{
+  std::string output = format;
+
+  size_t i = output.find("{frame");
+  if (i != std::string::npos) {
+    size_t j = output.find("}", i+6);
+    if (j != std::string::npos) {
+      output.replace(i, j - i + 1, newFrameFormat);
+    }
+  }
+
+  return output;
+}
+
+std::string add_frame_format(
+  const std::string& format,
+  const std::string& newFrameFormat)
+{
+  std::string output = format;
+
+  size_t i = output.find("{frame");
+  if (i == std::string::npos) {
+    output =
+      base::join_path(
+        base::get_file_path(format),
+        base::get_file_title(format))
+      + newFrameFormat + "." + 
+      base::get_file_extension(format);
+  }
+
+  return output;
+}
+
+} // namespace app
diff --git a/src/app/filename_formatter.h b/src/app/filename_formatter.h
new file mode 100644
index 0000000..b74602d
--- /dev/null
+++ b/src/app/filename_formatter.h
@@ -0,0 +1,44 @@
+/* Aseprite
+ * Copyright (C) 2001-2015  David Capello
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+#ifndef APP_FILENAME_FORMATTER_H_INCLUDED
+#define APP_FILENAME_FORMATTER_H_INCLUDED
+#pragma once
+
+#include <string>
+
+namespace app {
+
+  std::string filename_formatter(
+    const std::string& format,
+    const std::string& filename,
+    const std::string& layerName = "",
+    int frame = -1,
+    bool replaceFrame = true);
+
+  std::string set_frame_format(
+    const std::string& format,
+    const std::string& newFrameFormat);
+
+  std::string add_frame_format(
+    const std::string& format,
+    const std::string& newFrameFormat);
+
+} // namespace app
+
+#endif
diff --git a/src/app/filename_formatter_tests.cpp b/src/app/filename_formatter_tests.cpp
new file mode 100644
index 0000000..e0004fe
--- /dev/null
+++ b/src/app/filename_formatter_tests.cpp
@@ -0,0 +1,142 @@
+/* Aseprite
+ * Copyright (C) 2001-2015  David Capello
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <gtest/gtest.h>
+
+#include "app/filename_formatter.h"
+
+#include "base/path.h"
+
+using namespace app;
+
+TEST(FilenameFormatter, Basic)
+{
+  EXPECT_EQ("C:/temp/file.png",
+    filename_formatter("{fullname}",
+      "C:/temp/file.png"));
+
+  EXPECT_EQ("file.png",
+    filename_formatter("{name}",
+      "C:/temp/file.png"));
+
+  EXPECT_EQ("C:/temp/other.png",
+    filename_formatter("{path}/other.png",
+      "C:/temp/file.png"));
+}
+
+TEST(FilenameFormatter, WithoutFrame)
+{
+  EXPECT_EQ("C:/temp/file.png",
+    filename_formatter("{path}/{title}.png",
+      "C:/temp/file.ase"));
+
+  EXPECT_EQ("C:/temp/file.png",
+    filename_formatter("{path}/{title}{frame}.{extension}",
+      "C:/temp/file.png"));
+
+  EXPECT_EQ("C:/temp/file{frame}.png",
+    filename_formatter("{path}/{title}{frame}.{extension}",
+      "C:/temp/file.png", "", -1, false));
+
+  EXPECT_EQ("C:/temp/file (Background).png",
+    filename_formatter("{path}/{title} ({layer}).{extension}",
+      "C:/temp/file.png", "Background"));
+}
+
+TEST(FilenameFormatter, WithFrame)
+{
+  EXPECT_EQ("C:/temp/file0.png",
+    filename_formatter("{path}/{title}{frame}.{extension}",
+      "C:/temp/file.png", "", 0));
+
+  EXPECT_EQ("C:/temp/file1.png",
+    filename_formatter("{path}/{title}{frame}.{extension}",
+      "C:/temp/file.png", "", 1));
+
+  EXPECT_EQ("C:/temp/file10.png",
+    filename_formatter("{path}/{title}{frame}.{extension}",
+      "C:/temp/file.png", "", 10));
+
+  EXPECT_EQ("C:/temp/file0.png",
+    filename_formatter("{path}/{title}{frame0}.{extension}",
+      "C:/temp/file.png", "", 0));
+
+  EXPECT_EQ("C:/temp/file1.png",
+    filename_formatter("{path}/{title}{frame1}.{extension}",
+      "C:/temp/file.png", "", 0));
+
+  EXPECT_EQ("C:/temp/file2.png",
+    filename_formatter("{path}/{title}{frame1}.{extension}",
+      "C:/temp/file.png", "", 1));
+
+  EXPECT_EQ("C:/temp/file00.png",
+    filename_formatter("{path}/{title}{frame00}.{extension}",
+      "C:/temp/file.png", "", 0));
+
+  EXPECT_EQ("C:/temp/file01.png",
+    filename_formatter("{path}/{title}{frame01}.{extension}",
+      "C:/temp/file.png", "", 0));
+
+  EXPECT_EQ("C:/temp/file002.png",
+    filename_formatter("{path}/{title}{frame000}.{extension}",
+      "C:/temp/file.png", "", 2));
+
+  EXPECT_EQ("C:/temp/file0032.png",
+    filename_formatter("{path}/{title}{frame0032}.{extension}",
+      "C:/temp/file.png", "", 0));
+
+  EXPECT_EQ("C:/temp/file-Background-2.png",
+    filename_formatter("{path}/{title}-{layer}-{frame}.{extension}",
+      "C:/temp/file.png", "Background", 2));
+}
+
+TEST(SetFrameFormat, Tests)
+{
+  EXPECT_EQ("{path}/{title}{frame1}.{extension}",
+    set_frame_format("{path}/{title}{frame}.{extension}",
+      "{frame1}"));
+
+  EXPECT_EQ("{path}/{title}{frame01}.{extension}",
+    set_frame_format("{path}/{title}{frame}.{extension}",
+      "{frame01}"));
+
+  EXPECT_EQ("{path}/{title}{frame}.{extension}",
+    set_frame_format("{path}/{title}{frame01}.{extension}",
+      "{frame}"));
+}
+
+TEST(AddFrameFormat, Tests)
+{
+  EXPECT_EQ(base::fix_path_separators("{path}/{title}{frame001}.{extension}"),
+    add_frame_format("{path}/{title}.{extension}",
+      "{frame001}"));
+
+  EXPECT_EQ("{path}/{title}{frame1}.{extension}",
+    add_frame_format("{path}/{title}{frame1}.{extension}",
+      "{frame001}"));
+}
+
+int main(int argc, char** argv)
+{
+  ::testing::InitGoogleTest(&argc, argv);
+  return RUN_ALL_TESTS();
+}
diff --git a/src/base/CMakeLists.txt b/src/base/CMakeLists.txt
index 7f5f778..18b52f3 100644
--- a/src/base/CMakeLists.txt
+++ b/src/base/CMakeLists.txt
@@ -31,6 +31,7 @@ set(BASE_SOURCES
   mutex.cpp
   path.cpp
   program_options.cpp
+  replace_string.cpp
   serialization.cpp
   sha1.cpp
   sha1_rfc3174.c
diff --git a/src/base/LICENSE.txt b/src/base/LICENSE.txt
index 220ac0d..7ed8f20 100644
--- a/src/base/LICENSE.txt
+++ b/src/base/LICENSE.txt
@@ -1,4 +1,4 @@
-Copyright (c) 2001-2014 David Capello
+Copyright (c) 2001-2015 David Capello
 
 Permission is hereby granted, free of charge, to any person obtaining
 a copy of this software and associated documentation files (the
diff --git a/src/base/README.md b/src/base/README.md
index 2c06880..08396a0 100644
--- a/src/base/README.md
+++ b/src/base/README.md
@@ -1,5 +1,5 @@
 # Aseprite Base Library
-*Copyright (C) 2001-2013 David Capello*
+*Copyright (C) 2001-2015 David Capello*
 
 > Distributed under [MIT license](LICENSE.txt)
 
diff --git a/src/base/replace_string.cpp b/src/base/replace_string.cpp
new file mode 100644
index 0000000..281e02a
--- /dev/null
+++ b/src/base/replace_string.cpp
@@ -0,0 +1,33 @@
+// Aseprite Base Library
+// Copyright (c) 2001-2015 David Capello
+//
+// This file is released under the terms of the MIT license.
+// Read LICENSE.txt for more information.
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "base/replace_string.h"
+
+namespace base {
+
+void replace_string(
+  std::string& subject,
+  const std::string& replace_this,
+  const std::string& with_that)
+{
+  if (replace_this.empty())     // Do nothing case
+    return;
+
+  size_t i = 0;
+  while (true) {
+    i = subject.find(replace_this, i);
+    if (i == std::string::npos)
+      break;
+    subject.replace(i, replace_this.size(), with_that);
+    i += with_that.size();
+  }
+}
+  
+} // namespace base
diff --git a/src/base/replace_string.h b/src/base/replace_string.h
new file mode 100644
index 0000000..0076830
--- /dev/null
+++ b/src/base/replace_string.h
@@ -0,0 +1,22 @@
+// Aseprite Base Library
+// Copyright (c) 2001-2015 David Capello
+//
+// This file is released under the terms of the MIT license.
+// Read LICENSE.txt for more information.
+
+#ifndef BASE_REPLACE_STRING_H_INCLUDED
+#define BASE_REPLACE_STRING_H_INCLUDED
+#pragma once
+
+#include <string>
+
+namespace base {
+
+  void replace_string(
+    std::string& subject,
+    const std::string& replace_this,
+    const std::string& with_that);
+
+}
+
+#endif
diff --git a/src/base/replace_string_tests.cpp b/src/base/replace_string_tests.cpp
new file mode 100644
index 0000000..b93985f
--- /dev/null
+++ b/src/base/replace_string_tests.cpp
@@ -0,0 +1,31 @@
+// Aseprite Base Library
+// Copyright (c) 2001-2015 David Capello
+//
+// This file is released under the terms of the MIT license.
+// Read LICENSE.txt for more information.
+
+#include <gtest/gtest.h>
+
+#include "base/replace_string.h"
+
+inline std::string rs(const std::string& s, const std::string& a, const std::string& b) {
+  std::string res = s;
+  base::replace_string(res, a, b);
+  return res;
+}
+
+TEST(ReplaceString, Basic)
+{
+  EXPECT_EQ("", rs("", "", ""));
+  EXPECT_EQ("aa", rs("ab", "b", "a"));
+  EXPECT_EQ("abc", rs("accc", "cc", "b"));
+  EXPECT_EQ("abb", rs("acccc", "cc", "b"));
+  EXPECT_EQ("aabbbbaabbbb", rs("aabbaabb", "bb", "bbbb"));
+  EXPECT_EQ("123123123", rs("111", "1", "123"));
+}
+
+int main(int argc, char** argv)
+{
+  ::testing::InitGoogleTest(&argc, argv);
+  return RUN_ALL_TESTS();
+}

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/pkg-games/aseprite.git



More information about the Pkg-games-commits mailing list