1 /** 2 * Reading and writing ini-like files used in some Unix systems and Freedesktop specifications. 3 * ini-like is informal name for the file format that look like this: 4 * --- 5 # Comment 6 [Group name] 7 Key=Value 8 # Comment inside group 9 AnotherKey=Value 10 11 [Another group] 12 Key=English value 13 Key[fr_FR]=Francais value 14 15 * --- 16 * Authors: 17 * $(LINK2 https://github.com/FreeSlave, Roman Chistokhodov) 18 * Copyright: 19 * Roman Chistokhodov, 2015-2016 20 * License: 21 * $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost License 1.0). 22 * See_Also: 23 * $(LINK2 http://standards.freedesktop.org/desktop-entry-spec/latest/index.html, Desktop Entry Specification) 24 */ 25 26 module inilike; 27 28 public import inilike.common; 29 public import inilike.range; 30 public import inilike.file; 31 32 unittest 33 { 34 import std.exception; 35 36 final class DesktopEntry : IniLikeGroup 37 { 38 this() { 39 super("Desktop Entry"); 40 } 41 protected: 42 @trusted override void validateKey(string key, string value) const { 43 if (!isValidDesktopFileKey(key)) { 44 throw new IniLikeEntryException("key is invalid", groupName(), key, value); 45 } 46 } 47 } 48 49 final class DesktopFile : IniLikeFile 50 { 51 //Options to manage .ini like file reading 52 static struct DesktopReadOptions 53 { 54 IniLikeFile.ReadOptions baseOptions; 55 56 alias baseOptions this; 57 58 bool skipExtensionGroups; 59 bool ignoreUnknownGroups; 60 bool skipUnknownGroups; 61 } 62 63 @trusted this(IniLikeReader)(IniLikeReader reader, DesktopReadOptions options = DesktopReadOptions.init) 64 { 65 _options = options; 66 super(reader, null, options.baseOptions); 67 enforce(_desktopEntry !is null, new IniLikeReadException("No \"Desktop Entry\" group", 0)); 68 } 69 70 @safe override bool removeGroup(string groupName) nothrow { 71 if (groupName == "Desktop Entry") { 72 return false; 73 } 74 return super.removeGroup(groupName); 75 } 76 77 protected: 78 @trusted override IniLikeGroup createGroupByName(string groupName) 79 { 80 if (groupName == "Desktop Entry") { 81 _desktopEntry = new DesktopEntry(); 82 return _desktopEntry; 83 } else if (groupName.startsWith("X-")) { 84 if (_options.skipExtensionGroups) { 85 return null; 86 } 87 return createEmptyGroup(groupName); 88 } else { 89 if (_options.ignoreUnknownGroups) { 90 if (_options.skipUnknownGroups) { 91 return null; 92 } else { 93 return createEmptyGroup(groupName); 94 } 95 } else { 96 throw new IniLikeException("Unknown group"); 97 } 98 } 99 } 100 101 inout(DesktopEntry) desktopEntry() inout { 102 return _desktopEntry; 103 } 104 105 private: 106 DesktopEntry _desktopEntry; 107 DesktopReadOptions _options; 108 } 109 110 string contents = 111 `# First comment 112 [Desktop Entry] 113 Key=Value 114 # Comment in group`; 115 DesktopFile.DesktopReadOptions options; 116 117 auto df = new DesktopFile(iniLikeStringReader(contents), options); 118 assert(!df.removeGroup("Desktop Entry")); 119 assert(!df.removeGroup("NonExistent")); 120 assert(df.group("Desktop Entry") !is null); 121 assert(df.desktopEntry() !is null); 122 assert(equal(df.desktopEntry().byIniLine(), [IniLikeLine.fromKeyValue("Key", "Value"), IniLikeLine.fromComment("# Comment in group")])); 123 assert(equal(df.leadingComments(), ["# First comment"])); 124 125 assertThrown(df.desktopEntry().writeEntry("$Invalid", "Valid value")); 126 127 IniLikeEntryException entryException; 128 try { 129 df.desktopEntry().writeEntry("$Invalid", "Valid value"); 130 } catch(IniLikeEntryException e) { 131 entryException = e; 132 } 133 assert(entryException !is null); 134 df.desktopEntry().writeEntry("$Invalid", "Valid value", IniLikeGroup.InvalidKeyPolicy.save); 135 assert(df.desktopEntry().value("$Invalid") == "Valid value"); 136 137 assert(df.desktopEntry().appendValue("Another$Invalid", "Valid value", IniLikeGroup.InvalidKeyPolicy.skip).isNull()); 138 assert(df.desktopEntry().setValue("Another$Invalid", "Valid value", IniLikeGroup.InvalidKeyPolicy.skip) is null); 139 assert(df.desktopEntry().value("Another$Invalid") is null); 140 141 contents = 142 `[X-SomeGroup] 143 Key=Value`; 144 145 auto thrown = collectException!IniLikeReadException(new DesktopFile(iniLikeStringReader(contents))); 146 assert(thrown !is null); 147 assert(thrown.lineNumber == 0); 148 149 contents = 150 `[Desktop Entry] 151 Valid=Key 152 $=Invalid`; 153 154 thrown = collectException!IniLikeReadException(new DesktopFile(iniLikeStringReader(contents))); 155 assert(thrown !is null); 156 assert(thrown.entryException !is null); 157 assert(thrown.entryException.key == "$"); 158 assert(thrown.entryException.value == "Invalid"); 159 160 options = DesktopFile.DesktopReadOptions.init; 161 options.invalidKeyPolicy = IniLikeGroup.InvalidKeyPolicy.skip; 162 assertNotThrown(new DesktopFile(iniLikeStringReader(contents), options)); 163 164 contents = 165 `[Desktop Entry] 166 Name=Name 167 [Unknown] 168 Key=Value`; 169 170 assertThrown(new DesktopFile(iniLikeStringReader(contents))); 171 172 options = DesktopFile.DesktopReadOptions.init; 173 options.ignoreUnknownGroups = true; 174 175 assertNotThrown(df = new DesktopFile(iniLikeStringReader(contents), options)); 176 assert(df.group("Unknown") !is null); 177 178 options.skipUnknownGroups = true; 179 df = new DesktopFile(iniLikeStringReader(contents), options); 180 assert(df.group("Unknown") is null); 181 182 contents = 183 `[Desktop Entry] 184 Name=Name1 185 [X-Extension] 186 Name=Name2`; 187 188 options = DesktopFile.DesktopReadOptions.init; 189 options.skipExtensionGroups = true; 190 191 df = new DesktopFile(iniLikeStringReader(contents), options); 192 assert(df.group("X-Extension") is null); 193 }