Mercurial > jhg
comparison src/org/tmatesoft/hg/internal/ConfigFile.java @ 483:e31e85cf4d4c
Handle include and unset directives in config files
author | Artem Tikhomirov <tikhomirov.artem@gmail.com> |
---|---|
date | Tue, 07 Aug 2012 19:14:53 +0200 |
parents | 9fb990c8a724 |
children | db48a77ec8ff |
comparison
equal
deleted
inserted
replaced
482:6c67debed07e | 483:e31e85cf4d4c |
---|---|
1 /* | 1 /* |
2 * Copyright (c) 2011 TMate Software Ltd | 2 * Copyright (c) 2011-2012 TMate Software Ltd |
3 * | 3 * |
4 * This program is free software; you can redistribute it and/or modify | 4 * This program is free software; you can redistribute it and/or modify |
5 * it under the terms of the GNU General Public License as published by | 5 * it under the terms of the GNU General Public License as published by |
6 * the Free Software Foundation; version 2 of the License. | 6 * the Free Software Foundation; version 2 of the License. |
7 * | 7 * |
14 * the terms of a license other than GNU General Public License | 14 * the terms of a license other than GNU General Public License |
15 * contact TMate Software at support@hg4j.com | 15 * contact TMate Software at support@hg4j.com |
16 */ | 16 */ |
17 package org.tmatesoft.hg.internal; | 17 package org.tmatesoft.hg.internal; |
18 | 18 |
19 import java.io.BufferedReader; | |
20 import java.io.ByteArrayOutputStream; | 19 import java.io.ByteArrayOutputStream; |
21 import java.io.File; | 20 import java.io.File; |
22 import java.io.FileOutputStream; | 21 import java.io.FileOutputStream; |
23 import java.io.FileReader; | |
24 import java.io.IOException; | 22 import java.io.IOException; |
25 import java.io.PrintStream; | 23 import java.io.PrintStream; |
26 import java.nio.ByteBuffer; | 24 import java.nio.ByteBuffer; |
27 import java.nio.channels.FileChannel; | 25 import java.nio.channels.FileChannel; |
28 import java.nio.channels.FileLock; | 26 import java.nio.channels.FileLock; |
31 import java.util.Iterator; | 29 import java.util.Iterator; |
32 import java.util.LinkedHashMap; | 30 import java.util.LinkedHashMap; |
33 import java.util.List; | 31 import java.util.List; |
34 import java.util.Map; | 32 import java.util.Map; |
35 | 33 |
34 import org.tmatesoft.hg.core.SessionContext; | |
35 import org.tmatesoft.hg.repo.HgInvalidFileException; | |
36 import org.tmatesoft.hg.util.LogFacility; | |
37 | |
36 /** | 38 /** |
37 * .ini / .rc file reader | 39 * .ini / .rc file reader |
38 * @author Artem Tikhomirov | 40 * @author Artem Tikhomirov |
39 * @author TMate Software Ltd. | 41 * @author TMate Software Ltd. |
40 */ | 42 */ |
41 public class ConfigFile { | 43 public class ConfigFile { |
42 | 44 |
45 private final SessionContext sessionContext; | |
43 private List<String> sections; | 46 private List<String> sections; |
44 private List<Map<String,String>> content; | 47 private List<Map<String,String>> content; |
45 | 48 |
46 public ConfigFile() { | 49 public ConfigFile(SessionContext ctx) { |
47 } | 50 sessionContext = ctx; |
48 | 51 } |
49 public void addLocation(File path) throws IOException { | 52 |
53 public void addLocation(File path) throws HgInvalidFileException { | |
50 read(path); | 54 read(path); |
51 } | 55 } |
52 | 56 |
53 public boolean hasSection(String sectionName) { | 57 public boolean hasSection(String sectionName) { |
54 return sections == null ? false : sections.indexOf(sectionName) != -1; | 58 return sections == null ? false : sections.indexOf(sectionName) != -1; |
109 } else { | 113 } else { |
110 section.put(key, newValue); | 114 section.put(key, newValue); |
111 } | 115 } |
112 } | 116 } |
113 | 117 |
114 // TODO handle %include and %unset directives | 118 private void read(File f) throws HgInvalidFileException { |
115 // TODO "" and lists | |
116 // XXX perhaps, single string to keep whole section with substrings for keys/values to minimize number of arrays (String.value) | |
117 private void read(File f) throws IOException { | |
118 if (f == null || !f.canRead()) { | 119 if (f == null || !f.canRead()) { |
119 return; | 120 return; |
120 } | 121 } |
121 if (sections == null) { | 122 if (sections == null) { |
122 sections = new ArrayList<String>(); | 123 sections = new ArrayList<String>(); |
123 content = new ArrayList<Map<String,String>>(); | 124 content = new ArrayList<Map<String,String>>(); |
124 } | 125 } |
125 BufferedReader br = null; | 126 new Parser().go(f, this); |
126 try { | 127 ((ArrayList<?>) sections).trimToSize(); |
127 br = new BufferedReader(new FileReader(f)); | 128 ((ArrayList<?>) content).trimToSize(); |
128 String line; | |
129 String sectionName = ""; | |
130 Map<String,String> section = new LinkedHashMap<String, String>(); | |
131 while ((line = br.readLine()) != null) { | |
132 line = line.trim(); | |
133 int x; | |
134 if ((x = line.indexOf('#')) != -1) { | |
135 // do not keep comments in memory, get new, shorter string | |
136 line = new String(line.substring(0, x).trim()); | |
137 } | |
138 if (line.length() <= 2) { // a=b or [a] are at least of length 3 | |
139 continue; | |
140 } | |
141 if (line.charAt(0) == '[' && line.charAt(line.length() - 1) == ']') { | |
142 sectionName = line.substring(1, line.length() - 1); | |
143 if (sections.indexOf(sectionName) == -1) { | |
144 sections.add(sectionName); | |
145 content.add(section = new LinkedHashMap<String, String>()); | |
146 } else { | |
147 section = null; // drop cached value | |
148 } | |
149 } else if ((x = line.indexOf('=')) != -1) { | |
150 // share char[] of the original string | |
151 String key = line.substring(0, x).trim(); | |
152 String value = line.substring(x+1).trim(); | |
153 if (section == null) { | |
154 int i = sections.indexOf(sectionName); | |
155 assert i >= 0; | |
156 section = content.get(i); | |
157 } | |
158 if (sectionName.length() == 0) { | |
159 // add fake section only if there are any values | |
160 sections.add(sectionName); | |
161 content.add(section); | |
162 } | |
163 section.put(key, value); | |
164 } | |
165 } | |
166 } finally { | |
167 ((ArrayList<?>) sections).trimToSize(); | |
168 ((ArrayList<?>) content).trimToSize(); | |
169 if (br != null) { | |
170 br.close(); | |
171 } | |
172 } | |
173 assert sections.size() == content.size(); | 129 assert sections.size() == content.size(); |
174 } | 130 } |
175 | 131 |
176 public void writeTo(File f) throws IOException { | 132 public void writeTo(File f) throws IOException { |
177 byte[] data = compose(); | 133 byte[] data = compose(); |
208 ps.println(); | 164 ps.println(); |
209 } | 165 } |
210 ps.flush(); | 166 ps.flush(); |
211 return bos.toByteArray(); | 167 return bos.toByteArray(); |
212 } | 168 } |
169 | |
170 private static class Parser implements LineReader.LineConsumer<ConfigFile> { | |
171 | |
172 private String sectionName = ""; | |
173 private Map<String,String> section = new LinkedHashMap<String, String>(); | |
174 private File contextFile; | |
175 | |
176 // TODO "" and lists | |
177 // XXX perhaps, single string to keep whole section with substrings for keys/values to minimize number of arrays (String.value) | |
178 public boolean consume(String line, ConfigFile cfg) throws IOException { | |
179 int x; | |
180 if ((x = line.indexOf('#')) != -1) { | |
181 // do not keep comments in memory, get new, shorter string | |
182 line = new String(line.substring(0, x).trim()); | |
183 } | |
184 if (line.length() <= 2) { // a=b or [a] are at least of length 3 | |
185 return true; | |
186 } | |
187 if (line.charAt(0) == '[' && line.charAt(line.length() - 1) == ']') { | |
188 sectionName = line.substring(1, line.length() - 1); | |
189 if (cfg.sections.indexOf(sectionName) == -1) { | |
190 cfg.sections.add(sectionName); | |
191 cfg.content.add(section = new LinkedHashMap<String, String>()); | |
192 } else { | |
193 section = null; // drop cached value | |
194 } | |
195 } else if (line.startsWith("%unset")) { | |
196 if (section != null) { | |
197 section.remove(line.substring(7).trim()); | |
198 } | |
199 } else if (line.startsWith("%include")) { | |
200 processInclude(line.substring(9).trim(), cfg); | |
201 } else if ((x = line.indexOf('=')) != -1) { | |
202 // share char[] of the original string | |
203 String key = line.substring(0, x).trim(); | |
204 String value = line.substring(x+1).trim(); | |
205 if (section == null) { | |
206 int i = cfg.sections.indexOf(sectionName); | |
207 assert i >= 0; | |
208 section = cfg.content.get(i); | |
209 } | |
210 if (sectionName.length() == 0) { | |
211 // add fake section only if there are any values | |
212 cfg.sections.add(sectionName); | |
213 cfg.content.add(section); | |
214 } | |
215 section.put(key, value); | |
216 } | |
217 return true; | |
218 } | |
219 | |
220 public void go(File f, ConfigFile cfg) throws HgInvalidFileException { | |
221 contextFile = f; | |
222 LineReader lr = new LineReader(f, cfg.sessionContext.getLog()); | |
223 lr.ignoreLineComments("#"); | |
224 lr.read(this, cfg); | |
225 } | |
226 | |
227 // include failure doesn't propagate | |
228 private void processInclude(String includeValue, ConfigFile cfg) { | |
229 File f; | |
230 // TODO handle environment variable expansion | |
231 if (includeValue.startsWith("~/")) { | |
232 f = new File(System.getProperty("user.home"), includeValue.substring(2)); | |
233 } else { | |
234 f = new File(contextFile.getParentFile(), includeValue); | |
235 } | |
236 try { | |
237 if (f.canRead()) { | |
238 new Parser().go(f, cfg); | |
239 } else { | |
240 LogFacility lf = cfg.sessionContext.getLog(); | |
241 lf.dump(ConfigFile.class, LogFacility.Severity.Debug, "Can't read file to include: %s", f); | |
242 } | |
243 } catch (HgInvalidFileException ex) { | |
244 LogFacility lf = cfg.sessionContext.getLog(); | |
245 lf.dump(ConfigFile.class, LogFacility.Severity.Warn, "Can't include %s (%s)", f, includeValue); | |
246 } | |
247 } | |
248 } | |
213 } | 249 } |