changeset 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 6c67debed07e
children ae4d6604debd
files cmdline/org/tmatesoft/hg/console/Main.java cmdline/org/tmatesoft/hg/console/Remote.java src/org/tmatesoft/hg/core/HgUpdateConfigCommand.java src/org/tmatesoft/hg/internal/ConfigFile.java src/org/tmatesoft/hg/internal/Internals.java src/org/tmatesoft/hg/internal/LineReader.java src/org/tmatesoft/hg/internal/NewlineFilter.java src/org/tmatesoft/hg/repo/HgInternals.java src/org/tmatesoft/hg/repo/HgLookup.java src/org/tmatesoft/hg/repo/HgRepository.java
diffstat 10 files changed, 139 insertions(+), 78 deletions(-) [+]
line wrap: on
line diff
--- a/cmdline/org/tmatesoft/hg/console/Main.java	Tue Aug 07 14:27:13 2012 +0200
+++ b/cmdline/org/tmatesoft/hg/console/Main.java	Tue Aug 07 19:14:53 2012 +0200
@@ -44,8 +44,10 @@
 import org.tmatesoft.hg.core.Nodeid;
 import org.tmatesoft.hg.internal.BasicSessionContext;
 import org.tmatesoft.hg.internal.ByteArrayChannel;
+import org.tmatesoft.hg.internal.ConfigFile;
 import org.tmatesoft.hg.internal.DigestHelper;
 import org.tmatesoft.hg.internal.IntMap;
+import org.tmatesoft.hg.internal.Internals;
 import org.tmatesoft.hg.internal.PathGlobMatcher;
 import org.tmatesoft.hg.internal.PhasesHelper;
 import org.tmatesoft.hg.internal.RelativePathRewrite;
@@ -109,7 +111,8 @@
 
 	public static void main(String[] args) throws Exception {
 		Main m = new Main(args);
-		m.dumpCommitLastMessage();
+		m.readConfigFile();
+//		m.dumpCommitLastMessage();
 //		m.buildFileLog();
 //		m.testConsoleLog();
 //		m.testTreeTraversal();
@@ -130,7 +133,20 @@
 //		m.dumpCompleteManifestHigh();
 //		m.bunchOfTests();
 	}
-	
+
+	// TODO as test
+	private void readConfigFile() throws Exception {
+		ConfigFile configFile = new ConfigFile(HgInternals.getContext(hgRepo));
+		configFile.addLocation(new File(System.getProperty("user.home"), "test-cfg/aaa/config1"));
+		for (String s : configFile.getSectionNames()) {
+			System.out.printf("[%s]\n", s);
+			for (Map.Entry<String, String> e : configFile.getSection(s).entrySet()) {
+				System.out.printf("%s = %s\n", e.getKey(), e.getValue());
+			}
+		}
+		
+	}
+
 	private void dumpCommitLastMessage() throws Exception {
 		System.out.println(hgRepo.getCommitLastMessage());
 	}
--- a/cmdline/org/tmatesoft/hg/console/Remote.java	Tue Aug 07 14:27:13 2012 +0200
+++ b/cmdline/org/tmatesoft/hg/console/Remote.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
@@ -35,6 +35,7 @@
 import javax.net.ssl.TrustManager;
 import javax.net.ssl.X509TrustManager;
 
+import org.tmatesoft.hg.internal.BasicSessionContext;
 import org.tmatesoft.hg.internal.ConfigFile;
 
 /**
@@ -90,7 +91,7 @@
 	 empty result
 	 */
 	public static void main(String[] args) throws Exception {
-		ConfigFile cfg =  new ConfigFile();
+		ConfigFile cfg =  new ConfigFile(new BasicSessionContext(null));
 		cfg.addLocation(new File(System.getProperty("user.home"), ".hgrc"));
 		String svnkitServer = cfg.getSection("paths").get("svnkit");
 //		URL url = new URL(svnkitServer + "?cmd=branches&nodes=30bd389788464287cee22ccff54c330a4b715de5");
--- a/src/org/tmatesoft/hg/core/HgUpdateConfigCommand.java	Tue Aug 07 14:27:13 2012 +0200
+++ b/src/org/tmatesoft/hg/core/HgUpdateConfigCommand.java	Tue Aug 07 19:14:53 2012 +0200
@@ -43,21 +43,24 @@
 	private Map<String,List<String>> toRemove;
 	private Map<String,Map<String,String>> toSet;
 
-	private HgUpdateConfigCommand(File configurationFile) {
+	private final SessionContext sessionCtx;
+
+	private HgUpdateConfigCommand(SessionContext sessionContext, File configurationFile) {
+		sessionCtx = sessionContext;
 		configFile = configurationFile;
 	}
 	
 	public static HgUpdateConfigCommand forRepository(HgRepository hgRepo) {
 		// XXX HgRepository to implement SessionContextProvider (with getContext())?
-		return new HgUpdateConfigCommand(new File(HgInternals.getRepositoryDir(hgRepo), "hgrc"));
+		return new HgUpdateConfigCommand(HgInternals.getContext(hgRepo), new File(HgInternals.getRepositoryDir(hgRepo), "hgrc"));
 	}
 	
 	public static HgUpdateConfigCommand forUser(SessionContext ctx) {
-		return new HgUpdateConfigCommand(Internals.getUserConfigurationFileToWrite(ctx));
+		return new HgUpdateConfigCommand(ctx, Internals.getUserConfigurationFileToWrite(ctx));
 	}
 	
 	public static HgUpdateConfigCommand forInstallation(SessionContext ctx) {
-		return new HgUpdateConfigCommand(Internals.getInstallationConfigurationFileToWrite(ctx));
+		return new HgUpdateConfigCommand(ctx, Internals.getInstallationConfigurationFileToWrite(ctx));
 	}
 	
 	/**
@@ -118,7 +121,7 @@
 	 */
 	public void execute() throws HgException {
 		try {
-			ConfigFile cfg = new ConfigFile();
+			ConfigFile cfg = new ConfigFile(sessionCtx);
 			cfg.addLocation(configFile);
 			if (toRemove != null) {
 				for (Map.Entry<String,List<String>> s : toRemove.entrySet()) {
--- 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);
+			}
+		}
+	}
 }
--- a/src/org/tmatesoft/hg/internal/Internals.java	Tue Aug 07 14:27:13 2012 +0200
+++ b/src/org/tmatesoft/hg/internal/Internals.java	Tue Aug 07 19:14:53 2012 +0200
@@ -246,8 +246,10 @@
 	 * @see http://www.selenic.com/mercurial/hgrc.5.html
 	 */
 	public ConfigFile readConfiguration(HgRepository hgRepo, File repoRoot) throws IOException {
-		ConfigFile configFile = new ConfigFile();
-		File hgInstallRoot = findHgInstallRoot(HgInternals.getContext(hgRepo)); // may be null
+		// XXX Internals now have sessionContext field, is there real need to extract one from the repo?
+		SessionContext sessionCtx = HgInternals.getContext(hgRepo);
+		ConfigFile configFile = new ConfigFile(sessionCtx);
+		File hgInstallRoot = findHgInstallRoot(sessionCtx); // may be null
 		//
 		if (runningOnWindows()) {
 			if (hgInstallRoot != null) {
--- a/src/org/tmatesoft/hg/internal/LineReader.java	Tue Aug 07 14:27:13 2012 +0200
+++ b/src/org/tmatesoft/hg/internal/LineReader.java	Tue Aug 07 19:14:53 2012 +0200
@@ -109,10 +109,12 @@
 			} catch (IOException ex) {
 				throw new HgInvalidFileException(ex.getMessage(), ex, file);
 			} finally {
-				try {
-					statusFileReader.close();
-				} catch (IOException ex) {
-					log.dump(MqManager.class, Warn, ex, null);
+				if (statusFileReader != null) {
+					try {
+						statusFileReader.close();
+					} catch (IOException ex) {
+						log.dump(MqManager.class, Warn, ex, null);
+					}
 				}
 //				try {
 //					consumer.end(file, paramObj);
--- a/src/org/tmatesoft/hg/internal/NewlineFilter.java	Tue Aug 07 14:27:13 2012 +0200
+++ b/src/org/tmatesoft/hg/internal/NewlineFilter.java	Tue Aug 07 19:14:53 2012 +0200
@@ -24,12 +24,12 @@
 import static org.tmatesoft.hg.util.LogFacility.Severity.Warn;
 
 import java.io.File;
-import java.io.IOException;
 import java.nio.ByteBuffer;
 import java.util.ArrayList;
 import java.util.Map;
 
 import org.tmatesoft.hg.repo.HgInternals;
+import org.tmatesoft.hg.repo.HgInvalidFileException;
 import org.tmatesoft.hg.repo.HgInvalidStateException;
 import org.tmatesoft.hg.repo.HgRepository;
 import org.tmatesoft.hg.util.Adaptable;
@@ -312,10 +312,10 @@
 //				return;
 //			}
 			// XXX perhaps, add HgDataFile.hasWorkingCopy and workingCopyContent()?
-			ConfigFile hgeol = new ConfigFile();
+			ConfigFile hgeol = new ConfigFile(HgInternals.getContext(hgRepo));
 			try {
 				hgeol.addLocation(cfgFile);
-			} catch (IOException ex) {
+			} catch (HgInvalidFileException ex) {
 				HgInternals.getContext(hgRepo).getLog().dump(getClass(), Warn, ex, null);
 			}
 			nativeRepoFormat = hgeol.getSection("repository").get("native");
--- a/src/org/tmatesoft/hg/repo/HgInternals.java	Tue Aug 07 14:27:13 2012 +0200
+++ b/src/org/tmatesoft/hg/repo/HgInternals.java	Tue Aug 07 19:14:53 2012 +0200
@@ -150,6 +150,7 @@
 	
 	// expose otherwise package-local information primarily to use in our own o.t.hg.core package
 	public static SessionContext getContext(HgRepository repo) {
+		// TODO SessionContext.Source and HgRepo to implement it
 		return repo.getContext();
 	}
 
--- a/src/org/tmatesoft/hg/repo/HgLookup.java	Tue Aug 07 14:27:13 2012 +0200
+++ b/src/org/tmatesoft/hg/repo/HgLookup.java	Tue Aug 07 19:14:53 2012 +0200
@@ -132,10 +132,10 @@
 
 	private ConfigFile getGlobalConfig() {
 		if (globalCfg == null) {
-			globalCfg = new ConfigFile();
+			globalCfg = new ConfigFile(getContext());
 			try {
 				globalCfg.addLocation(new File(System.getProperty("user.home"), ".hgrc"));
-			} catch (IOException ex) {
+			} catch (HgInvalidFileException ex) {
 				// XXX perhaps, makes sense to let caller/client know that we've failed to read global config? 
 				getContext().getLog().dump(getClass(), Warn, ex, null);
 			}
--- a/src/org/tmatesoft/hg/repo/HgRepository.java	Tue Aug 07 14:27:13 2012 +0200
+++ b/src/org/tmatesoft/hg/repo/HgRepository.java	Tue Aug 07 19:14:53 2012 +0200
@@ -325,7 +325,7 @@
 			} catch (IOException ex) {
 				String m = "Errors while reading user configuration file";
 				getContext().getLog().dump(getClass(), Warn, ex, m);
-				return new HgRepoConfig(new ConfigFile()); // empty config, do not cache, allow to try once again
+				return new HgRepoConfig(new ConfigFile(getContext())); // empty config, do not cache, allow to try once again
 				//throw new HgInvalidControlFileException(m, ex, null);
 			}
 		}