From f54383aa59e5e29543cb26328f4b059aac2a2f3b Mon Sep 17 00:00:00 2001 From: artyfarty Date: Tue, 24 Jan 2012 23:30:14 +0400 Subject: [PATCH] Extracted builder in library. Added example pack --- UniversalEmoticonPackBuilder.sln | 28 ++ UniversalEmoticonPackBuilder/Program.cs | 117 +-------- UniversalEmoticonPackBuilder/Writers.cs | 239 ----------------- .../emoticon_build.csproj | 14 +- UniversalEmoticonPackBuilderLib/Builders.cs | 240 ++++++++++++++++++ UniversalEmoticonPackBuilderLib/Ionic.Zip.dll | Bin 0 -> 462336 bytes .../Properties/AssemblyInfo.cs | 36 +++ .../SmileConfig.cs | 6 +- .../TemplateWriter.cs | 4 +- .../UniversalEmoticonPackBuilderLib.csproj | 61 +++++ UniversalEmoticonPackBuilderLib/main.cs | 128 ++++++++++ examplePack/config.json | 7 + examplePack/ff.gif | Bin 0 -> 2844 bytes examplePack/fi.gif | Bin 0 -> 3062 bytes examplePack/fl.gif | Bin 0 -> 2717 bytes examplePack/ft.gif | Bin 0 -> 2789 bytes examplePack/map.txt | 4 + 17 files changed, 527 insertions(+), 357 deletions(-) delete mode 100644 UniversalEmoticonPackBuilder/Writers.cs create mode 100644 UniversalEmoticonPackBuilderLib/Builders.cs create mode 100644 UniversalEmoticonPackBuilderLib/Ionic.Zip.dll create mode 100644 UniversalEmoticonPackBuilderLib/Properties/AssemblyInfo.cs rename {UniversalEmoticonPackBuilder => UniversalEmoticonPackBuilderLib}/SmileConfig.cs (91%) rename {UniversalEmoticonPackBuilder => UniversalEmoticonPackBuilderLib}/TemplateWriter.cs (95%) create mode 100644 UniversalEmoticonPackBuilderLib/UniversalEmoticonPackBuilderLib.csproj create mode 100644 UniversalEmoticonPackBuilderLib/main.cs create mode 100644 examplePack/config.json create mode 100644 examplePack/ff.gif create mode 100644 examplePack/fi.gif create mode 100644 examplePack/fl.gif create mode 100644 examplePack/ft.gif create mode 100644 examplePack/map.txt diff --git a/UniversalEmoticonPackBuilder.sln b/UniversalEmoticonPackBuilder.sln index 9f1fbee..c7d454c 100644 --- a/UniversalEmoticonPackBuilder.sln +++ b/UniversalEmoticonPackBuilder.sln @@ -5,20 +5,48 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "qip_buildmap", "qip_buildma EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "emoticon_build", "UniversalEmoticonPackBuilder\emoticon_build.csproj", "{FB29B9CB-D6D8-43F4-9011-538A0B9C7451}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "UniversalEmoticonPackBuilderLib", "UniversalEmoticonPackBuilderLib\UniversalEmoticonPackBuilderLib.csproj", "{2619794A-0A5D-4ED8-A3A8-1D36F1594CDC}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Debug|Mixed Platforms = Debug|Mixed Platforms Debug|x86 = Debug|x86 + Release|Any CPU = Release|Any CPU + Release|Mixed Platforms = Release|Mixed Platforms Release|x86 = Release|x86 EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution + {FC8DA446-6B5C-4BD0-881F-D683068A5259}.Debug|Any CPU.ActiveCfg = Debug|x86 + {FC8DA446-6B5C-4BD0-881F-D683068A5259}.Debug|Mixed Platforms.ActiveCfg = Debug|x86 + {FC8DA446-6B5C-4BD0-881F-D683068A5259}.Debug|Mixed Platforms.Build.0 = Debug|x86 {FC8DA446-6B5C-4BD0-881F-D683068A5259}.Debug|x86.ActiveCfg = Debug|x86 {FC8DA446-6B5C-4BD0-881F-D683068A5259}.Debug|x86.Build.0 = Debug|x86 + {FC8DA446-6B5C-4BD0-881F-D683068A5259}.Release|Any CPU.ActiveCfg = Release|x86 + {FC8DA446-6B5C-4BD0-881F-D683068A5259}.Release|Mixed Platforms.ActiveCfg = Release|x86 + {FC8DA446-6B5C-4BD0-881F-D683068A5259}.Release|Mixed Platforms.Build.0 = Release|x86 {FC8DA446-6B5C-4BD0-881F-D683068A5259}.Release|x86.ActiveCfg = Release|x86 {FC8DA446-6B5C-4BD0-881F-D683068A5259}.Release|x86.Build.0 = Release|x86 + {FB29B9CB-D6D8-43F4-9011-538A0B9C7451}.Debug|Any CPU.ActiveCfg = Debug|x86 + {FB29B9CB-D6D8-43F4-9011-538A0B9C7451}.Debug|Mixed Platforms.ActiveCfg = Debug|x86 + {FB29B9CB-D6D8-43F4-9011-538A0B9C7451}.Debug|Mixed Platforms.Build.0 = Debug|x86 {FB29B9CB-D6D8-43F4-9011-538A0B9C7451}.Debug|x86.ActiveCfg = Debug|x86 {FB29B9CB-D6D8-43F4-9011-538A0B9C7451}.Debug|x86.Build.0 = Debug|x86 + {FB29B9CB-D6D8-43F4-9011-538A0B9C7451}.Release|Any CPU.ActiveCfg = Release|x86 + {FB29B9CB-D6D8-43F4-9011-538A0B9C7451}.Release|Mixed Platforms.ActiveCfg = Release|x86 + {FB29B9CB-D6D8-43F4-9011-538A0B9C7451}.Release|Mixed Platforms.Build.0 = Release|x86 {FB29B9CB-D6D8-43F4-9011-538A0B9C7451}.Release|x86.ActiveCfg = Release|x86 {FB29B9CB-D6D8-43F4-9011-538A0B9C7451}.Release|x86.Build.0 = Release|x86 + {2619794A-0A5D-4ED8-A3A8-1D36F1594CDC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {2619794A-0A5D-4ED8-A3A8-1D36F1594CDC}.Debug|Any CPU.Build.0 = Debug|Any CPU + {2619794A-0A5D-4ED8-A3A8-1D36F1594CDC}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {2619794A-0A5D-4ED8-A3A8-1D36F1594CDC}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {2619794A-0A5D-4ED8-A3A8-1D36F1594CDC}.Debug|x86.ActiveCfg = Debug|Any CPU + {2619794A-0A5D-4ED8-A3A8-1D36F1594CDC}.Release|Any CPU.ActiveCfg = Release|Any CPU + {2619794A-0A5D-4ED8-A3A8-1D36F1594CDC}.Release|Any CPU.Build.0 = Release|Any CPU + {2619794A-0A5D-4ED8-A3A8-1D36F1594CDC}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {2619794A-0A5D-4ED8-A3A8-1D36F1594CDC}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {2619794A-0A5D-4ED8-A3A8-1D36F1594CDC}.Release|x86.ActiveCfg = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/UniversalEmoticonPackBuilder/Program.cs b/UniversalEmoticonPackBuilder/Program.cs index 540517e..bb7c63c 100644 --- a/UniversalEmoticonPackBuilder/Program.cs +++ b/UniversalEmoticonPackBuilder/Program.cs @@ -7,18 +7,19 @@ using System.Threading; using System.Runtime.Serialization; using System.Runtime.Serialization.Json; -using Ionic.Zip; +using UniversalEmoticonPackBuilderLib; +using UniversalEmoticonPackBuilderLib.Builders; -namespace QIPSmileBuilder +namespace ConsoleUniversalEmoticonPackBuilder { class Program { - public static CMDArgs Config; + public static BuilderConfig Config; static void Main(string[] args) { // Default config - Config = new CMDArgs() + Config = new BuilderConfig() { version = "1.0.0", author = "VA", @@ -35,116 +36,20 @@ static void Main(string[] args) if (File.Exists(cfg_file)) { Config = - (CMDArgs) - new DataContractJsonSerializer(typeof(CMDArgs)) + (BuilderConfig) + new DataContractJsonSerializer(typeof(BuilderConfig)) .ReadObject( new StreamReader(cfg_file, Encoding.UTF8).BaseStream ); Directory.SetCurrentDirectory(new FileInfo(cfg_file).DirectoryName); } - // Initialize - var packinfo = new PackInfo() { name = Config.name, version = Config.version, author = Config.author }; - - var build_dir = "build/";// +Directory.CreateDirectory("build/build_" + (DateTime.Now - new DateTime(1970, 1, 1, 0, 0, 0)).TotalSeconds).Name + "/"; - - - // Cleanup and create build dir - try - { - Directory.CreateDirectory(build_dir).Delete(true); - Directory.CreateDirectory(build_dir); - } - catch (IOException) - { - - } - - // Load builders - List builders = new List(); - foreach (var b in Config.builders) - switch (b) { - case "qip": - builders.Add(new QipSmileConfig(build_dir, packinfo)); - break; - case "pidgin": - builders.Add(new PidginSmileConfig(build_dir, packinfo)); - break; - case "adium": - builders.Add(new AdiumSmileConfig(build_dir, packinfo)); - break; - case "miranda": - builders.Add(new MirandaSmileConfig(build_dir, packinfo)); - break; - case "wim": - builders.Add(new WIMSkin(build_dir, packinfo)); - break; - case "gmail": - builders.Add(new GMailUserjs(build_dir, packinfo)); - break; - } - - - // Load pack - Dictionary pack = new Dictionary(); - - if (Config.map.Length > 0) - { - string line; - var map = new StreamReader(Config.map); - while ((line = map.ReadLine()) != null) - { - if (line.IndexOf('#') == 0) continue; - var spl = line.Split(new[] { ':' }, 2); - if (spl.Length < 2) continue; - - var sname = spl[0]; - var scode = spl[1]; - pack.Add(sname, scode); - } - } - else - throw new Exception("No map defined");// pack = Config.Pack; - - // Build! - foreach (var s in pack) - builders.ForEach(c => c.CopySmiley(s.Key, s.Value.Split(','))); - - builders.ForEach(c => c.Finish()); + BuildPacks(Config); } - } - - // class that stores emoticon pack info - class PackInfo { - public string name; - public string version; - public string author; - public string FullName { - get { - return String.Format("{0} {1} by {2}", name, version, author); - } - } - - public string Description { - get { - return FullName; - } + private static void BuildPacks(BuilderConfig Config) + { + throw new NotImplementedException(); } } - - // Class for config json deserialization - [DataContract] - class CMDArgs { - [DataMember] - public string name; - [DataMember] - public string version; - [DataMember] - public string author; - [DataMember] - public string map; - [DataMember] - public List builders; - } } diff --git a/UniversalEmoticonPackBuilder/Writers.cs b/UniversalEmoticonPackBuilder/Writers.cs deleted file mode 100644 index 81cf45f..0000000 --- a/UniversalEmoticonPackBuilder/Writers.cs +++ /dev/null @@ -1,239 +0,0 @@ -using QIPSmileBuilder; -using System.IO; -using System; -using System.Text; -using System.Xml; -using System.Linq; - -class QipSmileConfig : SmileConfig -{ - private StreamWriter defineFile; - private int counter = 0; - - public QipSmileConfig(string path, PackInfo pack) - : base(pack, "QIP", path) - { - this.path = packRootPath + String.Format("{0}/", PackFullName); - this.imagePath = this.path; - - Directory.CreateDirectory(this.path); - - defineFile = new StreamWriter(this.path + "_define.ini", false, Encoding.GetEncoding("windows-1251")); - defineFile.AutoFlush = true; - } - - protected override void WriteEntry(string file, string[] equivs) - { - defineFile.WriteLine(String.Join(",", equivs)); - counter++; - } - - protected override void EndFile() - { - defineFile.Flush(); - defineFile.Close(); - - using (var w = new StreamWriter(this.path + "_define_vis.ini", false, Encoding.GetEncoding("windows-1251"))) - { - w.Write("1-{0}", counter + 1); - w.Flush(); - w.Close(); - } - } - - protected override string RenameFile(string fname) - { - var ext = fname.Split('.').Last(); - return toBase26(counter, 2) + "." + ext; - } - - static string toBase26(int x, int digits) - { - string base26Characters = "abcdefghijklmnopqrstuvwxyz"; - - char[] result = new char[digits]; - for (int i = digits - 1; i >= 0; --i) - { - result[i] = base26Characters[x % base26Characters.Length]; - x /= base26Characters.Length; - } - return new string(result); - } -} - -class PidginSmileConfig : SmileConfig -{ - private StreamWriter pidginTheme; - - public PidginSmileConfig(string path, PackInfo pack) - : base(pack, "Pidgin", path) - { - this.path = packRootPath + String.Format("{0}/", PackFullName); - this.imagePath = this.path; - Directory.CreateDirectory(this.path); - - pidginTheme = new StreamWriter(this.path + "theme", false, Encoding.GetEncoding("windows-1251")); - pidginTheme.AutoFlush = true; - pidginTheme.WriteLine("Name={0}", pack.name); - pidginTheme.WriteLine("Description={0}", pack.Description); - pidginTheme.WriteLine("Icon=ff.gif"); - pidginTheme.WriteLine("Author={0}", pack.author); - pidginTheme.WriteLine("[default]"); - } - - protected override void WriteEntry(string file, string[] equivs) - { - pidginTheme.WriteLine("{0}\t{1}", file, String.Join("\t", equivs)); - } - - protected override void EndFile() - { - pidginTheme.Flush(); - pidginTheme.Close(); - } -} - -class AdiumSmileConfig : SmileConfig -{ - private XmlTextWriter plistWriter; - - public AdiumSmileConfig(string path, PackInfo pack) - : base(pack, "Adium", path) - { - this.path = packRootPath + String.Format("{0}.AdiumEmoticonSet/", PackFullName); - this.imagePath = this.path; - Directory.CreateDirectory(this.path); - - plistWriter = new XmlTextWriter(this.path + "Emoticons.plist", Encoding.UTF8); - - plistWriter.Formatting = Formatting.Indented; - plistWriter.WriteStartDocument(); - plistWriter.WriteDocType("plist", "-//Apple Computer//DTD PLIST 1.0//EN", "http://www.apple.com/DTDs/PropertyList-1.0.dtd", null); - - plistWriter.WriteStartElement("plist"); - plistWriter.WriteAttributeString("version", "1.0"); - plistWriter.WriteStartElement("dict"); - plistWriter.WriteElementString("key", "AdiumSetVersion"); - plistWriter.WriteElementString("integer", "1"); - plistWriter.WriteElementString("key", "Emoticons"); - plistWriter.WriteStartElement("dict"); - } - - protected override void WriteEntry(string file, string[] equivs) - { - plistWriter.WriteElementString("key", file); - plistWriter.WriteStartElement("dict"); - plistWriter.WriteElementString("key", "Equivalents"); - plistWriter.WriteStartElement("array"); - foreach (var eq in equivs) - plistWriter.WriteElementString("string", eq); - plistWriter.WriteEndElement(); - plistWriter.WriteElementString("key", "Name"); - plistWriter.WriteElementString("string", equivs[0]); - plistWriter.WriteEndElement(); - } - - protected override void EndFile() - { - plistWriter.WriteEndElement(); - plistWriter.WriteEndElement(); - plistWriter.WriteEndElement(); - plistWriter.WriteEndDocument(); - plistWriter.Flush(); - plistWriter.Close(); - } -} - -class MirandaSmileConfig : SmileConfig -{ - private StreamWriter mirandaTheme; - - public MirandaSmileConfig(string path, PackInfo pack) - : base(pack, "Miranda", path) - { - this.path = packRootPath + String.Format("{0}/", PackFullName); - this.imagePath = this.path + "Animated/"; - Directory.CreateDirectory(this.imagePath); - - mirandaTheme = new StreamWriter(this.path + pack.name + ".msl", false, Encoding.GetEncoding("utf-8")); - mirandaTheme.AutoFlush = true; - mirandaTheme.WriteLine("Name\t=\t{0}", pack.name); - mirandaTheme.WriteLine("Author\t=\t{0}", pack.author); - mirandaTheme.WriteLine("Date\t=\t{0}", DateTime.Now); - mirandaTheme.WriteLine("Version\t=\t{0}", pack.version); - } - - protected override void WriteEntry(string file, string[] equivs) - { - mirandaTheme.WriteLine("Smiley\t= \"{0}\", 0, \"{1}\"", file, String.Join("\", \"", equivs)); - } - - protected override void EndFile() - { - mirandaTheme.Flush(); - mirandaTheme.Close(); - } -} - -class WIMSkin : SmileConfig -{ - private StreamWriter wimSkin; - - public WIMSkin(string path, PackInfo pack) - : base(pack, "WIM", path) - { - this.path = packRootPath + String.Format("{0}/", PackFullName); - this.imagePath = this.path + "PlurkSmilies/"; - Directory.CreateDirectory(this.path); - - wimSkin = new StreamWriter(this.path + pack.name + ".lua", false, Encoding.GetEncoding("utf-8")); - wimSkin.AutoFlush = true; - wimSkin.WriteLine("WIM_ClassicSkin.emoticons.definitions = {"); - - this.path += "Emoticons/"; - Directory.CreateDirectory(this.path); - } - - protected override void WriteEntry(string file, string[] equivs) - { - wimSkin.WriteLine("[\"{0}\"] = \"Interface\\\\AddOns\\\\PlurkSmilies\\\\Emoticons\\\\{1}\",", equivs[0], file); - } - - protected override void EndFile() - { - wimSkin.WriteLine("};"); - wimSkin.Flush(); - wimSkin.Close(); - } -} - -class GMailUserjs : SmileConfig -{ - private TemplateWriter tw; - private int counter = 0; - - public GMailUserjs(string path, PackInfo pack) - : base(pack, "Gmail", path) - { - this.path = packRootPath + String.Format("{0}/", PackFullName); - this.imagePath = this.path + "emo/"; - - Directory.CreateDirectory(this.imagePath); - - tw = new TemplateWriter("chrome.user.js", String.Format("{0}{1}.user.js", this.path, this.PathPackFullName), pack); - tw.AddReplacement("__PACKURL__", @"http://artyfarty.ru/emo/"); - - } - - protected override void WriteEntry(string file, string[] equivs) - { - tw.WriteLine(String.Format("['{0}','{1}'],", equivs[0], file)); - counter++; - } - - protected override void EndFile() - { - tw.data = tw.data.Substring(0, tw.data.Length - 2); - tw.Flush(); - } -} \ No newline at end of file diff --git a/UniversalEmoticonPackBuilder/emoticon_build.csproj b/UniversalEmoticonPackBuilder/emoticon_build.csproj index 824c9fd..2dd7e28 100644 --- a/UniversalEmoticonPackBuilder/emoticon_build.csproj +++ b/UniversalEmoticonPackBuilder/emoticon_build.csproj @@ -8,7 +8,7 @@ {FB29B9CB-D6D8-43F4-9011-538A0B9C7451} Exe Properties - QIPSmileBuilder + ConsoleUniversalEmoticonPackBuilder QIPSmileBuilder v4.0 Client @@ -34,9 +34,6 @@ 4 - - .\Ionic.Zip.dll - @@ -49,15 +46,18 @@ - - - Always + + + {2619794A-0A5D-4ED8-A3A8-1D36F1594CDC} + UniversalEmoticonPackBuilderLib + + + \ No newline at end of file diff --git a/UniversalEmoticonPackBuilderLib/main.cs b/UniversalEmoticonPackBuilderLib/main.cs new file mode 100644 index 0000000..e0502b6 --- /dev/null +++ b/UniversalEmoticonPackBuilderLib/main.cs @@ -0,0 +1,128 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Runtime.Serialization; +using System.IO; +using UniversalEmoticonPackBuilderLib.Builders; + +namespace UniversalEmoticonPackBuilderLib +{ + public static class UniversalEmoticonPackBuilder { + public static void BuildPacks(PackInfo packinfo, string build_dir, List requested_builders, string mapfile) + { + // Cleanup and create build dir + try + { + Directory.CreateDirectory(build_dir).Delete(true); + Directory.CreateDirectory(build_dir); + } + catch (IOException) + { + + } + + // Load builders + List builders = new List(); + foreach (var b in requested_builders) + switch (b) + { + case "qip": + builders.Add(new QipSmileConfig(build_dir, packinfo)); + break; + case "pidgin": + builders.Add(new PidginSmileConfig(build_dir, packinfo)); + break; + case "adium": + builders.Add(new AdiumSmileConfig(build_dir, packinfo)); + break; + case "miranda": + builders.Add(new MirandaSmileConfig(build_dir, packinfo)); + break; + case "wim": + builders.Add(new WIMSkin(build_dir, packinfo)); + break; + case "gmail": + builders.Add(new GMailUserjs(build_dir, packinfo)); + break; + } + + + // Load pack + Dictionary pack = new Dictionary(); + + if (mapfile.Length > 0) + { + string line; + var map = new StreamReader(mapfile); + while ((line = map.ReadLine()) != null) + { + if (line.IndexOf('#') == 0) continue; + var spl = line.Split(new[] { ':' }, 2); + if (spl.Length < 2) continue; + + var sname = spl[0]; + var scode = spl[1]; + pack.Add(sname, scode); + } + } + else + throw new Exception("No map defined");// pack = Config.Pack; + + // Build! + foreach (var s in pack) + builders.ForEach(c => c.CopySmiley(s.Key, s.Value.Split(','))); + + builders.ForEach(c => c.Finish()); + } + + public static void BuildPacks(BuilderConfig Config) + { + BuildPacks( + new PackInfo() { name = Config.name, version = Config.version, author = Config.author }, + "build/", + Config.builders, + Config.map + ); + } + } + + [DataContract] + public class BuilderConfig + { + [DataMember] + public string name; + [DataMember] + public string version; + [DataMember] + public string author; + [DataMember] + public string map; + [DataMember] + public List builders; + } + + // class that stores emoticon pack info + public class PackInfo + { + public string name; + public string version; + public string author; + + public string FullName + { + get + { + return String.Format("{0} {1} by {2}", name, version, author); + } + } + + public string Description + { + get + { + return FullName; + } + } + } +} diff --git a/examplePack/config.json b/examplePack/config.json new file mode 100644 index 0000000..082b75a --- /dev/null +++ b/examplePack/config.json @@ -0,0 +1,7 @@ +{ + "name":"Example", + "version":"1.0", + "author":"artyfarty", + "map":"map.txt", + "builders":["qip","adium","pidgin"] +} \ No newline at end of file diff --git a/examplePack/ff.gif b/examplePack/ff.gif new file mode 100644 index 0000000000000000000000000000000000000000..d2c0d2f557c34d654fe798acdb71e30053c16bce GIT binary patch literal 2844 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 h!c8M)nuew5ZJNn$B2ZAuqPXOE8zgi@4VSVg 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 literal 0 HcmV?d00001 diff --git a/examplePack/fl.gif b/examplePack/fl.gif new file mode 100644 index 0000000000000000000000000000000000000000..f5cd9148553dbafc64511e4c7902517f4259a127 GIT binary patch literal 2717 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 DbaeOm literal 0 HcmV?d00001 diff --git a/examplePack/ft.gif b/examplePack/ft.gif new file mode 100644 index 0000000000000000000000000000000000000000..3b77703b81191ed5a76d9d0a1d17a2aacc1b6603 GIT binary patch literal 2789 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