diff --git a/src/app/CMakeLists.txt b/src/app/CMakeLists.txt index 65f79fb9a..9e7d05088 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 62aaa2bf3..9119a06cc 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 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 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"; + fn = filename_formatter(format, + value.value(), show->name()); + fmt = filename_formatter(format, + value.value(), show->name(), -1, false); - 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(command)->setFilename(fn); - ctx->executeCommand(command); + Params params; + params.set("filename", fn.c_str()); + params.set("filename-format", fmt.c_str()); + ctx->executeCommand(command, ¶ms); } } else { @@ -313,8 +324,10 @@ void App::initialize(const AppOptions& options) layer->setReadable(layer->name() == importLayerSaveAs); } - static_cast(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, ¶ms); } } } @@ -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 7f7877342..055745002 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("").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("").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 19901d8a0..a9c5b8b0f 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 0defd7804..8e66ec921 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 fop(fop_to_save_document(context, document)); + base::UniquePtr 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) +{ +} + +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 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(); -protected: - void onLoadParams(Params* params) override { - m_filename = params->get("filename"); - } + char exts[4096]; + get_writable_extensions(exts, sizeof(exts)); - // Returns true if there is a current sprite to save. - // [main thread] - bool onEnabled(Context* context) override { - return context->checkFlags(ContextFlags::ActiveDocumentIsWritable); - } + for (;;) { + std::string newfilename = app::show_file_selector(dlgTitle, filename, exts); + if (newfilename.empty()) + return; - void saveAsDialog(const ContextReader& reader, const char* dlgTitle, bool markAsSaved) - { - const Document* document = reader.document(); - std::string filename; + filename = newfilename; - if (!m_filename.empty()) { - filename = m_filename; - } - else { - filename = document->filename(); + // Ask if the user wants overwrite the existent file. + int ret = 0; + if (base::is_file(filename)) { + ret = ui::Alert::show("Warning< per example: we back doing nothing - else if (ret != 2) - return; - - // "no": we must back to select other file-name } - } + else + break; - { - ContextWriter writer(reader); - Document* documentWriter = writer.document(); - std::string oldFilename = documentWriter->filename(); + // "yes": we must continue with the operation... + if (ret == 1) { + break; + } + // "cancel" or per example: we back doing nothing + else if (ret != 2) + return; - // Change the document file name - documentWriter->setFilename(filename.c_str()); - m_selectedFilename = filename; - - // Save the document - save_document_in_background(writer.context(), documentWriter, markAsSaved); - - if (documentWriter->isModified()) - documentWriter->setFilename(oldFilename); - - update_screen_for_document(documentWriter); + // "no": we must back to select other file-name } } - static bool confirmReadonly(const std::string& filename) { - if (!base::has_readonly_attr(filename)) - return true; + ContextWriter writer(reader); + Document* documentWriter = writer.document(); + std::string oldFilename = documentWriter->filename(); - int ret = ui::Alert::show("Warning<setFilename(filename.c_str()); + m_selectedFilename = filename; - if (ret == 1) { - base::remove_readonly_attr(filename); - return true; - } - else - return false; + // Save the document + save_document_in_background(writer.context(), documentWriter, + markAsSaved, m_filenameFormat); + + if (documentWriter->isModified()) + documentWriter->setFilename(oldFilename); + + update_screen_for_document(documentWriter); } +} - std::string m_filename; - std::string m_selectedFilename; -}; +//static +bool SaveFileBaseCommand::confirmReadonly(const std::string& filename) +{ + if (!base::has_readonly_attr(filename)) + return true; + + int ret = ui::Alert::show("Warning<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 f40097965..f329dd120 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 e15d792d6..dcbfd20da 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); frametotalFrames(); ++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((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 122482172..7fce95a19 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 d61e27a61..a9609df62 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(document)); int ret; - FileOp* fop = fop_to_save_document(context, static_cast(document)); + FileOp* fop = fop_to_save_document(context, + static_cast(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 - if (fop->document->sprite()->totalFrames() == 1) { - fop->seq.filename_list.push_back(fop->document->filename()); - } - // To save more frames - else { - char left[256], right[256]; - int width, start_from; + std::string fn = fop->document->filename(); + std::string fn_format = fn_format_arg; + bool default_format = false; - start_from = split_filename(fop->document->filename().c_str(), left, right, &width); - if (start_from < 0) { - start_from = 1; - width = 1; + 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) { + fn = filename_formatter(fn_format, fn); + fop->seq.filename_list.push_back(fn); + } + // Save multiple frames + else { + 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 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); framedocument->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 35dd7788c..99ba0bafa 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 000000000..028beb0ea --- /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 + +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 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 000000000..b74602d0e --- /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 + +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 000000000..e0004fefe --- /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 + +#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 7f5f7785b..18b52f3df 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 220ac0d42..7ed8f201a 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 2c06880a6..08396a0b0 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 000000000..281e02ab1 --- /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 000000000..0076830d6 --- /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 + +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 000000000..b93985ff5 --- /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 + +#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(); +}