diff 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
line wrap: on
line diff
--- a/src/org/tmatesoft/hg/internal/ConfigFile.java	Tue Aug 07 14:27:13 2012 +0200
+++ b/src/org/tmatesoft/hg/internal/ConfigFile.java	Tue Aug 07 19:14:53 2012 +0200
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2011 TMate Software Ltd
+ * Copyright (c) 2011-2012 TMate Software Ltd
  *  
  * 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
@@ -16,11 +16,9 @@
  */
 package org.tmatesoft.hg.internal;
 
-import java.io.BufferedReader;
 import java.io.ByteArrayOutputStream;
 import java.io.File;
 import java.io.FileOutputStream;
-import java.io.FileReader;
 import java.io.IOException;
 import java.io.PrintStream;
 import java.nio.ByteBuffer;
@@ -33,6 +31,10 @@
 import java.util.List;
 import java.util.Map;
 
+import org.tmatesoft.hg.core.SessionContext;
+import org.tmatesoft.hg.repo.HgInvalidFileException;
+import org.tmatesoft.hg.util.LogFacility;
+
 /**
  * .ini / .rc file reader
  * @author Artem Tikhomirov
@@ -40,13 +42,15 @@
  */
 public class ConfigFile {
 
+	private final SessionContext sessionContext;
 	private List<String> sections;
 	private List<Map<String,String>> content;
 
-	public ConfigFile() {
+	public ConfigFile(SessionContext ctx) {
+		sessionContext = ctx;
 	}
 
-	public void addLocation(File path) throws IOException {
+	public void addLocation(File path) throws HgInvalidFileException {
 		read(path);
 	}
 	
@@ -111,10 +115,7 @@
 		}
 	}
 	
-	// TODO handle %include and %unset directives
-	// TODO "" and lists
-	// XXX perhaps, single string to keep whole section with substrings for keys/values to minimize number of arrays (String.value)
-	private void read(File f) throws IOException {
+	private void read(File f) throws HgInvalidFileException {
 		if (f == null || !f.canRead()) {
 			return;
 		}
@@ -122,54 +123,9 @@
 			sections = new ArrayList<String>();
 			content = new ArrayList<Map<String,String>>();
 		}
-		BufferedReader br = null;
-		try {
-			br = new BufferedReader(new FileReader(f));
-			String line;
-			String sectionName = "";
-			Map<String,String> section = new LinkedHashMap<String, String>();
-			while ((line = br.readLine()) != null) {
-				line = line.trim();
-				int x;
-				if ((x = line.indexOf('#')) != -1) {
-					// do not keep comments in memory, get new, shorter string
-					line = new String(line.substring(0, x).trim());
-				}
-				if (line.length() <= 2) { // a=b or [a] are at least of length 3
-					continue;
-				}
-				if (line.charAt(0) == '[' && line.charAt(line.length() - 1) == ']') {
-					sectionName = line.substring(1, line.length() - 1);
-					if (sections.indexOf(sectionName) == -1) {
-						sections.add(sectionName);
-						content.add(section = new LinkedHashMap<String, String>());
-					} else {
-						section = null; // drop cached value
-					}
-				} else if ((x = line.indexOf('=')) != -1) {
-					// share char[] of the original string
-					String key = line.substring(0, x).trim();
-					String value = line.substring(x+1).trim();
-					if (section == null) {
-						int i = sections.indexOf(sectionName);
-						assert i >= 0;
-						section = content.get(i);
-					}
-					if (sectionName.length() == 0) {
-						// add fake section only if there are any values 
-						sections.add(sectionName);
-						content.add(section);
-					}
-					section.put(key, value);
-				}
-			}
-		} finally {
-			((ArrayList<?>) sections).trimToSize();
-			((ArrayList<?>) content).trimToSize();
-			if (br != null) {
-				br.close();
-			}
-		}
+		new Parser().go(f, this);
+		((ArrayList<?>) sections).trimToSize();
+		((ArrayList<?>) content).trimToSize();
 		assert sections.size() == content.size();
 	}
 
@@ -210,4 +166,84 @@
 		ps.flush();
 		return bos.toByteArray();
 	}
+
+	private static class Parser implements LineReader.LineConsumer<ConfigFile> {
+		
+		private String sectionName = "";
+		private Map<String,String> section = new LinkedHashMap<String, String>();
+		private File contextFile;
+
+		// TODO "" and lists
+		// XXX perhaps, single string to keep whole section with substrings for keys/values to minimize number of arrays (String.value)
+		public boolean consume(String line, ConfigFile cfg) throws IOException {
+			int x;
+			if ((x = line.indexOf('#')) != -1) {
+				// do not keep comments in memory, get new, shorter string
+				line = new String(line.substring(0, x).trim());
+			}
+			if (line.length() <= 2) { // a=b or [a] are at least of length 3
+				return true;
+			}
+			if (line.charAt(0) == '[' && line.charAt(line.length() - 1) == ']') {
+				sectionName = line.substring(1, line.length() - 1);
+				if (cfg.sections.indexOf(sectionName) == -1) {
+					cfg.sections.add(sectionName);
+					cfg.content.add(section = new LinkedHashMap<String, String>());
+				} else {
+					section = null; // drop cached value
+				}
+			} else if (line.startsWith("%unset")) {
+				if (section != null) {
+					section.remove(line.substring(7).trim());
+				}
+			} else if (line.startsWith("%include")) {
+				processInclude(line.substring(9).trim(), cfg);
+			} else if ((x = line.indexOf('=')) != -1) {
+				// share char[] of the original string
+				String key = line.substring(0, x).trim();
+				String value = line.substring(x+1).trim();
+				if (section == null) {
+					int i = cfg.sections.indexOf(sectionName);
+					assert i >= 0;
+					section = cfg.content.get(i);
+				}
+				if (sectionName.length() == 0) {
+					// add fake section only if there are any values 
+					cfg.sections.add(sectionName);
+					cfg.content.add(section);
+				}
+				section.put(key, value);
+			}
+			return true;
+		}
+		
+		public void go(File f, ConfigFile cfg) throws HgInvalidFileException {
+			contextFile = f;
+			LineReader lr = new LineReader(f, cfg.sessionContext.getLog());
+			lr.ignoreLineComments("#");
+			lr.read(this, cfg);
+		}
+		
+		// include failure doesn't propagate
+		private void processInclude(String includeValue, ConfigFile cfg) {
+			File f; 
+			// TODO handle environment variable expansion
+			if (includeValue.startsWith("~/")) {
+				f = new File(System.getProperty("user.home"), includeValue.substring(2));
+			} else {
+				f = new File(contextFile.getParentFile(), includeValue);
+			}
+			try {
+				if (f.canRead()) {
+					new Parser().go(f, cfg);
+				} else {
+					LogFacility lf = cfg.sessionContext.getLog();
+					lf.dump(ConfigFile.class, LogFacility.Severity.Debug, "Can't read file to  include: %s", f);
+				}
+			} catch (HgInvalidFileException ex) {
+				LogFacility lf = cfg.sessionContext.getLog();
+				lf.dump(ConfigFile.class, LogFacility.Severity.Warn, "Can't include %s (%s)", f, includeValue);
+			}
+		}
+	}
 }